Unityのナビゲーション機能でOffMeshLinkを使って移動

Unityのナビゲーション機能についてはすでにいくつか記事にしていて、ナビゲーション機能を使って、
キャラクターを移動させたり、キャラクターの移動出来るエリアや移動コストの設定まで出来ました。

ナビゲーション機能やそれを使った移動方法に関しては以下の記事を参考にしてください。

敵キャラをUnityのナビゲーション機能を使って移動させる
Unityのナビゲーション機能を使って敵キャラクターを移動させます。ナビゲーション機能はあらかじめBakeされたフィールドを移動出来る機能です。
Unityで主人公をナビゲーション機能とマウスクリックで移動させる
Unityでキャラクターはナビゲーション機能で移動させるが、移動先の指定はマウスクリックで行えるようにします。ナビゲーション機能を使っているので障害物を避けて最適な経路で目的地に進んでくれます。
Unityのナビゲーション機能で移動エリアの可・不可を設定する
Unityのナビゲーション機能でキャラクターが移動出来るエリアと移動出来ないエリアを作成します。また移動出来るエリアに移動コストを設定し移動コストが大きいエリアを回避して移動できるようにします

他のもいくつかナビゲーションに関する記事がありますので、カテゴリのナビゲーションから探してみてください。

スポンサーリンク

オフメッシュリンクとはなんぞや!?

今回はナビゲーションの中のナビゲーションオフメッシュリンクの機能を使ってみます。

エージェント(ナビゲーション機能で動くゲームオブジェクト)が移動出来る場所は
staticにしたゲームオブジェクトをBakeする事で作成しましたが、地続きでない場所へは行けませんでした。

また、Bakeのエージェントの設定によっては高い所へ移動も可能ですが、普通に考えて人型キャラクターが1mも2mも段差があるようなところをひょいと登ってしまうのは違和感があります。

なのでBakeの設定でStep Height(エージェントの移動出来る段差)は0.3(30cm)とかそんな値に設定するのが普通です。

そんな時はジャンプをして高い所に到達させたいところです。

オフメッシュリンクは地続きでない場所を移動したり、高い所から下りたりするような位置を繋ぐ機能です。
ナビゲーション機能を使った移動では地続きの場所をルートを探して移動しますが、高い段差から飛び降りる事や遠い所にある地面にジャンプして移動はしません。

そんな時にオフメッシュリンクを設定しておけば、ショートカットを使って移動出来るようになります。

ベイクする時に自動でオフメッシュリンクを生成する

さてこのオフメッシュリンクですが、実はBakeする時に自動で作成する事が出来ます。

オフメッシュリンク1

上のように地面にするゲームオブジェクトを選択して、Generate OffMeshLinksにチェックを入れておきます。

オフメッシュリンク2

Generated Off Mesh Linksで値を設定します。

Drop Heightはどのくらいの高さなら下りるオフメッシュリンクを作成するか
Jump Distanceはどのくらいの距離なら飛ぶオフメッシュリンクを作成するか

です。
今回の例であれば高さが5m以内、離れている距離が5m以内のところに自動でオフメッシュリンクが作成されます。
設定が終わったのでBakeしてみます。

オフメッシュリンク3

Navigationタブを表示した状態でSceneタブを見ると丸印と矢印が表示されます。

これが自動で作成されたオフメッシュリンクで片方向の矢印はそちらにしか移動出来ず(画面の例で言うと下りるだけの場所)
双方向に矢印があれば行き来が出来ます(画面の例で言うとエリアを横に飛ぶ場所)

自動生成したオフメッシュリンクの移動を確認する

それでは実際にキャラクターを動かしてオフメッシュリンクを使って移動出来るか確認してみます。

オフメッシュリンク5

上のように主人公に設定するNavMeshAgentコンポーネントでAuto Traverse Off Mesh Linkにチェックを入れてください。

Auto Traverse Off Mesh Linkにチェックを入れると移動中にオフメッシュリンクがあったら
自動でオフメッシュリンクを使い移動します。

チェックを外すとオフメッシュリンクの所で止まってしまいます。

オフメッシュリンク4

上がナビゲーション機能とマウスクリックで移動する機能を使ってオフメッシュリンクを移動した結果です。
オフメッシュリンクの部分に来ると自動で矢印が繋がった先に移動します。

