This document is about: QUANTUM 3
SWITCH TO

コンポーネント

データアセットクラス

コンポーネントは、エンティティにアタッチできる特別な構造体であり、それらをフィルタリング(アタッチされたコンポーネントに基づいてアクティブなエンティティのサブセットのみを反復処理)するために使用されます。

カスタムコンポーネントに加えて、Quantumにはいくつかの事前構築されたコンポーネントがあります:

  • Transform2D/Transform3D: 固定小数点(FP)値を使用した位置と回転。
  • PhysicsCollider、PhysicsBody、PhysicsCallbacks、PhysicsJoints(2D/3D): Quantumの状態なし物理エンジンで使用される。
  • PathFinderAgent、SteeringAgent、AvoidanceAgent、AvoidanceObstacle: NavMeshに基づくパスファインディングと移動。

コンポーネント

これはDSL内のコンポーネントの基本的な例定義です:

C#

component Action
{
    FP Cooldown;
    FP Power;
}

上記のようにそれらをコンポーネントとしてラベル付けすることで、適切なコード構造(マーカーインターフェース、IDプロパティなど)が生成されます。コンパイルされると、これらはUnityエディタでエンティティプロトタイプと共に使用できるようになります。エディタでは、カスタムコンポーネントは Entity Component ComponentName として名付けられます。

コンポーネントに対して作業するためのAPIは Frame クラスを通じて提供されます。コンポーネントのコピーで作業するか、ポインタを介して作業するかを選択できます。アクセスの種類を区別するために、コピーで作業するためのAPIは Frame を介して直接アクセスでき、ポインタにアクセスするためのAPIは Frame.Unsafe の下で入手できます - 後者はメモリを変更するためです。

コンポーネントの追加、取得、設定に必要な最も基本的な関数は、同じ名前の関数です。

Add<T> は、エンティティにコンポーネントを追加するために使用されます。各エンティティは特定のコンポーネントのコピーを1つだけ持つことができます。デバッグの助けとして、Add<T>AddResult 列挙型を返します。

C#

public enum AddResult {
    EntityDoesNotExist     = 0, // 渡されたEntityRefが無効です。
    ComponentAlreadyExists = 1, // 対象のエンティティにはすでにこのコンポーネントがアタッチされています。
    ComponentAdded         = 2  // コンポーネントはエンティティに正常に追加されました。
}

エンティティがコンポーネントを持っている場合、Get<T> を使用してそれを取得できます。これにより、コンポーネント値のコピーが返されます。コピーで作業しているため、Set<T> を使用してコンポーネントに変更した値を保存する必要があります。Add メソッドと同様に、操作の結果を確認したり反応したりするために使用できる SetResult を返します。

C#

public enum SetResult {
    EntityDoesNotExist = 0, // 渡されたEntityRefが無効です。
    ComponentUpdated   = 1, // コンポーネント値が正常に更新されました。
    ComponentAdded     = 2  // エンティティにはまだこのタイプのコンポーネントがなかったため、新しい値で追加されました。
}

たとえば、ヘルスコンポーネントの初期値を設定する場合、次のようにします:

C#

private void SetHealth(Frame f, EntityRef entity, FP value){
    var health = f.Get<Health>(entity);
    health.Value = value;
    f.Set(entity, health);
}

以下の表は、すでに提示されたメソッドとコンポーネントおよびその値を操作するためのその他のメソッドの要約です:

