🎮

[Unity] モデルに移動とアニメーションを適用する

2023/09/18に公開

https://zenn.dev/aruk_vs/articles/794ce1a536cbf2
前回追加したMMDモデルに移動とアニメ―ション、接地判定を付与します。
アニメーションの素材はMixamoからお借りしました。

  • idle
  • walking
  • jogging
  • running
  • Jump

以上の五つのアニメーションを自由に遷移させていく事を目指します。

仕様

  • 無操作時はidle状態。
  • 方向キー(WASD)を入力するとモデルが移動し、Walk/Jogging状態に遷移。
  • Wlak/Joggingの切り替えはLeft Controlを押す度に入れ替わる。
  • マウスの右クリックを押しながら方向キーを入力するとRun状態に遷移。
  • Spaceボタンを押すとジャンプ。空中ジャンプは不可
  • 全ての遷移ParametersはBoolで管理。

前準備

MMDモデルが落ちないようColliderとRigidbodyを導入しておきます。
ColliderにはSA Bone Colliderを使用し、Rigidbodyと共にコンポーネントに追加します。

移動の付与

操作モデルをキーボードの入力に合わせて移動するようにします。
ProjectウィンドウのAssetsにScriptsというフォルダを作成し、"Controller"を作成。
コードを編集し以下の様にモデルが動きました。

Controller.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Controller : MonoBehaviour
{
    private float horizontalInput;
    private float verticalInput;
    [SerializeField] float moveSpeed = 2f;
    [SerializeField] float walkSpeed = 2f;
    [SerializeField] float jogSpeed = 7f;
    [SerializeField] float runSpeed = 10f;
    private const float RotateSpeed = 900f;
    private bool jogOn = false;
    private Rigidbody playerRb;
    // Start is called before the first frame update
    void Start()
    {
        playerRb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        horizontalInput = Input.GetAxisRaw("Horizontal");
        verticalInput = Input.GetAxisRaw("Vertical");

        // 右クリックを押している間run
        if (Input.GetMouseButton(1))
        {
            moveSpeed = runSpeed;
        }
        else
        {
            moveSpeed = walkSpeed;
            if (jogOn)
            {
                moveSpeed = jogSpeed;
            }
        }
        // LeftCtrでwalk/jogを切り替える。
        if (Input.GetKeyDown(KeyCode.LeftControl))
        {
            if (!jogOn)
            {
                jogOn = true;
                moveSpeed = jogSpeed;
            }
            else
            {
                jogOn = false;
                moveSpeed = walkSpeed;
            }
        }

        // jumpボタン(=Space)でジャンプ
        if (Input.GetButtonDown("Jump"))
        {
            playerRb.velocity = Vector3.up * 10;
        }
    }

    void FixedUpdate()
    {
        // カメラの方向から、X-Z平面の単位ベクトルを取得
        Vector3 cameraForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;

        // 方向キーの入力値とカメラの向きから、移動方向を決定
        Vector3 moveForward = cameraForward * verticalInput + Camera.main.transform.right * horizontalInput;

        // 移動方向にスピードを掛ける。ジャンプや落下がある場合は、別途Y軸方向の速度ベクトルを足す。
        playerRb.velocity = moveForward * moveSpeed + new Vector3(0, playerRb.velocity.y, 0);

        // キャラクターの向きを進行方向に
        if (moveForward != Vector3.zero)
        {
            Quaternion from = transform.rotation;
            Quaternion to = Quaternion.LookRotation(moveForward);
            transform.rotation = Quaternion.RotateTowards(from, to, RotateSpeed * Time.deltaTime);
        }
    }
}

WASDキーを入力するとキャラクターが動き、LeftCtrや右クリック如何によってその移動速度が制御されます。
移動方向はカメラの向きから決定されます。
参考元: https://tech.pjin.jp/blog/2016/11/04/unity_skill_5/

接地判定

空中ジャンプを禁止するための設定を行います。
基本的には過去に勉強した通りに行いますので下の記事参照。
https://zenn.dev/aruk_vs/articles/cd38e1b038eef9

アニメーションの付与

Projectの適当な場所(今回は"Asset/Animator"を作成)に"Animator Controller"を作成し、適当に名付けます(今回は"BasecAC")。
ダブルクリックするとAnimatorウインドウに編集画面が出てくるので目的のアニメーションが含まれた遷移図を作成、左の"Parameters"Tabに遷移条件の変数を作成します。

