テラシュールブログ

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

【Unity】エディタ拡張でstaticなフィールドに置いたインスタンスが、コンパイル/再生時に初期化される問題の対策について

長ったらしいタイトルですが、その通り「エディタ拡張で、staticなフィールドに置いたインスタンスコンパイル/再生時に初期化される問題の対策について」です。

初期化されるパラメータ

Unityのエディタ拡張は、ホットリロードの要領でソースコードを書き換えると即座にコンパイル・修正したコードを反映させます。

正確には、「シリアライズ」→「コンパイル」→「シリアライズしたデータ戻し」としているみたいです。

f:id:tsubaki_t1:20170131225147j:plain

これよく見ると、シリアライズしたデータを一時退避し書き出したデータを再設定する事で、コンパイル前に設定したパラメータを保持しています。
逆を言えば、シリアライズ出来なかったパラメータが破棄されます。

例:シリアライズしないデータがある場合

 例えば下のようなコードを用意します。

gist.github.com

ウィンドウを表示後、Pushボタンを押すと#が増えるという簡単な物ですが、ソースコードコンパイルやゲームの再生を行うとパラメータが破棄されてしまいます。

f:id:tsubaki_t1:20170131230141g:plain

解決方法

この問題は、要するに「シリアライズ出来ないパラメータがある」事が問題となります。なので、シリアライズ出来る項目で固めておけば、良いのです。

シリアライズ出来る項目

シリアライズ出来る項目は、以下の通り。

  • static ではないこと
  • const ではないこと
  • readonly ではないこと
  • [Serializable]の属性を持つクラスまたは構造体
  • UnityEngine.Objectから派生したクラス
    (MonobehaviourやScriptableObject)
  • プリミティブな型(int, float, double, bool, string, 等々)
  • シリアライズ出来る型の配列もしくはList

この項目から外れている物は、シリアライズ出来ないのでホットリロード時に破棄されます。

上のサンプルコードでは、stringはシリアライズ出来ますがstaticに格納していたためシリアライズ出来ず、リロード時に値が破棄されていた訳です。

 

つまり、破棄しないように、もしくは破棄しても流し込むようにすれば、値を保持し続けてくれる訳です。

f:id:tsubaki_t1:20170131235250g:plain

解決法1:シリアライズ出来る場所に移動

手っ取り早い解決方法は、シリアライズ出来る場所にフィールドを持っていく事です。上のコードの場合、staticではなくフィールドに配置すればシリアライズ出来ます。

gist.github.com

解決法2:ScriptableSingletonを使用する

どうしてもstatic値が欲しい場合があります。例えば一つしかウィンドウを出したくないといった場合、staticに自分のインスタンスを登録して有無をチェックするコードが使用されますが、staticがデシリアライズ時に破棄されるため十分ではありません。

そんなときはScriptableSingletonを使用します。

ScriptableSingletonはシングルトンのような動作をしつつ、シリアライズ可能な値をシリアライズして保持してくれるので、staticのように全体で共有して扱う値も保持してくれます。

エディタの情報やアセット、GameObjectのインスタンスを保持する場合、これが何かと便利です。

gist.github.com

但し、シングルトンのような挙動をおこなうせいで、初期化コードを書いた方が良いケースが多々あります。
(これのランタイム版があれば色々と楽なのですが…)

解決法3:シリアライズするタイミングで、IOに書き出してしまう

微妙な方法ではありますが、シリアライズするタイミングのコールバックを取得して、IOに書き込んでしまうというアプローチです。

ISerializationCallbackReceiverを実装している場合、シリアライズ前とデシリアライズ後にコールバックが呼ばれるので、何らかの処理を挟む事が出来ます。

この方法なら、ディクショナリだろうが何だろうが、自分でシリアライズしてデシリアライズ出来るなら、値を保持出来ます。
なお、このコールバック中にUnityAPIは呼べません。つまりEditorPrefsは使えません。まぁ、UnityのAPIが必要な物は大体シリアライズ出来るので大した問題では。

gist.github.com

関連

monkey coders' - 実行中にスクリプトを編集したときのNullReferenceExceptionを回避する

docs.unity3d.com

tsubakit1.hateblo.jp

kanonji.info

 

【Unity】アニメーション中、指定の位置に到達するように位置を補正する

f:id:tsubaki_t1:20170130223038g:plain

ジャンプやダッシュジャンプを用いて障害物を登るというケースはよく有ります。この高さにアニメーションが一致すれば問題は無いのですが、任意の段差や距離をジャンプしたいといったケースは多々あります。

 

そういったケースの場合、幾つか解決の手法はありますが、今回はAnimator.MatchTargetを使用した解決法をメモします。

Animator.MatchTarget

Animator.MatchTargetは、アニメーションの指定タイミングでゲームオブジェクトの位置及び回転が到達するように調整するAPIです。

例えば下のように、ジャンプ開始モーションから補正を開始して着地時に指定位置に到達するような、微妙な位置を調整したり出来ます。

f:id:tsubaki_t1:20170130224713g:plain

到達して欲しい位置は、単純なRoot位置だけではなくBodyやLeftHand、RightHand、LeftFoot、RightFootといった調整が出来ます。

