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
移動入力なし00:Idle
移動入力あり・Shiftなし31:Walk
移動入力あり・Shiftあり62: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欄に割り当てます。

  1. Animatorウィンドウを開き、ParametersにFloat型のSpeedを追加します。初期値は0にします。
  2. グラフの空白部分を右クリックし、Create StateからFrom New Blend Treeを選びます。
  3. 作成したステートをLocomotionと名付け、右クリックのSet as Layer Default Stateで初期ステートにします。
  4. LocomotionをダブルクリックしてBlend Treeを開きます。
  5. Blend Typeを1D、ParameterをSpeedにします。
  6. Motionの追加操作(+からAdd Motion Field)で3枠を作り、それぞれIdle・Walk・Runのクリップを割り当てます。
  7. Automate Thresholdsをオフにし、下表のThresholdを入力します。
MotionThreshold
Idle0
Walk1
Run2

この構成は「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+ShiftSpeedが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はEditorとは別のパッケージです。上記の入力関連資料は1.14.2を参照しています。添付画面だけでは導入済みのパッケージ版は特定できないため、Package Managerで自分の版も確認してください。

訪問数 5 回, 今日の訪問数 5回

広告