Unity 6|Animatorの1D Blend TreeでIdle・Walk・Runを切り替える【後編】
前編では、Shiftキーを押している間だけCharacterControllerの水平移動速度を変えました。今回は、停止・歩行・走行の見た目をAnimatorで切り替えます。
前編の移動が動く状態から始めてください。まだ前編を読んでいない場合は、先に前編を完成させましょう。ここでは、Idle・Walk・Runという3つのアニメーションクリップを用意し、1つの1D Blend Treeで管理します。
1. 移動速度とSpeedを分けて考える
CharacterControllerは座標を動かし、Animatorはモデルの見た目を動かします。本教材では、Animatorに渡すFloatパラメータをSpeedという名前にします。
| 入力 | 水平移動の速度設定 | Animatorの目標Speed |
|---|---|---|
| 移動入力なし | 0 | 0:Idle |
| 移動入力あり・Shiftなし | 3 | 1:Walk |
| 移動入力あり・Shiftあり | 6 | 2:Run |
Speedの0・1・2は、今回決めるアニメーション用の目盛りです。実際の移動速度3・6や、クリップの再生速度とは別の値です。Shiftだけを押している場合は、Speedを0にします。

2. モデルとクリップを準備する
停止するIdle、歩くWalk、走るRunのクリップを用意します。ここでの名前は教材上の呼び名なので、手元のクリップ名が異なっていても構いません。同じモデルで再生できる組み合わせを使ってください。
人型のモデル(Humanoid)なら、そのモデル用のAvatarと互換性のあるクリップを使います。人型以外のモデル(Generic)なら、同じ骨格(ボーンの並び)に作られたクリップが必要です。迷ったら、同じ配布元のモデルとクリップの組み合わせを選べば安心です。カプセルだけでは人の歩行アニメーションは再生できないので、見た目用のモデルを用意しましょう。
プレイヤーのルートにCharacter Controller・Player Input・PlayerController、見た目のモデルにAnimatorがある構成でも使えます。PlayerControllerのAnimator欄には、その見た目用Animatorをドラッグします。
FBXに入っているクリップを使う場合は、ProjectでFBXを選び、InspectorのAnimationタブで対象クリップのLoop Timeにチェックを入れてApplyを押します。これで歩きや走りが繰り返し再生されます。今回は、その場で足踏みするタイプ(in-place:位置が前に進まないクリップ)を使います。移動を含むクリップを使う場合は、必要に応じてRoot Transform PositionのXZやYをBake Into Poseにして、モデルの位置が流れない状態に調整してください。
AnimatorコンポーネントのApply Root Motionはオフにします。Root Motionは「アニメーション自身が座標を動かす」機能です。この教材では座標の移動をCharacterControllerに任せるので、二重に動かないようにオフにします。OnAnimatorMoveで移動させる別のスクリプトがある場合も、この教材の移動と重複しない構成にしてください。
3. 教材用Animator Controllerを作る
既存Controllerを上書きしないように、新しいAnimator Controllerを作ります。ProjectのCreateからAnimation内のAnimator Controllerを選び、例えばSprintLocomotionControllerと名付けて、モデルのAnimatorのController欄に割り当てます。
- Animatorウィンドウを開き、ParametersにFloat型の
Speedを追加します。初期値は0にします。 - グラフの空白部分を右クリックし、Create StateからFrom New Blend Treeを選びます。
- 作成したステートをLocomotionと名付け、右クリックのSet as Layer Default Stateで初期ステートにします。
- LocomotionをダブルクリックしてBlend Treeを開きます。
- Blend Typeを1D、ParameterをSpeedにします。
- Motionの追加操作(+からAdd Motion Field)で3枠を作り、それぞれIdle・Walk・Runのクリップを割り当てます。
- Automate Thresholdsをオフにし、下表のThresholdを入力します。
| Motion | Threshold |
|---|---|
| Idle | 0 |
| Walk | 1 |
| Run | 2 |
この構成は「1つのLocomotionステートの中に、3つのMotionがある」形です。Idle・Walk・Runを3つの別ステートとして作り、Transitionを張る作業は不要です。
Thresholdは、そのクリップが完全に効くパラメータ値です。Speedが0.5ならIdleとWalk、1.5ならWalkとRunが混ざります。プレビューでSpeedを0、0.5、1、1.5、2と変え、割り当てたクリップを確認しましょう。Compute Thresholdsから速度を自動計算する操作は、今回の0・1・2という独自の目盛りでは使いません。
4. C#からSpeedを渡す
前編のPlayerControllerにAnimatorの参照を追加します。
[SerializeField] private Animator animator;
[SerializeField] private float animationDampTime = 0.1f;
private static readonly int SpeedHash = Animator.StringToHash("Speed");
StringToHashは、パラメータ名から識別用の整数を作ります。Animator側も大文字小文字を含め、Speedという名前にそろえてください。
Updateでは移動入力がある場合だけ、歩きか走りを選びます。
float animationSpeed = 0f;
if (direction.sqrMagnitude > 0.001f)
{
animationSpeed = isSprinting ? 2f : 1f;
}
animator.SetFloat(SpeedHash, animationSpeed,
animationDampTime, Time.deltaTime);
direction.sqrMagnitudeは入力ベクトルの長さの2乗です。ほぼゼロなら停止として扱います。これは前編の向きを変える判定と同じしきい値です。
Blend Treeは中間値を混ぜて再生できます。ただし、スクリプトが0から2へ一瞬で値を変えるだけでは、中間値を時間をかけて通りません。今回はSetFloatのdampTime付きの形式を使い、0.1秒という設定を目安に変化をなめらかにします。0.1秒できっちり変化が完了するという意味ではありません。
5. 後編の完成コード
前編のPlayerController.csの中身を次のコードに置き換えます。前編と後編のスクリプトを同時に入れないでください。ファイル名は引き続きPlayerController.csです。
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class PlayerController : MonoBehaviour
{
// キャラクターの移動速度
[SerializeField]
private float moveSpeed = 3f;
// Shiftを押している間の移動速度
[SerializeField] private float sprintSpeed = 6f;
// ジャンプ時の上方向の速度
[SerializeField]
private float jumpPower = 3f;
// モデルに付いているAnimatorをInspectorで指定
[SerializeField] private Animator animator;
[SerializeField] private float animationDampTime = 0.1f;
private static readonly int SpeedHash = Animator.StringToHash("Speed");
// コンポーネント
private CharacterController _characterController;
// キャラクター自身のTransform
private Transform _transform;
// 移動速度
private Vector3 _moveVelocity;
// Input Action
private InputAction _move;
private InputAction _jump;
private InputAction _sprint;
private void Start()
{
if (animator == null)
{
Debug.LogError("AnimatorをInspectorで指定してください。");
enabled = false;
return;
}
// Character Controllerを取得
_characterController =
GetComponent<CharacterController>();
// Transformを取得
_transform = transform;
// Player Inputを取得
PlayerInput input =
GetComponent<PlayerInput>();
// Player Action Mapを取得
InputActionMap actionMap =
input.currentActionMap;
if (actionMap == null)
{
Debug.LogError(
"Player InputのDefault Mapを確認してください。"
);
enabled = false;
return;
}
// Default Mapの有効化はPlayerInputに任せる
// MoveとJumpを取得
_move = actionMap.FindAction("Move");
_jump = actionMap.FindAction("Jump");
_sprint = actionMap.FindAction("Sprint");
if (_move == null || _jump == null || _sprint == null)
{
Debug.LogError(
"Move・Jump・Sprintアクションを確認してください。"
);
enabled = false;
}
}
private void Update()
{
// 地面に接しているか確認
bool isGrounded =
_characterController.isGrounded;
// 移動方向を取得
Vector2 moveValue =
_move.ReadValue<Vector2>();
// 押している間だけダッシュ速度にする
bool isSprinting = _sprint.IsPressed();
float currentSpeed = isSprinting ? sprintSpeed : moveSpeed;
// 左右・前後の移動速度を設定
Vector2 direction =
Vector2.ClampMagnitude(moveValue, 1f);
_moveVelocity.x =
direction.x * currentSpeed;
_moveVelocity.z =
direction.y * currentSpeed;
// 移動している方向に向きを変える
Vector3 horizontalDirection =
new Vector3(
_moveVelocity.x,
0f,
_moveVelocity.z
);
if (horizontalDirection.sqrMagnitude > 0.001f)
{
_transform.LookAt(
_transform.position +
horizontalDirection
);
}
// 入力の有無とSprintからアニメーション用の目標値を決める
float animationSpeed = 0f;
if (direction.sqrMagnitude > 0.001f)
{
animationSpeed = isSprinting ? 2f : 1f;
}
animator.SetFloat(SpeedHash, animationSpeed,
animationDampTime, Time.deltaTime);
// 地面にいる場合
if (isGrounded)
{
// 下方向の速度を小さく保つ
if (_moveVelocity.y < 0f)
{
_moveVelocity.y = -2f;
}
// ジャンプボタンが押されたら
if (_jump.WasPressedThisFrame())
{
_moveVelocity.y = jumpPower;
}
}
else
{
// 空中では重力で下方向に加速
_moveVelocity.y +=
Physics.gravity.y * Time.deltaTime;
}
// キャラクターを移動
_characterController.Move(
_moveVelocity * Time.deltaTime
);
}
}
保存後、プレイヤーのPlayerControllerのAnimator欄に、モデルのAnimatorを割り当てます。Animation Damp Timeは0.1から始めましょう。WalkやRunのアセットをこの欄に入れるのではなく、Animatorコンポーネントを持つモデルを指定します。
6. 再生して確認する
| 操作・確認 | 期待する結果 |
|---|---|
| 入力なし | Speedが0へ近づき、Idleになる |
| Wを押す | Speedが1へ近づき、歩く |
| W+Shift | Speedが2へ近づき、走る |
| Wを押したままShiftを離す | 歩きへ戻る |
| Shiftだけ | Idleになる |
| 方向を変える | 移動方向を向き、歩行・走行を続ける |
Play中にAnimatorのParametersを見ると、Speedがなめらかに変わる様子を確認できます。値が変わらない場合はコードとAnimatorの参照、値が変わるのに見た目が変わらない場合はControllerとMotionの設定を順に確認します。
この手順は、公式資料とコードを読み合わせて作成しています。ただし、筆者のUnity Editorでのコンパイルや、各モデル・クリップでの実際の動作確認はまだ行っていません。うまく動かない場合は、次の「よくあるつまずき」の表を確認してください。上の表は、皆さんが自分で試して確かめるための手順です。
7. この教材での割り切り
今回のSpeedは入力から作っています。壁に向かってWを押せば、座標がほぼ動かなくても歩行アニメーションになります。これは「動こうとしている状態」を見た目に使っているためです。
ゲームパッドのスティックを少し倒すと移動量は小さくなりますが、Speedの目標は歩行なら1、ダッシュなら2です。アナログの強さに合わせる仕組みや、衝突後の実速度を測って止める仕組みは、別の発展課題にできます。
Spaceのジャンプ処理は残していますが、Jumpステートはまだ作っていません。空中でもIdle・Walk・Runのいずれかが再生されます。ジャンプの見た目まで完成させたい場合は、接地状態とジャンプ用ステートを追加する段階へ進みましょう。
8. よくあるつまずき
| 症状 | 確認する点 |
|---|---|
| Animatorを指定するエラー | PlayerControllerのAnimator欄にモデル側のAnimatorを入れる |
| Speedが見つからない | Float型か、大文字小文字や空白が一致しているか |
| ずっと同じモーション | Locomotionが初期ステートか、Blend TreeのParameterがSpeedか |
| 足は動くが体が滑る | クリップの足運びと移動速度3・6を調整する |
| モデルが勝手に流れる | in-place素材、Root Transform設定、Apply Root Motion、OnAnimatorMoveを確認 |
| 腕や脚がおかしい | Avatar、Rig、クリップの互換性を確認 |
まずは入力・移動・見た目の役割を分けて理解しましょう。Sprintで移動速度を選び、同じ入力からSpeedの目標値を決めれば、ダッシュの操作と走行アニメーションを対応させられます。
参考資料
- シリーズの基礎:Input SystemとCharacterController
- Unity公式:PlayerInput
- Unity公式:InputAction(IsPressed・WasPressedThisFrame)
- Unity公式:CallbackContext
- Unity公式:CharacterController.Move(Unity 6.3)
Input SystemはEditorとは別のパッケージです。上記の入力関連資料は1.14.2を参照しています。添付画面だけでは導入済みのパッケージ版は特定できないため、Package Managerで自分の版も確認してください。
- Unity公式:1D Blending
- Unity公式:Animation Blend Trees
- Unity公式:Animator.SetFloat
- Unity公式:Animator.applyRootMotion










ディスカッション
コメント一覧
まだ、コメントがありません