【Unity中級者向け】Animator応用編⑤|StateMachineBehaviourで会話の開始・終了に処理を入れる
前回は、Animator Override Controllerを使い、Animator Controllerの構成をそのままに、キャラクターごとのアニメーションを差し替える方法を紹介しました。
今回は、StateMachineBehaviourを使って、ステートの開始・終了にスクリプトの処理を連動させます。会話アニメーションを例に、「会話中は移動を止め、会話が終わったら移動を再開する」仕組みを作ってみましょう。
ゲームでは、アニメーションに合わせて操作を制限したい場面があります。
例えば、キャラクターが移動中にダメージを受けたとき。
移動キーを押し続けていても、ひるんでいる間は移動を止め、ひるみが終わったら再び操作できるようにしたいですよね。
こうした「特定の状態に入ったら処理を始め、そこから出たら元に戻す」仕組みに使えるのが、StateMachineBehaviourです。
今回は追加のAssetを使わず、応用編③で使用したTalk01のアニメーションで同じ仕組みを体験します。
作る動作は次のとおりです。
方向キーで前後左右へ移動する
Tキーを押すと会話を開始する
会話中は方向キーを押しても移動しない
もう一度Tキーを押すと会話が終了し、移動できるようになる
自分で移動キーを離して止まるのではなく、Animatorの状態に合わせてゲーム側が移動を制限することが、今回のポイントです。
Animator 応用編【全5回】
Blend Tree入門 ― Idle / Walk / Runを滑らかにつなぐ
2D Blend Tree ― 上下左右の移動アニメーション
Animator Layer / Avatar Mask ― 上半身だけ別アニメーション
Animator Override Controller ― キャラごとにモーションを差し替える
StateMachineBehaviour ― Stateの開始・終了に処理を入れる
1.StateMachineBehaviourとは?
StateMachineBehaviourは、Animatorのステートに追加するスクリプトです。
普段使うMonoBehaviourとの違いを整理しましょう。

ここでいうステートは、Animatorウィンドウにある Idle や Walk、Talkなどの四角い箱のことです。
StateMachineBehaviourでは、次のメソッドを使用できます。

今回は OnStateEnter と OnStateExit を使います。Talkに入ると移動を禁止し、Talkを出ると移動を許可します。

2.応用編③のAnimatorを確認する
今回は、応用編③で作成したLayer/Avatar Maskの環境を利用します。Override Controllerの設定は必須ではありません。
以下の構成を確認してください。
Base Layer:MoveX/MoveYを使用する移動用の2D Blend Tree

UpperBody Layer:会話用のTalkステート

TalkステートのMotion:Talk01

UpperBody LayerのWeight:1

Avatar Mask:上半身を有効にしたもの

IsTalking:Bool型のParameter
Apply Root Motion:OFF

今回はコードに合わせて、移動用のFloatパラメーターを「MoveX」「MoveY」、会話用のBoolを「IsTalking」とします。大文字・小文字も合わせてください。
UpperBody Layerには、会話していないときに使うEmptyステートと、会話用のTalkステートを用意します。EmptyはMotionを設定しない通常ステートです。

EmptyをUpperBody Layerのデフォルトステートにします。動作確認を分かりやすくするため、両方のTransition Durationはまず0に設定してください。
Talk01は、会話中に繰り返し再生できるようにLoop TimeをONにします。

会話終了はクリップの終わりではなく、IsTalkingをfalseにして切り替えます。
LayerのWeightを0にするだけの会話終了は、今回使いません。
Weightを変えてもTalkステートから出るとは限らず、OnStateExitによる移動再開を確認できないためです。
ここまでの設定、詳しくは Animation 応用編①~③ をご確認ください。
3.まずは開始・終了をログで確認する
TalkステートにBehaviourを追加する
AnimatorウィンドウでUpperBody Layerを開きます。
Talkステートを選択します。
Inspectorの「Add Behaviour」を押します。
「New Script」から、名前を「TalkStateBehaviour」にして作成・追加します。

