〜UnityのDOTSのドキュメントに触れてみる〜その3
前回の続きで、Unity公式のDOSTのドキュメントを少しずつ読みながら実装していこうと思います。
UnityECSについて
UnityECS
とは下記の3つの頭文字を取ってECS
の構成になっています。
- Entity
- Component
- System
Entity
Entity
とは、Unityでいうところの空のゲームオブジェクトに変わるものです。
ただの入れ物を想像しると分かり易いかもしれないです、MVVM
やMVP
等で例えると、View
の立ち位置のイメージです。
Component
Component
とは、現在のUnityでも使用しているコンポーネントです、ただ中身としては下記の違いになると思います。
- 現在のコンポーネント:ゲームオブジェクトにアタッチしアタッチしたゲームオブジェクトに対してデータの付与、処理等をする
- ESCのComponent:データの宣言のみ、処理などは無く
Entity
にデータを付与するのみのデータ宣言
System
System
とは、現在のUnityだとコンポーネントと重なっている部分です。
System
は動作、ロジックの部分に当たりますEntity
を回転させたり移動させたい、Entity
に付与されているComponent
を編集したいを実現させるのがSystem
になります。
ECSまとめ
今までゲームオブジェクトとコンポーネントでまとまっていたものがそれぞれECSで分割して綺麗にしようというのがECSでした。
MVC
、MVP
、MVVM
を知っている人/設計していた人だと分かり易かったのではないでしょうか?
知らない人は下記の様なイメージを持つと分かり易いかもしれないです。
- 今まで:ゲームオブジェクト(入れ物)にコンポーネント(データと処理)をアタッチして動きを作る
- ECS:
Entity
(入れ物)にComponent
(中身)でデータを付与しSystem
(動作)で動きを作る
実践
まず最初にパッケージマネージャーからEntities
パッケージをインストールします
コンポーネント(データ)作成方法
今回は2つのデータをEntity
にアタッチしたいので2データ作成しました。
データの作成はIIComponentData
を継承したstruct
を作成するだけのシンプルな作りになってます。
using Unity.Entities;
using Unity.Mathematics;
namespace ECS.Example
{
/// <summary>
/// ECSのサンプルコンポーネント
/// </summary>
public struct Velocity : IComponentData
{
public float3 value; // 速度
}
}
using Unity.Entities;
namespace ECS.Example
{
/// <summary>
/// ECSのサンプルコンポーネント
/// </summary>
public struct ECSSampleComponent : IComponentData
{
public int hitPoints; // HP
public int maxHitPoints; // 最大HP
}
}
解説
-
IIComponentData
を継承したstruct
を作成します。 -
struct
の中にEntity
にアタッチしたいデータを定義します。
GameObject->Entity変換Script作成
現状では、完全に1からEntityを作る事はできないのでGameObject
を変換する形になっています。
使いやすいようにしたり、EntityBase
のようなものを作成するとは思いますが、基本的にはこの形が固定になっているのかなと思います。
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);
}
}
}
}
解説
-
MonoBehaviour
を継承した、変換クラスをGameObject
にアタッチします。 - そのクラスの中に更に
Baker<T>
を継承したBaker
クラスを作成します。
(Tには変換されるゲームオブジェクトのコンポーネントを指定します) -
Baker
クラスでオーバーライド関数のBake
を作成、引数に変換されるゲームオブジェクトのコンポーネントを指定します。 -
Entity entity = GetEntity(TransformUsageFlags.None)
で引数で設定したEntity
を作成して取得します。
(今回は空のEntity
を取得しています。他のタイプについては後述します) - インスペクターから設定してある
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について
- None:位置回転スケールが不要なデータのみのエンティティに使用
- Renderable:静的なオブジェクトに使用、描画したいが実行時に動かさないもの
- Dynamic:動的なオブジェクトに使用、移動・回転・スケールを持っていて動作をさせたいもの
- WorldSpace:親がいても常にワールド空間基準で扱いたいオブジェクト
- NonUniformScale:x,y,zのスケールがx≠y≠zなスケールのオブジェクト
- ManualOverride:他に
Bake
された時にTransformUsageFlags
を無視するオブジェクト
あとがき
ゲームオブジェクトのように1からEntity
作る事ができなく、変換作業にサブシーンやAuthoring
処理が必要になるので少し複雑に見えますがUnity
側でデータと入れ物の分離ができているので、慣れれば扱いやすいんじゃないかなと思います。
変換処理がほぼ固定ではあるので、EntityBaseAuthoring
のようなものを作って簡単に変換ができるような環境が作れれば良いのかなと思ってます。
次回は実際の処理をする箇所System
について実装していきます。
Discussion