Gaming Life

一日24時間、ゲームは10時間

UE4 UE4で偏りを持った乱数(正規乱数)を生成して敵AIのエイムを適度に散らしてみる

 マケプレのセールで買ったアセットが4.18未対応で泣いた。

 ブループリントで乱数を生成するには、Rand Float in Range 等を使う。で、この ブログ を見ると分かる通り、帰ってくる乱数は例えば1-10までの整数乱数を生成するとだいたいどの数字も同じ確率で出現する。これを一様分布に従う一様乱数って言ったりする。まあ読んで字の如くなので難しくはない。

 しかし一様乱数だけしかBPで使えないってのは不便。例えばコマンド式のRPGドラクエみたいな)でダメージ計算する時、計算結果に乱数を乗じて実際敵に与えるダメージに幅を持たせるといったことをするがこの時乗じる乱数が一様乱数だとどうも違和感が出てしまう。他にも敵キャラが自分に向けて銃で遠くから攻撃する場合、毎回正確にプレイヤーに弾が飛んできたらたまったもんじゃないので乱数を使って照準のズレをもたせたくなる。この場合も一様乱数を使うと外す確率も当たる確率も同じになってしまい、プレイヤーはこれなら殆ど弾飛んでこないし特攻しても大丈夫だろう、と思われかねない(勿論この心理を逆についてプレイヤーとの距離が閾値を超えたら乱数のレンジを狭めエイム精度を大幅に上げるみたいなことも有りだろうが)。

 というわけでゲームで使う乱数はある程度偏りを持っていてほしいとなるのである。それもある値が頻繁に出現する乱数が。

 で作り方。

f:id:ai_gaminglife:20171220212510p:plain

 Float型乱数を5個取ってきてその平均をとるだけ。これでMaxとMinのちょうど真ん中の値(Max100,Min-100なら0)付近が頻繁に出現し、MaxとMinに近づくほどその値の出現頻度が低い乱数が生成できる。

 これを応用してTwinStickShooterテンプレートの敵キャラAI(制作の様子は 過去ブログ 参照)。

f:id:ai_gaminglife:20171220212620p:plain

 このエネミーはプレイヤーの位置と自身の位置を取得してベクトル減算して攻撃の単位方向ベクトルを得ているのだが、この時使用するプレイヤーの位置ベクトルを「Break Vector」し、X座標の値だけ「GaussRandomFloatInRange」で取得した乱数(エイムの振れ幅)を足してベクトルに戻している。こうすることでエネミーの攻撃を「違和感なく」散らすことができた。


 UE4での偏った乱数の生成方法の話はおしまい。以下ちょっとしたなんでこうなるかの理論の話と本当に偏った乱数を生成できるのかExcelで実験する話なので興味のない方は読み飛ばして頂いて構いません。

なんで偏った乱数になるのか

 ここで擬似的に生成している乱数というのは正規分布に従う乱数、正規乱数と言われるもので、なぜこうなるか説明するには中心極限定理やら大数の法則の話をしなきゃならない。更にいうともっと正確な(厳密に正確ではない。PCで作る乱数はあくまで擬似的なもので真に正しい乱数は作れない)正規乱数を作るには一様乱数から正規乱数を取得できる Box-Muller法(wikipedia) を使ったほうがいいのだがlogやらcosやら出てくるのでBPで実装するのはよろしくない。こんな面倒なことをしなくてもC++の乱数ライブラリstd::random で正規乱数が生成できるらしいが現時点で筆者がC++を書けないのでこのやり方は説明できない(このせいでUE4ソースコードから乱数生成アルゴリズム読み解こうとしたのにさっぱりわからなかった。いつかリベンジしたい)。

 中心極限定理はざっくり言えば

X が平均 μ,標準偏差 σ のある分布に従うならば,大きさ n の無作為標本に基づく標本平均 は,n が無限に大きくなるとき,平均μ,標準偏差 σ/√n の正規分布に近づく。

 とのこと。わかり辛い。ようはある分布に従ってるサンプルを幾つか取ってきて得た平均を並べると正規分布っぽくなりますよって話。ここでは一様分布に従う乱数を5個取ってきて得た平均が正規分布に従っているように見えることを利用して正規乱数を得ていたのである。

Excelで検証

 UE4で検証した方が良いのだろうがBPで実装する時間がないのですぐできるExcelで検証してみた。

○ Rand()で生成した乱数10000個のヒストグラム

f:id:ai_gaminglife:20171220212645p:plain

○ ( Rand() + Rand() + Rand() + Rand() + Rand() ) / 5で生成した乱数10000個のヒストグラム

f:id:ai_gaminglife:20171220212657p:plain

 確かに正規分布特有の釣鐘型のヒストグラムが得られている。

