【UE4】Flocking（操舵行動）を実装してみた

#はじめに
タイトルの通りですがUE4でFlockingと呼ばれる魚の群れや動物の群れのようなものを表現する処理を作りました。

>2016-04-28 にアップしたプロジェクトから若干の修正を加えています。

#プロジェクトデータをアップしてます
プロジェクトをOneDriveにてアップしました。落として遊んでみてください。
UIでパラメータを動的に変更出来るようになっているので各パラメータを弄ってどのように動きが変化するかを観察してみてください。7-zip形式となっております。
[プロジェクトデータ「Flocking」](https://onedrive.live.com/redir?resid=99121011A816BCEF!660&authkey=!AH2MsJ4M7RKacKQ&ithint=file%2c7z "プロジェクトデータ「Flocking」")

#Flockingとは？
Flockingとは「分離行動」「整列行動」「結合行動」の３つの行動から生まれる操舵力をあわせる事で「魚の群れ」「鳥の群れ」等の群衆効果を生み出すアルゴリズムです。
非常に古い歴史を持ち、AIの実装としては良く知られています。
ゲームだけではなく映画「バットマン・リターンズ」やアニメーション「Stanley and Stella in "Breaking the Ice"」でも使用されています。

#操舵行動の種類
各画像は [Craig Raynolds Boids](http://www.red3d.com/cwr/boids/ "") から引っ張りました。
##分離行動(Separation)
![separation.gif](https://qiita-image-store.s3.amazonaws.com/0/104467/921dd12a-6f27-15d0-1dbb-209d7b370852.gif)
緑オブジェクトが持つ近傍領域内にいる青オブジェクト達から離れる操舵力を作ります。
この行動の力が強ければ強いほどオブジェクト同士の距離が離され、バラバラになります。
##整列行動(Alignment)
![alignment.gif](https://qiita-image-store.s3.amazonaws.com/0/104467/9b48605b-2568-8965-4d04-3adc654c47dd.gif)
近傍領域内にいる青オブジェクト達と同じ方向、同じ速度を持つ操舵力を作ります。
この行動の力が強ければ強いほどイワシの群れのようなある程度まとまった集団を作りやすくなります。
##結合行動(Cohesion)
![cohesion.gif](https://qiita-image-store.s3.amazonaws.com/0/104467/6a181646-0474-9192-5786-92325e48e779.gif)
近傍領域内にいる青オブジェクト達の中心位置に向かう操舵力を作ります。
この行動の力が強ければ強いほど集団の密度が増します。

#UE4での各行動の実装
各行動は「Boidブループリント内の各関数」に実装されています。プロジェクトを落とした方はそちらを見ながら読んでいただけると分かりやすいかと思います。
##分離行動
「自身の位置 － 他オブジェクトの位置」で「他オブジェクトから自身の位置へと向かうベクトル」が得られます。上で得たベクトルを正規化し周囲のオブジェクト数で割ることでベクトルのスケーリングを行っています。これらの計算を行った回数を変数Countへ加えます。
![1_Separate1.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/fecee2ca-104c-c9eb-9db6-1fdbade37843.png)

変数Steerにはスケーリングを行ったベクトルが周囲のオブジェクト数分加算された結果が保持されていますので、変数Countで除算することでベクトルの平均化をしています。
これにより周囲のオブジェクトから満遍なく離れられるベクトルが求められます。
![2_Separate2.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/201768ae-bd52-8e09-b3d4-68e4b1b82acd.png)

最後に上記で平均化したベクトルから自身が今進んでいる方向と減算することで、今進んでいる位置から他オブジェクトと離れることの出来る方向への操舵力を生み出しています。
![3_Separate3.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/8994b9f5-5c34-8682-9217-cd0bebb36dc1.png)

##整列行動
近傍領域内にいる他オブジェクトの向いている方向を取得し変数Sumへ加算します。加算した回数を変数Countでカウントします。
![4_Align.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/4f5b5964-1068-6435-cb25-7bdf414d3ee0.png)

上記で加算したベクトルを変数Countで除算することによりそれぞれのオブジェクトが向いている方向の平均を取得します。ここで得た値が向かわせたい方向となります。
![5_Align.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/6ee614d5-1c43-e7eb-40b7-ef589dbac871.png)

変数Sumには向かわせたい方向の情報が入っていますので、正規化しMaxSpeedと乗算することによりスケーリングを行います。
後は分離行動と同様に今向いている方向と減算することで最終的な操舵力を生み出します。
![6_Align.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/eb2e97fd-cfc6-0510-360b-a3bb398df600.png)


##結合行動
近傍領域内にいる他オブジェクトとの重心を求めるため、他オブジェクトの位置を全て変数Sumへと加えます。そしてカウントします。
![7_Cohesion.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/265069a2-aee0-7354-cce8-aea4cdac89b2.png)

変数Sumには周囲のオブジェクトの位置が全て加えられた結果が入っていますので、Countで除算します。これにより他オブジェクトとの重心位置が求められます。
求めた重心位置をSeek関数へ渡すことで自身の位置から重心位置へ向かうベクトルが得られます。
![8_Cohesion.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/7a97a4fb-4139-2c54-c671-f8ce5ecf127e.png)

##探索行動（Seek)
目次Flockingでは説明がありませんでしたが、この行動は「オブジェクトを目標位置に方向づける操舵力」を求めています。
実装は非常に簡単で、目標位置から自身の位置を減算し「自身の位置から目標位置へと向かうベクトル」を求めます。その値を正規化しスケーリングすることで最終的な操舵力を求めています。
![9_Seek.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/68ac6cba-7ec5-b6a9-f573-b41a80a4e63d.png)

#各行動の組み合わせ ＆ 各行動の重み
上の見出しで説明した各行動はFlocking関数で呼び出されます。

|||
|:-:|:-:|
|![10_Flocking.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/0f661ed9-56f3-7d07-59a3-5df2fb578374.png)|![11_Flocking.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/f88b0893-0f13-6a8c-a875-26a47915cef0.png)|

各行動で求められた方向ベクトルは各行動の「重み」と乗算されApplyForce関数へと渡されます。ApplyForce関数は渡されたベクトルを推進力として加えているだけです。
![12_ApplyForce.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/0ac00dcb-e500-2279-d9f6-dcc876ec5efd.png)

各行動の重みとして「Sep Weight」「Ali Weight」「Coh Weight」があります。
これらの値を変更することで「各行動の比重」を決めています。
例えばSep Weightが他の値と比べて大きな値の場合は分離行動によるベクトルが大きくなり、各オブジェクトが離れやすくなります。
これらの値を調整することで最終的なFlockingの見栄えが決まります。

#オブジェクトを求めた方向へと向かわせる
Update関数ではFlocking関数により得られた推進力と加速度を合わせ「オブジェクトが向くべき方向」を求め回転し前進させています。
下の画像は「オブジェクトが向くべき方向」を求めている処理です。
![13_Update.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/fd112eae-c41a-0e1f-d122-48ed25a6e0dc.png)

次に上で求めた方向にオブジェクトを回転させ前進する処理です。
Find Look at Rotation関数で回転を求め、RInterp Toで徐々に方向転換させています。
![14_Update.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/2bfbf60a-77ac-c982-764a-34f63a86f7fa.png)


#目の前に壁がある場合反転させる
うまく機能してはいませんが、目の前に壁があった場合反転させるという処理も加えてあります。
![15_Update.PNG](https://qiita-image-store.s3.amazonaws.com/0/104467/4b219f0b-3f12-3dba-dc75-d4a00ac39fbb.png)
LineTraceで前方にレイを出し、壁と衝突したら Mirror Vector by Normalで反射ベクトルを求め、Velocityにセットしているだけです。
ただ、速度が遅い場合には有効ですがMaxSpeedやMaxForceを大きくすると簡単に壁を突き抜けます。ここらへんは自前で壁のめり込み防止処理を組んだほうがよいかもしれません。

#おわりに
Flockingの見栄えはパラメータ調整が命です。パラメータによってはイワシの群れや羊の群れ等も表現出来ます。
実装もシンプルですが非常に奥深くユニークなものですので、一度自分で実装してみるとより理解が深まると思います。