見出し画像

Unity DOTS 入門 (12) - ECB

ECB」を使って、「エンティティの作成」などJob処理中に実行できない命令を実行する方法を説明します。

・Unity 2019.3.14.f1
・Entities 0.11.1

1. ECB

ECB」(Entity Command Buffer)は、「エンティティの作成」など、Job処理中に実行できない命令を、Job完了後にメインスレッドで実行するために保持するバッファです。

チャンク構造の変更が必要な命令は全て、Job処理中に実行できない命令になります。

2. ECBシステム

デフォルトワールドの3つの実行タイミングには、3つのシステムグループがあります。各システムグループ内の最初と最後には、「ECBシステム」(Entity Command Buffer System)があります。

画像3

「システム」はこの「ECBシステム」から「ECB」を取得し、そこにJob処理中に実行できない命令を追加します。

3. ECBで実行可能な命令

ECBで実行可能な命令は、EntityCommandBufferのメソッドに定義されています。

・AddBuffer<T>(Entity) : バッファの追加。
・AddComponent<T>(Entity, T) : コンポーネントの追加。
・AddComponent(EntityQuery, ComponentType) : コンポーネントの追加。
・AddSharedComponent<T>(Entity, T) : 共有コンポーネントの追加。
・AddSharedComponent<T>(EntityQuery, T) : 共有コンポーネントの追加。
・CreateEntity(EntityArchetype) : エンティティの生成。
・DestroyEntity(Entity) : エンティティの破棄。
・DestroyEntity(EntityQuery) : エンティティの破棄。
・RemoveComponent<T>(Entity) : コンポーネントの削除。
・RemoveComponent(Entity, ComponentType) : コンポーネントの削除。
・RemoveComponent(EntityQuery, ComponentType) : コンポーネントの削除。
・SetBuffer<T>(Entity) : バッファの更新。
・SetComponent<T>(Entity, T) : コンポーネントの更新。
・SetSharedComponent<T>(Entity, T) : 共有コンポーネントの更新。
・Instantiate(Entity) : インスタンスの生成。
・ToConcurrent() : 並列ECBに変換。
・Playback(EntityManager) : 命令の実行。
・Dispose() : 破棄。

4. DOTSパッケージのインストール

Unity DOTS 入門 (1)」と同様。

5. GameObjectのプレハブの作成

立方体(Cube)の「GameObjectプレハブ」を作成します。

(1) Hierarchyウィンドウの「+ → 3D Object → Cube」で「Cube」を生成。(2) 「Cube」をProjectウィンドウにドラッグ&ドロップし、プレハブ「Cube」を生成。
(3) Hierarchyウィンドウの「Cube」を削除。

6. ECBの実装

スクリプト「Spawner」を作成し、「ECB」を使って「エンティティ」を生成します。

(1) Hierarchyウィンドウの「+ → Create Empty」で空のゲームオブジェクトを作成し、名前に「Spawner」を指定。
(2) Projectウィンドウにスクリプト「Spawner」を生成。

using Unity.Entities;

public struct Spawner : IComponentData
{
    public Entity prefab;
}

(3) Projectウィンドウにスクリプト「SpawnerAuthoring」を生成。

using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

[RequiresEntityConversion]
[ConverterVersion("joe", 1)]
public class SpawnerAuthoring : MonoBehaviour,
    IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
    public GameObject prefab; // GameObjectプレハブ

    // Entityプレハブに変換するGameObjectプレハブを指定
    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        referencedPrefabs.Add(prefab);
    }

    // GameObjectをエンティティに変換
    public void Convert(Entity entity, EntityManager entityManager,
        GameObjectConversionSystem conversionSystem)
    {
        // Spawnerコンポーネントの追加
        var spawner = new Spawner
        {
            prefab = conversionSystem.GetPrimaryEntity(prefab)
        };
        entityManager.AddComponentData(entity, spawner);
    }
}

◎ IDeclareReferencedPrefabs
「IDeclareReferencedPrefabs」を継承し、DeclareReferencedPrefabs()でEntityプレハブに変換するGameObjectプレハブを指定することで、conversionSystem.GetPrimaryEntity()でEntityプレハブを取得できるようになります。

(4) 「Spawner」にスクリプト「SpawnerAuthoring」を追加し、「Prefab」に「Cube」をドラッグ&ドロップ。

画像1

(5) Projectウィンドウにスクリプト「SpawnerSystem」を生成。

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

[UpdateInGroup(typeof(SimulationSystemGroup))]
public class SpawnerSystem : SystemBase
{
    // ECBシステム
    BeginInitializationEntityCommandBufferSystem entityCommandBufferSystem;

    protected override void OnCreate()
    {
        // ECBシステムの取得
        entityCommandBufferSystem =
            World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        // ECBを取得し、並列ECBに変換
        var commandBuffer = entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();

        // 並列Jobの実行
        Entities
            .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
            .ForEach((Entity entity, int entityInQueryIndex, in Spawner spawnerFromEntity, in LocalToWorld location) =>
        {
            // エンティティの生成
            var instance = commandBuffer.Instantiate(entityInQueryIndex, spawnerFromEntity.prefab);

            // コンポーネントの設定
            var position = new float3(0f, 2f, 0f);
            commandBuffer.SetComponent(entityInQueryIndex, instance, new Translation {Value = position});

            // エンティティの破棄
            commandBuffer.DestroyEntity(entityInQueryIndex, entity);
        }).ScheduleParallel();

        // 指定したJob完了後にECBに登録した命令を実行
        entityCommandBufferSystem.AddJobHandleForProducer(Dependency);
    }
}

◎ ECBシステムの取得
ECBシステム」を取得するには、World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>()を使います。

◎ ECBの取得
ECB」を取得するには、entityCommandBufferSystem.CreateCommandBuffer()を使います。

◎ 並列ECB
「並列Job」(ScheduleParallel())から「ECB」を使用する場合は、ToConcurrent()を呼び出して、「並列ECB」に変換する必要があります。

var commandBuffer = entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();

さらに、命令の順番が分散方法に依存しないようにするため、entityQueryIndexを各命令に渡す必要があります。

// エンティティの生成
var instance = commandBuffer.Instantiate(entityInQueryIndex, spawnerFromEntity.prefab);

// コンポーネントの設定
var position = new float3(0f, 2f, 0f);
commandBuffer.SetComponent(entityInQueryIndex, instance, new Translation {Value = position});

// エンティティの破棄
commandBuffer.DestroyEntity(entityInQueryIndex, entity);

◎ 指定したJob完了後にECBに登録した命令を実行
指定したJob完了後にECBに登録した命令を実行します。Jobの指定は、を使います。

entityCommandBufferSystem.AddJobHandleForProducer(Dependency);

(6) 実行。
立方体が生成されることを確認できます。

画像2


この記事が気に入ったらサポートをしてみませんか?