メソッド 戻り値 追加情報
Add<T>(EntityRef entityRef) AddResult 列挙型、上記参照。 無効なエンティティ参照が許可されています。
Get<T>(EntityRef entityRef) T
現在の値を持つ T のコピー。
無効なエンティティ参照は許可されていません。
エンティティにコンポーネント T が存在しない場合は例外がスローされます。
Set<T>(EntityRef entityRef) SetResult 列挙型、上記参照。 無効なエンティティ参照が許可されています。
Has<T>(EntityRef entityRef) bool
true = エンティティが存在し、コンポーネントがアタッチされている
false = エンティティは存在しない、またはコンポーネントがアタッチされていない。
無効なエンティティ参照が許可されており、
コンポーネントが存在しないことも許可されています。
TryGet<T>(EntityRef entityRef, out T value) bool
true = エンティティが存在し、コンポーネントがアタッチされている。
false = エンティティは存在しない、またはコンポーネントがアタッチされていない。
無効なエンティティ参照が許可されています。
TryGetComponentSet(EntityRef entityRef,
out ComponentSet componentSet)
bool
true = エンティティが存在し、すべてのコンポーネントがアタッチされている。
false = エンティティは存在しない、またはセットの1つ以上のコンポーネントがアタッチされていない。
無効なエンティティ参照が許可されています。
Remove<T>(EntityRef entityRef) 戻り値なし。
エンティティが存在し、コンポーネントがアタッチされていればコンポーネントを削除します。
それ以外の場合は何もしません。
無効なエンティティ参照が許可されています。

コンポーネントに直接取り組むための便利な方法を提供し、Get/Setを使用することからの - 小さな - オーバーヘッドを避けるために、Frame.Unsafe は Get と TryGet の不安全なバージョンを提供します(以下の表を参照)。

メソッド 戻り値 追加情報
GetPointer<T>(EntityRef entityRef) T* 無効なエンティティ参照は許可されていません。
エンティティにコンポーネント T が存在しない場合は例外がスローされます。
TryGetPointer<T>(EntityRef entityRef
out T* value)
bool
true = エンティティが存在し、コンポーネントがアタッチされている。
false = エンティティは存在しない、またはコンポーネントがアタッチされていない。
無効なエンティティ参照が許可されています。

モノリシックな構造体は避け、複数の構造体に分割すべきです。これにより、IL2CPPのコンパイル時に bracket nesting level exceeded maximum エラーが発生する場合があります。

シングルトンコンポーネント

シングルトンコンポーネント は、任意の時点で一つだけ存在できる特別なタイプのコンポーネントです。特定のタイプ T のシングルトンコンポーネントのインスタンスは、ゲーム状態全体で 任意の エンティティに対して一つだけ存在することができます - これはECSデータバッファのコア部分で強制されています。この制約はQuantumによって厳格に守られています。

カスタムの シングルトンコンポーネント は、DSLで singleton component を使用して定義できます。

C#

singleton component MySingleton {
    FP Foo;
}

シングルトンは IComponentSingleton というインターフェースを継承します。このインターフェースは IComponent から継承されるため、通常のコンポーネントに期待される一般的な機能をすべて実行できます:

  • 任意のエンティティにアタッチできます。
  • すべての通常の安全および不安全なメソッド(例:Get、Set、TryGetPointerなど)で管理できます。
  • Unityエディタを介してエンティティプロトタイプに置くことができ、コード内でエンティティにインスタンス化することもできます。

通常のコンポーネント関連のメソッドに加えて、シングルトン専用の特別なメソッドがいくつかあります。通常のコンポーネントと同様に、メソッドは値型とポインタを返すかどうかに基づいて SafeUnsafe に分けられています。

メソッド 戻り値 追加情報
API - Frame
SetSingleton<T> (T component,
EntityRef optionalAddTarget = default)
void シングルトンが存在しない場合にシングルトンを設定します。
-------
EntityRef(オプション)、どのエンティティに追加するかを指定します。
指定しない場合は、シングルトンを追加するために新しいエンティティが作成されます。
GetSingleton<T>() T シングルトンが存在しない場合は例外をスローします。
エンティティ参照は必要なく、自動的に見つかります。
TryGetSingleton<T>(out T component) bool
true = シングルトンが存在
false = シングルトンが存在しない
シングルトンが存在しない場合は例外をスローしません。
エンティティ参照は必要なく、自動的に見つかります。
GetOrAddSingleton<T>(EntityRef optionalAddTarget = default) T シングルトンを取得して返します。
シングルトンが存在しない場合は、SetSingletonのように作成されます。
-----
EntityRef(オプション)、シングルトンを作成する必要がある場合は、どのエンティティに追加するかを指定します。
エンティティ参照が渡されない場合は、新しいエンティティが作成されてシングルトンが追加されます。
GetSingletonEntityRef<T>() EntityRef シングルトンを現在保持しているエンティティを返します。
シングルトンが存在しない場合は例外をスローします。
TryGetSingletonEntityRef<T>(out EntityRef entityRef) bool
true = シングルトンが存在します。
false = シングルトンが存在しません。
シングルトンを現在保持しているエンティティを取得します。シングルトンが存在しない場合は例外をスローしません。
API - Frame.Unsafe
Unsafe.GetPointerSingleton<T>() T* シングルトンポインタを取得します。
存在しない場合は例外をスローします。
TryGetPointerSingleton<T>(out T* component) bool
true = シングルトンが存在します。
false = シングルトンが存在しません。
シングルトンポインタを取得します。
GetOrAddSingletonPointer<T>(EntityRef optionalAddTarget = default) T* シングルトンを取得または追加して返します。
シングルトンが存在しない場合は作成されます。
-----
EntityRef(オプション)、シングルトンを作成する必要がある場合は、どのエンティティに追加するかを指定します。
エンティティ参照が渡されない場合は、新しいエンティティが作成されてシングルトンが追加されます。

