はじめに
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に[表示されなくなりました]
(https://helpdesk.unity3d.co.jp/hc/ja/articles/900002171066-Unity-2020-1-%E3%81%8B%E3%82%89%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%81%A7%E7%99%BA%E8%A6%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95)。
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に代わっています。
// 以前
var em = World.Active.EntityManager;
// いま
var em = World.DefaultGameObjectInjectionWorld.EntityManager;
Hybrid Rendererを使うときに、RenderBoundsが必要になりました。
RenderBoundsはAABBを持つクラスですが、SetComponentDataなどでパラメータを設定する必要はありません。
RenderBoundsが無い場合はエラーにはなりませんが描画されなくなりますので注意しましょう。
// 以前
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を生成するコードです。
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で書くと以下のようになります。
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になりました。
簡単な実装ですので変更点は少ないですが以上となります。