テラシュールブログ

旧テラシュールウェアブログ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を生成するサンプル

https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/Samples/Assets/HelloECS/HelloCube_05_SpawnFromMonoBehaviourgithub.com

大規模な場所でConvertToEntityをする場合はSubSceneを使用するのをオススメ

tsubakit1.hateblo.jp

*1:ハイブリットECS

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

【Unity】Scene上に構築したステージを、Entity群に変換してECSで利用可能にする「SubScene」

f:id:tsubaki_t1:20190307014827j:plain

ECS 0.0.12 preview 26から、GameObjectとComponentをEntity&ComponentDataに変換する機能が追加されました。まずは、この中で最も便利であろうSubSceneについて紹介します。

GameObjectをECS向けに変換する

preview26でGameObjectConversionUtility.ConvertGameObjectHierarchy(...)GameObjectConversionUtility.ConvertScene(...)等のAPIが追加されました。このAPIは特定のコンポーネントを対応するComponentDataに変換するといった機能を持ています。例えば下のような感じ。

  • TransformをPositionRotationに変換する
  • MeshRendererとMeshFilterをRenderMeshに変換する

f:id:tsubaki_t1:20190306232854j:plain
GameObjectをEntityへ変換

これはPrefabからEntityや、シーンに配置したGameObjectからEntity、SceneまるごとEntityといったレベルでの変換が可能です。

当然、変換なので変換先のComponentDataが無い物は変換出来ない為に完全簡単に移行という訳には行きませんし、現状、ライトマップ等のSceneに紐づく情報は剥がれます。が、Unityエディターのワークフローでシーンにオブジェクトを配置し、ゲームで利用する事が可能になるのでECSを利用する上では、使いやすくなりそうです。

f:id:tsubaki_t1:20190306233151j:plain
StealthデモのステージをEntity群に変換して表示

ということで実際にScene上のステージをEntity群に変換して利用して見るわけですが、そこで既に便利な機能「SubScene」があったので、そちらを使います。

SubScene

SubSceneの説明によると、機能は大体こんな感じみたいです。Unityがシーン展開に使用しているようなデシリアライズとは異なり、ECSのメモリ構造を概ねそのまま展開して配置出来るので楽という発想でしょう。

シーンを保存する時、UnityはSubSceneをネイティブのバイナリフォーマットに変換します。このフォーマットはメモリに対応していて、RAM内のデータを少し変更するだけでロードもしくはストリーミングが可能です。このフォーマットは大量のEntityをストリーミングするのに理想的です。

いくつかのロードが超早いと評判のゲームは、このアプローチを使っていると聞いていたと聞いているので、とても良さそうです。

実際に使ってみる

【動作環境】

  • Unity 2019.1 b5(b4以前はSubSceneの機能が正常に動作しないかもしれません)
  • Entity 0.0.12 preview
  • Hybrid Renderer
  • Windows 10

1. 選択したオブジェクトのサブシーン化

シーンにあるオブジェクトをサブシーンにします。

オブジェクトを選択して 右クリック→New SubScene from Selection をクリックすると、指定した範囲内のオブジェクトをサブシーンにします。Stageのようなオブジェクトが既に存在し、Colliderとは別に配置されている場合には、こちらのほうがやりやすいかもしれません。

よくあるGameObjectをサブフォルダのように使うが如く、シーンを階層化出来ます。SubSceneの下にSubSceneを配置するなども可能です。

https://user-images.githubusercontent.com/1644563/53929490-3a2ed100-40d1-11e9-83c5-f07ff6d06a9d.gif

シーンファイルは、親シーンと同名のフォルダ以下に配置されるみたいです。

なお、基本的に動かさないオブジェクトにはStatic Optimize Entityを追加します。これを追加してあると、描画時に軽くなります(staticフラグが付いてるなら自動で設定してくれても良いのに)

f:id:tsubaki_t1:20190307125536j:plain
動かさないならStatic Optimize Entityを追加

