📚

〜UnityのDOTSのドキュメントに触れてみる〜その4

に公開

前回の続きで、Unity公式のDOSTのドキュメントを少しずつ読みながら実装していこうと思います。

UnityECSについておさらい

UnityECSとは下記の3つの頭文字を取ってECSの構成になっています。

  • Entity
  • Component
  • System

Entity

Entityとは、Unityでいうところの空のゲームオブジェクトに変わるものです。
ただの入れ物を想像しると分かり易いかもしれないです、MVVMMVP等で例えると、Viewの立ち位置のイメージです。

Component

Componentとは、現在のUnityでも使用しているコンポーネントです、ただ中身としては下記の違いになると思います。

  • 現在のコンポーネント:ゲームオブジェクトにアタッチしアタッチしたゲームオブジェクトに対してデータの付与、処理等をする
  • ESCのComponent:データの宣言のみ、処理などは無くEntityにデータを付与するのみのデータ宣言

System

Systemとは、現在のUnityだとコンポーネントと重なっている部分です。
Systemは動作、ロジックの部分に当たりますEntityを回転させたり移動させたい、Entityに付与されているComponentを編集したいを実現させるのがSystemになります。

ECSまとめ

今までゲームオブジェクトとコンポーネントでまとまっていたものがそれぞれECSで分割して綺麗にしようというのがECSでした。
MVCMVPMVVMを知っている人/設計していた人だと分かり易かったのではないでしょうか?
知らない人は下記の様なイメージを持つと分かり易いかもしれないです。

  • 今まで:ゲームオブジェクト(入れ物)にコンポーネント(データと処理)をアタッチして動きを作る
  • ECS:Entity(入れ物)にComponent(中身)でデータを付与しSystem(動作)で動きを作る

実践

前回はEntityComponentを作成したので今回はSystemを作成して、実際にEntityについているComponentを更新していきます。

システム(動作)作成方法

システムはJob統合版とシステミのみのパターンが解説されていたので2パターンで作成していきます。

システムのみ

ECSSampleSystem.cs
using Unity.Entities;
using Unity.Burst;
using Unity.Transforms;

namespace ECS.Example
{
    /// <summary>
    /// ECSのシステムサンプル
    /// </summary>
    public partial struct ECSSampleSystem : ISystem
    {
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="state">ISystemのSystem情報</param>
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            // 下記条件のEntityをループ
            // LocalTransform:書き込み可で取得
            // Velocity:読み取り専用で取得
            // ECSSampleComponent;ECSSampleComponentがアタッチされている事が必須
            foreach (var (transform, velocity) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<Velocity>>().WithAll<ECSSampleComponent>())
            {
                // 取得した位置を移動させる
                transform.ValueRW.Position += velocity.ValueRO.value * SystemAPI.Time.DeltaTime;
            }
        }
    }
}

解説

  1. ISystemを継承したstructを作成します。
  2. ISystemOnUpdateをオーバーライドして更新処理を実装します。
  3. foreachSystemAPI.Queryを使用して特定のエンティティをループして処理を行います。
  4. 取得したエンティティのコンポーネントデータデータを更新し、移動させます。

システム(Job統合版)

Job部分

ECSSampleJob.cs
using Unity.Entities;
using Unity.Transforms;

namespace ECS.Example
{
    /// <summary>
    /// ECSのSystem統合Jobサンプル
    /// </summary>
    public partial struct ECSSampleJob : IJobEntity
    {
        public float deltaTime; // 経過時刻
        
        /// <summary>
        /// Job実行
        /// </summary>
        /// <param name="transform">移動する位置</param>
        /// <param name="velocity">移動速度</param>
        public void Execute(ref LocalTransform transform, in Velocity velocity)
        {
            // 位置を移動
            transform.Position += velocity.value * deltaTime;
        }
    }
}

解説

  1. IJobEntityを継承したstructを作成します。
  2. IJobEntityExecuteをオーバーライドして実行処理を実装します。
  3. エンティティのコンポーネントデータデータを更新し、移動させます。
    ※ 今回はIJobEntityを継承していて、このJobを実行するとExecuteに実装した内容の処理が、LocalTransform/Velocityを両方とも持つエンティティに実行されます。

システム部分

ECSSampleJobSystem.cs
using Unity.Entities;
using Unity.Burst;

namespace ECS.Example
{
    /// <summary>
    /// ECSのJob統合Systemサンプル
    /// </summary>
    public partial struct ECSSampleJobSystem : ISystem
    {
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="state">ISystemのSystem情報</param>
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            // ECSSampleJobを作成
            ECSSampleJob job = new ECSSampleJob {
                deltaTime = SystemAPI.Time.DeltaTime
            };
            
            // 並列処理でJobを実行
            job.ScheduleParallel();
        }
    }
}

解説

  1. ISystemを継承したstructを作成します。
  2. ISystemOnUpdateをオーバーライドして更新処理を実装します。
  3. ECSSampleJobを作成しScheduleParallel()を実行し更新処理を行います。

あとがき

前回にまたがってしまいましたが、ECSの作りの表面は理解できました。
システムのSystemAPIで対象のエンティティに対して処理を行っていくのかと思いきや、IJobEntityだとSystemAPIを使用しなくても引数に設定すれば勝手に探してきてくれる
IJobEntity+ISystemを組み合わせたものISystem単体のものを比べるとかなり可読性が上がったように見えます。
IJobEntityで単体で動作する処理を作成し、ISystemで色々なJobを組み合わせて処理をしていくのが処理が分かれていて綺麗なのかなと思います。
1通り実装してみての感想ですが、Entity/Componet/System/Jobとそれぞれ役割がUnity側で決められているので、分かりやすくしっかり分けれれば可読性も悪くなさそうなのでできれば使っていきたいと思いました。

ファースト・スクラッチTech Blog

Discussion