🦔

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

に公開

前回の続きで、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(動作)で動きを作る

実践

まず最初にパッケージマネージャーからEntitiesパッケージをインストールします

コンポーネント(データ)作成方法

今回は2つのデータをEntityにアタッチしたいので2データ作成しました。
データの作成はIIComponentDataを継承したstructを作成するだけのシンプルな作りになってます。

Velocity.cs
using Unity.Entities;
using Unity.Mathematics;

namespace ECS.Example
{
    /// <summary>
    /// ECSのサンプルコンポーネント
    /// </summary>
    public struct Velocity : IComponentData
    {
        public float3 value;    // 速度
    }
}
ECSSampleComponent.cs
using Unity.Entities;

namespace ECS.Example
{
    /// <summary>
    /// ECSのサンプルコンポーネント
    /// </summary>
    public struct ECSSampleComponent : IComponentData
    {
        public int hitPoints;   // HP
        public int maxHitPoints;    // 最大HP
    }
}

解説

  1. IIComponentDataを継承したstructを作成します。
  2. structの中にEntityにアタッチしたいデータを定義します。

GameObject->Entity変換Script作成

現状では、完全に1からEntityを作る事はできないのでGameObjectを変換する形になっています。
使いやすいようにしたり、EntityBaseのようなものを作成するとは思いますが、基本的にはこの形が固定になっているのかなと思います。

ECSSampleEntityAuthoring.cs
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;

namespace ECS.Example
{
    /// <summary>
    /// GameObject->Entityの変換クラス
    /// </summary>
    public class ECSSampleEntityAuthoring : MonoBehaviour
    {
        [Header("最大HP")]
        [SerializeField]
        int MaxHitPoints;
        
        /// <summary>
        /// シーン内のゲームオブジェクト1つにつき1回処理される変換クラス
        /// </summary>
        class Baker : Baker<ECSSampleEntityAuthoring>
        {
            /// <summary>
            /// GameObject->Entityの変換処理
            /// </summary>
            /// <param name="authoring">変換されるゲームオブジェクトのコンポーネント</param>
            public override void Bake(ECSSampleEntityAuthoring authoring)
            {
                // Entityを作成、取得する
                Entity entity = GetEntity(TransformUsageFlags.Dynamic);
                
                // コンポーネントデータを作成
                ECSSampleComponent ecsSampleComponent = new ECSSampleComponent()
                {
                    hitPoints = authoring.MaxHitPoints,
                    maxHitPoints = authoring.MaxHitPoints
                };
                Velocity velocity = new Velocity() { value = new float3(1.0f, 0.0f, 0.0f) };
                
                // 取得したEntityにコンポーネントをアタッチ 
                AddComponent(entity, ecsSampleComponent);
                AddComponent(entity, velocity);
            }
        }
    }
}

解説

  1. MonoBehaviourを継承した、変換クラスをGameObjectにアタッチします。
  2. そのクラスの中に更にBaker<T>を継承したBakerクラスを作成します。
    (Tには変換されるゲームオブジェクトのコンポーネントを指定します)
  3. Bakerクラスでオーバーライド関数のBakeを作成、引数に変換されるゲームオブジェクトのコンポーネントを指定します。
  4. Entity entity = GetEntity(TransformUsageFlags.None)で引数で設定したEntityを作成して取得します。
    (今回は空のEntityを取得しています。他のタイプについては後述します)
  5. インスペクターから設定してあるMaxHitPoints変数からコンポーネントを作成します。
// コンポーネントデータを作成
ECSSampleComponent ecsSampleComponent = new ECSSampleComponent()
{
    hitPoints = authoring.MaxHitPoints,
    maxHitPoints = authoring.MaxHitPoints
};

6AddComponent(entity, ecsSampleComponent);で4.で作成したEntityに5.で作成したコンポーネントをアタッチします。

サブシーン作成

GameObject->Entityの変換準備の前にサブシーンを作成します。
ヒエラルキーで右クリック->「New Sub Scene」->「Empty Scene...」からサブシーンを作成します。

↓作成後のヒエラルキー

サブシーンの中にEntityに変換したいゲームオブジェクトを作成して、そのゲームオブジェクトにScriptをアタッチします
(今回は空のGameObject)

検証

サブシーンが下記画像のようになっているのでサブシーンを開きます。
なっていない場合は右側のチェックマークにチェックをつけてください、この時AutoLoadSceneにチェックがついてたらUnityが自動で更新のタイミングに変換してくれます。

Entityに変換したいオブジェクトを選択してインスペクターの下の方にコンポーネントを確認してECSSampleComponentがアタッチされていたら成功です。
Bake関数にログを残していた場合はこの変換のタイミングでログが出力されます。

TransformUsageFlagsについて

  1. None:位置回転スケールが不要なデータのみのエンティティに使用
  2. Renderable:静的なオブジェクトに使用、描画したいが実行時に動かさないもの
  3. Dynamic:動的なオブジェクトに使用、移動・回転・スケールを持っていて動作をさせたいもの
  4. WorldSpace:親がいても常にワールド空間基準で扱いたいオブジェクト
  5. NonUniformScale:x,y,zのスケールがx≠y≠zなスケールのオブジェクト
  6. ManualOverride:他にBakeされた時にTransformUsageFlagsを無視するオブジェクト

あとがき

ゲームオブジェクトのように1からEntity作る事ができなく、変換作業にサブシーンやAuthoring処理が必要になるので少し複雑に見えますがUnity側でデータと入れ物の分離ができているので、慣れれば扱いやすいんじゃないかなと思います。
変換処理がほぼ固定ではあるので、EntityBaseAuthoringのようなものを作って簡単に変換ができるような環境が作れれば良いのかなと思ってます。
次回は実際の処理をする箇所Systemについて実装していきます。

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

Discussion