ComponentTypeRef

ComponentTypeRef 構造体は、ランタイム中にコンポーネントをその型で参照する方法を提供します。これは、ポリモーフィズムを介して動的にコンポーネントを追加する場合に便利です。

C#

// アセットまたはプロトタイプで設定例
ComponentTypeRef componentTypeRef;

var componentIndex = ComponentTypeId.GetComponentIndex(componentTypeRef);

frame.Add(entityRef, componentIndex);

コンポーネントタイプ参照

ComponentTypeRef 構造体は、ランタイム中にコンポーネントをその型で参照する方法を提供します。これは、ポリモーフィズムを介して動的にコンポーネントを追加する場合に便利です。

C#

// アセットまたはプロトタイプで設定例
ComponentTypeRef componentTypeRef;

var componentIndex = ComponentTypeId.GetComponentIndex(componentTypeRef);

frame.Add(entityRef, componentIndex);

機能の追加

コンポーネントは特別な構造体であるため、C#ファイルで partial 構造体定義を書くことによってカスタムメソッドで拡張できます。たとえば、前に定義した Action コンポーネントを次のように拡張することができます:

C#

namespace Quantum
{
    public partial struct Action
    {
        public void UpdateCooldown(FP deltaTime) {
            Cooldown -= deltaTime;
        }
    }
}

リアクティブコールバック

コンポーネント特有のリアクティブコールバックが2つあります:

  • ISignalOnComponentAdd<T>: コンポーネントタイプ T がエンティティに追加されたときに呼び出されます。
  • ISignalOnComponentRemove<T>: コンポーネントタイプ T がエンティティから削除されたときに呼び出されます。

これらは、コンポーネントが追加または削除されるときにその一部を操作する必要がある場合に特に便利です(たとえば、カスタムコンポーネントでリストを割り当てたり解放したりする場合など)。

これらのシグナルを受け取るためには、単にシステム内でそれらを実装します。

コンポーネントイテレーター

特定のコンポーネントのみが必要な場合、ComponentIterator(安全)および ComponentBlockIterator(不安全)が最適です。

C#

foreach (var pair in frame.GetComponentIterator<Transform3D>()) {
    var component = pair.Component;
    component.Position += FPVector3.Forward * frame.DeltaTime;
    frame.Set(pair.Entity, component);
}

コンポーネントブロックイテレーターはポインタを介して最速のアクセスを提供します。

C#

// この構文は、エンティティの EntityRef と要求されたコンポーネントの型 T の EntityComponentPointerPair 構造体を返します。
foreach (var pair in frame.Unsafe.GetComponentBlockIterator<Transform3D>()) {
    pair.Component->Position += FPVector3.Forward * frame.DeltaTime;
}

// また、次の構文を使用して構造体を分解し、EntityRef およびコンポーネントへの直接アクセスを得ることも可能です。
foreach (var (entityRef, transform) in frame.Unsafe.GetComponentBlockIterator<Transform3D>()) {
    transform->Position += FPVector3.Forward * frame.DeltaTime;
}

フィルター