あとがき

 まだまだUE4を触りだして間もないがBPは偉大だ。しかしやはりC++は知っていると便利。いつか勉強したい(するのだろうか)。

 ちなみに以前乱数について簡単なスライドを作成して公開したので寒いネタのオンパレードですが興味のある方は是非見て下さい。

ai-gaminglife.hatenablog.com

UE4 特定のStaticMeshをナビメッシュの計算対象外にする

 小ネタ。日本語情報が見当たらなかったのでブログに残しておく。

ai-gaminglife.hatenablog.com

 極め本22章23章でLevelに配置した TargetPoint を永遠に巡回するみたいなAIがあるが(上記過去記事参照)、StaticMesh コンポーネントを持った Pawn を件のAIで制御すると動きがおかしくなった。

 (録画ミスでラジオの音源入ってるけど許して)

 それもそのはず。StaticMesh は NavMesh の計算対象なので初期位置に TargetPoint を置くとそこは Actor が入れない場所になってしまい AI Move To が失敗してしまうから。

 これは StaticMesh コンポーネントの Colision -> Can Ever Affect Navigation のチェックを外せばいいだけ。こうすると

f:id:ai_gaminglife:20171217231104p:plain

 問題解決。


 UE4で制作しているというアークの新作格闘ゲームドラゴンボールファイターズの新PVが発表されましたが、あれは本当に同じエンジンを使っているのだろうか……当然専用ツールやエンジン改造をしてるでしょうがそれでも信じられない。レイヤーズストーリーゼロといい日本のゲーム会社からUE4で制作されたセルアニメ風の映像が沢山登場していて今後も楽しみ。和ゲーで育ってた人間なのでここ2年くらいの和ゲー大躍進が嬉しい。

UE4 DelayとForLoopについて色々実験した

 ゲーム作ってると数秒おきにアクションを起こす、みたいなことをしたくなる。SideScrollerテンプレートだとこれをSet Timer By Function Nameを使って解決してるが直感的にはForLoopのLoop Bodyに処理とDelayを置いて解決したい。だがこの方法ではうまくいかないことはUE4を多少触ったことがある人はわかると思う。

 というわけでDelayとForLoopを組み合わせた実験をしてみた。まだ完全な理解に至ってるわけじゃないが実験を通じなんとなくはDelayのことがわかったので実験結果を簡単にまとめてみる。

実験

 ForLoopとSequenceとPrintStringとDelayを色々組み合わせてPrintの表示の様子をみる。以降「0.2秒間を開ける」を「0.2s」と表記する。

1 f:id:ai_gaminglife:20171216141253p:plain

結果:Hello->Hello->Hello->Worldと間をおかず表示される

2 f:id:ai_gaminglife:20171216141324p:plain

結果:World->0.2s->Hello

3 f:id:ai_gaminglife:20171216141606p:plain

結果:Hello->Hello->Hello->World->0.2s->Hiroyuki

4 f:id:ai_gaminglife:20171216141651p:plain

結果:0.2s->Hello->0.3s->World

5 f:id:ai_gaminglife:20171216141734p:plain

結果:0.2sec->World->0.3s->Hello

6 f:id:ai_gaminglife:20171216141850p:plain

結果:0.2s->Hello->0.3s->World

7 f:id:ai_gaminglife:20171216141945p:plain

結果:0.2s->World->0.3s->Hello

 これだけ結果を見せればDelayがどんな挙動をするかは分かるはず。某ブログではDelayを「中断処理」ではなく「登録処理」と考えるとよいと書いてあったがまさにそのとおりだと思う

それでも

 Set Timerは個人的にわかりにくいのでちゃんとDelayで待ってからLoop Bodyの処理を順にできるFor Loopがほしい。そこで更に調べるとこんなマクロを組めば直感どおりの挙動をしてくれることが分かった。

f:id:ai_gaminglife:20171216142441p:plain

 とりあえずFor loop With Delayと名付けた。これを使えば

f:id:ai_gaminglife:20171216142533p:plain

結果:Hello->0.2s->Hello->World->0.2sec->Hello

と表示される。

 しかしこのマクロもそこそこ複雑なのでやはりSet Timerを使ったほうがよいという結論に至った。

 (12/18追記)

 Twitterですごくいい方法を教えてもらった。

 知らなかった…すごくいい方法なので今後これを使っていきたい

