ナビメッシュエージェントコンポーネントを付けたキャラクターを自然に椅子に座らせてみました。
モーションを用意
歩きと立ち(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度振り向くモーションを使うと少し不自然な動きになりました。