本書は Unity Entities 101 公式ドキュメントの中国語翻訳版です
エンティティとコンポーネント
エンティティは、GameObject の軽量なアンマネージド型の代替手段です。
- GameObject とは異なり、エンティティはマネージドオブジェクトではなく、単なる一意の識別番号です。
- エンティティのコンポーネントは通常、構造体の値です。
- エンティティのコンポーネントには、
MonoBehaviourの「イベント関数」(OnUpdate、OnStartなど)に相当する機能はありません。 - エンティティのコンポーネント型はメソッドを持つことができますが、通常は推奨されません。
- 1 つのエンティティは、特定の型に対して 1 つのコンポーネントしか持つことができません。例:1 つのエンティティが 2 つの Foo 型コンポーネントを同時に持つことはできません。
- エンティティには組み込みの親子関係の概念はありません。代わりに、標準の
Parentコンポーネントが別のエンティティへの参照を保持し、エンティティのトランスフォーム階層を構築できるようにします。
基本的なコンポーネント型は、IComponentData を実装する構造体を作成することで定義されます。
// an entity component type with two fields
public struct Health : IComponentData
{
public int HitPoints;
public float ArmourRating:
}
IComponentData 構造体はアンマネージド型である必要があるため、マネージドフィールド型を含めることはできません。許可されるフィールド型は次のとおりです:
- Blittable 型
- bool
- char
BlobAssetReference<T>(Blob データ構造への参照)Collections.FixedString(固定長の文字バッファ)Collections.FixedList- 固定配列(アンセーフコンテキストでのみ使用可能)
- 上記と同じ制限を満たすその他の構造体型
ワールドとエンティティマネージャ
エンティティの ID 番号は、それが属するワールド内でのみ一意です。
ワールドはまた、システムの集合を持ちます。システムはメインスレッドで実行されるコードの単位で、通常は毎フレーム実行されます。ワールド内のエンティティは、通常、そのワールドのシステムとそのシステムがスケジュールしたジョブからのみアクセスできます(ただし、これは強制ではありません)。
ワールド内のエンティティは、そのワールドの EntityManager を介して作成、破棄、変更されます。主なメソッドは次のとおりです:
CreateEntity():新しいエンティティを作成します。Instantiate():新しいエンティティを作成し、既存のエンティティのすべてのコンポーネントをコピーします。DestroyEntity():既存のエンティティを破棄します。AddComponent<T>():既存のエンティティに型 T のコンポーネントを追加します。RemoveComponent<T>():既存のエンティティから型 T のコンポーネントを削除します。HasComponent<T>():エンティティが現在型 T のコンポーネントを持っている場合に true を返します。GetComponent<T>():エンティティの型 T のコンポーネントの値を取得します。SetComponent<T>():エンティティの型 T のコンポーネントの値を上書きします。
アーキタイプ
アーキタイプは、ワールド内におけるコンポーネント型の一意の組み合わせを表します。
- コンポーネント型 A、B、C をすべて持つエンティティは同じアーキタイプに格納され、
- コンポーネント型 A と B のみを持ち C を持たないエンティティは別のアーキタイプに格納され、
- コンポーネント型 B と D を持つすべてのエンティティはさらに別のアーキタイプに格納されます。
実際には、エンティティにコンポーネントを追加または削除すると、そのエンティティが属するアーキタイプが変更されるため、EntityManager はエンティティを新しいアーキタイプに移動する必要があります。
エンティティにコンポーネントを追加または削除すると、EntityManager はそのエンティティを対応するアーキタイプに移動します。たとえば、あるエンティティが当初 X、Y、Z の 3 つのコンポーネント型を持っていた場合、その Y コンポーネントを削除すると、EntityManager はそのエンティティを X と Z のみのアーキタイプに移動し、X と Z の値は保持されます。そのようなアーキタイプが現在のワールドに存在しない場合、EntityManager は自動的に作成します。
注記:多数のエンティティをアーキタイプ間で頻繁に移動すると、そのオーバーヘッドが無視できなくなる可能性があります。
アーキタイプは、エンティティの作成と変更時に EntityManager によって自動的に生成されるため、明示的に作成する必要はありません。
アーキタイプ内のすべてのエンティティが削除された場合でも、そのアーキタイプは、所属するワールドが破棄されるまで存在し続けます。
チャンク
アーキタイプ内のエンティティは、そのアーキタイプに属する 16KiB のメモリブロック(チャンクと呼ばれます)に格納されます。各チャンクには最大 128 のエンティティが格納されます(アーキタイプ内の各エンティティが必要とする容量が 16KiB/128 を超える場合、チャンクあたりの最大エンティティ数は減少します)。
各型のエンティティ ID とコンポーネントは、チャンク内のそれぞれ独立した配列に格納されます。たとえば、コンポーネント型 A と B を持つエンティティのアーキタイプでは、各チャンクに 3 つの配列が格納されます:
- エンティティ ID 用の配列
- A コンポーネント用の配列
- B コンポーネント用の配列
チャンク内の 1 番目のエンティティの ID とコンポーネントはこれらの配列のインデックス 0 に、2 番目はインデックス 1 に、3 番目はインデックス 2 に格納され、以降同様に続きます。
チャンクの配列は常に密に保たれます:
- 新しいエンティティがチャンクに追加されると、配列の最初の空きインデックスに格納されます。
- エンティティがチャンクから削除される(エンティティが破棄されるか、別のアーキタイプに移動する)と、チャンク内の最後のエンティティが移動して空いた位置を埋めます。
チャンクの作成と破棄は EntityManager によって処理されます:
- 既存のチャンクがすべて満杯のアーキタイプにエンティティが追加された場合にのみ、新しいチャンクが作成されます。
- チャンク内の最後のエンティティが削除された場合にのみ、そのチャンクは破棄されます。
チャンク内でエンティティを追加、削除、または移動する EntityManager の操作は、すべて構造変更と呼ばれます。構造変更はメインスレッドでのみ実行でき、ジョブ内では実行できません(ただし、後述する EntityCommandBuffer を使用することで回避できます)。
クエリ
EntityQuery は、指定されたコンポーネント型セットを持つすべてのエンティティを効率的に検索します。たとえば、コンポーネント型 A と B を持つすべてのエンティティを検索するクエリは、アーキタイプが他にどのようなコンポーネント型を含んでいても、A と B を含むすべてのアーキタイプのチャンクを収集します。このようなクエリは、コンポーネント型 A と B を持つエンティティだけでなく、たとえば A、B、C を持つエンティティも一致します。
注記:クエリに一致するアーキタイプは、次に新しいアーキタイプがワールドに追加されるまでキャッシュされます。ワールド内の既存のアーキタイプセットはプログラムのライフサイクルの早い段階で安定する傾向があるため、このキャッシュ機構によりクエリのオーバーヘッドが大幅に削減されることがよくあります。
クエリは、一致から除外するコンポーネント型を指定することもできます。たとえば、「コンポーネント型 A と B を持ち、コンポーネント型 C を持たないすべてのエンティティを検索する」クエリは、A と B を持つエンティティと一致しますが、A、B、C をすべて持つエンティティとは一致しません。
エンティティ ID
エンティティ ID は Entity 構造体で表され、index(インデックス)と version(バージョン)の 2 つの整数パラメータを持ちます。
ID でエンティティを検索するために、ワールドの EntityManager はエンティティのメタデータ配列を維持しています。エンティティのインデックスは、このメタデータ配列内のスロットを表し、そのスロットにはエンティティが存在するチャンクへのポインタと、チャンク内でのインデックスが格納されています。インデックスにエンティティが存在しない場合、そのインデックスのチャンクポインタは null です。たとえば、現在インデックス 1、2、5 にエンティティが存在しない場合、これらのスロットのチャンクポインタはすべて null になります:

