テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】GameObjectとコンポーネントをEntityとComponentDataに変換してECSで使えるようにする

SubSceneで独自のコンポーネントや、まだ変換先の無いコンポーネントをComponentDataに差し替えるアプローチについてです。

GameObjectベースのデータをECSベースへ変換する

ECS 0.0.12 preview 26からGameObjectをEntityに変換して使用するといったワークフローが充実してきました。今まではGameObjectEntityなど、どちらかと言えば ComponentをEntityに登録するアプローチ、言うなれば GameObjectワークフローをECSで使用する為のアプローチ*1 でした。

そしてpreview 26において*2 GameObjectをECS用に変換するアプローチが登場します。 特に分かりやすい機能の使い方としてはSubSceneで、既存のシーンをECS用のデータに変換するというものです。

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20190306/20190306232854.jpg

GameObjectをEntityへ変換するAPI

GameObjectをECSへ変換するためのAPIは幾つかあります。

まずGameObjectConversionUtility.ConvertGameObjectHierarchy(...)です。これは指定のGameObjectをEntityへ変換します。基本的に個別の制御になっています。 なお変換後はRenderBounds等が無い為に表示はされませんが、ECSとしては生成されている状態です。複数回呼ぶ場合はキャッシュするなりするのが良さそうに見えます。

Entity entity = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab, World.Active);
World.Active.EntityManager.Instantiate(entity);

このAPIコンポーネント版が、ConvertToEntityです。登録したオブジェクトを自動的にEntityへ変換します。

これはGameObjectEntityと異なりTransform等の変換可能なコンポーネントは全て変換されます。またECSの計算結果をGameObjectへ戻す機能は提供されていません。

f:id:tsubaki_t1:20190309150306j:plain
ConvertToEntity

それとGameObjectConversionUtility.ConvertSceneがありますが、ランタイムで呼ぶようなものではないのでSubSceneワークフローで変換したものを使うのが良さそうです。

MonoBehaviourをComponentDataに変換してもらうためのコード

MonoBehaviourを自分で記述しているならば、IConvertGameObjectToEntityを利用してEntityを変換可能にします。

実装方法はこんな感じです。ComponentDataProxyと異なり、一つのコンポーネントで複数のComponentDataを追加出来る点がポイントかもしれません。

public class MyDataProxy : MonoBehaviour, IConvertGameObjectToEntity
{
    [SerializeField] int m_Value;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        // コンポーネントを追加
        dstManager.AddComponentData(entity, new PlayerTag());
        dstManager.AddComponentData(entity, new MyData { Value = m_Value });
    }
}

[System.Serializable]
public struct MyData : IComponentData { public int Value; }

public struct PlayerTag : IComponentData { }

このアプローチでは、他のEntityを参照するEntityも作れます。

public class Follow : MonoBehaviour, IConvertGameObjectToEntity
{
    [SerializeField] GameObject obj;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var target = conversionSystem.GetPrimaryEntity(obj); // GetPrimaryEntityで対象のEntityを取得
        dstManager.AddComponentData(entity, new Target { Value = target });
    }
}

public struct Target : IComponentData
{
    public Entity Value;
}

コンポーネントをComponentDataに変換してもらうためのコード

Hierarchyを汚さず、かつコンポーネントのカスタムが出来ない場合はGameObjectConversionSystemを使います。

例えば下のはBoxColliderを自分自身の定義したMyBoxColliderへ変換するコードです。IConvertGameObjectToEntityと異なり「コンポーネントの組み合わせで差し替える対象を変更出来る」「既存のコンポーネントを差し替えられる」「実行順番を制御できる」という点で、有利です。

public class ColliderConversionSystem : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
            Entities.ForEach((BoxCollider collider) => {
            var entity = GetPrimaryEntity(collider);
            var myBoxCollider = new MyBoxCollider
            {
                Size = collider.size,
                IsTrigger = collider.isTrigger ? 1 : 0
            };

            DstEntityManager.AddComponentData(entity, myBoxCollider);
        });
    }
}

感想

ある意味、実装と設定が明確に別れたといった感じでしょうか。

少し面倒ですが、大分納得できる感じになりました。

現在マニュアルで紹介されているサンプルは全てこのアプローチで構成されている辺り、主流になって欲しいアプローチなのかもしれません。

関連

Entityを生成するサンプル

github.com

*1:ハイブリットECS

*2:実はもう少し前からあったが