ただ通常の移動と同じでジャンプするでもなくスーッと空中浮遊するようにしか移動できません。
ここはジャンプして斜め上に飛んでから着地してほしいところですね。

これに関しては後で対処してみます。

自分でオフメッシュリンクを作成する方法

さて、自動でオフメッシュリンクを作成したわけですが、自分で特定の位置を繋げて移動させたい場合もあります。

そんな時はオフメッシュリンクのコンポーネントがあるのでそれを取りつけて自分でオフメッシュリンクを作成する事が出来ます。

オフメッシュリンク6

まずは上のようにCubeでOffMeshLinkという名前にした障害物を作成します。

BakeでStep Heightを0.3にしているのでこの障害物には乗る事が出来ないので障害物の先をクリックして移動しようとすると、迂回して到達しようとします。

オフメッシュリンク7

OffMeshLinkゲームオブジェクトは上のようにstaticにチェックを入れてBake(全staticのゲームオブジェクトが再度Bakeされます)します。

オフメッシュリンク8

障害物があって移動出来ないのでOffMeshLinkゲームオブジェクトの子要素(子要素でなくてもOK)にLink1とLink2をcreateEmptyで作成し、障害物の両端に置きます。

次にOffMeshLinkゲームオブジェクトのAdd ComponentからNavigation→OffMeshLinkを追加します。

オフメッシュリンク9

StartとEndに先ほど作成したLink1とLink2を設定します。

Bi Directionalにチェックを入れると双方向の移動が可能となります。
Activatedにチェックを入れるとオフメッシュリンクを使用出来ます。

それでは設定が終了したので、自前のオフメッシュリンクが作用するかどうか確認してみます。

オフメッシュリンク10

見事にすり抜けました!!

さきほど自動でオフメッシュリンクを作成し移動させた時に空中浮遊して移動する違和感がありましたが、障害物を作成し自前のオフメッシュリンクを使って障害物を越えさせたい時は貫通してしまい全く使えません。

これは自前のオフメッシュリンクが原因ではなくオフメッシュリンクはスーッと始点から終点へと移動してしまうので、穴を飛び越えるような場合はまだ違和感で済みますが、障害物を飛び越える(見た目的に)事は出来ません。

そこでスクリプトを使って斜め上に移動してから下りる処理を加えなければいけません。

オフメッシュリンクをうまく飛び越えるスクリプトを作成する

オフメッシュリンクをうまく飛び越えるにはまずはオフメッシュリンクを自動で移動するにチェックを入れていたのを外します。

NavMeshAgentのAuto Traverse Off Mesh Linkのチェックです。

インスペクタでチェックを外してもいいんですが、今回はスクリプトで外すようにします。
自動でオフメッシュリンクを移動しないようになるとオフメッシュリンクのところで止まってしまいます。

そうなったらスクリプトでキャラクターを移動させる処理を入れ、移動が完了したらオフメッシュリンク利用の終了を知らせなければいけません。

キャラクターの移動に関してはオフメッシュリンクの始点と終点のデータを使い移動させます。
ではキャラクターを移動させるスクリプトNavMoveに機能を追加&修正します。

マウスクリックした位置に移動させるスクリプトは

Unityで主人公をナビゲーション機能とマウスクリックで移動させる
Unityでキャラクターはナビゲーション機能で移動させるが、移動先の指定はマウスクリックで行えるようにします。ナビゲーション機能を使っているので障害物を避けて最適な経路で目的地に進んでくれます。

を参照してください。

agent.isOnOffMeshLinkプロパティはエージェントがオフメッシュリンク上にいるかどうかです。
オフメッシュリンク上にいれば到達点に向けて移動させるという処理を行います。

agent.currentOffMeshLinkDataプロパティはエージェントが現在使用しようとしているオフメッシュリンクのデータが入っています。

そこから始点と終点を取得し、transform.LookAtで終点の方にキャラクターの方向を向けさせます。

飛ぶ時に終点の方を向けさせなくてもいい場合は必要ありませんが、後ろ向きに飛ぶアニメーションをしてしまいます。

取得したオフメッシュリンクデータのlinkTypeプロパティでそのオフメッシュリンクのタイプを取得出来ます。

