Unity ECSのご紹介~EntityをSystemから生成する方法~

こんにちは。
株式会社アドグローブ ゲーム事業部エンジニアの山田です。

Unityが新たなシステムとして打ち出しているECSをご存知でしょうか。
本記事では「Unity ECSを触ったことがない」という方に向けたECSの導入として、EntityをSystemから生成する方法についてご紹介します。

Unity ECSとは

ECS(Entity Component System)はデータ指向でコーディングするためのフレームワークで、今までGameObjectとComponentで構成されてきた仕組みをEntityとComponent、Systemで構成する仕組みになります。

また、ECSはUnityで多く用いられてきたオブジェクト指向ではなく、データ指向を用いることでパフォーマンスの向上、最適化などを実現するDOTS(Data Oriented Tech Stack)を構成する要素の一つとなっています。

Entity、Conponent、Systemそれぞれの役割については下記のようになっております。

  • Entity:GameObjectと同じようにモノを表す構造体で、これに複数のComponentが紐づいていく
  • Component:Entityと紐づいて扱われるデータのことで、例えば攻撃力やHPなどの情報を持つ
  • System:各オブジェクトの振る舞いを持ち、対象となるEntityの挙動などを実装していく

    参照:https://unity.com/ja/ecsunity.com

事前準備

Unityバージョン:2022.3.26f1
ECSを使用するために下記のパッケージをPackageManagerからインストールします。

  • com.unity.entities
  • com.unity.entities.graphics
  • com.unity.physics
  • com.unity.render-pipelines.universal

SubSceneについて

ECSではSubScene内でEntityを扱っていくことになるため、Scene内にSubSceneを追加していきます。
Hierarchy上で右クリック -> New Sub Scene -> Empty Scene... でSubSceneを追加します。

次にSubScene内のEntityやComponentを見ることができるか確認してみます。
Hierarchy上のSubSceneを右クリック -> GameObject -> 3D Object -> Cube で追加したCubeを確認すると、Entity Baking PreviewにCube(Entity)とその下に紐づいたComponentが表示されます。

(※Cubeなどを追加しても、GameViewで表示されない場合は、URPのセットアップができていない可能性があるためProjectSettings -> Graphics -> Scriptable Render Pipeline Settings がURPのものになっているか確認してください。)

Entityを生成する方法

それでは、実際にEntityの生成を通して、ComponentやSystemをどのように実装していくのか紹介します。

①Entityに変換

下記コードは今回Entity生成に使用するComponentと、GameObjectをEntityに変換する部分になります。
EntitySpawnerAuthoring.cs

using Unity.Entities;
using UnityEngine;

/// <summary>スポナーが持つデータ</summary>
public struct EntitySpawnerComponent : IComponentData
{
    /// <summary>生成するEntity</summary>
    public Entity SpawnEntity { get; set; }
}

/// <summary>生成されたEntityが持つComponent</summary>
public struct SpawnEntityComponent : IComponentData
{
    // 生成されたEntityかどうかを見分けるためのComponentなので、データは無し
}

/// <summary>GameObjectをEntityに変換する</summary>
public class EntitySpawnerAuthoring : MonoBehaviour
{
    /// <summary>生成するPrefab</summary>
    [SerializeField]
    private GameObject SpawnEntityPrefab;

    class Baking : Unity.Entities.Baker<EntitySpawnerAuthoring>
    {
        public override void Bake(EntitySpawnerAuthoring authoring)
        {
            // スクリプトをアタッチしているオブジェクトをEntityに変換
            var entity = GetEntity(TransformUsageFlags.None);

            // 生成するEntityをComponentに設定
            var spawnerComponent = new EntitySpawnerComponent ()
            {
                SpawnEntity = GetEntity(authoring.SpawnEntityPrefab, TransformUsageFlags.None)
            };

            // スクリプトをアタッチしているEntityにEntitySpawnerComponent を紐づける
            AddComponent(entity, spawnerComponent);
        }
    }
}

上記をアタッチしたオブジェクトをSubSceneに追加し、SpawnEntityPrefabにCubeを追加します。

これで各Entityに持たせるComponentと、データが紐づけられるEntityの準備が整いました。

②Entityを生成するSystem

次にSystemを実装して、実際にEntityを生成します。

EntitySpawnSystem.cs

using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

/// <summary>Entityを生成するSystem</summary>
public partial struct EntitySpawnSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        // このSystemはEntitySpawnerComponentがないと動作しないように条件を追加
        state.RequireForUpdate<EntitySpawnerComponent>();
    }

    public void OnUpdate(ref SystemState state)
    {
        // スポナーコンポーネントを取得
        var spawner = SystemAPI.GetSingletonRW<EntitySpawnerComponent>();

        // Entityの数が増えすぎても邪魔なので、一旦5体生成されたら処理を抜けるようにしておきます
        // 生成されたEntityが持つコンポーネントを元にEntityの数を取得します
        var spawnEntityQuery = state.GetEntityQuery(ComponentType.ReadOnly<SpawnEntityComponent>());
        var spawnEntityCount= spawnEntityQuery .CalculateEntityCount();

        if (spawnEntityCount> 4)
        {
            return;
        }

        // Entityを指定した数だけ生成し、コンポーネントを追加しておきます
        var spawnEntities = state.EntityManager.Instantiate(spawner.ValueRO.SpawnEntity, 1, Allocator.Temp);
        state.EntityManager.AddComponent<SpawnEntityComponent>(spawnEntities);

        // 生成されたEntityをわかりやすくするために位置を調整します
        var movement = new float3(0f, 1f, 0f);
        var position = movement * spawnEntityCount;

        foreach(var entity in spawnEntities)
        {
            var transform = SystemAPI.GetComponentRW<LocalTransform>(entity);
            transform.ValueRW = LocalTransform.FromPosition(position);
        }
    }
}

ここまでを実装してプレイモードにすると、SpawnEntityPrefabに設定したオブジェクトが5つ生成、表示されるかと思います。

動作確認について

実は、プレイモードにすると生成したEntityがHierarchy上に表示されていません。
ECSにはECS用のウィンドウが別途用意されているため、そちらから現在実行中のSystemや、生成されたEntity、Componentの確認ができます。

Window -> Entities -> Hierarchy を表示すると新しくEntities Hierarchyウィンドウが表示され、ここからEntityがどのComponentを持っているのか、今どの値がComponentに入っているのかなどが確認できるようになっています。

まとめ

今回はECSの導入としてEntityをSystemから生成する方法について紹介してみました。
ECSの導入自体はパッケージのインストールだけで簡単にできますし、Entity生成の実装もComponentの用意さえできれば、SystemではComponentの参照と生成をするだけなので、意外と簡単だったかと思います。
他の機能を実装するときでも、データと処理が分かれていることを意識すれば、今までのUnityでの実装とある程度同じように実装できるかと思います。

今回紹介しておりませんが、ECS以外のDOTSを構成する要素であるBurstCompileや、C# JobSystemといった要素とも組み合わせることで、より高いパフォーマンスの実現や、より多くのオブジェクトの動作を行うことができるようになっていきます。

次回は上記のBurstCompileやC# JobSystemにも触れつつ、ECSでの当たり判定の実装について紹介したいと思います。

最後までお読みいただきありがとうございました。


参考サイト

はじめての Unity ECS - Entity Component System を使ってみよう! - YouTube


アドグローブでは、さまざまなポジションで一緒に働く仲間を募集しています!
詳細については下記からご確認ください。みなさまからのご応募お待ちしております。

採用情報