#概要
前回はミニマップ上にプレイヤーアイコンを表示し、プレイヤーの向きに応じてアイコンの向きも回転するようにしました。
今回は、レベル上のオブジェクトをミニマップ上に表示しようと思います。
このようなミニマップ上に表示するモノを総称しPOI(Point Of Interest)と呼称します。
Point of interest - 地図などの上の地点、スポット
前回↓
https://qiita.com/drafts/dd3e3c9160ed431a3594/edit
#動作環境
Windows 10, UE4.18.3, VisualStudio 2017
#下準備
前回プレイヤーアイコンを自作したのと同様に、今回はレベル上のオブジェクトをミニマップ上にアイコンとして表示します。まずは、そのアイコンを準備して下さい。私は、今回もPaintソフトで適当に自作したものを用います。以下が用いたアイコン"POI.png"です。
#実装
では早速実装に移ります。今回は、2つの新規Assetの作成・実装とWBP_Hudに追加実装を行います。
##アイコンを保持するWBPの作成
ミニマップ上にアイコンを表示するために、アイコンを保持するウィジェットブループリント"WBP_POI"を新規作成しましょう。基本的には、前回作成したWBP_PlayerIconと同様の作りです。
CanvasPanelを削除し、Overlayを追加します。その子要素としてImageを追加し、事前に作成したTextureを設定しましょう。
AlinmentをCenterにし、サイズを適宜調整してください。
次に、同アセットWBP_POIのグラフエディタを開き、新規関数の追加と自身のOwnerの参照を保持する変数の作成を行います。
変数の型はActorで構いませんが、スポーン時に公開するのを忘れないで下さい。この関数を呼び出すことで、ミニマップ上の描画位置を更新します。関数名は"UpdateTlanslationOnMiniMap"としました。関数の内部実装は後で行いますので、今はこのままで置いておきます。
ですが、完成像をイメージし易いように、以下に実装の概要を記載しておきます。
WBP_PlayerIconを追加した時と同様に、WBP_HudにWBP_POI追加用関数を作ります。WBP_HudはWBP_POIを新規作成し、それに対する参照を変数として保持しておきます。そしてTick内で、この保持した変数を経由してUpdateTlanslationOnMiniMapを呼び出します。
長くなってしまったので、次に進みます...
##自身がPOIであることを示すActorComponentの作成
ちょっと"?"な説明になってしまいましたが、もう少し詳しく説明します...
先ほどWBP_POIを作成したのですが、これだけでは意味が分かりませんね。ミニマップの中心に1つだけ表示すれば良いプレイヤーアイコンと違い「何個表示すれば良いのか?」、「どこに表示すれば良いのか?」、「そもそもどれがPOIなの?」と様々な疑問が浮かんだかもしれません。
そのために、次は**「このComponentを追加したActorをPOIとして扱うActorComponent(AC)」**を新規作成します。
このACの役目は単純で、BeginPlayでWBP_Hudの関数AddPOI(後で作ります)を呼び出します。このACを保持するActorがPOIであることをWBP_Hudに伝えるだけです。あとは、WBP_Hud側でその参照を保持し、ミニマップ上の位置更新・描画を行ってもらいます。ACはActorComponentを親クラスとするブループリントを選択することで作成可能です。
##WBP_HudにPOI追加関数を新規実装
順番は前後してしまいましたが、いよいよWBP_HudにPOI追加関数を実装します。と言っても、基本的には前回作成したAddPlayerIconと同様です。呼び出される度に、新規ウィジェットとしてWBP_POIを作成し自身のOverlayの子要素として追加するだけです。
この時点での、実装内容は以下の通りです。ちなみに、下図は完成状態ではないので注意してください。この実装に対して疑問を抱いている方は、UEに慣れてきた証拠だと思います。
関数(仮)を作成したので、WBP_POIに戻り、AddPOIを呼び出してみましょう。↓
##動作確認
長くなりそうなので、一旦ここで動作確認を挟んでおきましょう。この段階で上手くいかない場合は、今までの手順を見直すか、私に直接連絡して頂いてもokです!
適当なActorを選択し、新規コンポーネントとしてAC_POIを追加してみましょう。
私は、以下のStaticMeshActorにAC_POIを追加しました。
それではゲームを開始してみましょう。下図のようにPOIアイコンが中心に表示され続けていれば動作確認成功です。
##動作確認結果の考察
いきなり残りの実装を進める前に、一旦考えを纏める為に何が足りないか少し考察してみましょう。
AC_POIをActorに追加することによって、ミニマップ上にPOIを表示することができています。後は何が問題なのでしょうか?...アイコンの位置と実際のActorの位置がずれていることですね。
ですので、次はミニマップ上のPOIの位置更新を行います。
##AddPOI関数の修正
先ほど述べたように、AddPOI関数は未完成の状態でした。足りない部分を実装していきましょう。
今度は未完成部分に注目してみましょう。以下に未完成部分と、修正内容を示します。
・WBP_POIを作成する際に"OwnerActorRef"に値を渡していない
→関数にActorリファレンス型のインプットを追加し、そのインプットをOwnerActorRefに渡す
・作成したWBP_POIを変数として保持していない(位置更新処理が行えない)
→WBP_POI型の参照配列として保持(POIは複数追加予定なので)
↓
AddPOI関数にActorリファレンス型のインプットを追加したので、関数呼び出し時(AC_POI)にインプットに値を渡してやりましょう。
自身を保持しているOwnerActorを取得する既存関数があるので、それを利用します。
##WBP_HudのTickでWBP_POIの位置更新を行う
新規作成したWBP_POIを変数として保持したので、WBP_HudからWBP_POIにアクセスすることが可能になりました。
これで、ミニマップ上のアイコンの位置更新の準備が整いました!
次は、WBP_Hud内のTickでPOIWidgetRefArrayに格納されている全てのPOIに対して位置更新処理を行っていきます。
今回は、位置更新処理の方法を2種類紹介します。簡易verと応用verの2種類です。
どちらでもミニマップ上にアイコンを表示することは可能ですが、この後は応用verを用いた実装に対して追加実装を行うので、できれば応用verに挑戦してみて下さい!
##位置更新処理の実装~簡単ver~
配列の全要素に対して、PlayerとPOIの位置の差分を取り、ミニマップ上の縮尺に合わせるために係数をかけています。
今回は、「SceneCapture2Dのキャプチャ範囲(Width)が1000」「ミニマップの一辺が300」に設定して実装を進めたので、このような係数をかけています。
そして、算出した位置関係を基にSetRenderTranslationを用いて描画位置の更新を行っています。座標軸が異なるので、座標軸を合わせるのも忘れないようにしてください。
##位置更新処理の実装~応用ver~
応用verでは、三角比を用いて位置更新を行います。距離と角度を用いて、ミニマップ上の描画位置を決定します。
この概念は下図を参考にして下さい。Playerの位置を原点の位置とし、POIまでの距離(=R)となす角度(θ)を用いて円周上の点(x,y)を表します。この円周上の点が、ミニマップに表示されるアイコンの位置を表します。
また、見た目をすっきりさせるために、新たに以下の関数を2つ追加しました。
・CalcDistAngle
・ConvertToMiniMapDist
CalcDistAngleは2つのインプット(PlayerActorRef, POIActorRef)を受け取り、2つの出力(Dist, Angle)を返す関数です。2つのActorの直線距離と、どの角度に位置するかを算出します。
ConvertToMiniMapDistは、先ほど求めた直線距離をミニマップ上の距離(縮尺)に変換する関数です。
実装内容は以下を参照下さい。
##動作確認
必要な実装が終わったので、動作確認してみましょう。POIの位置と連動してミニマップ上にアイコンが表示されていればokです。
プレイヤーを動かしてもアイコンの位置が正しく表示されているかを確認して下さい。
##最後の追加実装
申し訳ありませんが、実はまだ完成ではないのです。このままではアイコンがミニマップ外に出た場合でさえも描画され続けてしまいます。
これを修正するのに加えて、ミニマップを円形にしてみましょう。
##ミニマップを円形にする
半径の異なる2つのSphereMaskを用いて、ミニマップを円形にしてみましょう。そのために、SceneTexture2Dがキャプチャした画像をそのまま出力していた**"M_MiniMap"に修正を加えます。**
まずは、半径の小さいSphereMaskでTextureを円形にくりぬきます(言い方が少し変だと思いますが...)。Lerpを用いて、周辺部を黒色で描画し、中心部はTextureSampleをそのまま出力します。
しかし、このままでは周辺部の黒色部分も画面上に描画されてしまう(下図参照)ので、半径の大きいSphereMaskをオパシティマスクとしています。半径が異なるSphereMaskを用いることで、円形ミニマップの枠として残るようになります。
以下に"M_MiniMap"の実装と、オパシティマスク設定前後の図を示します。
##描画距離上限を設定する
次は、ミニマップ外にアイコンが描画されてしまうのを防止するために、描画距離上限を設定します。
そのために、先ほど作成した**"ConvertToMiniMapDist"に修正を加えます。**
描画距離上限を設定するために、Actor間の直線距離をClampで一定範囲に収めています。また、図で"Max=160"に設定しているのは、円形ミニマップの半径が150(1辺が300なので、300/2)だからです。描画距離上限外のPOIを、枠の少し外側に描画するためのオフセットとして"+10"しています。
三角比を用いた実装を行ったので、これらの処理が容易に実装できますね!
##最終動作確認
これでやっと完成です。最後に動作確認してみましょう。
描画距離上限外のPOIが正しく枠の少し外側に表示され続けていれば確認完了です。
#おわりに
今回は、レベル上のオブジェクト(POI)を円形ミニマップ上にアイコンとして表示しました。
プレイヤーとの位置関係に応じて、ミニマップ上のアイコンの位置更新を行いました。また、三角比を用いた位置更新を行うことで、描画距離上限外のPOIを任意の円周上の場所に表示させることができました。
これまでに紹介した内容で、基本的なミニマップの作成は完了しました。しかし、まだまだ追加要素はたくさんあるので、気が向いたら番外編として投稿する予定です。
#参考サイト
https://qiita.com/tamfoi/items/0f5534a3ed328828a46e
http://historia.co.jp/archives/5567/
https://www.youtube.com/watch?v=Z6qzaT4ZOh0