MonoBehaviourとの連携
UnityECSは素晴らしいフレームワークですが、UnityECSは現行のすべてのUnityの機能を完全には扱うことはできません。
前回から行っているAnimationについても既存のMonoBehaviourと連携しHybridアプローチを行う必要があります。
GameObjectとEntityを紐づけ
例えばMonoBehaviour側が何かイベントを発行してそれに対してECSで処理をしたい場合を考えてみましょう。
MonoBehaviourからECS側に何か送る場合はあらかじめComponent
に送りたいデータを提示して、SetComponent
から送る方法やAddComponent
でタグをつけて、タグが付いていたらそのSystem
で処理する方法があります。
いずれにしてもこのGameObjectに紐づけられているEntityを知ることが必要です。
まずGameObjectにEntityを格納するためのEntityHolder
コンポーネントを作成し、アタッチします。
public class EntityHolder : MonoBehaviour
{
public Entity MyEntity;
}
単純にEntityを保持するだけのクラスです。
次にECS側からGameObjectに紐づけます。
これは以前PlayerHybridSystem
でやったことの逆をすればいいわけです。
protected override void OnUpdate()
{
EntityCommandBuffer ecb = SystemAPI.GetSingletonRW<EndSimulationEntityCommandBufferSystem.Singleton>().ValueRW.CreateCommandBuffer(World.Unmanaged);
//Create
foreach (var (hybridData, entity) in
SystemAPI.Query<CharacterHybridData>()
.WithNone<CharacterHybridLink>().WithEntityAccess())
{
// AuthoringでアタッチされたGameObjectを実体化する。
GameObject tmpObject = GameObject.Instantiate(hybridData.MeshPrefab);
Animator animator = tmpObject.GetComponent<Animator>();
// その実体化されたGameObjectの`EntityHolder`を取得し
+ EntityHolder holder = tmpObject.GetComponent<EntityHolder>();
+ // entityを入れてやればよい
+ holder.MyEntity = entity;
// こちらは以前やったもので、Entity側にGameObjectを紐づけている
ecb.AddComponent(entity, new CharacterHybridLink
{
Object = tmpObject,
Animator = animator,
});
ecb.AddComponent<CharacterHybridData>(entity);
}
ecb.Dispose();
}
ECS側のAuthoringの写真も一応載せます。
AuthoringのCharacter Authoring
のMesh Prefab
に先ほどアタッチしたGameObjectのプレファブを入れるわけです。
MonoBehaviourでAddComponent,SetComponent
ここまでくればあとはMonoBehaviour側でAddComponent
やSetComponent
をやってしまえばいいわけです。
例えばメカニムのStateMachineBehaviour
を使って呼び出してみる
public class AttackStateBehaviour : StateMachineBehaviour
{
Entity entity;
//OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
if(entity != null)
{
var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
// AddComponentでタグをつける
manager.AddComponent<AttackState>(entity);
}
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
//override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
//OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
if(entity != null)
{
var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
// AddComponentでタグをつける
manager.AddComponent<NormalState>(entity);
}
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
これはメカニムのステートマシーンに入ったとき対象のEntityにAttackState
というタグをつけて、ステートマシーンから出たときにNormalState
というタグをつけているサンプルになります。
GameObject側からEntityManager.〇〇Component
を呼ぶ場合はWorld.DefaultGameObjectInjectionWorld.EntityManager
でEntityManager
を取得することで呼ぶことができます。
それぞれのStateの処理をECSのSystem
で行っています。
SetComponent
の例ものせておきます。
public class AttackStateBehaviour : StateMachineBehaviour
{
Entity entity;
//OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
if(entity != null)
{
var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
// GetComponentで現在のコンポーネントの値を取得
PlayerInputs inputs = manager.GetComponentData<PlayerInputs>(entity);
// データを書き換えて
inputs.FirePressed = true;
// 再設定する
manager.SetComponentData(entity, inputs);
}
}
//OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
entity = animator.transform.GetComponent<EntityHolder>().MyEntity;
if(entity != null)
{
var manager = World.DefaultGameObjectInjectionWorld.EntityManager;
// 現在のコンポーネントのデータを参照しなくてもいい場合は新しくコンポーネントを作成しセットしてもいい
// 同じコンポーネントにデータがある場合はそれも初期化されるので注意
manager.SetComponentData(entity, new PlayerInputs
{
FirePressed = true,
});
}
}
}
これでUnityECSとMonoBehaviourの連携をうまくすることができるようになります。