OffMeshLinkType.LinkTypeJumpAcrossは自動で作成した遠い地面に飛ぶオフメッシュリンク
OffMeshLinkType.LinkTypeDropDownは自動で作成した地面に降りるオフメッシュリンク
OffMeshLinkType.LinkTypeManualは自分で作成したオフメッシュリンク

になります。上2つのオフメッシュリンクはBakeした時に自動で作成した場合のもので、3つ目は自分で設置したものにつけられます。

今回の場合は遠い地面に飛ぶ場合と自前のオフメッシュリンクの時にジャンプするアニメーションに切り替えます。

地面に降りる時はその直前のアニメーションをそのまま使います。

一度アニメーションを変更したらオフメッシュリンクの利用が終わるまでは再度アニメーションパラメータを変更したくないので、linkJumpFlagを使って1度しか実行しないようにします。

gravityは落下時の速度に使用します。
normalizedTimeはアニメーションを開始してからの時間を入れておきます。
Mathf.Clamp関数は値を制御する時に使用する関数で

Mathf.Clamp(制御したい値, 最小値, 最大値)

で制御したい値を最小値から最大値の間に設定出来ます。
offset変数はキャラクターがジャンプした時に少し上に飛びださせる為の値でY座標を

1 – normalizedTime * 補正値

としてアニメーション開始時を最大、1秒後を最小となるように補正した値になります。
このoffset値が重要でこの値がないとオフメッシュリンクを下りる時に壁をすり抜けてしまいます。

最後にキャラクターの位置を設定します。

transform.position = Vector3.MoveTowards(transform.position, endPos, gravity * Time.deltaTime) + offset

Vector3.MoveTowards(始点, 終点, 速さ)

で始点から終点まで『速さ』で指定した速さでVector3の値を取得出来ます。
そこにoffset値を加えた値をキャラクターの位置に代入しています。

コメント化しているVector3.Lerpでも同じような処理が出来ますが補正値が少し変わります。

Vector3.Lerp(始点, 終点, 割合)

Vector3.Lerpの場合は始点と終点との間の割合でVector3の値を算出します。
割合は0~1の間で指定し0の時は始点、1の時は終点が設定されます。

MoveTowards、Lerp、どちらを使用するかはお好みでどうぞ。

と言いたいところなんですが、Lerpの場合はなめらかに移動してしまうので用途としてはちょっと違うかもしれません(^_^;)
第3引数をちゃんと考えればうまく出来そうな気もしますが・・・MoveTowardsの方がわかりやすい&ちゃんと出来てるのでこちらを使った方がいいかもしれません。

最後にキャラクターが終点に移動が完了したら

agent.CompleteOffMeshLink()

を呼び出す必要があります。

Auto Traverse Off Mesh Linkのチェックをしていない場合、これを呼び出さないと
オフメッシュリンクの利用が終わりません。

オフメッシュリンクの利用が終わったらアニメーションパラメータのLinkJumpをfalseにします。

アニメーターコントローラにオフメッシュリンクを飛ぶ状態と遷移の作成をする

スクリプトの追加と修正が終わったので最後にアニメーターコントローラで状態と遷移を作成します。

オフメッシュリンク13

上のようにLinkJump状態を作成します。アニメーションは好きなアニメーションを設定してください。

Ilde→LinkJump、Run→LinkJumpの遷移を作りHas Exit Timeのチェックを外し、遷移条件をLinkJumpがtrueの時を加えます。

LinkJump→Runに遷移を繋げHas Exit Timeのチェックを外し、遷移条件をLinkJumpがfalseの時を加えます。

オフメッシュリンク14

上がLinkJump→Runへの遷移のインスペクタです。

最終的なオフメッシュリンクの移動を確認する

アニメータコントローラの作成も終わったので、Unityの実行ボタンを押して確認してみましょう。

オフメッシュリンク11

上の動画が障害物の所に作った自前のオフメッシュリンクを飛び越えるところです。
オフメッシュリンクの所にきたらジャンプして飛び越えているようになります。

オフメッシュリンク12

上が自動で作成されたオフメッシュリンクを移動する時の様子です。
遠い地面に飛ぶ時はジャンプアニメーション、ただ下りる時はその前のアニメーションのまま落ちていきます。

これでナビゲーション機能のオフメッシュリンクを使ってワープする事が出来るようになりました。

我ながらがんばってオフメッシュリンクの機能を使ったと思います。(^_^;)