Unityのバージョンによって表示が異なる場合があります。作成後、TalkステートのInspectorにTalkStateBehaviourが追加されていることを確認してください。
GameObjectのAdd Componentではなく、TalkステートのAdd Behaviourから追加します。
ログを表示するコードを書く
TalkStateBehaviour.csを開き、中身を次のコードに置き換えます。
public class TalkStateBehaviour : StateMachineBehaviour
{
// 会話開始時に呼ばれる
public override void OnStateEnter(
Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
Debug.Log("会話開始"); // 会話開始時にログを出力
}
// 会話終了時に呼ばれる
public override void OnStateExit(
Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
Debug.Log("会話終了"); // 会話終了時にログを出力
}
}普段のスクリプトと違い、クラス名の後ろ(継承)がMonoBehaviourではなくStateMachineBehaviour になっています。
overrideは、Unityが用意しているメソッドに、こちらで処理を書くための指定です。
今回は3つの引数を詳しく使いませんが、それぞれ次の情報を受け取っています。
animator:このステートを動かしているAnimator
stateInfo:対象ステートの情報
layerIndex:対象Layerの番号
再生して確認する
Playerについているスクリプトファイルのチェックを一旦外して検証します。
ゲームを再生し、AnimatorのParametersでIsTalkingをON/OFFにします。
ONにすると「会話開始」、OFFにすると「会話終了」がConsoleに表示されれば成功です。
Talk01がループしても、1周ごとに開始・終了のログが出るわけではありません。 今回確認しているのは、クリップの周回ではなく、Talkステートへの出入りです。

4.キー入力と移動を管理するスクリプトを作る
次は、Tキーで会話を切り替え、会話中の移動を止める処理を作ります。
今回は、前回までと同じInput.GetAxisを使う方式で説明します。
Input Systemのみのプロジェクトでは、
Project Settings → Player → Other Settings
の「Active Input Handling」を「Both」にする必要があります。変更後に再起動を求められた場合は再起動してください。
PlayerTalkController.csを作成し、次のコードを入力します。
public class PlayerTalkController : MonoBehaviour
{
Animator animator; // Animatorコンポーネントへの参照
float moveSpeed = 3f; // 移動速度
bool moveLocked; // 移動禁止フラグ
void Awake()
{
animator = GetComponent<Animator>(); // Animatorコンポーネントを取得
}
void Update()
{
if (Input.GetKeyDown(KeyCode.T)) // Tキーが押されたとき
{
bool isTalking = animator.GetBool("IsTalking"); // 現在の会話状態を取得
animator.SetBool("IsTalking", !isTalking); // 会話状態を切り替える
}
float x = Input.GetAxis("Horizontal"); // 左右方向の入力を取得
float y = Input.GetAxis("Vertical"); // 前後方向の入力を取得
Vector3 movement = new Vector3(x, 0f, y); // 移動ベクトルを作成
if (moveLocked) movement = Vector3.zero; // 移動禁止中は移動ベクトルをゼロに
movement = Vector3.ClampMagnitude(movement, 1f); // 移動ベクトルの大きさを1に制限
animator.SetFloat("MoveX", movement.x); // AnimatorのMoveXパラメータを更新
animator.SetFloat("MoveY", movement.z); // AnimatorのMoveYパラメータを更新
// プレイヤーの位置を更新
transform.position += movement * moveSpeed * Time.deltaTime;
}
public void SetMovementLocked(bool locked) // 移動禁止フラグを設定するメソッド
{
moveLocked = locked; // 移動禁止フラグを更新
if (locked) // 移動禁止中は
{
animator.SetFloat("MoveX", 0f); // AnimatorのMoveXパラメータを0に設定
animator.SetFloat("MoveY", 0f); // AnimatorのMoveYパラメータを0に設定
}
}
}このコードは、キャラクターをワールド座標のX/Z方向へ移動させる、今回の実習用の例です。
キャラクターの回転は変更せず、応用編②の前後左右のBlend Treeを利用します。
障害物との衝突や重力の処理は含めていません。
GameObjectへ追加する
PlayerTalkControllerは、キャラクターを移動させるGameObjectにアタッチします。
以前の移動・会話入力スクリプトは、今回の確認中は無効にしてください。 複数のスクリプトが同時に移動やParameterを変更すると、正しく比較できません。