フィルターは、コンポーネントのセットに基づいてエンティティをフィルタリングし、システムが必要とするコンポーネントのみを取得する便利な方法です。フィルターは、安全(Get/Set)および不安全(ポインタ)コードの両方で使用できます。

ジェネリック

フィルターを作成するには、単にフレームが提供する Filter() API を使用します。

C#

var filtered = frame.Filter<Transform3D, PhysicsBody3D>();

ジェネリックフィルターは最大8つのコンポーネントを含むことができます。特定の without および any ComponentSet フィルターを作成することで、より具体的にすることができます。

C#

var without = ComponentSet.Create<CharacterController3D>();
var any = ComponentSet.Create<NavMeshPathFinder, NavMeshSteeringAgent>();
var filtered = frame.Filter<Transform3D, PhysicsBody3D>(without, any);

コンポーネントセット は最大8つのコンポーネントを保持できます。
without パラメータとして渡された ComponentSet は、セット内の指定されたコンポーネントのいずれかを持つエンティティをすべて除外します。 any セットは、エンティティが指定されたコンポーネントのいずれかを持っていることを確認します。指定されたコンポーネントを1つも持たない場合、そのフィルターによってエンティティは除外されます。

フィルターを介して反復処理するのは、filter.Next() を使用したwhileループと同じくらい簡単です。これにより、すべてのコンポーネントのコピーと、それにアタッチされているエンティティの EntityRef が格納されます。

C#

while (filtered.Next(out var e, out var t, out var b)) {
    t.Position += FPVector3.Forward * frame.DeltaTime;
    frame.Set(e, t);
}

注意: コンポーネントの コピー を通じて反復処理し、それに取り組んでいる状態です。したがって、新しいデータをそれぞれのエンティティに再設定する必要があります。

ジェネリックフィルターは、コンポーネントポインタで作業する機会も提供します。

C#

while (filtered.UnsafeNext(out var e, out var t, out var b)) {
    t->Position += FPVector3.Forward * frame.DeltaTime;
}

この場合、コンポーネントのデータを直接変更しています。

FilterStruct

通常のフィルターに加えて、 FilterStruct アプローチを使用することもできます。このためには、受け取りたい各コンポーネント型の__public__ プロパティを持つ構造体を最初に定義する必要があります。

C#

struct PlayerFilter
{
    public EntityRef Entity;
    public CharacterController3D* KCC;
    public Health* Health;
    public FP AccumulatedDamage;
}

ComponentSet のように、 FilterStruct は最大8つの異なるコンポーネントポインタをフィルタリングできます。

注意: FilterStruct として使用される構造体には、必ず _EntityRef_ フィールドが必要です!

FilterStruct 内の コンポーネント型 メンバーは ポインタでなければなりません; ポインタのみがフィルターによって埋められます。コンポーネントポインタに加えて、他の変数を定義することもできますが、これらはフィルターによって無視され、管理はご自分で行うことになります。

C#

var players = f.Unsafe.FilterStruct<PlayerFilter>();
var playerStruct = default(PlayerFilter);

while (players.Next(&playerStruct))
{
    // 何かを行う
}

Frame.Unsafe.FilterStruct<T>() には、オプションのComponentSets anywithout を利用してフィルターをさらに指定するオーバーロードがあります。

シングルトンコンポーネント

シングルトンコンポーネント は、任意の時点で1つだけ存在できる特別なタイプのコンポーネントです。同じタイプ T のシングルトンコンポーネントのインスタンスは、ゲーム全体の 任意の エンティティに対して1つだけ存在できます。これはECSデータバッファのコア部分で強制されています。この制約はQuantumによって厳格に守られています。

カスタムの シングルトンコンポーネント は、DSLで singleton component を使用して定義できます。

C#

singleton component MySingleton {
    FP Foo;
}

シングルトンは IComponentSingleton というインターフェースを継承します。このインターフェースは IComponent から継承されるため、通常のコンポーネントに期待される一般的な機能をすべて実行できます:

  • 任意のエンティティにアタッチできます。
  • すべての通常の安全および不安全なメソッド(例:Get、Set、TryGetPointerなど)で管理できます。
  • Unityエディタを介してエンティティプロトタイプに配置することができ、エンティティにコード内でインスタンス化することもできます。