例えば上のGifアニメでは、左足が着地する位置に合わせて位置を調整しています。

実際に使ってみる

StateMachineBehaviourの用意

実際に使ってみます。

まず、普通にジャンプするアニメーションを用意します。そのままだとジャンプ力が足りず、変な所に着地してしまいます。

f:id:tsubaki_t1:20170131002757g:plain

このアニメーションに補正をかける訳ですが、実行時にステートマシンを調べるのも面倒なので、StateMachineBehaviourを使います。Mecanimでステートを選択中にAdd Behaviourで挙動を追加。

f:id:tsubaki_t1:20170130230223j:plain

追加するコードはこんな感じです。

gist.github.com

座標とウェイトの調整

コンポーネントは下のようになります。

まずTarget Body Partは「MatchPositionが到達して欲しいパーツ」です。先程も書いた通り「ルート、体、右手、右足、左手、左足」が指定出来ます。

逆にMatchPositionは、指定パーツが到達する位置(絶対値)で、Match Rotationが回転です。

この動きを、Weightsで制御します。Position Wieghtは、X/Y/Z方向にそれぞれ対応したウェイトです。例えばYを0に設定しておくと、MatchPositionでY軸にズレていてもY方向に動きません。

f:id:tsubaki_t1:20170130232653j:plain

 

今回は単なる移動なので、とりあえずMatch PositionとPosition Weightを調整します。
Target Body PartをLeft footに変更し、Match Positionを想定の位置に調整。

f:id:tsubaki_t1:20170130233027j:plain

アニメーションのタイミング調整

このStateMachineBehaviourのStartとEndが重要なポイントです。
MatchTargetは、Startから開始してEndのタイミングで対象の位置に到達します。そのため、キャラクターが動き始めたタイミングをStart、停止するタイミングをEndに指定します。

見るべきは、時間ではなくパーセントの方です。例えば下のアニメーションの場合、だいたい50.6%~76%くらいの位置で移動していますので、startは0.506、endは0.76と設定しておきます。この辺りは要微調整です

f:id:tsubaki_t1:20170130233452g:plain

f:id:tsubaki_t1:20170130234034j:plain

これで、タイミングや位置の調整が上手くいっているならば、着地のタイミングで対象位置に到達するような感じでアニメーションしてくれます。

f:id:tsubaki_t1:20170130234545g:plain

注意点

ApplyRootMotoinで動いている

MatchTargetですが、ApplyRootMotoinで動作しています。つまり、ApplyRootMotoinにチェックが入っていないと動作しません

が、逆を言えばApply Root Motoinにチェックが入っていればRootMotionの無いアニメーションでも動作します。

例えばユニティちゃんのアニメーションにはRootMotoinが入っていませんが、着地のタイミングに合わせて目標地点に到達するように調整出来ます。

f:id:tsubaki_t1:20170130235601g:plain

もしApplyRootMotoinを付けたくない場合は、下の記事が参考になりそうです。

gametukurikata.com

移動速度は距離を時間で割った速度で、等速

MatchTargetですが、基本的に移動速度は一定速度(距離を時間で割った速度)です。そのため、距離があまりにも想定アニメーションと比較して離れすぎている場合、非常に気持ちの悪い感じの動きになります。

また、基本的に目標地点へ一直線で動くので、カーブを描いて到達して欲しいようなケースでは有効ではないです。

なので、補正程度に使用するのが良さそうです。

MatchTarget補正中にステートの補完が走ると変な位置に到達する

MatchTargetで移動中、アニメーションのステートがブレンドを開始すると、妙な位置に到達する事があります。

f:id:tsubaki_t1:20170131000649j:plain

機能するMatchTargetは一つのみ

機能するMatchTargetは一つのみです。重複する場合、上に配置したStateMachieneBehaviourが優先されます。また、同様にサブステートがある場合は親に設定したStateMachieneBehaviourが優先されます。

なお、下のStartが上より早い場合、無視されるっぽいです。

f:id:tsubaki_t1:20170131002217j:plain

画面外に跳ねて、地面に着地する的な。

f:id:tsubaki_t1:20170131002326g:plain

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

Galaxy S7と接続中のBluetoothヘッドホンがブチブチ切れる問題

先日BoseBluetooth ノイズキャンセリングヘッドホン QC 30を購入して楽しんでいたのですが、最近Bluetoothの音がブチブチ切れ音が遅延する問題に悩まされてました。

てっきりQC30の問題かと思いましたが、プロジェクターやiPadでは問題なく再生出来るのでGalasy s7 edgeの問題と仮定、ググったところ見事に同様の問題がヒット。

smartphone-watch.net

が、しかし

特定のBluetooth機器とだけでなく、複数のBluetooth機器で同じ症状が出るということで、こちらは対処法も見つかっていません。

おおぅ…マジか

解決

他のサイトでBluetoothのON/OFFや端末の再起動を試すも効果がなし。一瞬、専用のiPod Touchでも買おうかとよぎりましたが、もう少し調べてみることに。

すると、どうやらBluetoothのサービスを強制終了すれば治る可能性があるとの記述が…

