【Unity】ナビメッシュエージェントを椅子に座らせる

投稿者: | 2023-12-14

ナビメッシュエージェントコンポーネントを付けたキャラクターを自然に椅子に座らせてみました。

モーションを用意

歩きと立ち(Idle)以外に、椅子に座るモーションをインポートします。

椅子を置く

Cubeを作り、キャラクターに座るアニメーションをさせて、シーンビューでアニメーションに合わせてCubeのスケールや位置を調節します。

椅子に座っているときのキャラクターの位置に空のゲームオブジェクトを起きます。

スタート地点にも空のゲームオブジェクトを置いて、2つをスクリプトにアタッチします。

アニメーターコントローラー

キャラクターにアニメーターコンポーネントを付けて、アニメーターコントローラーをアタッチします。アニメーターコントローラーでは2つのBool型のパラメーターを作りました。

デフォルトは立っているだけの状態です。Bool型のパラメーターをオンオフして各ステートに遷移します。

デフォルト→歩き
歩き→デフォルト
デフォルト→座る
座る→立つ

立っている状態からデフォルトへは時間経過によって遷移します。

立ち上がるステートでは、座るモーションのSpeedをマイナスにして逆再生しています。

デフォルトステートへの遷移では、Exit Timeを高めにして、モーションを途中で終わらせています。

スクリプト

スクリプトでパラメーターを切り替えます。

using System.Collections;
using UnityEngine;
using UnityEngine.AI;

public class AnimationTest : MonoBehaviour
{
    // 180度回転するモーションの開始地点
    [SerializeField] Transform sittingPoint;

    // スタート地点
    [SerializeField] Transform startPoint;

    NavMeshAgent agent;
    Animator animator;

    // 座る地点からキャラクターまでの距離
    float distanceFromSittingPoint => Vector3.Distance(sittingPoint.position, transform.position);

    // スタート地点からキャラクターまでの距離
    float distanceFromStartPoint => Vector3.Distance(startPoint.position, transform.position);

    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
        agent = GetComponent<NavMeshAgent>();
    }

    private void Update()
    {
        // 目的地に付いたとき
        if (!agent.pathPending && agent.remainingDistance < 0.5f)
        {
            // 座る地点に近いとき
            if (distanceFromSittingPoint < distanceFromStartPoint)
            {
                // 歩いているとき
                if (animator.GetBool("Walking"))
                {
                    // 座る
                    StopCoroutine("SitDown");
                    StartCoroutine("SitDown");
                }
            }
            // スタート地点に近いとき
            else
            {
                // 歩きモーションをやめる
                animator.SetBool("Walking", false);

                // エージェントを停止
                agent.isStopped = true;
            }
        }

        // 左クリック
        if(Input.GetMouseButtonDown(0))
        {

            // 座る地点に近いとき
            if (distanceFromSittingPoint < distanceFromStartPoint)
            {
                // 座っているとき
                if (animator.GetBool("Sitting"))
                {
                    // 立ち上がる
                    StopCoroutine("StandUp");
                    StartCoroutine("StandUp");
                }
                // 座っていない時
                else
                {
                    // 歩いてないときはリターン
                    if (!animator.GetBool("Walking")) return;

                    // スタート地点に向かう
                    agent.SetDestination(startPoint.position);
                }
            }
            else
            {
                // エージェントを再開
                agent.isStopped = false;

                // 座る地点に向かう
                agent.SetDestination(sittingPoint.position);

                // 歩きモーションを開始
                animator.SetBool("Walking", true);
            }
        }
    }

    // 座る
    IEnumerator SitDown()
    {
        // 歩きモーションをやめる
        animator.SetBool("Walking", false);

        // エージェントを停止
        agent.isStopped = true;

        // 位置と回転の更新を停止
        agent.updateRotation = false;
        agent.updatePosition = false;

        // ルートモーションを適用しない
        animator.applyRootMotion = false;

        // 座るモーションを開始
        animator.SetBool("Sitting", true);

        // 繰り返し
        for (int i = 0; i < 500; i++)
        {
            // 座る地点に少しずつ移動・回転
            transform.position = Vector3.Lerp(transform.position, sittingPoint.position, 0.02f);
            transform.rotation = Quaternion.Lerp(transform.rotation, sittingPoint.rotation, 0.02f);

            yield return null;
        }

        transform.position = sittingPoint.position;
        transform.rotation = sittingPoint.rotation;

    }

    IEnumerator StandUp()
    {
        //agent.SetDestination(startPoint.position);

        // 座るモーションを終了
        animator.SetBool("Sitting", false);

        yield return new WaitForSeconds(1.2f);

        agent.updateRotation = true;
        agent.updatePosition = true;
        //agent.Warp(sittingPoint.position);
        
        // ルートモーションを適用
        animator.applyRootMotion = true;

        // エージェントを再開
        agent.isStopped = false;

        // 歩くモーションを開始
        animator.SetBool("Walking", true);

        // スタート地点に向かう。
        agent.SetDestination(startPoint.position);

    }
}


