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(エンティティ)、データ(コンポーネント)、および動作(システム)を分離します。
この図では、「システム」が、「Translation」コンポーネントと「Rotation」コンポーネントを読み取り、それらを乗算して、「LocalToWorld」コンポーネントを更新しています。
◎ アーキタイプ
「アーキタイプ」は、コンポーネント種別の組み合わせのことです。
この図では、「エンティティA」「エンティティB」は「アーキタイプM」を共有していますが、「エンティティC」は「アーキタイプN」を持っています。
実行時にコンポーネントの追加・削除を行うと、「アーキタイプ」は自動的に変更になります。例えば、「エンティティB」から「Renderer」コンポーネントを削除すると、「アーキタイプN」に変更になります。
◎ チャンク
「ECS」は、「チャンク」と呼ばれるメモリ領域(必ず16KB)を持っています。「ECS」は、エンティティのデータを「アーキタイプ」別に「チャンク」に格納します。つまり、「チャンク」の中には同じ「アーキタイプ」のエンティティのデータ群が格納されることになります。
「チャンク」がいっぱいになると、「ECS」は同じ「アーキタイプ」の新規「チャンク」を追加します。コンポーネントを追加・削除すると、エンティティの「アーキタイプ」が変更され、「ECS」はそのエンティティのデータを別の「チャンク」に移動します。
この記事が気に入ったらサポートをしてみませんか?