自己紹介
明後日(5/26)にゲームジャムを控え、大興奮が止まらない大学生
記事の参考動画
DOTS使ってますか?
Unity DOTS楽しいですよね。この前、Ver.1.0.8がリリースされてついにExperimentalが外れましたね。
一方で、機能が足りないところはしばしば…
今回は
今回は、機能が足りないところを既存のGameObject関連の機能と連携させてみたいと思います。
簡易的なゲームにも必須なUI(Unity UI)を連携させますが、ほとんど同じです。
タイトルは分かりやすく、EntityとGameObjectの連携と書きましたが、正確にはMonoBehaviourとECSの連携といったほうが正確かもしれません。
MonoBehaviour → ECS
これは非常に簡単に行えます。
MonoBehaviour側
public class Button : MonoBehaviour
{
private EntityManager entityManager;
private Entity entity;
private async void OnEnable()
{
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
await UniTask.Delay(100);
//Entityを取得
entity = entityManager.CreateEntityQuery(typeof(Spawner)).GetSingletonEntity();
}
public void OnButtonClick()
{
entityManager.AddComponent(entity, typeof(CharacterSpawnTag));
}
}
このOnButtonClick
をuGUIから呼び出せば、EntityにComponentを追加しそれをSystemで拾ってあげれば…連携完了!
一方で、この方法ではMonoBehaviourから数値などを含むコンポーネントを追加できない問題があります。
このAddComponent
はBaker<T>
を継承したクラスで利用できるものとは異なるため、数値などを渡す方法は異なります。その方法はAddComponent
したのち、SetComponent
メソッドで変更できます。
なぜ、直接的に指定できないのかは不明です。もしかしたら、直接指定できるのかもしれませんが、私が調べた限りは無理でした。
スクリプトの詳細解説
まずは7行目、Entityを管理するEntityManagerを取得します。
これを利用してEntityにコンポーネントを張り付けていきます。
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
次にEntityManagerから対象となるEntityを取得します。
今回はSpawner
というコンポーネントがついているEntityを1つ取得しています。
このGetSingletonEntity
メソッドでは対象となりうるEntityが複数個あるとエラーとなりますので気を付けてください。
その前に、UniTask
という外部のパッケージで少し遅延を発生させています。
これは、ECSの初期化が完了してからEntityを取得しなければエラーとなるからです。
await UniTask.Delay(100);
//Entityを取得
entity = entityManager.CreateEntityQuery(typeof(Spawner)).GetSingletonEntity();
最後に、OnButtonClick
からAddComponentで取得したEntityにコンポーネントをつけます。
今回はCharacterSpawnTag
というコンポーネントデータを作成しEntityに張り付けています。
ECSの設定
[BurstCompile]
public partial struct CharacterSpawnSystem : ISystem
{
private void OnCreate(ref SystemState state)
{
}
private void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach(var (_, characterObj, entity) in SystemAPI.Query<CharacterSpawnTag, CharacterGameObjectsData>().WithEntityAccess())
{
//何か処理を書く
ecb.RemoveComponent<CharacterSpawnTag>(entity);
}
}
}
こちらがECS側のスクリプト(一部略)です。
内容は、EntityCommandBuffer
を取得しそのEntityを使って何かを行い、その後MonoBehaviour側で指定したコンポーネントを外せば一度だけ処理されるということになりました。
今回はボタンを利用しましたが、それ以外のUnityEvent
とも連携出来ることになります。
ECS → MonoBehaviour
こちらは、MonoBehaviourのAction
をECSから呼び出すというイメージです。
ECS側の設定
using System;
using Unity.Entities;
public partial class ECSToMono : SystemBase
{
public Action<int> action;
protected override void OnUpdate()
{
action?.Invoke(100);
}
}
このようにAction
を定義し、ECS側で発火(Ivoke
)させます。今回はOnUpdateの中で発火していますが、このようなことはあまりないかもしれません。
また、Actionのジェネリクスは指定するメソッドの引数です。
次はMonoBehaviour側のスクリプトです。
using Unity.Entities;
using UnityEngine;
public class ECSToMonoReceiver : MonoBehaviour
{
private void OnEnable()
{
var system = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<ECSToMono>();
system.action += TestAction;
}
private void OnDisable()
{
var system = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<ECSToMono>();
system.action -= TestAction;
}
public void TestAction(int i)
{
Debug.Log(i);
}
}
ここのキーポイントは、システムの取得方法です。
World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<T>()
でsystemを取得できます。
ここから先ほど書いたaction
というフィールドにアクセスし関数を代入します。
なお、これはISystem
ではMonoBehaviourから取得することが出来なさそうだったのでSystemBase
を使う必要がありそうです。もし、ISystem
で取得できそうだったら教えてほしいです。
これで、コンソールに100と表示されます。
ECS→MonoBehaviourの連携ができました!
まとめ
MonoBehaviourからECSは
- コンポーネントをMonoBehabiourから設定
- それをSytemで検知しデータを取得
という手順でした。
ECSからMonoBehaviourは
- Action型の変数をSystemに定義
- そのAction型の変数にMonoBehaviourに定義したメソッドを代入
という手順です。
いい感じに設定できましたね。やったね。
さいごに
ほかにもDOTS(主にECS)の記事を書いていますので良かったら見てください。
ゲームジャム頑張るぞー!!