How To Fix Galaxy S7 Bluetooth Problems | RecomHub

 

実際に試した所、コレが当たりでした。

やり方

  1. 設定を開く
  2. アプリーケーション管理を開く
  3. 右上のその他よりシステムアプリを表示を選択
  4. 一覧よりBluetooth共有を選択
  5. 強制終了を選択して、ついでにキャッシュを削除
  6. これでBluetoothが切断されるので、
    BluetoothをON/OFFしてBluetoothを再起動

再接続時以降はヘッドホンがブチブチ切れる事は無くなりました。

 

上手く行くかはわかりませんが、もし同様の問題があるなら試してみるのも良いかもしれません。

【Unity】知らないと面倒くさい事になるかもしれないAnimatorの「Write Defaults」の動作について

 今回はWrite Defaultsについて。

この挙動は少し分かりにくいので、少し補足します。

Write Defaultsという項目

f:id:tsubaki_t1:20170116000110j:plain

AnimatorのWrite Defaultsの項目のマニュアルを見ると、下のような物のようです。

Write Defaults : AnimatorStates を書くかどうかにかかわらず、その Motion によってアニメーション化されてないプロパティーによってデフォルト値に戻します。

 さて、「Motionによってアニメーション化されていないプロパティのデフォルト値」とは何でしょう。

キーが未定義なアニメーションは起動時のパラメータをデフォルト値として使う

プロパティのデフォルト値は、要するにAnimatorの起動時の位置やパラメータです。

AnimationClipが変更するオブジェクトや座標のパラメータを事前に保持しておき、特にキーが設定されていない項目は、その値を基準にブレンドを行うみたいです。

例えば下のGifアニメでは、最終的な座標(左下)のみを持つアニメーションを用意し、キーを何も持たないアニメーションからブレンドさせています。
Animatorを起動すると、デフォルト値はAnimator起動時の位置になるので、UIを配置した位置から最終的な座標(左下)へブレンドする形でアニメーションしています。*1

http://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20150205/20150205101008.gif

f:id:tsubaki_t1:20170116002224j:plain

Write Defaultsという項目

ネタバラシをすると、Write Defaultsの項目は要するに「アニメーションキーが設定されていない項目はデフォルト値を使用する」という項目です。

例えば下のようなUIを用意し、それぞれ「Sample 1のUIみ動かすアニメーション」「Sample 2のUIのみ動かすアニメーション」「Sample 3のUIのみ動かすアニメーション」を用意します。

f:id:tsubaki_t1:20170116003958j:plain

f:id:tsubaki_t1:20170116004112j:plain

このUIを動かすアニメーションを順番に再生すると、特にキーを設定しなくてもUIが仕舞われる感じのアニメーションが再生されます。

これはキーを設定していない項目はデフォルト値を元にブレンドしている為です。

f:id:tsubaki_t1:20170116004548j:plain

f:id:tsubaki_t1:20170116004403g:plain

逆にWrite Defaultのチェックを外していた場合、該当ステートではデフォルト値による上書きが無くなります。上のアニメーションの場合、UIを仕舞う挙動が無くなり、最後にアニメーションした位置がそのまま使用されます。

f:id:tsubaki_t1:20170116004809j:plain

f:id:tsubaki_t1:20170116004814g:plain

注意点

デフォルト値が保持されるケースの問題

なお保持するのはAnimationClipが変更する項目で、それ以外の項目は保持しないみたいです。保持するのは現在再生中のAnimationClipが変更するパラメータではなく、AnimationControllerが保持する全AnimationClipのいずれかが変更するパラメータです。つまり、AnimationControllerに含まれてるだけで効果を持ちます。

Write Defaultの有無に関わらず、AnimationClipで動かすパラメータはスクリプト上で動かす場合は注意が必要です。

tsubakit1.hateblo

デフォルト値がリセットされるケース

Write Defaultsを使用する上で一番の問題は、ここで使用するデフォルト値はAnimatorを含むGameObjectを非アクティブにするとリセットされ、アクティブ化した際のパラメータが利用されるという意味不明な仕様です。

例えばWrite Defaultsを使用中にGameObjectを一度非アクティブにすると、デフォルト値が変わってしまう為UIの切替が中途半端になります。

f:id:tsubaki_t1:20170116010217g:plain

tsubakit1.hateblo.jpので、Animatorを含むGameObjectの非アクティブ化は避け(親オブジェクトの非アクティブ化でも同様)、Animatorのみをdisableにしないと、ややこしい事が起こります。*2

f:id:tsubaki_t1:20170116012707j:plain

ちなみに、表情にBlendShapeを使いつつデフォルト値を前提に使用している場合、状態が混ざって面白い顔になる事があります。上のような感じ。
(他の表情が50とかの状態がデフォルト値となり、色々な表情が混ざる)

関連

tsubakit1.hateblo.jp

*1:この手法でUIを動かす場合、ブレンド率はリニア強制なので硬い動きしか表現出来ません。やるならブレンドツリーでやる事をお薦め。というかTweenお薦め

*2:正直、この仕様のせいでWrite Defaultsは死に機能になってる感がある