〜UnityのDOTSのドキュメントに触れてみる〜その4
前回の続きで、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(動作)で動きを作る
実践
前回はEntityとComponentを作成したので今回はSystemを作成して、実際にEntityについているComponentを更新していきます。
システム(動作)作成方法
システムはJob統合版とシステミのみのパターンが解説されていたので2パターンで作成していきます。
システムのみ
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;
}
}
}
}
解説
-
ISystemを継承したstructを作成します。 -
ISystemのOnUpdateをオーバーライドして更新処理を実装します。 -
foreachとSystemAPI.Queryを使用して特定のエンティティをループして処理を行います。 - 取得したエンティティのコンポーネントデータデータを更新し、移動させます。
システム(Job統合版)
Job部分
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;
}
}
}
解説
-
IJobEntityを継承したstructを作成します。 -
IJobEntityのExecuteをオーバーライドして実行処理を実装します。 - エンティティのコンポーネントデータデータを更新し、移動させます。
※ 今回はIJobEntityを継承していて、このJobを実行するとExecuteに実装した内容の処理が、LocalTransform/Velocityを両方とも持つエンティティに実行されます。
システム部分
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();
}
}
}
解説
-
ISystemを継承したstructを作成します。 -
ISystemのOnUpdateをオーバーライドして更新処理を実装します。 -
ECSSampleJobを作成しScheduleParallel()を実行し更新処理を行います。
あとがき
前回にまたがってしまいましたが、ECSの作りの表面は理解できました。
システムのSystemAPIで対象のエンティティに対して処理を行っていくのかと思いきや、IJobEntityだとSystemAPIを使用しなくても引数に設定すれば勝手に探してきてくれる
IJobEntity+ISystemを組み合わせたものISystem単体のものを比べるとかなり可読性が上がったように見えます。
IJobEntityで単体で動作する処理を作成し、ISystemで色々なJobを組み合わせて処理をしていくのが処理が分かれていて綺麗なのかなと思います。
1通り実装してみての感想ですが、Entity/Componet/System/Jobとそれぞれ役割がUnity側で決められているので、分かりやすくしっかり分けれれば可読性も悪くなさそうなのでできれば使っていきたいと思いました。
Discussion