2. SubSceneの編集

SubSceneに持っていっても、編集は可能です。

Editボタンを押せば、Entity & ComponentDataの代わりにGameObject & Componentが表示され、編集することが出来ます。この状態になると、普通のシーンと同様の操作系になります。

https://user-images.githubusercontent.com/1644563/53894988-65cf9e00-4074-11e9-9352-e3c3744e807b.gif

3. SubSceneをEntity群に変換

Entity群に変換します。

変換するには、SubSceneのRebuild Entity Cacheボタンを押すだけです。

既存のシーンの場合は、SceneAssetに追加すれば変換が可能です。

f:id:tsubaki_t1:20190307000032g:plain
SubSceneの追加

この手順が完了すると、Assets/EntityCache/ResourcesフォルダとStreaming Assets/EntityCacheフォルダにEntityをシリアライズしたファイルが生成されます。

PrefabはSharedComponentDataをシリアライズした情報、あとはSceneHeader(シーン情報とシリアライズしたバイナリを紐付ける)といった感じでしょうか。

また変換後でもEditボタンで編集が可能です。

f:id:tsubaki_t1:20190307001209j:plain
キャッシュをビルドすると出来る成果物

これでゲームを実行すると、Sceneから変換したEntity群がロードされます。

f:id:tsubaki_t1:20190307003219p:plain
実行結果

SubSceneに変換出来ない場合

変換出来ない場合、同一のコンポーネントが複数セットされている事が考えられます。

というのも、ECSは仕組み上「一つのEntityに同じComponentを複数登録できない」という制限があります。なので、Colliderなど一つのオブジェクトに複数のコンポーネントを登録しているものを排除します。

また変換出来ないEntityの情報は尽く剥がされます。これはスクリプトで拡張が出来ますが(Renderer等もHybrid Rendererパッケージで拡張されて変換可能になっている)無いものは変換出来ません。

ArgumentException: It is not allowed to have two components of the same type on the same entity. (BoxCollider and BoxCollider)と出ます。

f:id:tsubaki_t1:20190307125435j:plain
複数のBoxColliderがある場合、変換出来ない

既存のシーンを変換する

上でもチラっと書きましたが、New SubScene from Selectionを使用してSceneを作らない場合でも、SubSceneのSceneAssetにSceneを適応すればSubSceneとして扱えます(変換も可能です)

ただし、その場合は一旦シーンを開き直さないとSubSceneをSceneと認識しませんでした。

f:id:tsubaki_t1:20190307000358j:plain
SubSceneに既存のSceneを使う

その他機能

また、キャラクターのコンポーネント情報がどうなっているのか等を確認したい場合、LivelinkでEntityの情報をGameObjectに反映させるという事ができそうな感じがあります。

f:id:tsubaki_t1:20190307005052j:plain
Livelink

MeshRendererやLODは使えないの?

MeshRendererやLOD以外でも、独自スクリプトでも差し替えは可能そうです。詳しくはこちら

tsubakit1.hateblo.jp

感想

まだ機能が限られていますが、現状でも「ステージの大雑把な情報やキャラクターはGameObject & Component」「細かく大量にあるステージ上の小物はEntityで制御」のような使い分けができそうです。

またSubSceneで一つのシーンを複数で分割し、同時編集するといった事も今までのマルチシーン構想よりスムーズにできそうです。特にGameObjectをフォルダのように利用して階層分けしていた場合、より高速でスムーズなアプローチとして期待出来るかもしれません。

ところでSubSceneは普通に現行のGameObjectベースのシーンシステムに欲しいんですが…

関連

データをロードする部分は大体コレ

tsubakit1.hateblo.jp

SubSceneのサンプル

https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/Samples/Assets/HelloECS/HelloCube_04_SubScenegithub.com

【Unity】「アニメーションが再生されない」「止まる」時の対処法

今回は「アニメーションをセットアップしたのに再生されない」や「アニメーションが止まる」といった場合の対処法についてです。知っていれば「当然そうなる」(そういう設定をしている)という動作なのですが、知らないと面倒かもしれないので一覧としてメモします。

