Advanced Turn Based Tile Toolkit for UE4の使い方[実践編⑤] ZOC:Zone Of Controlの実装

Post 2021年8月20日金曜日

Advanced Turn Based Tile Toolkit TBS(ターン制ストラテジー) UE4 アセット

ぜひ基礎編を開きながらご覧ください。

この記事には、改良版完全版があります。あわせてご確認ください。


お約束
この記事作成にあたって使用した(主な)UE4バージョン4.26.2
このtoolkitのバージョンv3.0 (live 12.12.20)

本日のゴール

敵に接触するとそれ以上動けないようにする


ZOCってなに

いわゆるZOC:Zone Of Controlってやつですね。


概要

これが最善の案なのかはわかりませんが、非常に苦労しました。

①敵の周囲のマスのIndex番号を全てリストアップ

②敵の周囲のマスからスタートする経路検索を全てキャンセル


概要の解説

①はまあわかると思いますが、②の意味が分かりませんね。

このアセットでは、経路検索を下記のようにして実装しているようです。

①経路検索をスタートするマスをParentと呼び、その周囲のマスを全てピックアップ(6角形タイルの場合、最大6マスある。)

②Parentから周囲のマスに移動するコストを調べる。そのマスに移動するためのコストが計算される。

③次にそのタイルをParentとして、周囲のマスを検索する。

とこのように、次々にコストを計算していきます。


<例>

例に従ってやってみると、上のタイルPにPlayerがいるとします。

①タイルPをParent(=親)として周囲のマスを検索。タイルAとタイルBがあります。

②移動コストをそれぞれ調べます。タイルAはコスト2、タイルBはコスト1でした。

③次にタイルAをParent = 親として経路検索。タイルPへはコスト2、タイルBへはコスト1、タイルCへはコスト1でした。タイルPからのコストは2だったので、タイルAを経由したルートではP:4、B:3、C:3となります。

④次にタイルBをParent = 親として経路検索。タイルPへはコスト1、タイルAへはコスト1、タイルCへはコスト1でした。タイルPからのコストは1だったので、タイルBを経由したルートではP:2、B:2、C:2となります。


これらのコストのうち、最小のコストが選択されるというわけです。

つまり移動コストは

P:0

A:2

B:1

C:2

となります。Aに行くルートはこの場合P→AでもP→B→Aでも等しくなりますが、どっちが選択されるんでしょう。(わかりません)

そしてこの検索方法をとると、最も効率がいいルートがわかるので、ルート表示が使えるというわけです。




ZOCの実装では、この敵の周囲のマスをParentとするすべてのルートをキャンセルします。


タイルEに敵がいるとしたら、タイルBには入れるけど出れないということです。

緑色で囲ったところのルートをキャンセルすれば、実装できますね。


とはいえまずはタイルBを探す(=敵に隣接しているマスを探す)ことからスタートです。



実践① 敵の周囲のタイルをリストアップ

Unitがどこにいるのかの情報は、実はGridManagerに保存されています。

「Grid Units」がそれです。


Index番号とそのIndex番号に居るユニットが表のように保存されています。

敵と味方の判別はそれぞれのユニットに保存されている「Faction」という情報から調べられます。

ではこれで、いま経路検索しているキャラクターの敵がどこにいるのか調べましょう。


先日上げた、経路検索に関するメモの通り、経路検索はGrid Managerの「Run Pathfinding」関数から実行されているので早速見てみましょう。

Grid Manager 「Run Pathfinding」関数

なんだかよくわかりませんが、もう一個「Run Pathfinding」があります。これはこの関数とは別物で、"BP Path"の「Run Pathfinding」関数です。ややこしい。

---

今アクティブなUnitの情報を取り出したいですね。
今アクティブなUnitのIndexはRunPathfindingのStart Indexでわかるので、さっそくGrid Unitsを使ってみましょう。



右クリックして「Grid Units」をGetし、Findノードをつなぎます。Findノードには、Start Indexをつなぎます。すると、今アクティブなユニットのBPが取り出せます。

そこで、そのBPから引っ張ってGet Factionを取り出します。


次に、全キャラクターの情報を引っ張り出しましょう。

