読者です 読者をやめる 読者になる 読者になる

テラシュールブログ

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

UnityのGameObjectを非アクティブにするとAnimatorがリセットされる挙動、その対処法

Unity 3D 2D GUI(uGUI/NGUI/旧UI) エディタ拡張・エディタ機能 アセット・ファイル管理 ScriptableObject アニメーション

マニュアルに書いてあったか記憶が微妙なのですが、UnityのAnimatorはGameObjectが非アクティブになるとリセットされます。

ここで言うリセットとはアニメーションが最初に戻るだけ等の生易しい物ではなく、パラメータの持つ値やトリガー状況、アニメーションのステート、さらにはアニメーター初期座標までもがリセット・初期化される糞っぷりです。

f:id:tsubaki_t1:20150205095903g:plain

これはUnityの「非アクティブなオブジェクトは削除されたものとして扱う」的な思想で考えればまあ理屈は通るのですが、UIにもTweenではなくAnimatorで実装するワークフローを目指しているように見える以上、余りにも片手落ちです。

 

ちなみにアニメーター初期座標とは、アニメーターが持つ各要素の初期値の事を指します。Animatorは何もキーフレームが無い状態ならば、Animatorがアクティブになった時の座標を利用するようになっています。

下は「最終的な座標」のみを指定したアニメーションとアニメーター初期座標を利用したアニメーションをブレンドしたものです。実行した位置が初期座標となり、最終的には左下へ移動します。

f:id:tsubaki_t1:20150205101008g:plain

対処法

一番手っ取り早い対処法は、Animatorを持つGameObjectを非アクティブにしないことです。つまり、Animatorを含む全てのコンポーネントをDisableに設定して、オブジェクトの挙動を停止させます。

実はGameObjectのActive/Inactiveもソコソコのコストがかかるのでパフォーマンス的にはアリな選択肢ではあるのですが(コレを推奨している記事等もありました)rigidbody等のdisable化出来ない*1コンポーネントの存在や、初期状態でDisable化してあるコンポーネント等の存在も考えると、少し面倒な部類に入ります。

 

そこで、実行時にパラメータや状況を保持してしまう方法を考えます。

まずアニメーションの再生状況は以下のコードから取得出来ます。

float time = animator.GetCurrentAnimatorStateInfo (currentLayerCount).normalizedTime;

後はココで取得したtimeをAnimator再起動時にanimator.Playと一緒に呼んでやれば、アニメーションを再開する事ができます。

 

パラメータは面倒な所です。何故かと言えばプロパティ名をランタイム時に取得することが出来ません。ですので、エディタで事前に取得してしまいます。

まずAnimatorControllerをUnityEditorInternal.AnimatorControllerとして取得し、GetParameterで参照用ハッシュコードとパラメータタイプを事前に取得しておきます。後は実行時にその値を取得・流し込んでやればOKという訳です。

1点注意点として、AnimatorはInactiveになった瞬間パラメータを放棄するので、Inactive前にパラメータの収集が完了している必要があります。

 

コード

今回も上記の対処を行ったコードを用意しました。

使い方は以下のとおりです。

 

まずScriptableObjectをAssetsファイルとして出力する汎用スクリプトを使用してAnimatorParameterをAssetsファイルとして出力します。ScriptableObjectToAssetの使い方はScriptableObjectの使い方

f:id:tsubaki_t1:20150205104120p:plain

作ったファイルを選択し、AnimatorControllerと同じ階層に移動させます。

次にAnimatorParameterのAnimatorControllerに解析したいAnimatorControllerをドラッグ&ドロップしてSetupを押します。

これでAnimatorControllerのパラメータハッシュ値がキャッシュされます。(ついでに名前も変わります)

f:id:tsubaki_t1:20150205110227p:plain

f:id:tsubaki_t1:20150205110414p:plain

 

次にリセット時にも復帰したいAnimatorを選択して、ResumeAnimatorを追加します。その後、ResumeAnimatorに先ほど作成したScriptableObjectアセットを登録します。

f:id:tsubaki_t1:20150205110736p:plain

 

最後にオブジェクトをInactive化する前に、下のコードを呼ぶようにします。

        var target = gameObject;
        //target(及び子)オブジェクトのResumeAnimatorでパラメータキャッシュを実行
        ResumeAnimator.RestoreAnimator(target);

        target.SetActive(false); //targetをInactive(非アクティブ)

f:id:tsubaki_t1:20150205111349g:plain

ちなみにAnimatorParameterの更新は自動的に行うように作っていないので、Animatorのパラメータを更新する度にSetupボタンを押すか、AssetPostProcesserで変更があった際に自動的に呼ぶような設計にしてやる必要があります。

ScriptableObjectToAssetはScriptableObject名をラベルに登録するので、l:AnimatorParameterでプロジェクト内の全AnimatorParameterを簡単に見つけることが出来ます。ラベル管理便利

f:id:tsubaki_t1:20150205112127p:plain

f:id:tsubaki_t1:20150205112150p:plain

Unity Editorで特定のフォルダからファイルを検索する - テラシュールブログ

ラベルを利用したアセットの管理について - テラシュールブログ

 

コードはこちら


AnimatorParameter.cs

*1:代わりにIsKinematic