なおアニメーションをDCCツール(MayaやBlender)から出力する事それ自体に問題がある場合は含みません。

Animation ClipのLoopが無効になっている

アニメーションが途中で止まる場合、AnimationClipのLoopが外れているかもしれません。

AnimationClipは多くの場合FBX(等のファイル)から取得しますが、このアニメーションはAnimationタブの中から切り出して取得します。この時、Loopの設定が外れていると、アニメーションの完了時にアニメーション再生がポーズします。

この設定の特徴は、AnimatorControllerで見たときにステートの進行が最大値で停止しているので分かりやすいです。

f:id:tsubaki_t1:20190111202502j:plain
アニメーションのループ

f:id:tsubaki_t1:20190111202611j:plain
ステートの再生が最大で停止する

一つ厄介なのが、アニメーションの停止ではなくポーズであるという事です。アニメーションは停止しますがAnimatorによるパラメーターの更新は停止しておらず、Transformの情報は毎フレーム更新され続けています。

そのため、もしUI(特にLayoutGroup)に対して使用すると、毎フレームUIが更新され続けてすごい負荷になるといった事もあります。Playable APIを使用したアニメーションの場合は再生後に正しい意味で停止するので、動作後に停止するような動きを作る場合にはSimpleAnimatorやTween系を検討するのが良さそうです。

リグがアニメーションを取得したFBXとモデルのFBXで一致していない

FBXでよくあるもう一つの設定が、リグの不一致です。

このリグは基本的にアニメーションを再生するFBXと、アニメーションを取得するFBXで一致している必要があります。HumanoidはHumanoidのアニメーションを、LegacyはLegacyのアニメーションを使用できます。

f:id:tsubaki_t1:20190111203901j:plain
リグの設定一覧

またGenericやLegacyの場合はリターゲット機能は無いので、ボーン構造も含めて一致する必要があります。

アバターが一致しない

Humanoidでデータを取得する場合に重要なのがアバターです。アバターは人形のボーン構造と実際のモデルのボーン構造を一致させる効果があります。このボーン構造が一致していないと、アニメーションは再生しません。

例えば下の場合、キャラクターはMCUnitychanのボーン構造をしていますが、実際に適応しているのはEthanAvatar…異なるアバターが適応されているので動かないといった具合です。

f:id:tsubaki_t1:20190111205158j:plain
モデル本来のアバター

f:id:tsubaki_t1:20190111205101j:plain
モデルのアバターではないアバターを設定

どのアバターか分からない場合、オブジェクト内のSkinnedMeshRendererを探し、Meshを生成しているFBXをたどれば見つけられます。

f:id:tsubaki_t1:20190111205632g:plain
アバターを探せ

子のAnimatorを最適化している

少し特殊なケースですが、Animatorを階層構造にしたとき子のAnimatorが最適化していると動きません。

子の最適化を解除すれば、動作するようになります。最適化を描けたい場合は親のAnimatorで行います。

ステートマシンにClipが登録されていない

ステートマシンにクリップが登録されていない場合、当然アニメーションは再生しません。

登録したつもりでも登録してなかったり(後で差し替えるつもりだった)、GUIDの変更により剥がれる等。

f:id:tsubaki_t1:20190304125827j:plain
クリップが「無し」

AnyStateからのステート切り替えが常に成立している

AnyStateからステート切り替えが常に達成している場合、同じアニメーションが何度も再生されます。AnyStateはステートマシンを無視してステートを切り替えるので、ステート遷移条件が達成している限り現在のステート(自分自身)から自分自身のステートへ切り替えが発生してしまいます。

f:id:tsubaki_t1:20190304125037j:plain
AnyStateは他のステートからアニメーションを切り替える

また遷移間隔が0の場合は見た目殆ど停止している状態になります。

