見出し画像

Unity DOTS 入門 (6) - チャンク単位でのコンポーネントデータへのアクセス

Unity DOTS 入門 (1)」では、エンティティ単位でコンポーネントデータにアクセスしました。今回はチャンク単位でコンポーネントデータにアクセスします。

・Unity 2019.3.14.f1
・Entities 0.11.1

1. チャンク単位でのコンポーネントデータへのアクセス

チャンク単位でコンポーネントデータにアクセスします。

(1) スクリプト「RotatorSystem」を以下のように変更。

・エンティティ単位でコンポーネントデータにアクセス

using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using System;

public class RotatorSystem : SystemBase
{
    protected override void OnUpdate()
    {
        float deltaTime = Time.DeltaTime;

        // エンティティ単位でのコンポーネントデータへのアクセス
        Entities
            .ForEach((ref Rotation rotation, in Rotator rotator) =>
            {
                rotation.Value = math.mul(
                    math.normalize(rotation.Value),
                    quaternion.Euler(0f, math.radians(rotator.speed * deltaTime), 0f));
            })
            .ScheduleParallel();
    }
}


・チャンク単位でコンポーネントデータにアクセス

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

public class RotatorSystem : SystemBase
{
    EntityQuery query; 

    protected override void OnCreate()
    {
        // クエリの生成
        query = GetEntityQuery(
            typeof(Rotation),
            ComponentType.ReadOnly<Rotator>());
    }   

    protected override void OnUpdate()
    {
        // チャンク内のアクセス対象となる基本コンポーネント種別の準備
        var rotationType = GetArchetypeChunkComponentType<Rotation>();
        var rotatorType = GetArchetypeChunkComponentType<Rotator>(true);

        // Jobの生成
        var job = new RotatorJob()
        {
            RotationType = rotationType,
            RotatorType = rotatorType,
            DeltaTime = Time.DeltaTime
        };

        // Jobのスケジューリング
        Dependency = job.Schedule(query, Dependency);
    }
    
    // チャンク単位でコンポーネントデータにアクセスするJobの定義
    [BurstCompile]
    struct RotatorJob : IJobChunk
    {
        public float DeltaTime;
        public ArchetypeChunkComponentType<Rotation> RotationType;
        [ReadOnly] public ArchetypeChunkComponentType<Rotator> RotatorType;

        // チャンク単位でのコンポーネントデータへのアクセス
        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            // コンポーネントデータ群の取得
            var chunkRotations = chunk.GetNativeArray(RotationType);
            var chunkRotators = chunk.GetNativeArray(RotatorType);

            // コンポーネントデータの反復
            for (var i = 0; i < chunk.Count; i++)
            {
                // コンポーネントデータの取得
                var rotation = chunkRotations[i];
                var rotator = chunkRotators[i];

                // 回転
                chunkRotations[i] = new Rotation
                {
                    Value = math.mul(
                        math.normalize(rotation.Value),
                        quaternion.Euler(0f, math.radians(rotator.speed * DeltaTime), 0f))
                };
            }
        }
    }  
}

◎ クエリの生成
エンティティを検索するには、エンティティの条件を指定する「クエリ」を生成します。「クエリ」を生成するには、GetEntityQuery()を使います。

今回は、「Rotation」コンポーネント(読み書き可)と「Rotator」コンポーネント(読み込み専用)を持つエンティティを検索対象とします。

query = GetEntityQuery(
    typeof(Rotation), // Rotationコンポーネント(読み書き可)            
    ComponentType.ReadOnly<Rotator>()); // Rotatorコンポーネント(読み込み専用)

◎ ComponentType
どんなコンポーネントであるかは「ComponentType」で指定します。以下の書式で「コンポーネント名」と「読み書き可 /  読み込み専用」を指定します。

ComponentType.ReadWrite<Rotation>() or type(Rotation): 読み書き可なRotationコンポーネント
・ ComponentType.ReadOnly<Rotation>() : 読み込み専用なRotationコンポーネント

◎ EntityQueryDesc
GetEntityQuery()
に「ComponentType」の代わりに「EntityQueryDesc」を指定することで、より詳細な検索条件を指定することができます。

