Unityのナビゲーション機能については、すでにいくつか記事にしていて、ナビゲーション機能を使って
キャラクターを移動させたり、キャラクターの移動出来るエリアや移動コストの設定まで出来ました。
ナビゲーション機能やそれを使った移動方法に関しては以下の記事を参考にしてください。
他のもいくつかナビゲーションに関する記事がありますので、カテゴリのナビゲーションから探してみてください。
オフメッシュリンクとはなんぞや!?
今回はナビゲーションの中のナビゲーションオフメッシュリンクの機能を使ってみます。
エージェント(ナビゲーション機能で動くゲームオブジェクト)が移動出来る場所は
staticにしたゲームオブジェクトをBakeする事で作成しましたが、地続きでない場所へは行けませんでした。
また、Bakeのエージェントの設定によっては高い所へ移動も可能ですが、普通に考えて人型キャラクターが1mも2mも段差があるようなところをひょいと登ってしまうのは違和感があります。
なのでBakeの設定でStep Height(エージェントの移動出来る段差)は0.3(30cm)とかそんな値に設定するのが普通です。
地続きでない場所や段差がある時の移動はジャンプをして高い所や遠い所に到達させたいところです。
その為の機能がオフメッシュリンクで、オフメッシュリンクは地続きでない場所を移動したり、高い所から下りたりするような位置を繋ぐ機能です。
ナビゲーション機能を使った移動では地続きの場所をルートを探して移動しますが、高い段差から飛び降りる事や遠い所にある地面にジャンプして移動はしません。
そんな時にオフメッシュリンクを設定しておけば、ショートカットを使って移動出来るようになります。
ベイクする時に自動でオフメッシュリンクを生成する
さてこのオフメッシュリンクですが、実はBakeする時に自動で作成する事が出来ます。
上のように地面にするゲームオブジェクトを選択して、Generate OffMeshLinksにチェックを入れておきます。
Generated Off Mesh Linksで値を設定します。
Drop Heightはどのくらいの高さなら下りるオフメッシュリンクを作成するか
Jump Distanceはどのくらいの距離なら飛ぶオフメッシュリンクを作成するか
です。
今回の例であれば高さが5m以内、離れている距離が5m以内のところに自動でオフメッシュリンクが作成されます。
設定が終わったのでBakeしてみます。
Navigationタブを表示した状態でSceneタブを見ると丸印と矢印が表示されます。
これが自動で作成されたオフメッシュリンクで片方向の矢印はそちらにしか移動出来ず(画面の例で言うと下りるだけの場所)
双方向に矢印があれば行き来が出来ます(画面の例で言うとエリアを横に飛ぶ場所)
自動生成したオフメッシュリンクの移動を確認する
それでは実際にキャラクターを動かしてオフメッシュリンクを使って移動出来るか確認してみます。
上のように主人公に設定するNavMeshAgentコンポーネントでAuto Traverse Off Mesh Linkにチェックを入れてください。
Auto Traverse Off Mesh Linkにチェックを入れると移動中にオフメッシュリンクがあったら
自動でオフメッシュリンクを使い移動します。
チェックを外すとオフメッシュリンクの所で止まってしまいます。
上がナビゲーション機能とマウスクリックで移動する機能を使ってオフメッシュリンクを移動した結果です。
オフメッシュリンクの部分に来ると自動で矢印が繋がった先に移動します。
ただ通常の移動と同じでジャンプするでもなくスーッと空中浮遊するようにしか移動できません。
ここはジャンプして斜め上に飛んでから着地してほしいところですね。
これに関しては後で対処してみます。
自分でオフメッシュリンクを作成する方法
さて、自動でオフメッシュリンクを作成したわけですが、自分で特定の位置を繋げて移動させたい場合もあります。
そんな時はオフメッシュリンクのコンポーネントがあるのでそれを取りつけて自分でオフメッシュリンクを作成する事が出来ます。
まずは上のようにCubeでBlockという名前にした障害物を作成します。
OffMeshLinkゲームオブジェクトは上のようにstaticにチェックを入れてBake(全staticのゲームオブジェクトが再度Bakeされます)します。
NavMeshのBakeの設定でStep Heightを0.3にしているのでこの障害物には乗る事が出来ません。
障害物の先をクリックして移動しようとすると、迂回して到達しようとします。
障害物(Block)があって移動出来ないのでその子要素にOffMeshLinkゲームオブジェクトをcreateEmptyで作成し、その子要素(子要素でなくてもOK)にLink1とLink2をcreateEmptyで作成し、障害物の両端に置きます。
次にOffMeshLinkゲームオブジェクトのAdd ComponentからNavigation→OffMeshLinkを追加します。
StartとEndに先ほど作成したLink1とLink2を設定します。
Bi Directionalにチェックを入れると双方向の移動が可能となります。
Activatedにチェックを入れるとオフメッシュリンクを使用出来ます。
それでは設定が終了したので、自前のオフメッシュリンクが作用するかどうか確認してみます。
見事にすり抜けました!!
さきほど自動でオフメッシュリンクを作成し移動させた時に空中浮遊して移動する違和感がありましたが、障害物を作成し自前のオフメッシュリンクを使って障害物を越えさせたい時は貫通してしまい全く使えません。
これは自前のオフメッシュリンクが原因ではなくオフメッシュリンクはスーッと始点から終点へと移動してしまうので、穴を飛び越えるような場合はまだ違和感で済みますが、障害物を飛び越える(見た目的に)事は出来ません。
そこでスクリプトを使って斜め上に移動してから下りる処理を加えなければいけません。
オフメッシュリンクをうまく飛び越えるスクリプトを作成する
オフメッシュリンクをうまく飛び越えるにはまずはオフメッシュリンクを自動で移動するにチェックを入れていたのを外します。
NavMeshAgentのAuto Traverse Off Mesh Linkのチェックです。
インスペクタでチェックを外してもいいんですが、今回はスクリプトで外すようにします。
自動でオフメッシュリンクを移動しないようになるとオフメッシュリンクのところで止まってしまいます。
(多少動きが変わりますがAuto Traverse Off Mesh Linkのチェックを外さなくても今回のスクリプトだと動作します)。
そうなったらスクリプトでキャラクターを移動させる処理を入れ、移動が完了したらオフメッシュリンク利用の終了を知らせなければいけません。
キャラクターの移動に関してはオフメッシュリンクの始点と終点のデータを使い移動させます。
ではキャラクターを移動させるスクリプトNavMoveに機能を追加&修正します。
Unity5.5バージョン以降でC#でスクリプトを組んでいる場合は
using UnityEngine.AI;
というusingディレクティブを入れておいた方がいいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | using UnityEngine; using System.Collections; using UnityStandardAssets.Utility; public class NavMove : MonoBehaviour { // アニメータコントローラ private Animator animator; // レイを飛ばす距離 private float rayRange; // 移動する位置 private Vector3 targetPosition; // マウスクリックで位置を決めるか [SerializeField] private bool isMouseDownMode; // ナビゲーションエージェント private UnityEngine.AI.NavMeshAgent agent; // 再生秒数 private float normalizedTime = 0f; // オフメッシュリンクをジャンプ中かどうか private bool useLinkJump = false; // リンクジャンプ中のY座標の値 private float offsetY; // ジャンプアニメーションカーブ [SerializeField] private AnimationCurve animCurve; // ジャンプアニメーションの再生時間 private float animationTime; // オフメッシュリンクデータ private UnityEngine.AI.OffMeshLinkData offMeshLinkData; // オフメッシュリンクのスタート位置 private Vector3 startPos; // オフメッシュリンクのエンド位置 private Vector3 endPos; void Start() { animator = GetComponent<Animator>(); rayRange = 1000f; targetPosition = transform.position; agent = GetComponent<UnityEngine.AI.NavMeshAgent>(); agent.autoTraverseOffMeshLink = false; // LinkJump状態に設定したアニメーションクリップ名からアニメーションの長さを取得 foreach (var item in animator.runtimeAnimatorController.animationClips) { if (item.name == "LinkJump4") { animationTime = item.length; } } } void Update() { // マウスクリックまたはmouseDownModeがOffの時マウスの位置を移動する位置にする if (Input.GetButtonDown("Fire1") || !isMouseDownMode) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, rayRange, LayerMask.GetMask("Field"))) { targetPosition = hit.point; agent.SetDestination(targetPosition); } } // オフメッシュリンクを使用中 if (agent.isOnOffMeshLink) { if (!useLinkJump) { // エージェントの現在のオフメッシュリンクデータを取得 offMeshLinkData = agent.currentOffMeshLinkData; // オフメッシュリンクのスタート位置 startPos = offMeshLinkData.startPos; // オフメッシュリンクのエンド位置 endPos = offMeshLinkData.endPos; // オフメッシュリンクのタイプがジャンプして飛ぶタイプの時ジャンプアニメーション再生 if (offMeshLinkData.linkType == UnityEngine.AI.OffMeshLinkType.LinkTypeJumpAcross || offMeshLinkData.linkType == UnityEngine.AI.OffMeshLinkType.LinkTypeManual) { animator.SetBool("LinkJump", true); } useLinkJump = true; normalizedTime = 0f; } // オフメッシュリンクを使用した時は飛ぶ先の方向を向かせる transform.LookAt(new Vector3(endPos.x, transform.position.y, endPos.z)); normalizedTime += Time.deltaTime; // アニメーションカーブの横軸から縦軸を取得 offsetY = animCurve.Evaluate(normalizedTime * (1f / animationTime)); // アニメーション終了時にジャンプ終了 if (normalizedTime * (1f / animationTime) >= 1f) { agent.CompleteOffMeshLink(); useLinkJump = false; animator.SetBool("LinkJump", false); } // エージェントの位置をセット agent.transform.position = Vector3.Lerp(startPos, endPos, normalizedTime * (1f / animationTime)) + offsetY * Vector3.up; } else { // オフメッシュリンクのエンド位置に着いたらオフメッシュリンク移動が完了 if (useLinkJump) { agent.CompleteOffMeshLink(); useLinkJump = false; animator.SetBool("LinkJump", false); } else { // 目的地に近付いたら走るアニメーションをやめる if (agent.remainingDistance < 0.1f) { animator.SetFloat("Speed", 0f); } else { animator.SetFloat("Speed", agent.desiredVelocity.magnitude); } } } } } |
スクリプトが長いので少しづつ解説します。
Startメソッド
StartメソッドではAnimatorControllerのLinkJump状態に設定しているアニメーションクリップの名前で検索し、そのクリップの長さを取得しておきます。
ジャンプ中はこのアニメーションクリップの長さを使用します。
Updateメソッド
マウスクリックした位置に移動させるスクリプトは
を参照してください。
agent.isOnOffMeshLinkプロパティはエージェントがオフメッシュリンク上にいるかどうかです。
オフメッシュリンク上にいれば到達点に向けて移動させるという処理を行います。
agent.currentOffMeshLinkDataプロパティはエージェントが現在使用しようとしているオフメッシュリンクのデータが入っています。
そこから始点と終点を取得し、
取得したオフメッシュリンクデータのlinkTypeプロパティでそのオフメッシュリンクのタイプを取得出来ます。
OffMeshLinkType.LinkTypeJumpAcrossは自動で作成した遠い地面(横にジャンプ等)に飛ぶオフメッシュリンク
OffMeshLinkType.LinkTypeDropDownは自動で作成した地面に降りるオフメッシュリンク
OffMeshLinkType.LinkTypeManualは自分で作成したオフメッシュリンク
になります。上2つのオフメッシュリンクはBakeした時に自動で作成した場合のもので、3つ目は自分で設置したものにつけられます。
今回の場合は遠い地面に飛ぶ場合と自前のオフメッシュリンクの時にジャンプするアニメーションに切り替えます。
地面に降りる時はその直前のアニメーションをそのまま使います。
useLinkJumpはオフメッシュリンクを使っている時にtrueにし、normalizedTimeはジャンプアニメーションの再生時間を入れます。
transform.LookAtで終点の方にキャラクターの方向を向けさせます。
Y軸はキャラクター自身の角度にし、傾けさせないようにします。
飛ぶ時に終点の方を向けさせなくてもいい場合は必要ありませんが、後ろ向きに飛ぶアニメーションをしてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | normalizedTime += Time.deltaTime; // アニメーションカーブの横軸から縦軸を取得 offsetY = animCurve.Evaluate (normalizedTime * (1f / animationTime)); // アニメーション終了時にジャンプ終了 if (normalizedTime * (1f / animationTime) >= 1f) { agent.CompleteOffMeshLink (); useLinkJump = false; animator.SetBool ("LinkJump", false); } // エージェントの位置をセット agent.transform.position = Vector3.Lerp (startPos, endPos, normalizedTime * (1f / animationTime)) + offsetY * Vector3.up; |
↑の処理ではnormalizedTimeにリンクジャンプをしている時間を足していきます。
その時間の比率をanimationTimeで計算しそれをアニメーションカーブのEvaluateに設定します。
アニメーションカーブについては後でやります。
アニメーションの再生が終わったらagent.CompleteOffMeshLinkを使ってオフメッシュリンクの使用が完了した事を知らせます。
Auto Traverse Off Mesh Linkのチェックをしていない場合、これを呼び出さないとオフメッシュリンクの利用が終わりません。
オフメッシュリンクの利用が終わったらアニメーションパラメータのLinkJumpをfalseにします。
最後にエージェントの位置をスタート位置からエンド位置に向け移動させますが、その時に計算したオフセット値を足しています。
agent.isOnOffMeshLinkがfalseになった時で、useLinkJumpがtrueの時はオフメッシュリンクの利用が終了したのにスクリプト上ではリンクジャンプしている時なので、
強制でリンクジャンプを終了させています。
この場合はアニメーションの再生が終わる前に強制で次の状態へと遷移します。
アニメーションカーブの作成
NavMoveスクリプトではインスペクタでAnimationCurveを設定出来るようにしてます。
このアニメーションカーブを使うとX軸とY軸のグラフを使ってX軸に対するY軸の値を視覚的に作成する事が出来ます。
NavMoveのインスペクタでは
↑のようにAnimationCurve項目が表示されます。
AnimationCurveの部分をクリックすると、
↑のようなグラフが表示されます。
グラフの途中で右クリックをするとキーを打つ事が出来るのでカーブの変化点を入れる事が出来ます。
X軸(スクリプトではアニメーションの経過時間)の値によってY軸の値(スクリプトではエージェントのY軸の位置)をグラフで設定します。
今回の場合はX軸は最大1でY軸は2にしています。
これでアニメーションの再生位置に応じてエージェントのY軸の位置が変換するようになります。
1 2 3 | offsetY = animCurve.Evaluate (normalizedTime * (1f / animationTime)); |
Evaluateメソッドの引数に渡しているのがX軸の値で、得られる結果がY軸の値になります。
normalizedTimeがアニメーションの再生時間で、1f / animationTimeをかけることでアニメーションの全部の再生時間の割合が求められます。
アニメーターコントローラにオフメッシュリンクを飛ぶ状態と遷移の作成をする
スクリプトの追加と修正が終わったので最後にアニメーターコントローラで状態と遷移を作成します。
上のようにLinkJump状態を作成します。アニメーションは好きなアニメーションを設定してください。
ただし、アニメーションクリップの名前をスクリプトで指定した名前と合わせます。
スクリプトではLinkJump4としたのでアニメーションクリップの名前もLinkJump4としておきます。
↑のようにアニメーションクリップの元のファイルを選択し、インスペクタで名前をLinkJump4に変更し、このアニメーションを設定します。
Ilde→LinkJump、Run→LinkJumpの遷移を作りHas Exit Timeのチェックを外し、遷移条件をLinkJumpがtrueの時を加えます。
LinkJump→Runに遷移を繋げHas Exit Timeのチェックを外し、遷移条件をLinkJumpがfalseの時を加えます。
上がLinkJump→Runへの遷移のインスペクタです。
最終的なオフメッシュリンクの移動を確認する
アニメータコントローラの作成も終わったので、Unityの実行ボタンを押して確認してみましょう。
上の動画が障害物の所に作った自前のオフメッシュリンクを飛び越えるところです。
オフメッシュリンクの所にきたらジャンプして飛び越えているようになります。
上が自動で作成されたオフメッシュリンクを移動する時の様子です。
遠い地面に飛ぶ時はジャンプアニメーション、ただ下りる時はその前のアニメーションのまま落ちていきます。
これでナビゲーション機能のオフメッシュリンクを使ってワープする事が出来るようになりました。
我ながらがんばってオフメッシュリンクの機能を使ったと思います。(^_^;)