座る地点に置いた空のゲームオブジェクトをナビメッシュエージェントの目的地にします。アニメーターコントローラーのBool型パラメーターをTrueにして、歩きモーションを開始します。

            else
            {
                // エージェントを再開
                agent.isStopped = false;

                // 座る地点に向かう
                agent.SetDestination(sittingPoint.position);

                // 歩きモーションを開始
                animator.SetBool("Walking", true);
            }
        }

目的地に付いたら座る処理をするコルーチンをスタートします。

    private void Update()
    {
        // 目的地に付いたとき
        if (!agent.pathPending && agent.remainingDistance < 0.5f)
        {
            // 座る地点に近いとき
            if (distanceFromSittingPoint < distanceFromStartPoint)
            {
                // 歩いているとき
                if (animator.GetBool("Walking"))
                {
                    // 座る
                    StopCoroutine("SitDown");
                    StartCoroutine("SitDown");
                }
            }

コルーチンでは、歩きのパラメーターをfalseにして歩きモーションをやめ、エージェントも停止します。

    IEnumerator SitDown()
    {
        // 歩きモーションをやめる
        animator.SetBool("Walking", false);

        // エージェントを停止
        agent.isStopped = true;

        // 位置と回転の更新を停止
        agent.updateRotation = false;
        agent.updatePosition = false;

エージェントの位置と回転の更新を停止しないと、角度がきついときにキャラクターがカクつきました。

更新を停止しない
更新を停止する

更新を停止した場合、エージェントのシリンダーが停止ししたところにとどまります。

ルートモーションをオフにして、しゃがむモーションを開始します。

        // ルートモーションを適用しない
        animator.applyRootMotion = false;

        // 座るモーションを開始
        animator.SetBool("Sitting", true);

applyRootMotionがtrueのままだとキャラクターが宙に浮いてしまいます。

歩きモーションに移動が含まれているので、はじめにインスペクタでAnimatorコンポーネントのApply Root Motionのチェックを入れています。

しゃがみモーションの間に、キャラクターのTransformの位置や回転に直接値を入れて、キャラクターが少しずつ座る地点と同じ位置・回転に来るようにしています。

        // 繰り返し
        for (int i = 0; i < 500; i++)
        {
            // 座る地点に少しずつ移動・回転
            transform.position = Vector3.Lerp(transform.position, sittingPoint.position, 0.02f);
            transform.rotation = Quaternion.Lerp(transform.rotation, sittingPoint.rotation, 0.02f);

            yield return null;
        }

座っているときに左クリックを押すと、立ち上がるコルーチンをスタートします。座っても歩いてもいないときは、コルーチンを中断するなどの処理が必要になります。

        // 左クリック
        if(Input.GetMouseButtonDown(0))
        {

            // 座る地点に近いとき
            if (distanceFromSittingPoint < distanceFromStartPoint)
            {
                // 座っているとき
                if (animator.GetBool("Sitting"))
                {
                    // 立ち上がる
                    StopCoroutine("StandUp");
                    StartCoroutine("StandUp");
                }
                // 座っていない時
                else
                {
                    // 歩いてないときはリターン
                    if (!animator.GetBool("Walking")) return;

                    // スタート地点に向かう
                    agent.SetDestination(startPoint.position);
                }
            }

座る地点やスタート地点からキャラクター自身までの距離の計算は、ゲッタープロパティに記述しています。

    // 180度回転するモーションの開始地点
    [SerializeField] Transform sittingPoint;

    // スタート地点
    [SerializeField] Transform startPoint;

    NavMeshAgent agent;
    Animator animator;

    // 座る地点からキャラクターまでの距離
    float distanceFromSittingPoint => Vector3.Distance(sittingPoint.position, transform.position);

    // スタート地点からキャラクターまでの距離
    float distanceFromStartPoint => Vector3.Distance(startPoint.position, transform.position);

立ち上がる

立ち上がるコルーチンでは、アニメーターコントローラーにBool型の値をセットして、立ち上がるステートにさせます。

    IEnumerator StandUp()
    {
        //agent.SetDestination(startPoint.position);

        // 座るモーションを終了
        animator.SetBool("Sitting", false);

        yield return new WaitForSeconds(1.2f);

        // 位置と回転の更新を再開
        agent.updateRotation = true;
        agent.updatePosition = true;
        //agent.Warp(sittingPoint.position);
        
        // ルートモーションを適用
        animator.applyRootMotion = true;

        // エージェントを再開
        agent.isStopped = false;

        // 歩くモーションを開始
        animator.SetBool("Walking", true);

        // スタート地点に向かう。
        agent.SetDestination(startPoint.position);

    }

時間を置いて、ルートモーションの適用、エージェントの再開などの処理をして、歩きモーションに戻りスタート地点に向かいます。

スタート地点に付いたときは歩きモーションをやめて、エージェントを停止します。

            // スタート地点に近いとき
            else
            {
                // 歩きモーションをやめる
                animator.SetBool("Walking", false);

                // エージェントを停止
                agent.isStopped = true;
            }

180度振り向くモーションを使うと少し不自然な動きになりました。

コメントを残す

メールアドレスが公開されることはありません。