【UE4】NavLinkProxy配置作業をEditor Utility Widgetでちょっと楽にする

#はじめに
2018年に[【UE4】RecastNavMeshを拡張してNavLinkProxyを自動で配置させる.](https://qiita.com/Dv7Pavilion/items/30dc9d4cf80bf9e939db) という記事を書きました。この記事では現状全て手動で配置する必要のあるNavLinkProxyを自動配置させるためにRecastNavMeshクラスを拡張しデバッグ情報からNavMeshのエッジを取得しエッジに沿ってNavLinkProxyを配置させるという内容でした。

しかしNavMesh全体を対象とするため当然生成してほしくないところにも問答無用で生成されてしまいます。それを制御するようにNavMeshModifierVolumeを配置することも出来るとは思いますがある程度コツが必要になります。

そこで機能は弱くなりますが的確に狙った場所にサッとNavLinkProxyを配置出来るようなツールをUE4.22で追加されたEditor Utility Widgetを使って作成してみました。


#プロジェクトはこちら
作成バージョン：UE4.24.2
https://1drv.ms/u/s!Au-8FqgREBKZigReGzWo5ZJ5PjOJ?e=CefeSZ

#プロジェクトの使い方
Twitterにて今回作成したツールの使用動画を載せています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">GGJでUE4を使う皆さん、NavLinkProxyを使う予定はありますか？NavLinkProxyの配置作業を若干楽にしてくれるEditor Utility Widgetを作ってみました。いかがでしょうか？<a href="https://twitter.com/hashtag/UE4?src=hash&amp;ref_src=twsrc%5Etfw">#UE4</a> <a href="https://twitter.com/hashtag/GGJ2020?src=hash&amp;ref_src=twsrc%5Etfw">#GGJ2020</a> <a href="https://t.co/turG02lEDb">pic.twitter.com/turG02lEDb</a></p>&mdash; PavilionDV7 (@Dv7Pavilion) <a href="https://twitter.com/Dv7Pavilion/status/1223465706051620864?ref_src=twsrc%5Etfw">February 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

---

使い方の手順は以下のとおりです。

1. レベル上にNavMeshBoundsVolumeを配置（NavMeshを生成）

2. NavLinkProxyを配置したい場所に２つのBP_NavLinkVolumeを配置（それぞれNavLinkProxyの始点と終点になる）

3. EUWB_AutomaticallyNavLinkProxiesを起動して配置したBP_NavLinkVolumeを選択する
**※EUWB_AutomaticallyNavLinkProxiesはBP_NavLinkVolumeを選択したときのみ動作をする。それ以外のアクターを選択している状態だとエラーメッセージを出す。**
**※最初に選択した２つのBP_NavLinkVolume間でNavLinkProxyを生成する。３つ以上選択した場合３つ目以降は無視される。**

4. GENERATEボタンを押せばNavLinkProxyが生成される。

5. GENERATEボタンの上にあるUP,DOWNボタンは１組のBP_NavLinkVolumeに対していくつのNavLinkProxyを生成するか指定することが出来る。

実際にAIを動かしてみてジャンプ時に引っかかるようであれば個別にNavLinkProxyを選択してArc Paramパラメータを調整することが出来ます。


#プログラムの解説
プロジェクトをダウンロードされた方は実際に該当するプログラムやアセットを開きながら読んでいただければと思います。

##MyCustomNavLinkProxy
C++で作成されたこのクラスは[【UE4】RoboRecallを参考にNavLinkProxyを使いやすくする](https://qiita.com/Dv7Pavilion/items/f35ebc9ace203c6a7141)にて実装したカスタムNavLinkProxyをより洗練したものになります。

機能は記事で紹介されているものと同じでLeft、Rightプロパティを変更すると自動でSmartLinkも更新されるというものです。

本来であればSmartLinkプロパティは手動で設定する必要がありますが、これによって１ステップ作業を減らすことが出来ます。


##BP_CustomNavLinkProxy
C++のMyCustomNavLinkProxyをBlueprintで継承したものです。単独でArcParam（ArcParamについては後述）をオーバーライド出来るようにFloat型のArcParamをインスタンス編集可能で保持します。

更に、あるアクターがSmartLink到達時に呼ばれるReceive Smart Link Reachedを実装しBP_CharacterのJumpToイベントを呼び出します。これによりキャラクターはジャンプします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/c7fd0372-5e23-1419-5cd1-d7cb60bfc5ad.png" width=600>

##BP_NavLinkVolume
このBlueprintの役割はNavLinkProxyの生成場所を指定するのみです。

生成場所をわかりやすくするためにBoxCollisionコンポーネントを持っています。レベルエディタでBoxCollisionのサイズを調整出来るようにFVector型のShapeExtentを持ちます。

ConstructionScriptでは変更されたShapeExtentをBoxCollisionのExtentへと適用します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/ec43e17c-aeeb-ffcc-a700-52a14eec823f.png" width=600>

##EUWB_AutomaticallyNavLinkProxies
タイトルにもなっているNav Link Proxyの配置作業を担うEditor Utility Widgetです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/e13e6868-41ff-419e-0ff1-c8fa11572a65.png" width=600>

すべての処理はButton_GenerateのOnClickイベントから始まります。OnClickイベントが呼ばれると現在レベルエディタで選択中のアクターを取得します。それらが全てBP_NavLinkVolumeクラスであればNavLinkProxy生成処理へと進みます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/3ec35390-02f9-a58a-7a1c-f7d57da90a87.png" width=600>

###GenerateNavLinkProxies
この関数では実際にBP_CustomNavLinkProxyを生成するためにいくつかのステップを踏みます。

まずGetDivisionLocationsでBP_NavLinkVolumeのBoundsから最も幅を取っているX、Yいずれかの方向にPartition Count分の分割したときの位置を求めます。これをLeftとRightの両方行います。

分割位置を求めるGetDivitionLocations関数は以下のように実装しています。
BP_NavLinkVolumeのBoundsから最も長い方向を取得しその両端をStartとEndとして求めます。続いて分割数分ループをし両端を含めた等間隔の位置を求めます。（[参考](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1362668518)）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/9560fba4-9a87-1bdf-137b-e07eb2258729.png" width=600>

続いてBP_CustomNavLinkProxyをスポーンします。この時スポーン位置の計算は実際の動作とは関係がないので削除しても構いませんが、これによりどの組のBP_NavLinkVolumeに属しているかがわかりやすくなります。

スポーンが終わるとBP_CustomNavLinkProxyのSmartLinkの位置を設定します。SmartLinkの位置はBP_CustomNavLinkProxyのローカル座標で指定しなければならないのでInverseTransformLocationでワールド座標で記録されているLeftLocationとRightLocationをローカル座標に変換します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/f5fbd0da-4111-450b-90db-bc7a4ad39a80.png" width=600>

##BP_Character
このBlueprintはあくまでもデモ用として用意したものでありNavLinkProxyの配置作業については一切関係がありません。

プロパティとして目的地を表すTargetPoint型の参照とジャンプの軌道を決めるArcParamを決定するためのFloatCurveを持ちます。

BeginPlayではDestinationへと向かうようにMoveToLocation or Actorを呼ぶ。（AI Move Toではないのは単なる好みです。AI Move Toも中ではMoveToLocation or Actorを呼び出しています）

JumpToカスタムイベントで実際にキャラクタをジャンプさせているのですが、そのためにいくつかの準備をしています。まずジャンプをする必要があるのかをチェックするために目的地の高さと自身の「足元」の高さを比べます。目的地の高さが高ければ次の処理へ進みます。

SuggestProjectileVelocity Custom Arcでキャラクターがジャンプするために必要な加速度を求めます。ArcParamはJumpToの呼び出し元でArcParamパラメータが設定されていればそちらを使用し、設定されていなければFloatCurveから得ます。

Launch Characterでは各OverrideフラグをTRUEに設定することでキャラクターの加速度をLaunch Velocityに置き換えます。（FALSEの場合はキャラクターの現在の加速度に加算されます）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/a83d6b43-ea30-495a-e50b-8a661e6a4644.png" width=600>

###GetArcParam関数
SuggestProjectileVelocity Custom ArcのArcParamをFloatCurveから取り出す関数です。

SuggestProjectileVelocity Custom Arcに用いられるArcParamは**放物線の軌道を制御するパラメータ**です。
マウスオーバーしたときのツールチップにも書かれていますが
**0.5をデフォルト（中心）として1.0に近づくほど目的地に対して一直線に向かうような広い放物線。0.0に近づくほどZ方向（真上）に向かうような狭い放物線**
をArcParamによって決定することが出来ます。

自身から目的地へ向かう単位ベクトルと真上への単位ベクトルとの角度を求め角度に応じたArcParamを取り出します。角度が0に近いほど幅の狭い放物線になり角度が90度に近いほど、いい感じの広さの放物線が得られます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/5b79d93c-0921-4103-17df-485dd498f215.png" width=600>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/1b6d2a9d-d64c-b63a-b9c0-af95a7af53cf.png" width=600>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/8711162c-ea64-fdc3-5d85-ba9ba15bad54.png" width=600>


もちろんこれでもAIがジャンプに失敗（引っかかってしまったり）することもあるためBP_CustomNavLinkProxyには単独でArcParamを設定するようにしています。


#おわりに
今回紹介したツールは機能としては貧弱ながらも場合によってはとても便利で作業量を減らしてくれるものだと思います。

Editor Utility WidgetのおかげでSlate等の知識もいらず使い慣れているBlueprintで素早く作り出せたのでEditor Utility Widget様様です。