LoginSignup
0
0

【Unity ECS 2023 のお勉強】サンプルソースを読む (EntitiesSamples)

Posted at

概要

このサンプルを見ながら気になったり調べたりした内容をだらだら書いてるPostです。
備忘がメイン目的なのであんまり深く調べたりはできてません。

今回はこの中の EntitiesSamples に関しての記述です。
見出しはその中の HelloCube の各ディレクトリの内容です。

1. MainThreads

Spawner / Authoring / Baker

以下が詳しい。GameObjectをEntityの世界に召喚するための基本的な流れになるらしいから最初に知っとくと理解がはかどる。

RequireForUpdate: ISystemを起動するかどうかの条件指定

RotationSystemにある以下の記述

HelloCube/1.MainThread/RotationSystem.cs
[BurstCompile]
public void OnCreate(ref SystemState state)
{
    state.RequireForUpdate<Execute.MainThread>();
}

RequireForUpdate<IComponent名>() を記述することで、SubScene内にそのコンポーネントが存在しないと実行されないようにでき、IComponentをフラグとして用いる運用ができる。

具体的なやり方の一例

HelloCube/Common/ExecuteAuthoring.cs
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
{
}

 // 省略

SubScene内で以下のように指定。
image.png

これはサンプルソースだから一つのクラスでまとめてるんだろうから実際は別の運用にすると思うけど、フラグ管理としてIComponentを利用するケースがあると理解。

[補足1] Singletonに関して

サンプルにはないんだけど、そういうフラグはSingletonみたいに利用できるのかって気になったので調べた。
結論いうとできる。
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/components-singleton.html

たとえば、以下のように利用できる。

HelloCube/1.MainThread/RotationSystem.cs
[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 をまとめるような機能らしい。

サンプルソース

HelloCube/3.Aspects/RotationSystem.cs
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

HelloCube/4.Prefabs/FallAndDestroySystem.cs
// 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);

コマンドをひとまとめにし、特定のタイミングで一気に実行させるやり方らしい。

BeginSimulationEntityCommandBufferSystemCommandBuffer を実行するタイミングを指定しているらしく、他にも以下が存在している(ループ処理で毎回 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) を設定するやり方。
難しいことはやってないから見たらわかる感じ。

SetComponentEnabledSystem.Query<>().WithOptions(EntityQueryOptions.IgnoreComponentEnabledState) を覚えておけばよさそう。

8. GameObjectSync

GameObject(+MonoBehavior) と ECSのEntityとの連携をするための方法。
ISystem内で普通に GameObject.FindGameObject#GetComponent を呼び出せてる。
あと IComponentDatastruct じゃなく class で定義してる。

また、Entity側には実態となる描画オブジェクトは存在せず、Entityの更新結果をGameObjectのTransformに伝える形でモデルを動かしている。
現在ECSではアニメーションをする際に結構面倒だと感じているが、プレイヤーなどアニメーションの多いものならこの方法で自由度が高く行えそう。

ただし、それを呼び出す場合 [BurstCompile] は使えないから処理としてはやっぱり遅くなるらしい。

HelloCube/8.GameObjectSync/DirectoryInitSystem.cs
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回しか呼ばれない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0