UE4 ビヘイビアツリーで巡回+攻撃する敵AIを作るのに苦労した話

 UE4にはビヘイビアツリーと呼ばれるAIを簡単に作れる機能がある。 人工知能の作り方 によるとXBoxで2004年に発売された『Halo2』の開発のため発案、GDCで発表されて以降ゲームAIの分野に置いて最もポピュラーな方法として確立されたらしい。

 ビヘイビアツリーではキャラクターの行動をBehavior(日本語で振る舞いという意味)を基本として捉えツリー構造で表現する。原理としては、ルート(根)から実行順序を決める中間ノードを介して、枝の末端に実際の振る舞いを記述。中間ノードに従い末端の振る舞いを順に実行するだけの実にシンプルなもの。UE4ではこのツリー構造を視覚的に作ることができるので非常に楽にAIを実装することができる……というのが初心者なりの理解。  ビヘイビアツリーの更に詳しい原理や成り立ちは前述の本を読めばだいたい理解できるし、UE4での実装の仕方は皆大好き極め本や 公式のクイックスタートガイド(日本語化されている)を見れば大丈夫。

 だと思ってた。

 極め本や公式クイックスタートガイドを見ながら、上記程度の単純なAIを実装できたがツイートの日付を見ればわかる通りたったこれだけでかなりの時間を要してしまった。復習のため、また、今後勉強を始める方の為に躓いた点と解決策を書いておく。

実装したい機能

  • 使用テンプレート: TwinStickShooter
  • バージョン: UE4 ver4.18.1
  • 普段は指定の巡回ポイントを順に回る。巡回ポイントについたらその場で数秒停止。周りを監視する
  • プレイヤーを発見したら1秒程度停止する(攻撃準備の表現)。その後その場にとどまり0.2秒刻みでProjectileを発射して攻撃する
  • プレイヤーが視界から消えたら攻撃を止め5秒その場で停止、見つからなければ巡回行動に戻る

 完成版ビヘイビアツリー(画像)

f:id:ai_gaminglife:20171213212358p:plain

1.宙に浮いているキャラクターの移動

 極め本や公式のチュートリアルでは地面に足を付けたCharacterを動かしているのでそのままナビメッシュを利用すれば移動できる。だが今回使用するTwinStickShooterテンプレートは宙に浮いているPawnのためそのままではナビメッシュを利用できない。「Move to Location」や「Move to Actor」の「Use PathFinding」のチェックを外せばナビメッシュなしに移動できるのだが直線的にしか移動できないので後で不具合が起きそう。そもそもこの問題に直面していた時チェックを外しても移動すらできなかった。

 これは画像のようにCharacterクラスを無理やり使って解決した。

f:id:ai_gaminglife:20171213212423p:plain

 見た目上宙を浮いていればいいじゃないかと考えCharacterクラスのSkeltal Meshを空(カラ)に、新たにStatic Meshコンポーネントを追加してコリジョンを画像のように置いて解決した。ちなみに「Use PathFinding」のチェックを外しても動かなかったのはCharacter Movement Componentを追加するのを忘れてただけだった(なんじゃそれ)。

2.プレイヤーを最初に発見した時のみ起こす行動の分岐方法

   プレイヤーを最初に見つけた時のみ構えの動作をとってから(まだ構えのモーションは未実装)攻撃に移行させようとしたのだがこの分岐をどうやればいいのか最初は全くわからなかった。

 今回はブラックボードに新たにBool型Key「FirstSearch」を追加して解決。

 AIControllerで「Set Value as Bool」を使ってTrueに初期化しておき、   f:id:ai_gaminglife:20171213212501p:plain

 自作のデコレータ「TrueCheck」「FalseCheck」でKeyの情報を取ってきて分岐させた。

f:id:ai_gaminglife:20171213212523p:plain f:id:ai_gaminglife:20171213212529p:plain

(PerfomConditionCheckAI関数をオーバーライドしている)

 後は適切なときに「FirstSearch」をTrue/Falseに切り替えれば良いだけ。

 3.IsAtLocationについて

 チュートリアルのこの頁 では「IsAtLocation」というデコレータを使っているのだが これがどういう意味なのかわからない。未だによくわからないので今回は使わなかった。(どなたか教えてくださると助かります)

(追記)alweiさんにTwitterで教えて頂きました

 だそうです。確かにこれは便利だ…時間があればこれを使ったものも作りたい。

 

4.Projectileをプレイヤーが視界にいる間連射する

 これはビヘイビアツリーのAttackタスク内で以下の画像の通りにして実現した。

f:id:ai_gaminglife:20171213212552p:plain

 プレイヤーの位置を格納している「TargetLocation」から自身のLocationを引いて正規化(normlize)した値と「FindLookatRotation」で自身からプレイヤーへの向きを得て「FireShot」関数を呼び出す。  FireShotの中身はこんな感じ。ほぼテンプレそのまま。 f:id:ai_gaminglife:20171213212558p:plain f:id:ai_gaminglife:20171213212616p:plain

 このAttackタスクを実行後にWaitタスクが実行されるように置いて、連射できるようにした。

