LoginSignup
5
6

More than 3 years have passed since last update.

ECSの簡単な実装と変更点について

Last updated at Posted at 2020-12-03

はじめに

ECSの登場から2年半、qiitaにも情報が集積して手を付けやすくなりましたね。
今では情報の流れもゆっくりになり、ECSの更新が続き過去のコードにも変更が必要になりました。
そこで本記事では今のECSで簡単なコードを紹介して、過去のコードからの変更点をお見せできればと思います。

環境

Unityバージョン:2020.1.12f1
Entities: 0.16.0-preview 21
Hybrid Renderer : 0.10.0-preview 21

インストール

Unity2020.1から一部のパッケージがPackageManagerに表示されなくなりました

EntitiesやHybridRendererも表示されないので、こちらは手動でインストールする必要があります。

まずはPackageManagerを開き、「+」-「Add package from git URL...」で"com.unity.entities"を指定します。
同様に"com.unity.rendering.hybrid"を指定してHybridRendererをインストールします。

これでECSを使う準備ができました。

まずはシンプルな実装

まずはMonoBehaviourからEntityを生成するときの変更部分を見ていきましょう

ECSを扱うにはEntityManagerが外せないのですが、以前はWorld.Activeから取得していました。(独自Worldを除く)
現在はActiveが無くなり、DefaultGameObjectInjectionWorldに代わっています。

sample.cs
        // 以前
        var em = World.Active.EntityManager;

        // いま
        var em = World.DefaultGameObjectInjectionWorld.EntityManager;

Hybrid Rendererを使うときに、RenderBoundsが必要になりました。
RenderBoundsはAABBを持つクラスですが、SetComponentDataなどでパラメータを設定する必要はありません。
RenderBoundsが無い場合はエラーにはなりませんが描画されなくなりますので注意しましょう。

sample.cs
        // 以前
        var archetype = em.CreateArchetype(
                                typeof(Translation),
                                typeof(LocalToWorld),
                                typeof(RenderMesh));

        // いま
        var archetype = em.CreateArchetype(
                                typeof(Translation),
                                typeof(LocalToWorld),
                                typeof(RenderMesh),
                                typeof(RenderBounds));

ジョブの実行

少し前のECSではIJobForEachやIJobForEachWithEntityを使っていましたが、こちらは非推奨となり今後は削除されることになりました。
代わりにEntities.ForEachかIJobChunkを使用する必要があります。(ほとんどのパターンでEntities.ForEachの方が使いやすいかと思います)

Entities.ForEachの場合

下記はEntities.ForEachを使用して、3秒おきにEntityを生成するコードです。

SpawnerForEach.cs
public struct SpawnerComponent : IComponentData
{
    public float timer;
}
public class SpawnerForEach : JobComponentSystem
{
    private EntityArchetype m_spawnArchetype;
    private BeginSimulationEntityCommandBufferSystem m_commandBufferSystem;

    protected override void OnCreate()
    {
        // この辺りは以前と同じ
        m_commandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        m_spawnArchetype = em.CreateArchetype(typeof(Translation), typeof(LocalToWorld), typeof(RenderMesh), typeof(RenderBounds));
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps)
    {
        float dt = Time.DeltaTime;

        // ジョブ内で使用できるようにAsParallelWriter()を呼ぶ
        var commandBuffer = m_commandBufferSystem.CreateCommandBuffer().AsParallelWriter();
        var archeType = m_spawnArchetype;

        // BurstCompileを有効にしてCustom_ForEachという名前でジョブを実行
        inputDeps = Entities.
                        WithName("Custom_ForEach").
                        WithBurst().
                        ForEach((ref Translation pos, ref SpawnerComponent spawner) =>
        {
            spawner.timer += dt;
            if (spawner.timer >= 3.0f)
            {
                spawner.timer = 0.0f;

                // Entityを生成.
                var e = commandBuffer.CreateEntity(0, archeType);
                commandBuffer.SetComponent<Translation>(0, e, new Translation
                {
                    Value = new float3(0, 0, 0),
                });
            }
        }).Schedule( inputDeps);

        return inputDeps;
    }
}

EntityCommandBuffer.ToConcurrent()は非推奨になり代わりにAsParallelWriter()になりました。
余談ですが、WithName()に半角スペースが入っているとエラーになるので"_"などで代用しましょう。

IJobChunkの場合

先ほどの3秒ごとにEntityを生成するジョブをIJobChunkで書くと以下のようになります。

SpawnerJobChunk.cs
public class Spawner : JobComponentSystem
{
    private EntityQuery m_query;
    private EntityArchetype m_spawnArchetype;
    private BeginSimulationEntityCommandBufferSystem m_commandBufferSystem;

    [BurstCompile]
    struct SpawnJob : IJobChunk
    {
        public float dt;

        [ReadOnly]public ComponentTypeHandle<Translation> transHandle;
        public ComponentTypeHandle<SpawnerComponent> spawnerHandle;

        public EntityArchetype spawnArchetype;
        public EntityCommandBuffer.ParallelWriter commandBuffer;

        public void Execute( ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            var translations = chunk.GetNativeArray(transHandle);
            var spawners = chunk.GetNativeArray(spawnerHandle);

            for( int i = 0; i < chunk.Count; ++i)
            {
                var translation = translations[i];
                var spawner = spawners[i];

                spawner.timer += dt;
                if( spawner.timer >= 3.0f)
                {
                    spawner.timer = 0.0f;

                    var e = commandBuffer.CreateEntity(0,spawnArchetype);
                    commandBuffer.SetComponent<Translation>(0, e, new Translation
                    {
                        Value = new float3(0, 0, 0),
                    });
                }

                spawners[i] = spawner;
            }
        }
    }

    protected override void OnCreate()
    {
        // この辺りは変わらず
        m_query = GetEntityQuery( ComponentType.ReadOnly<Translation>(), ComponentType.ReadOnly<SpawnerComponent>());
        m_commandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        m_spawnArchetype = em.CreateArchetype(typeof(Translation), typeof(LocalToWorld), typeof(RenderMesh), typeof(RenderBounds));
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps)
    {
        var cmdBuffer = m_commandBufferSystem.CreateCommandBuffer().AsParallelWriter();

        var job = new SpawnJob
        {
            transHandle = GetComponentTypeHandle<Translation>(),
            spawnerHandle = GetComponentTypeHandle<SpawnerComponent>(),
            spawnArchetype = m_spawnArchetype,
            commandBuffer = cmdBuffer,
            dt = Time.DeltaTime,
        };

        inputDeps = job.Schedule(m_query, inputDeps);
        return inputDeps;
    }
}

ArchetypeChunkComponentTypeがリネームされ、ComponentTypeHandleになりました。

簡単な実装ですので変更点は少ないですが以上となります。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6