query = GetEntityQuery(new EntityQueryDesc()
{
    All = new ComponentType[] {typeof(A), typeof(B)},
    Any = new ComponentType[] {typeof(C), typeof(D)},
    None = new ComponentType[] {typeof(E)},
});
・All :  全て含む
・Any : いずれかを含む
・None : 含まない

◎ ArchetypeChunkComponentType
ArchetypeChunkComponentType」は、チャンク内のアクセス対象となる基本コンポーネント種別です。

今回は、「Rotation」コンポーネント(読み書き可)と「Rotator」コンポーネント(読み込み専用)のデータをアクセス対象としています。引数のtrue/falseは「読み込み専用」かどうかで、未指定時はfalseです。

var rotationType = GetArchetypeChunkComponentType<Rotation>();
var rotatorType = GetArchetypeChunkComponentType<Rotator>(true);

基本コンポーネント以外をアクセス対象として指定することも可能です。

・エンティティ : ArchetypeChunkEntityType
・基本コンポーネント : ArchetypeChunkComponentType<T>
・共有コンポーネント : ArchetypeChunkSharedComponentType<T>
・動的バッファコンポーネント : ArchetypeChunkBufferType<T>

◎ Dependency
Dependency」は、システムが依存する(並列実行できない)Jobの「JobHandle」です。

引数のないSchedule ()やScheduleParallel()によってJobをスケジュールした場合は、 自動的にそのJobの依存対象にDependencyが設定され、そのJobのJobHandleでDependencyが更新されます。そのため、システム内で引数のないSchedule()やScheduleParallel()を複数回実行した場合、記述した順番通りに実行されます。

◎ チャンク単位でコンポーネントデータにアクセスするJobの定義
チャンク単位でコンポーネントデータにアクセスするJobを定義するには、はじめに、「IJobChunk」を継承した構造体(struct)を準備します。次に、Jobの処理で使う変数をフィールドに準備します。最後に、Execute()でJobの処理を実装します。

IJobChunkのArchetypeChunkComponentTypeを保持するフィールドは、次のとおりです。「Rotator」は読み込み専用なので、[ReadOnly]を付加しています。

public ArchetypeChunkComponentType<Rotation> RotationType;
[ReadOnly] public ArchetypeChunkComponentType<Rotator> RotatorType;

Execute()の書式は、次のとおりです。

void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
・ chunk : チャンク
・ chunkIndex : チャンクの通し番号
・ firstEntityIndex : チャンクの最初のエンティティの通し番号

チャンクからコンポーネントデータ群を取得するには、chunk.GetNativeArray()を使います。コンポーネント群の数はchunk.Countで取得できます。

(3) 実行。
Unity DOTS 入門 (1)」と同様に実行できます。

【おまけ】 ECSのデータ構造

◎ ECS
ECS」は、ID(エンティティ)、データ(コンポーネント)、および動作(システム)を分離します。

画像1

この図では、「システム」が、「Translation」コンポーネントと「Rotation」コンポーネントを読み取り、それらを乗算して、「LocalToWorld」コンポーネントを更新しています。

◎ アーキタイプ
アーキタイプ」は、コンポーネント種別の組み合わせのことです。

画像2

この図では、「エンティティA」「エンティティB」は「アーキタイプM」を共有していますが、「エンティティC」は「アーキタイプN」を持っています。

実行時にコンポーネントの追加・削除を行うと、「アーキタイプ」は自動的に変更になります。例えば、「エンティティB」から「Renderer」コンポーネントを削除すると、「アーキタイプN」に変更になります。

◎ チャンク
「ECS」は、「チャンク」と呼ばれるメモリ領域(必ず16KB)を持っています。「ECS」は、エンティティのデータを「アーキタイプ」別に「チャンク」に格納します。つまり、「チャンク」の中には同じ「アーキタイプ」のエンティティのデータ群が格納されることになります。

「チャンク」がいっぱいになると、「ECS」は同じ「アーキタイプ」の新規「チャンク」を追加します。コンポーネントを追加・削除すると、エンティティの「アーキタイプ」が変更され、「ECS」はそのエンティティのデータを別の「チャンク」に移動します。

画像3



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