2020/11/09
今回はUnityのAnimatorに関するちょっとしたお話です。
つい最近、開発中のゲームで所謂「待機モーション」を実装しました。何もしないでいるとたまに周りを見渡したり、武器を構えなおしたりするあれです。
Unity的なやり方はあれこれありますが、私は「待機中(Idle)ステートがループした際に一定確率で実行する」ようにしました。
まずは待機モーションをIdleMotionという名前のステートで作成します。
Animator.Play()で実行するため、Idleからの遷移条件は設定していません。逆にモーション終了後は自動的にIdleに戻るようにします。
次にStateMachineBehaviourを継承したスクリプトを作成し、Idleステートに設定します。
using UnityEngine;
public class Idle : StateMachineBehaviour
{
[SerializeField]
[Range(0, 100f)]
private float idleMotionProp = 0;
private bool executeFlag = false;
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// ループするので1で割った余りをnormalizedTimeとして使用
if (executeFlag && (stateInfo.normalizedTime % 1) <= 0.1f) {
executeFlag = false;
}
if (!executeFlag && 0.9f <= (stateInfo.normalizedTime % 1)) {
executeFlag = true;
var executeProp = Random.Range(0, 100f);
if (executeProp <= idleMotionProp) {
animator.Play("IdleMotion");
}
}
}
}
ポイントは「OnStateUpdate()内で判定する」ことです。
アニメーションの再生がループしたタイミングではOnStateExit()が呼ばれません。なので「現在のループの再生が終わった」の判定をOnStateUpdate()内で行う必要があります。
具体的にはstateInfo.normalizedTimeを1で割ったあまりを使います。これはnormalizedTimeがループしている限り増え続けるためです。
1で割ったあまりは0~1の間になるので、「現在のループのどの再生位置か」が大まかにわかります。
なので再生位置の後ろのほうで乱数を生成し、引っかかったらAnimator.Play()で待機モーションを実行します。サンプルでは0.9以上としました。
また何度も判定されることを避けるため、ループの最初のほうでフラグをfalseにし、判定後にtrueにして「判定は1ループ1回のみ」にしました。
設定後はIdleステートのインスペクターからidleMotionPropをお好みの値にします。私の場合は30 (=30%の確率で実行) にしています。
これでうまくいけば、待機中にたまーに待機モーションが再生されます。
綺麗なやり方かと言われると微妙ですが、キャラクター本体側のスクリプトを汚さず、また複数のAnimatorControllerで使いまわせるので、これはこれで良しとします。









