このページでは、ナビゲーションシステムを使ってヒューマノイド型キャラクターを移動させる方法について説明します。
私たちはこの目的を達成するためにカスタムスクリプトと共に、Unity に組み込まれているアニメーションとナビゲーションシステムを使います。
ここで、あなたは Unity と Mecanim アニメーションシステムの基本操作に慣れているものと仮定します。
サンプルプロジェクトを利用できます。0 からスクリプトを追加したり、アニメーションやアニメーションコントローラを設定する必要はありません:
広い範囲を動けるように応答性と汎用性の高いアニメーションコントローラーを取得するため、異なる方向に移動するアニメーションのセットが必要です。これは時々、「 strafe 」セットとして参照されます。
移動するアニメーションに加えて、立っているキャラクターのアニメーションも必要です。
2D ブレンドツリーで Strafe セットを配置することで進めます。2D Simple Directional ブレンドタイプの選択や Compute Positions > Velocity XZ を使用してアニメーションを配置してください。
ブレンディングの制御を行うために、2つの Float「 Parameters 」velx と vely を加え、それらを「 Blend Tree 」に割り当てます。
ここでは、それぞれ異なる速度を持っている 7 つの「 run 」アニメーションクリップを配置しています。前方(と、左/右)と後方(と左後45度/右後45度)に加えて、Spot 上で「 Run 」するアニメーションクリップを使用してます。後者( Run_Spot アニメーションクリップ )は、「 Blend Tree 」の中でハイライトされています。Spot 上でアニメーション「 Run 」をさせる理由は2つあります。ひとつは他のアニメーションとブレンドされる時に「 Run 」しているスタイルを保持します。二つ目はブレンドするときアニメーションで足がすべるのを防ぎます。
次に、休止 (Idle) のアニメーションクリップをそのノード (Idle) に加えます。今度は、2 つの分離したアニメーションステートを2 つの遷移と組み合わせます。
「 Move 」 と「 Idle 」ステートの切り替えを制御するために、boolean 型コントロールパラメータ Move を追加します。その後、「 Transition (遷移)」の Has Exit Time プロパティーを無効にします(チェックを外します)。これにより、アニメーション中にいつでも「 Transition (遷移)」にトリガーすることができます。応答性のよい変遷を得るために、「 Transition (遷移)」時間を約0.10秒あたりに設定する必要があります。
新しく作成されたアニメーションコントローラーを移動したいキャラクターに配置します。
再生ボタンを押してキャラクターをヒエラルキーから選んでください。Animator Window のアニメーションの値を手動で制御することができ、「 Move 」ステートと「 Velocity 」を変更ができます。
次のステップは、アニメーションのパラメータを制御する他の手段を作成することについてです。
キャラクターに NavMeshAgent コンポーネントを配置します。キャラクターに合わせて半径、高さを調整し、さらに「 Animation Blend Tree 」の最大速度に合わせて「 Speed 」プロパティーを変更します。
キャラクターを配置したシーンの NavMesh を作成します。
次に、どこに移動するかをキャラクターに伝える必要があります。これは通常、アプリケーションに特有なものになります。ここで「 Move 」をクリックして選択し、キャラクターをユーザーが画面上でクリックした点に移動させます。
// ClickToMove.cs
using UnityEngine;
[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;
}
}
}
今、「 Play 」を押し、シーンの周辺をクリックすると、シーン内を移動するキャラクターが表示されます。ただし、アニメーションは動きとまったく一致しません。エージェントの状態と速度をアニメーションコントローラーへ伝えなければなりません。
エージェントから状態と速度の情報をアニメーションコントローラーに伝えるために別のスクリプトを追加します。
// LocomotionSimpleAgent.cs
using UnityEngine;
[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> ();
// position を自動的に更新してはいけません
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 を Low-pass フィルターにかけます
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;
// animation のパラメーターを更新します
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 position に更新します
transform.position = agent.nextPosition;
}
}
このスクリプトについて少し説明するだけの価値はあります。クリックすると上記のスクリプトに移動するだけでなく、Animator と NavMeshAgent コンポーネントを含むキャラクターの上に配置されます。
まず、スクリプトはキャラクターの位置を自動更新しないようにエージェントに指示します。スクリプトの最後で位置の更新を処理します。向き(方向)はエージェントによって更新されます。
アニメーションのブレンドはエージェントの速度を読むことで制御されます。それは(キャラクターの向きに基づいて) 相対速度に変換され、平滑化します。変換された水平方向速度コンポーネントは、Animator に渡されます。さらに「 Idle 」と 「 Moving 」間のステートの切り替えと、「 Speed 」 (すなわち「 velocity.magnitude 」) によって制御されます。
OnAnimatorMove()
コールバックで NavMeshAgent と一致するキャラクターの位置に更新します。
もう一度シーンを再生するとアニメーションはできるだけ動きが合うよう(意に沿うように)に表示します。
アニメーションされたキャラクターやナビゲーションされるキャラクターの品質を改善するために何ができるかいくつかの選択肢をみてみましょう。
キャラクターが興味を引く方を見て、その方向に曲がるためには、注目と期待をして欲しいことを伝えることは重要です。アニメーションシステム「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);
}
}
キャラクターにスクリプトを追加し、キャラクターのトランスフォームヒエラルキーの頭の変形に頭のプロパティーを割り当てます。「 LookAt 」スクリプトにはナビゲーションコントロールの概念がありません。したがって、どこを見るかをコントロールするために、LocomotionSimpleAgent.cs スクリプトに戻り、Looking(注視)をコントロールするために、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 の重み付けなどを調整してみることは、この設定を調査するよい方法です。