エンティティのバージョン番号により、エンティティが破棄された後でもエンティティインデックスを再利用できます。エンティティが破棄されると、そのインデックスに格納されているバージョン番号が増加するため、ID のバージョン番号がインデックスに格納されているバージョン番号と一致しない場合、その ID は破棄されたか、存在したことすらないエンティティを指していることになります。
タグコンポーネント
フィールドを持たない IComponentData 構造体は、タグコンポーネントと呼ばれます。タグコンポーネントはデータを格納しませんが、他のコンポーネント型と同様にエンティティに追加・削除でき、クエリに役立ちます。たとえば、モンスターを表すすべてのエンティティに Monster タグコンポーネントを持たせれば、Monster コンポーネント型をクエリすることで、すべてのモンスターエンティティを一致させることができます。
// a tag component
public struct Monster : IComponentData
{
}
動的バッファコンポーネント
DynamicBuffer は、可変長の配列コンポーネント型です。
// a dynamic buffer component type
public struct Waypoint : IBufferElementData
{
public float3 Value:
}
各エンティティのバッファは、長さ、容量、ポインタを格納します:
Lengthはバッファ内の要素数を示します。0 から始まり、バッファに値を追加するごとに増加します。Capacityはバッファのストレージサイズを示します。初期値は内部バッファ容量に一致します(デフォルトは 128/sizeof(T) ですが、IBufferElementData構造体のInternalBufferCapacity属性で指定できます)。容量を設定するとバッファサイズが変更されます。- ポインタはバッファの内容の位置を示します。初期状態では null であり、内容がチャンク内に直接格納されていることを意味します。設定された容量が内部バッファ容量を超えると、チャンク外により大きな新しい配列が割り当てられ、内容がその外部配列にコピーされ、ポインタがその新しい配列を指すようになります。バッファの長さが外部配列の容量を超えると、バッファの内容はチャンク外のさらに別の大きな新しい配列にコピーされ、古い配列は解放されます。バッファを縮小することもできます。
EntityManager がチャンク自体を破棄するとき、内部バッファ容量と外部容量(存在する場合)は解放されます。
あるいは、内部容量を 0 に設定することもできます。
EntityManager は、動的バッファに対して以下の主要なメソッドを提供します:
AddComponent<T>():エンティティに型 T のコンポーネントを追加します。T には動的バッファコンポーネント型も指定できます。AddBuffer<T>():エンティティに型 T の動的バッファコンポーネントを追加し、新しいバッファをDynamicBuffer<T>として返します。RemoveComponent<T>():エンティティから型 T のコンポーネントを削除します。T には動的バッファコンポーネント型も指定できます。HasBuffer<T>():エンティティが現在型 T の動的バッファコンポーネントを持っている場合に true を返します。GetBuffer<T>():エンティティの型 T の動的バッファコンポーネントをDynamicBuffer<T>として返します。
DynamicBuffer<T> は、単一のエンティティの型 T の動的バッファコンポーネントを表します。主要なプロパティとメソッドは次のとおりです:
Length:バッファの長さを取得または設定します。Capacity:バッファの容量を取得または設定します。Item[Int32]:指定されたインデックスの要素を取得または設定します。Add():要素をバッファの末尾に追加し、必要に応じてサイズを調整します。Insert():指定されたインデックスに要素を挿入し、必要に応じてサイズを調整します。RemoveAt():指定されたインデックスの要素を削除します。注記:構造変更操作を実行すると
DynamicBufferが「無効」になり、その後そのDynamicBufferを使用すると例外がスローされます。構造変更後に再びバッファを使用するには、再度取得する必要があります。
システム
システムは、エンティティワールドに属するコードの単位で、メインスレッドで実行されます(通常は毎フレーム 1 回)。通常、システムは自身の属するワールドのエンティティにのみアクセスしますが、これは強制ではありません。
システムは、ISystem インターフェースを実装する構造体として定義され、3 つの主要なメソッドを持ちます:
OnUpdate():通常は毎フレーム呼び出されます(システムが属するシステムグループによって異なります)。OnCreate():最初のOnUpdate呼び出し前、およびシステムが再開されたときに呼び出されます。OnDestroy():システムが破棄されるときに呼び出されます。注記:これらのメソッドにはデフォルトの空の実装があるため、不要な場合はシステムから省略できます。例:
OnCreateメソッドの本体を空のままにする場合は、メソッド全体を省略できます。
システムの Enabled プロパティが false に設定されている場合、その更新はスキップされます。
システムは、追加で ISystemStartStop インターフェースを実装することもできます。このインターフェースには以下のメソッドがあります:
OnStartRunning():最初のOnUpdate呼び出し前、およびシステムが再有効化されたとき(システムのEnabledプロパティがfalseからtrueに変更されたとき)に呼び出されます。OnStopRunning():OnDestroyの前、およびシステムが無効化されたとき(システムのEnabledプロパティがtrueからfalseに変更されたとき)に呼び出されます。
システムグループとシステム更新順序
ワールド内のシステムは、システムグループに編成されます。各システムグループは、子要素(システムおよび他のシステムグループ)の順序付きリストを持ち、デフォルトでは、各システムグループはソート順に子要素の OnUpdate を呼び出します。つまり、システムグループはシステムの更新順序を決定する階層を形成します。
- システムグループは、
ComponentSystemGroupを継承するクラスとして定義されます。 - システムグループが更新されるとき、そのグループは通常、ソート順に子要素を更新しますが、グループの更新メソッドをオーバーライドすることでこのデフォルト動作を変更できます。たとえば、
FixedStepSimulationGroupはカスタム更新動作を持ち、毎フレーム 0 回以上子要素を更新して、固定の更新間隔を近似します。 - 子要素がグループに追加またはグループから削除されると、グループの子要素は並べ替えられます。
UpdateInGroup属性を使用して、子要素をシステムグループに追加します。この属性を使用しない場合、システムとシステムグループはデフォルトでSimulationSystemGroupに追加されます。UpdateBefore属性とUpdateAfter属性を使用して、グループ内の子要素間の相対的なソート順序を決定できます。たとえば、FooSystem が[UpdateBefore(typeof(BarSystem))]属性を持つ場合、FooSystem はソート順で BarSystem より前の位置に配置されます。ただし、FooSystem と BarSystem が同じシステムグループに属していない場合、この属性は警告を発する以外に効果はありません。- グループのある子要素のソート属性に矛盾がある場合(例:子 A が子 B の前に更新されるようにマークされているが、子 B も子 A の前に更新されるようにマークされている場合)、そのグループの子要素をソートする際に例外がスローされます。
// A system group.
// Because this group doesn't override OnUpdate, it follows the default
// behaviour: its children will be updated in their sorted order.
public class MonsterSystemGroup : ComponentSystemGroup
{
}
// a child system of the MonsterSystemGroup
[UpdateInGroup(typeof(MonsterSystemGroup)]
public struct VampireSystem : ISystem
{
...
}
ワールドとシステムの作成
実行モードに入ると、デフォルトの自動ブートストラッププロセスにより、3 つのシステムグループを持つデフォルトのワールドが作成されます:
InitializationSystemGroup:Unity プレイヤーループの初期化フェーズの最後に更新されます。SimulationSystemGroup:Unity プレイヤーループのUpdateフェーズの最後に更新されます。通常、ゲームロジックを配置する場所です。PresentationSystemGroup:Unity プレイヤーループのPreLateUpdateフェ