通常のコンポーネントに関連するメソッドに加えて、シングルトン専用の特別なメソッドがいくつかあります。通常のコンポーネントと同様に、メソッドは値型とポインタを返すかどうかに基づいて SafeUnsafe に分けられています。

メソッド 戻り値 追加情報
API - Frame
SetSingleton<T> (T component,
EntityRef optionalAddTarget = default)
void シングルトンが存在しない場合にシングルトンを設定します。
-------
EntityRef(オプション)、追加するエンティティを指定します。
指定しない場合、新しいエンティティが作成されてシングルトンが追加されます。
GetSingleton<T>() T シングルトンが存在しない場合は例外をスローします。
エンティティ参照は必要なく、自動的に見つかります。
TryGetSingleton<T>(out T component) bool
true = シングルトンが存在
false = シングルトンが存在しない
シングルトンが存在しない場合は例外をスローしません。
エンティティ参照は必要なく、自動的に見つかります。
GetOrAddSingleton<T>(EntityRef optionalAddTarget = default) T シングルトンを取得して返します。
シングルトンが存在しない場合は、SetSingletonのように作成されます。
-----
EntityRef(オプション)、シングルトンを作成する必要がある場合は、追加するエンティティを指定します。
エンティティ参照が渡されない場合、新しいエンティティが作成されてシングルトンが追加されます。
GetSingletonEntityRef<T>() EntityRef シングルトンを現在保持しているエンティティを返します。
シングルトンが存在しない場合は例外をスローします。
TryGetSingletonEntityRef<T>(out EntityRef entityRef) bool
true = シングルトンが存在します。
false = シングルトンが存在しません。
シングルトンを現在保持しているエンティティを取得します。シングルトンが存在しない場合は例外をスローしません。
API - Frame.Unsafe
Unsafe.GetPointerSingleton<T>() T* シングルトンポインタを取得します。
存在しない場合は例外をスローします。
TryGetPointerSingleton<T>(out T* component) bool
true = シングルトンが存在します。
false = シングルトンが存在しません。
シングルトンポインタを取得します。
GetOrAddSingletonPointer<T>(EntityRef optionalAddTarget = default) T* シングルトンを取得または追加して返します。
シングルトンが存在しない場合は作成されます。
-----
EntityRef(オプション)、シングルトンを作成する必要がある場合は、追加するエンティティを指定します。
エンティティ参照が渡されない場合は、新しいエンティティが作成されてシングルトンが追加されます。

コンポーネントタイプ参照

ComponentTypeRef 構造体は、ランタイム中にコンポーネントをその型で参照する方法を提供します。これは、ポリモーフィズムを介して動的にコンポーネントを追加する場合に便利です。

C#

// アセットまたはプロトタイプで設定例
ComponentTypeRef componentTypeRef;

var componentIndex = ComponentTypeId.GetComponentIndex(componentTypeRef);

frame.Add(entityRef, componentIndex);

機能の追加

コンポーネントは特別な構造体であるため、C#ファイルで partial 構造体定義を書くことによってカスタムメソッドで拡張できます。たとえば、前に定義した Action コンポーネントを次のように拡張することができます:

C#

namespace Quantum
{
    public partial struct Action
    {
        public void UpdateCooldown(FP deltaTime) {
            Cooldown -= deltaTime;
        }
    }
}

リアクティブコールバック

コンポーネント特有のリアクティブコールバックが2つあります:

  • ISignalOnComponentAdd<T>: コンポーネントタイプ T がエンティティに追加されたときに呼び出されます。
  • ISignalOnComponentRemove<T>: コンポーネントタイプ T がエンティティから削除されたときに呼び出されます。

これらは、コンポーネントが追加または削除されるときにその一部を操作する必要がある場合に特に便利です(たとえば、カスタムコンポーネントでリストを割り当てたり解放したりする場合など)。

これらのシグナルを受け取るためには、単にシステム内でそれらを実装します。

コンポーネントイテレーター

特定のコンポーネントのみが必要な場合、ComponentIterator(安全)および ComponentBlockIterator(不安全)が最適です。

C#