それぞれのトランジションに条件付けをします、複数条件は全てANDになります。
※AnyState→Runの例。input,runOn,onGroundがどちらもtrueの場合に遷移する。

  • 終了時間あり: 遷移条件が満たされた場合即時に遷移がしたければチェックを外す。(というか基本外しておく方が都合が良い)
  • 中断要因: 遷移の割り込み設定
    • なし: 割り込まない
    • Current State: 設定した遷移中に他の遷移条件が満たされた場合即時に遷移を許可。
    • Next State: 設定した遷移先から更に先の遷移先への割り込み遷移を許可。
  • 順次づけられた割り込み: Any Stateで設定した順に割り込みが優先される
  • 自身に遷移: 条件が満たされた時自身に遷移する。

次にこれらの遷移を管理できるコードを書いていきます。
とはいえ先程のコードにAnimatorのparameter切り替えと接地判定を行うだけです。

Controller.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Controller : MonoBehaviour
{
    private float horizontalInput;
    private float verticalInput;
    [SerializeField] float moveSpeed = 2f;
    [SerializeField] float walkSpeed = 2f;
    [SerializeField] float jogSpeed = 7f;
    [SerializeField] float runSpeed = 10f;
    private Rigidbody playerRb;
    public Animator _animator;
    private bool walkInput = false;
    private bool runOn = false;
    private bool jogOn = false;
    private bool flying = false;
    public bool onGround = true;
    private bool jumpOn = false;
    private const float RotateSpeed = 900f;
    public GroundCheck3D rightGround, leftGround;
    void Start()
    {
        playerRb = GetComponent<Rigidbody>();
    }
    void Update()
    {
        horizontalInput = Input.GetAxisRaw("Horizontal");
        verticalInput = Input.GetAxisRaw("Vertical");
	// 接地状態でのみ速度の切り替えやモーション変更を行う
        if (onGround)
        {
            if ((horizontalInput == 0.0f) && (verticalInput == 0.0f))
            {
                walkInput = false;
            }
            else
            {
                walkInput = true;
            }
            if (Input.GetMouseButton(1))
            {
                moveSpeed = runSpeed;
                runOn = true;
            }
            else
            {
                moveSpeed = walkSpeed;
                if (jogOn)
                {
                    moveSpeed = jogSpeed;
                }
                runOn = false;
            }

            if (Input.GetKeyDown(KeyCode.LeftControl))
            {
                if (!jogOn)
                {
                    jogOn = true;
                    moveSpeed = jogSpeed;
                }
                else
                {
                    jogOn = false;
                    moveSpeed = walkSpeed;
                }
            }

            if (Input.GetButtonDown("Jump"))
            {
                playerRb.velocity = Vector3.up * 10;
                _animator.SetBool("jumpOn", true);
                jumpOn = true;
                walkInput = false;
                onGround = false;
            }
        }
    }

    void FixedUpdate()
    {
        // カメラの方向から、X-Z平面の単位ベクトルを取得
        Vector3 cameraForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
        // 方向キーの入力値とカメラの向きから、移動方向を決定
        Vector3 moveForward = cameraForward * verticalInput + Camera.main.transform.right * horizontalInput;
        // 移動方向にスピードを掛ける。ジャンプや落下がある場合は、別途Y軸方向の速度ベクトルを足す。
        playerRb.velocity = moveForward * moveSpeed + new Vector3(0, playerRb.velocity.y, 0);

        _animator.SetBool("input", walkInput);
        _animator.SetBool("jogOn", jogOn);
        _animator.SetBool("runOn", runOn);
        _animator.SetBool("jumpOn", jumpOn);
        _animator.SetBool("flying", flying);
        _animator.SetBool("onGround", onGround);

        // キャラクターの向きを進行方向に
        if (moveForward != Vector3.zero)
        {
            Quaternion from = transform.rotation;
            Quaternion to = Quaternion.LookRotation(moveForward);
            transform.rotation = Quaternion.RotateTowards(from, to, RotateSpeed * Time.deltaTime);
        }
    }
    /*
    Colliderに触れた時のみRaycastで接地判定を行う
    */
    private void OnCollisionEnter(Collision other)
    {
        // Colliderが地面に触れた時
        if (other.gameObject.CompareTag("Ground"))
        {
            if (rightGround.CheckGroundStatus() || leftGround.CheckGroundStatus())
            {
	        // 左右の足どちらかが接地判定になった場合jumpモーションをやめ接地する
                jumpOn = false;
                flying = false;
                onGround = true;
            }
            else
            {
	        // Colliderが地面に触れても接地判定でなければ接地しない
                onGround = false;
                flying = true;
            }
        }
    }
}

完成

方向キーを入力することでモデルが歩行し、LeftCtrでjog、右クリックを押しながら移動するとrunに移行し、Spaceボタンでジャンプします。またジャンプモーション中に接地すると自動的にモーションを終了するのが分かります。

Discussion