概要
このサンプルを見ながら気になったり調べたりした内容をだらだら書いてるPostです。
備忘がメイン目的なのであんまり深く調べたりはできてません。
今回はこの中の EntitiesSamples
に関しての記述です。
見出しはその中の HelloCube
の各ディレクトリの内容です。
1. MainThreads
Spawner / Authoring / Baker
以下が詳しい。GameObjectをEntityの世界に召喚するための基本的な流れになるらしいから最初に知っとくと理解がはかどる。
RequireForUpdate: ISystemを起動するかどうかの条件指定
RotationSystemにある以下の記述
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Execute.MainThread>();
}
RequireForUpdate<IComponent名>()
を記述することで、SubScene内にそのコンポーネントが存在しないと実行されないようにでき、IComponentをフラグとして用いる運用ができる。
具体的なやり方の一例
public class ExecuteAuthoring : MonoBehaviour
{
public bool MainThread;
public bool IJobEntity;
public bool Aspects;
public bool Prefabs;
public bool IJobChunk;
public bool Reparenting;
public bool EnableableComponents;
public bool GameObjectSync;
class Baker : Baker<ExecuteAuthoring>
{
public override void Bake(ExecuteAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
if (authoring.MainThread) AddComponent<MainThread>(entity);
if (authoring.IJobEntity) AddComponent<IJobEntity>(entity);
// 省略
}
}
}
public struct MainThread : IComponentData
{
}
// 省略
これはサンプルソースだから一つのクラスでまとめてるんだろうから実際は別の運用にすると思うけど、フラグ管理としてIComponentを利用するケースがあると理解。
[補足1] Singletonに関して
サンプルにはないんだけど、そういうフラグはSingletonみたいに利用できるのかって気になったので調べた。
結論いうとできる。
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/components-singleton.html
たとえば、以下のように利用できる。
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// Componentの取得
var mainThread = SystemAPI.GetSingleton<Execute.MainThread>();
// 以下省略
}
共通設定を入れておくとかすると活用できそう。
ただ、よくあるSingletonとは違って 一つしかないのを保証はしない
らしいし、 存在するかどうかも保証しない
らしい。そこは運用でちゃんとしろ(自分らでちゃんとインスタンスを1個しか入れないようにしろ)ってことみたい。ほかにもいろいろ制約がある。
2. IJobEntity
IJobEntityの実行の仕方のサンプル。こうやって引数渡したり処理を外だしできるから再利用しやすくなるよ、くらいの感じなのかな。
PostTransformMatrix
Google翻訳
不均一スケールなどの非アフィン変換効果を実装するために使用されるオプションの変換行列。
うん、なるほどね。
動きとその前のソースコードと名称を見る限りはたぶん LocalTransform
では Scale は float 値でもっており、y軸にだけ伸ばすというのができないから PostTransformMatrix
を使ってるみたい。また、名前の通りここで設定した内容は LocalTransform
のあとに適応されるらしい。
3. Aspects
IAspect
とは複数の IComponent
をまとめるような機能らしい。
サンプルソース
readonly partial struct VerticalMovementAspect : IAspect
{
readonly RefRW<LocalTransform> m_Transform;
readonly RefRO<RotationSpeed> m_Speed;
public void Move(double elapsedTime)
{
m_Transform.ValueRW.Position.y = (float)math.sin(elapsedTime * m_Speed.ValueRO.RadiansPerSecond);
}
}
いきなり沸いてでてきて、どこからも Add
とかもされてないのに、突然
foreach (var movement in
SystemAPI.Query<VerticalMovementAspect>())
{
movement.Move(elapsedTime);
}
みたいに使われるんで「何やつ!?」ってなるけど、struct内の変数
readonly RefRW<LocalTransform> m_Transform;
readonly RefRO<RotationSpeed> m_Speed;
を見てQueryを投げてくれるみたい。
4. Prefabs
Singletonが使われてる(作り方あっててよかった)
また、Randomの作り方のサンプルもある。
// Unlike new Random(), CreateFromIndex() hashes the random seed
// so that similar seeds don't produce similar results.
var random = Random.CreateFromIndex(updateCounter++);
というか、Structなのに普通に値を更新するメンバ変数も使うのか。
(そのあたりどうしようかとかなり悩んでたけど、公式サンプルが使ってるならやっていいのかな…まぁ、対象のISystemはゲーム起動中に1インスタンスしか起動されないだろうから問題はないのか)
CommandBuffer
// An EntityCommandBuffer created from EntityCommandBufferSystem.Singleton will be
// played back and disposed by the EntityCommandBufferSystem when it next updates.
var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
コマンドをひとまとめにし、特定のタイミングで一気に実行させるやり方らしい。
BeginSimulationEntityCommandBufferSystem
が CommandBuffer
を実行するタイミングを指定しているらしく、他にも以下が存在している(ループ処理で毎回 Initialization
-> Update
-> PreLateUpdate
が呼ばれる)
- Initialization
- BeginInitializationEntityCommandBufferSystem
- EndInitializationEntityCommandBufferSystem
- Update
- BeginSimulationEntityCommandBufferSystem
- EndSimulationEntityCommandBufferSystem
- PreLateUpdate
- BeginPresentationEntityCommandBufferSystem
- EndPresentationEntityCommandBufferSystem
並列処理
上記の場合は直接的な処理になるが、 ecb.AsParallelWriter()
みたいなやり方でコマンドを設定すると並列に処理が実行されるらしい。
ただ、サンプルでは DestroyEntity
を行っており、削除の処理は並列ではできない。
5.IJobChunk
IJobEntityと似ているが、実行時に手動でQueryを渡す必要がある。
かつ、 state.Dependency
に設定することでJobの実行が保証される( IJobEntity
では、 job.Schedule()
とするだけで保証されていた)
JobChunkに関しては、以下が詳しい
https://tsubakit1.hateblo.jp/entry/2018/10/16/231300
(2018年の記事のため、コードはもうだいぶ変わっている。ただ使い方の概念的なのはとても分かりやすく参考になる)
ちなみに、引数4つあるけど基本は chunk
使っておけばOKぽい?
6. Reparenting
Entity内の子Entity (Child) の取得などをするためのノウハウ。
Parent
Child
PreviousParent
という IComponentData
があらかじめ用意されている。
(その中で、 PreviousParent
は自動で Add / Removeするから気にしなくていいらしい)
また、特定のタイミングではなく任意のタイミングで実行できる CommandBuffer
についての記述もある。
var ecb = new EntityCommandBuffer(Allocator.Temp);
// ...コマンド設定
// 最後に実行する
ecb.PlayBack(state.EntityManager);
7. EnableableComponents
Componentの状態(Enable / Disable) を設定するやり方。
難しいことはやってないから見たらわかる感じ。
SetComponentEnabled
と System.Query<>().WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)
を覚えておけばよさそう。
8. GameObjectSync
GameObject(+MonoBehavior) と ECSのEntityとの連携をするための方法。
ISystem内で普通に GameObject.Find
や GameObject#GetComponent
を呼び出せてる。
あと IComponentData
を struct
じゃなく class
で定義してる。
また、Entity側には実態となる描画オブジェクトは存在せず、Entityの更新結果をGameObjectのTransformに伝える形でモデルを動かしている。
現在ECSではアニメーションをする際に結構面倒だと感じているが、プレイヤーなどアニメーションの多いものならこの方法で自由度が高く行えそう。
ただし、それを呼び出す場合 [BurstCompile]
は使えないから処理としてはやっぱり遅くなるらしい。
public void OnUpdate(ref SystemState state)
{
state.Enabled = false;
var go = GameObject.Find("Directory");
if (go == null)
{
throw new Exception("GameObject 'Directory' not found.");
}
var directory = go.GetComponent<Directory>();
var directoryManaged = new DirectoryManaged();
directoryManaged.RotatorPrefab = directory.RotatorPrefab;
directoryManaged.RotationToggle = directory.RotationToggle;
var entity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponentData(entity, directoryManaged);
}
OnUpdate
だけど、 処理の最初で state.Enabled = false;
としているため1回しか呼ばれない。