foreach (var pair in frame.GetComponentIterator<Transform3D>()) {
    var component = pair.Component;
    component.Position += FPVector3.Forward * frame.DeltaTime;
    frame.Set(pair.Entity, component);
}

コンポーネントブロックイテレーターはポインタを介して最速のアクセスを提供します。

C#

// この構文は、エンティティの EntityRef と要求されたコンポーネントの型 T の EntityComponentPointerPair 構造体を返します。
foreach (var pair in frame.Unsafe.GetComponentBlockIterator<Transform3D>()) {
    pair.Component->Position += FPVector3.Forward * frame.DeltaTime;
}

// また、次の構文を使用して構造体を分解し、EntityRef およびコンポーネントへの直接アクセスを得ることも可能です。
foreach (var (entityRef, transform) in frame.Unsafe.GetComponentBlockIterator<Transform3D>()) {
    transform->Position += FPVector3.Forward * frame.DeltaTime;
}

フィルター

フィルターは、コンポーネントのセットに基づいてエンティティをフィルタリングし、システムが必要とするコンポーネントのみを取得する便利な方法です。フィルターは、安全(Get/Set)および不安全(ポインタ)コードの両方で使用できます。

ジェネリック

フィルターを作成するには、単にフレームが提供する Filter() API を使用します。

C#

var filtered = frame.Filter<Transform3D, PhysicsBody3D>();

ジェネリックフィルターは最大8つのコンポーネントを含むことができます。特定の without および any ComponentSet フィルターを作成することで、より具体的にすることができます。

C#

var without = ComponentSet.Create<CharacterController3D>();
var any = ComponentSet.Create<NavMeshPathFinder, NavMeshSteeringAgent>();
var filtered = frame.Filter<Transform3D, PhysicsBody3D>(without, any);

コンポーネントセット は最大8つのコンポーネントを保持できます。
without パラメータとして渡された ComponentSet は、セット内の指定されたコンポーネントのいずれかを持つエンティティをすべて除外します。 any セットは、エンティティが指定されたコンポーネントのいずれかを持っていることを確認します。指定されたコンポーネントを1つも持たない場合、そのフィルターによってエンティティは除外されます。

フィルターを介して反復処理するのは、filter.Next() を使用したwhileループと同じくらい簡単です。これにより、すべてのコンポーネントのコピーと、それにアタッチされているエンティティの EntityRef が格納されます。

C#

while (filtered.Next(out var e, out var t, out var b)) {
    t.Position += FPVector3.Forward * frame.DeltaTime;
    frame.Set(e, t);
}

注意: コンポーネントの コピー を通じて反復処理し、それに取り組んでいる状態です。したがって、新しいデータをそれぞれのエンティティに再設定する必要があります。

ジェネリックフィルターは、コンポーネントポインタで作業する機会も提供します。

C#

while (filtered.UnsafeNext(out var e, out var t, out var b)) {
    t->Position += FPVector3.Forward * frame.DeltaTime;
}

この場合、コンポーネントのデータを直接変更しています。

FilterStruct

通常のフィルターに加えて、 FilterStruct アプローチを使用することもできます。このためには、受け取りたい各コンポーネント型の__public__ プロパティを持つ構造体を最初に定義する必要があります。

C#

struct PlayerFilter
{
    public EntityRef Entity;
    public CharacterController3D* KCC;
    public Health* Health;
    public FP AccumulatedDamage;
}

ComponentSet のように、 FilterStruct は最大8つの異なるコンポーネントポインタをフィルタリングできます。

注意: FilterStruct として使用される構造体には、必ず _EntityRef_ フィールドが必要です!

FilterStruct 内の コンポーネント型 メンバーは ポインタでなければなりません; ポインタのみがフィルターによって埋められます。コンポーネントポインタに加えて、他の変数を定義することもできますが、これらはフィルターによって無視され、管理はご自分で行うことになります。

C#

var players = f.Unsafe.FilterStruct<PlayerFilter>();
var playerStruct = default(PlayerFilter);

while (players.Next(&playerStruct)) {
    // 何かを行う
}

Frame.Unsafe.FilterStruct<T>() には、オプションのComponentSets anywithout を利用してフィルターをさらに指定するオーバーロードがあります。

Back to top