まとめ

  「IsAtLocation」デコレータや タスク内でTimerやDelayを実行するとどういう扱いになるかなどまだ良くわからないところもあるがとりあえずここまでで最低限やりたかった機能が実装できた。独自の方法のためもっと効率のいい方法はあると思うががむしゃらにやってみると案外うまくいくのでとりあえず効率無視でやってみる、と言うのが大切だと改めて感じた。

 12月中にはこのゲームを遊べるようにしたい。

ゲーム向けな美味しい乱数を生成する

 色々思うところがあって乱数について学んだことをスライドにまとめてみた。

 本来大学のLT大会で発表予定だったのですが長過ぎるのでブログで公開する。(次回のLT大会でこれのショートor発展verをやる予定)

 ネタが多めだが数式は殆ど使ってないので読みやすいはず。感想、間違ってるとこなどあればコメントTwitterで教えてください。

UE4 ちょっと見栄えのいいワープ機能を実装してみる

先日の第三回UE4GameJamに参加して以降、他の人の組んだBPを読み込んだり、いろんな機能を実験していたのだがその中で色々応用の効きそうなBPが実装できたので自分用のメモがてらブログにまとめる。

環境

使用ver: UE4 1.8.2

使用テンプレート: TwinStickShooter

ワープポイントのエフェクト

 今回は無料で手に入る「InfinityBladeEffect」内の「P_Summon_Portal」を利用。InfinityBladeEffectは実用的な上中身を見てるだけでCascadeの勉強になるので神。

ワープ機能を実装する

 これは ヒストリアさんのブログ が詳しいので参考にされたし。ただしこの記事では一つのActorを継承したBP「BP_WarpPoint」に「Box Collision」を二つ持たせて解決してるがこれでは使い勝手が悪いので、いわゆる「ダイレクトブループリント通信」を利用した。

 「BP_WarpPoint」型の変数を用意してDetailsから「Instance Editable」にチェックを入れる。これにより設置したレベル上でその変数の中身を変えることができる。 (ダイレクトBP通信については公式 がとても詳しいのでそちらを見るべき )

 レベル上にこの「BP_WarpPoint」を二つ設置し(それぞれ①、②とする)Details->Defaultから先程用意した「BP_WarpPoint」型の変数に①には②、②には①をセットする。これでヒストリアさんのブログで紹介されていたワープ機能と同等の機能が得られるはず。

ワープ中入力を受け取らないようにする&黒画面フェードアニメを実行する

 ワープ中に移動できるようにすると変なので

ワープポイントに入る ->移動を禁止 ->黒画面フェードアウト ->ワープ ->黒画面フェードイン ->移動禁止を解除する

という一連の機能を実装してみた。以下、順を追って説明する。

①移動を禁止する  いろいろな方法があるが今回は「SetActorTickEnabled」を利用した。このノードは「Enabled」がFalseの時入ってくる「Target」のEvent Tickが実行できないようにする。今回はこれで問題なく動くが、ワープ中もTickを実行したい場合はフラグを立てて分岐させれば良さそう。

②黒画面フェードアウト、フェードイン  これは alwaiさんのブログ が非常に詳しいです。ほぼこのサイトのままなのでフェードアウト、フェードインアニメーションの作成の仕方は省略。

 ブログの手順に従って作成したUMGアニメーション、「Fadeout」、「Fadein」を以下の関数で呼び出します。

f:id:ai_gaminglife:20171129022103p:plain f:id:ai_gaminglife:20171129022120p:plain

 最終的には以下のようになりました。

f:id:ai_gaminglife:20171129022131p:plain f:id:ai_gaminglife:20171129022135p:plain

 実際動かした動画。

まとめ

  • InfinityBladeEffectは神。
  • ほぼ独学でここまでやってきたけど3ヶ月も使ってると流石に慣れてきた。
  • 劇場版アニメゴジラ早く見たい

 最近はメトロイドライクなゲームの開発がストップしているので次回もTwinStickShooterテンプレートを改造するか、乱数について色々実験するかも。

UE4進捗1-4 MaterialInstanceDynamicを使ってマテリアルの色を変える

 pythonの勉強を始めたせいでUE4を触る時間が少なくなっているがブログを更新していかないと企画すらボツになってしまいそうなので大して変わってないが記事を上げる。    Blenderで自作のモデルを作成、マテリアルの編集は主にUE4でやっている。中身は殆ど aiweiさんのブログ のままなので実装はそちらを参考にしてください。変更点はこのモデルは贅沢にもマテリアル二つで構成されているため「Get Material」「Create Dynamic Material Instance」のElement Indexを1に変更したことくらい(これで一時間ほど悩んだ)。