このページでは、ナビゲーションシステムを使ってヒューマノイド型キャラクターを移動する方法について説明します。
それを行うために、カスタムのスクリプトと共に、Unity のビルトインのアニメーションシステムとナビゲーションシステムを使います。
ここでの説明は、Unity と Mecanim アニメーションシステムの基本操作に慣れていることを前提としています。
サンプルプロジェクトを利用できるので、スクリプトを加えたり、アニメーションやアニメーションコントローラを設定することを 0 から行う必要はありません。
応答性と汎用性の高いアニメーションコントローラーを取得し広い範囲を動けるように、様々な方向に移動するアニメーションのセットが必要です。これは「Strafe」セットと呼ばれることもあります。
移動するアニメーションに加えて、立っているキャラクターのアニメーションも必要です。
まず、2D ブレンドツリーで Strafe セットを配置します。Blend Type で 2D Simple Directional を選択し Compute Positions > Velocity XZ を選択し、アニメーションを配置してください。
ブレンディングの制御を行うために、2 つの Float パラメーター velx と vely を加え、それらをブレンドツリーに割り当てます。
ここでは、それぞれ異なる速度を持っている 7 つの Run (走行) アニメーションクリップを配置します。前方 (正面と左/右前方) と後方 (後方と左/右後方) に加えて、ある一点上で走るアニメーションクリップを使用します。下の 2D ブレンドマップでは、一点上で走るアニメーションクリップ (Run_Spot アニメーションクリップ) が中央でハイライトされています。一点上でアニメーションを走らせる理由は 2 つあります。1 つは他のアニメーションとブレンドする時に走行するスタイルを維持するためです。2 つ目はブレンドするときにアニメーションで足が滑っているように見えるのを防ぐためです。
次に、休止 (Idle) のアニメーションクリップをそのノード (Idle) に加えます。今度は、2 つの分離したアニメーションステートを2 つの遷移と組み合わせます。
Move (移動) と Idle (休止) 状態の切り替えを制御するために、boolean 型制御パラメーター move を加えます。その後、Transitions の Has Exit Time プロパティを無効にします。これにより、アニメーション中にいつでも遷移の発生が可能です。遷移の反応を良くするために、遷移時間を約 0.10 秒あたりに設定します。
新しく作成したアニメーションコントローラーを動かしたいキャラクターに配置します。
再生ボタンを押してキャラクターを Hierarchy ウィンドウ で選んでください。これで、Animator ウィンドウ のアニメーションの値を手動で制御することができ、動きの状態と速度を変更ができます。
次のステップは、アニメーションのパラメーターを制御する他の手段を作成についてです。
キャラクターに NavMesh Agent コンポーネントを配置します。キャラクターに合わせて半径、高さを調整します。さらにアニメーションブレンドツリーの最高速度に合わせて Speed プロパティを変更します。
キャラクターを配置したシーンの NavMesh を作成します。
次に、どこに移動するかをキャラクターに伝える必要があります。これは通常、アプリケーションごとに特有なものです。ここでは、クリックすると移動する (click to move) 挙動、つまり、ユーザーが画面上でクリックしたワールドの位置にキャラクターが移動する挙動を選択します。
// ClickToMove.cs
using UnityEngine;
using UnityEngine.AI;
[RequireComponent (typeof (NavMeshAgent))]
public class ClickToMove : MonoBehaviour {
RaycastHit hitInfo = new RaycastHit();
NavMeshAgent agent;
void Start () {
agent = GetComponent<NavMeshAgent> ();
}
void Update () {
if(Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out hitInfo))
agent.destination = hitInfo.point;
}
}
}
再生ボタンを押し、シーンのあちこちをクリックすると、キャラクターがシーン内を移動するのが見られます。ただし、アニメーションはその動きとまったく一致しません。エージェントの状態と速度をアニメーションコントローラーへ伝えなければなりません。
エージェントから状態と速度の情報をアニメーションコントローラーに伝えるためにもう 1 つスクリプトを加えます。
// LocomotionSimpleAgent.cs
using UnityEngine;
using UnityEngine.AI;
[RequireComponent (typeof (NavMeshAgent))]
[RequireComponent (typeof (Animator))]
public class LocomotionSimpleAgent : MonoBehaviour {
Animator anim;
NavMeshAgent agent;
Vector2 smoothDeltaPosition = Vector2.zero;
Vector2 velocity = Vector2.zero;
void Start ()
{
anim = GetComponent<Animator> ();
agent = GetComponent<NavMeshAgent> ();
// 位置を自動的に更新しません
agent.updatePosition = false;
}
void Update ()
{
Vector3 worldDeltaPosition = agent.nextPosition - transform.position;
// worldDeltaPosition をローカル空間にマップします
float dx = Vector3.Dot (transform.right, worldDeltaPosition);
float dy = Vector3.Dot (transform.forward, worldDeltaPosition);
Vector2 deltaPosition = new Vector2 (dx, dy);
// deltaMove にローパスフィルターを適用します
float smooth = Mathf.Min(1.0f, Time.deltaTime/0.15f);
smoothDeltaPosition = Vector2.Lerp (smoothDeltaPosition, deltaPosition, smooth);
// 時間が進んだら、velocity (速度) を更新します
if (Time.deltaTime > 1e-5f)
velocity = smoothDeltaPosition / Time.deltaTime;
bool shouldMove = velocity.magnitude > 0.5f && agent.remainingDistance > agent.radius;
// アニメーションのパラメーターを更新します
anim.SetBool("move", shouldMove);
anim.SetFloat ("velx", velocity.x);
anim.SetFloat ("vely", velocity.y);
GetComponent<LookAt>().lookAtTargetPosition = agent.steeringTarget + transform.forward;
}
void OnAnimatorMove ()
{
// position (位置) を agent (エージェント) の位置に更新します
transform.position = agent.nextPosition;
}
}
このスクリプトについて少し説明しましょう。このスクリプトは、前出の ClickToMove スクリプト同様、 Animator と NavMeshAgent コンポーネントがアタッチされたキャラクターに設定されます。
まず、このスクリプトはキャラクターの位置を自動更新しないようにエージェントに指示します。スクリプトの最後で位置の更新を処理します。向き (方向) はエージェントによって更新されます。
アニメーションのブレンドはエージェントの速度を読み込むことで制御されます。速度は、キャラクターの向きに基づいた相対速度に変換され、平滑化されます。変換された水平方向速度のコンポーネントは、Animator に渡されます。さらに Idle と Moving 間の状態の切り替えは、速度 (すなわち velocity.magnitude) によって制御されます。
OnAnimatorMove()
コールバックで、キャラクターの位置を NavMeshAgent と一致するように更新します。
シーンをもう 1 度再生すると、アニメーションと動きがほぼ完全に合っています。
アニメーションされたキャラクターや移動するキャラクターの品質を向上させるため、いくつかの方法をみてみましょう。
キャラクターに一点を注視させ、その方向を向かせることは、注目と期待を集めるために重要です。アニメーションシステムの LookAt API を使います。これはもうひとつのスクリプトを呼び出します。
// LookAt.cs
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Animator))]
public class LookAt : MonoBehaviour {
public Transform head = null;
public Vector3 lookAtTargetPosition;
public float lookAtCoolTime = 0.2f;
public float lookAtHeatTime = 0.2f;
public bool looking = true;
private Vector3 lookAtPosition;
private Animator animator;
private float lookAtWeight = 0.0f;
void Start ()
{
if (!head)
{
Debug.LogError("No head transform - LookAt disabled");
enabled = false;
return;
}
animator = GetComponent<Animator> ();
lookAtTargetPosition = head.position + transform.forward;
lookAtPosition = lookAtTargetPosition;
}
void OnAnimatorIK ()
{
lookAtTargetPosition.y = head.position.y;
float lookAtTargetWeight = looking ? 1.0f : 0.0f;
Vector3 curDir = lookAtPosition - head.position;
Vector3 futDir = lookAtTargetPosition - head.position;
curDir = Vector3.RotateTowards(curDir, futDir, 6.28f*Time.deltaTime, float.PositiveInfinity);
lookAtPosition = head.position + curDir;
float blendTime = lookAtTargetWeight > lookAtWeight ? lookAtHeatTime : lookAtCoolTime;
lookAtWeight = Mathf.MoveTowards (lookAtWeight, lookAtTargetWeight, Time.deltaTime/blendTime);
animator.SetLookAtWeight (lookAtWeight, 0.2f, 0.5f, 0.7f, 0.5f);
animator.SetLookAtPosition (lookAtPosition);
}
}
キャラクターにこのスクリプトを加え、キャラクターをヒエラルキーで表示し Head の Transform に head のプロパティを割り当てます。LookAt スクリプトにはナビゲーションコントロールの概念がありません。したがって、どこを見るかをコントロールするために、LocomotionSimpleAgent.cs スクリプトに戻り、注視の挙動を制御するために、2、3行追加します。Update()
の最終行に以下の行を追加してください。
LookAt lookAt = GetComponent<LookAt> ();
if (lookAt)
lookAt.lookAtTargetPosition = agent.steeringTarget + transform.forward;
これは、注視する点をほぼ経路の次の曲がり角に、または曲がり角がない場合は経路の終わりに設定するように LookAt スクリプトに伝えます。
試してみてください。
これまでキャラクターはエージェントが指示した位置によって完全に制御されていました。この方法では、他のキャラクターや障害物を確実に回避することが、直接キャラクターの位置に変換されます。ただし、アニメーションが指定された速度に満たない場合、足が滑っているように見える場合があります。ここでは、キャラクターの制約を少し緩めます。基本的に、障害物回避の品質を犠牲にしてアニメーションの品質を向上させます。
LocomotionSimpleAgent.cs スクリプトの OnAnimatorMove()
コールバックを以下の行と置き換えてください。
void OnAnimatorMove ()
{
// ナビゲーションサーフェスの高さを使って、アニメーションの動きに基づいて位置を更新します
Vector3 position = anim.rootPosition;
position.y = agent.nextPosition.y;
transform.position = position;
}
これを使うと、キャラクターがエージェント (緑のワイヤーフレームのシリンダー) の位置から徐々に離れることに気がつきます。キャラクターがアニメーションで離れるのを制限する必要があります。これはキャラクターに向かってエージェントを引っぱるか、または、エージェントの位置に向かってキャラクターを引っぱるかどちらでも行うことができます。LocomotionSimpleAgent.cs スクリプトの Update()
メソッドの最後に以下を追加してください。
// キャラクターをエージェントの方に引っ張ります
if (worldDeltaPosition.magnitude > agent.radius)
transform.position = agent.nextPosition - 0.9f*worldDeltaPosition;
または、エージェントをキャラクターについていくようにする場合は以下を追加します。
// エージェントをキャラクターの方に引っ張ります
if (worldDeltaPosition.magnitude > agent.radius)
agent.nextPosition = transform.position + 0.9f*worldDeltaPosition;
どの方法が一番よく動作するかは、それぞれのケースによります。
ここでは、ナビゲーションシステムを使用して移動し、それに応じたアニメーションを行うキャラクターを設定しました。ブレンドの回数や LookAt の重み付けなどで見た目を向上できます。これらを調整してみることは、この設定をさらに学習するのによいでしょう。
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.