先ほどのGrid Unitsから「Values」を取り出して、「For each Loop」をつなぎました。

これはGrid Unitsに保存されているすべてのユニットを、一つ一つ調べていく作業ですね。

ユニットには、Factionの情報が保存されているのでGet Factionを検索。これですべてのキャラクター(ユニット)の敵・味方の情報が得られました。

あとは経路検索しているキャラクターと照合すればいいですね。



Get Factionと、先ほどのFactionのデータを照合します。「==」で検索すると出てくるので、それをBranchにつなぎます。


True = 経路検索しているキャラに対して味方のキャラクター の場合

False =経路検索しているキャラに対して敵のキャラクター の場合

となります。

Falseの時、Index番号の情報が欲しいですね。


Index番号は、Grid Unitsの中に格納されていたので、ValuesだけでなくKeysノードを使ってIndex番号を取り出しておきます。

配列から~~番目の情報を取り出す際に使うノードは、そう、Get(Copy)ノードですね。


Keysノードをつないだのち、Copyで検索したらでてきます。
※それか、For Each LoopのAllay ElementからGet Indexで敵キャラのIndex番号を引っ張り出してくるという手もあります。こっちの方がお手軽ですが、まあKeysとGET(COPY)をやりたかっただけです。

このノードに、先ほどのFor each loopのAllay Indexをつなげてやれば、敵キャラのタイルのIndex番号が調べられますね。

しかーーーし!!
我々が調べたかったのは、敵キャラのいるタイルではなく、その周囲のタイルを知りたいのです。これでは終わりません。

しかーーーし!!

このアセットはすごいことに、特定のタイルの、周囲にあるタイルの情報まで格納してくれているのです。それがGrid Edgesです。早速使ってみましょう。


Grid EdgesをGetして、Findノードを作成します。そこに先ほどGet(COPY)で出てきたIndex番号をつないでやります。すると構造体が出てきます。

この構造体は、特定のタイルの周囲にあるタイルのIndex番号がまとまって格納されているようなものです。6つ保存されています。

※端っこのタイルは3つとかしか隣り合ってませんが、その場合6つのうち3つはマイナスの値が保存されています。

構造体のままだと扱いが難しいので、分割してやりましょう。青いところのピンで右クリックして分割を選択します。


Keysで、敵の周囲のマスのIndex番号をまとめて取り出せます。先ほどのBranchのFalseにつなぎましょう。


さて、あとはこのIndex番号を格納する変数が必要そうですね。

Integerの配列変数を定義します。名前は適当にZocAreaとでもしておきましょう。

この配列変数をGetして、先ほどのKeysから出てきたIndex番号の羅列をAppendして追加しておきます。


さて、これだけだと次のキャラクターに移った際にどんどん敵周囲キャラが増えていってしまうので、この変数は最初に初期化する必要がありますね。

Clearノードを一番最初に挟みましょう。


最後に、For each loopのCompletedのピンに、最初からあった「Find or Createなんちゃら」のノードをつなげます。


これで、敵周囲マスをすべてリストアップできました。疲れました。コンパイルしておきましょう。



実践② 敵の周囲のマスからスタートする経路検索を全てキャンセル

こっからが本当の地獄だ…

のはずなんですが、作業自体は一瞬で終わります。ここにたどり着くには数々の地獄がありましたが。


先ほどの、BP Pathの方のRun Pathfiding関数をダブルクリックして開きますと、自動的にBP Pathのタブが開くと思います。

左上の関数一覧から「AddTileIfValid」をダブルクリックして開きます。

右の方にあるここを書き換えます。



こんな感じで、変数のタブからGridManagerRefをGetし、そこから引っ張ってZocArea変数を取り出します。

Containsノードを作成。左側の緑のピンを引っ張って、Edgeで検索してEdge Indexを出しましょう。

あとは右側の赤いピンを引っ張ってBranchノードを作れば終わりです。


ブランチノードは、上のブランチのFalseにつなぎます。

Trueはリターンノードに、FalseはもともとあったADDノードにつながるようにします。

これで終了。コンパイルして実行しましょう。



無事できました。お疲れさまでした!!

またいいネタがあったら載せておきます。