コードの解説
movementLockedは「移動禁止中かどうか」を記録する変数です。
trueなら移動ベクトルをゼロにします。
MoveX/MoveYにもゼロを渡すので、Base LayerのBlend TreeはIdleへ戻り、足の歩行も止まります。
一方、Tキーの処理は移動制限とは別に実行しています。
移動禁止中にUpdate全体を終了してしまうと、会話を終了するTキーまで受け付けなくなるためです。
SetMovementLockedは、Behaviourから呼び出す窓口です。
trueを渡すと移動禁止、falseを渡すと移動許可になります。
5.Talkの開始・終了から移動制御を呼び出す
TalkStateBehaviour.csを、次のコードに置き換えます。
public class TalkStateBehaviour : StateMachineBehaviour
{
public override void OnStateEnter(
Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Animatorの親オブジェクトにアタッチされているPlayerTalkControllerを取得
PlayerTalkController player =
animator.GetComponentInParent<PlayerTalkController>();
if (player == null) return; // PlayerTalkControllerが見つからない場合は処理中断
player.SetMovementLocked(true); // 移動を禁止する
Debug.Log("会話開始:移動禁止"); // デバッグログを出力
}
public override void OnStateExit(
Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Animatorの親オブジェクトにアタッチされているPlayerTalkControllerを取得
PlayerTalkController player =
animator.GetComponentInParent<PlayerTalkController>();
if (player == null) return; // PlayerTalkControllerが見つからない場合は処理中断
player.SetMovementLocked(false);
Debug.Log("会話終了:移動許可");
}
}コードの解説
キャラクター側のスクリプトを取得する
GetComponentInParent は、Animatorが付いたGameObject自身と、その親をたどってPlayerTalkControllerを探します。
Animatorがモデルの子オブジェクトにあり、移動スクリプトが親に付いている構成でも取得できます。
ステートへの出入りで移動制限を切り替える
OnStateEnterでは、SetMovementLocked(true)を呼びます。これで移動禁止になります。
OnStateExitでは、SetMovementLocked(false)を呼びます。これで移動を再開できます。
役割を整理すると、次のようになります。
PlayerTalkController:入力を受け取り、移動する
TalkStateBehaviour:Talkへの出入りを受け取り、移動制限の切り替えを伝える

6.再生して動きを確認する
ゲームを再生して、次の順番で確認しましょう。
方向キーでキャラクターが移動する。
移動キーを押したままTキーを押す。
Talkが始まり、キャラクターが立ち止まる。
会話中に別の方向キーを押しても移動しない。
Tキーをもう一度押す。
Talkを出ると、移動キーに合わせて再び移動する。
今回のポイントは、移動キーを離さなくても立ち止まることです。
応用編③では「歩きながら会話する」動きを作りました。今回は同じLayerとMaskに処理を加えて、「会話中は立ち止まる」動きに変更できました。

7.実際のゲームでは、どんなときに便利?
会話のために自分でキーを離すだけなら、この仕組みは必要ありません。
また、今回の移動制限だけなら移動スクリプトでIsTalkingを確認する方法でも実現できます。
StateMachineBehaviourの便利な点は、ステートに連動する処理を、そのステート側にまとめられることです。
例えばDamageステートに入ったら操作を制限し、そこを出たら制限を解除する、といった使い方ができます。
入力から開始した場合でも、敵との接触などで開始した場合でも、そのステートに入れば同じ処理を実行できます。
今回はTalkを使いましたが、学んだ考え方はこうした場面にも応用できます。
なお、複数のLayerで攻撃やダメージが重なるゲームでは、1つのBoolだけで操作制限を管理すると、片方の終了で制限が解除されてしまうことがあります。
今回のコードは、Talkだけが移動制限を担当する実習用です。
8.Animation Eventとの違い
基礎編⑤で扱ったAnimation Eventも、アニメーションに合わせて処理を実行する機能でした。
使い分けのポイントは、ステートへの出入りか、クリップ内の特定の瞬間かです。

また、OnStateExitは「クリップを最後まで再生した」という通知ではありません。 別ステートへの切り替えなどで、対象ステートの評価が終わるタイミングに呼ばれます。
Transition Durationを設定すると、新旧ステートが重なって評価される時間があります。
そのため、今回はDurationを0にして、出入りと処理の対応を分かりやすくしています。

9.動かないときの確認ポイント

10.まとめ

Animatorの動きとスクリプトの処理を組み合わせて、ゲームに合ったキャラクターの振る舞いを作ってみてください。
こちらはUnity初心者向けの記事をテーマ別にまとめています。
▼ Unity初心者向け記事まとめはこちら
こちらのマガジン「ミニゲーム作成講座 全8回セット」でもシーンの切り替えなどを活用して、初心者でも知識ゼロから作れるミニゲーム作成講座を紹介しています。

さらにステップアップの「スマホで遊ぶジャンプゲーム 8回セット」のマガジンもぜひご活用ください。

ファイブボックスでは、スクラッチやUnityの個別指導のオンラインレッスンを行っています。
ご興味のある方は当サイト、オンラインレッスンから、無料体験授業へお問い合わせ下さい。
いいなと思ったら応援しよう!
よろしければ応援お願いします! いただいたチップはクリエイターとしての活動費に使わせていただきます!