f:id:tsubaki_t1:20190304124946j:plain
遷移間隔が0 = アニメーション遷移終了後に即座に他のステートに移れる

対処法としては、AnyStateからの遷移から「自身に遷移」のチェックボックスを外す事です。これで少なくとも「毎フレーム自分のステートを再生し直す」事は回避出来ます。ただ、根本からの解決を考える場合は、何度もAnyStateからジャンプするステートマシンを何とかするべきでしょう。

個人的にはAnyStateには遷移条件にTriggerを必ず含めるべきと思ってます。

f:id:tsubaki_t1:20190304125328j:plain

なおAnimator.Playは自分自身に遷移しても停止しません。3番目の引数であるNormalizeTimeまで指定すると、その時間で止まりますが。

Timescaleが0

Timescaleが0(時間の流れるスピードが0 = 時間停止 = ザ・ワールド)だと、アニメーションは再生しません。

正確にはコレは設定次第で、Animatorの設定を「スケールされていない時間」にすることで回避出来ます。

f:id:tsubaki_t1:20190304123920j:plain

これにより「止まった時の中でも動ける」ようになりますが、同時に「加速した時間の恩恵」も受けられなくなる点に注意です。

f:id:tsubaki_t1:20190304124008g:plain
時間が流れる速度を無視して、常に一定速度で動く

【Unity】Assembly definition fileのDefine Constrainsで、Scripting Define Symbolsで定義したシンボル毎に動作を切り替える

Assembly definition fileに少し便利な機能が追加されていたので、メモ

本記事はUnity 2018.3がベースです。

Assembly definition file

簡単に言えばコンパイルプロジェクトを分割することでコンパイルの高速化等を行うというものです。この機能を理解しないで適当に使うと逆にコンパイル時間が伸びるかもしれませんが、ちゃんと使うと結構有益なイメージです。

また、なんとなくIL2CPP時のインクリメンタルビルドにも影響がある気がしていますが、実際どうなのかは未確認です。

tsubakit1.hateblo.jp

Assembly definition fileとプラットフォームによる動作の切り替え

Assembly definition fileはプラットフォーム毎に含めるかどうかを定義することが出来ます。例えばIOSのみ、Androidのみコード群を含めると言った具合に。この機能を上手く使うと、エディター上の動作と実機の動作用のプロジェクトは完全に分割することが出来ます。

インターフェースをちゃんと定義する必要はあると思いますが、コード群が非常にスッキリするんじゃないでしょうか。勿論インターフェースの用意を忘れずに。

f:id:tsubaki_t1:20190303231729j:plain
プラットフォームごとにAssembly Definition Fileを分割

Define Constrains(制約を定義)

さて、今まではこういった「Assembly Definition Fileによる動作の切り替え」はプラットフォーム単位でしか出来ませんでしたが、「制約を定義(Define Constrains)」という機能が追加され、手前側でも追加でコントロールすることが可能になりました。

f:id:tsubaki_t1:20190303232243j:plain

この機能は、単純にScripting Define Symbolsにシンボルが記述されていなければ、Assembly Definition Fileのプロジェクトは含まれない というものです。

例えばプラットフォームEditorにて、シンボル毎に動作を切り替えたいとします。まぁデバッグ用とか、ロード先の切り替えとか云々。で、制約を定義でそれぞれ「MY_DEBUG1」と「MY_DEBUG2」というシンボルを要求する感じにします。

f:id:tsubaki_t1:20190303233848j:plain

あとはPlayerSettingsScripting Define Symbolsを差し替えてやるだけで、動作が切り替わります。

f:id:tsubaki_t1:20190303233815j:plain

なお含まれるのは「何もシンボルを指定していない」もしくは「シンボルが一致するもの」です。(なのでMY_DEBUG1とMY_DEBUG2に分けた)

感想

最初はAssembly definition file毎にシンボルを指定できる機能だと思ったのは秘密。mcs.rspをプロジェクト毎に置くといった事も駄目だし、うーん

関連

tsubakit1.hateblo.jp