今回は第3回UE4ぷちコンに提出した『はじけろ!ブルーマン!の頭』の話です。
それほど難しいことはやっていないのであんまりおもしろいことは書けませんが、何らかの参考になればと思います。
まず、このゲームを作成する前に実は2作品ほど作ってポシャりました。
1作目はぷちコン開始前にLeap Motionの習作として作成した『きゅっとしてドカーン』をもっときちんとゲームにしてスコアタできるゲームにしようと考えました。
が、うまくいかず。
手慰み程度のゲームとしては悪くないのですが、テクニックが要求されるでもなく、戦術的な思考が試されるわけでもないゲームになってしまい、ただ右手をグーパーして握力を鍛えるだけのゲームにしかならないと気づきました。
トロッコはLeapで円運動のジェスチャーを検出している間だけ加速し、もう一方の手で爆弾を投げて追いすがるブルーマンを吹っ飛ばして行こうと考えました。
この段階でKiteデモに用いられたアセット群が配信されていたので、これも利用して自然の中をブルーマンから逃げてみようとしたわけです。
しかしこちらもゲームとして面白くなる道が見えませんでした。
とりあえず真っ直ぐな道を作って試してみたのですが、カメラが固定されているため無作為に爆弾を投げるしかなく、円のジェスチャーをしながら別の行動を取るのも難しいとわかりました。
結局、この2作の検証とLeap Motion公式プラグインにジェスチャー検出機能を追加するのに1週間くらいかかってしまい、割と時間を無駄にした次第です。
特にプラグインの修正をするのにLeap Motionのドキュメントを読んだりするので時間がかかった印象ですね。
ただ、ここまでやってよくわかったことが2点ほどあります。
1つはNatural User Interface (NUI) はほんとうに正確な操作をさせるのが難しいという点です。
どうしてもノイズは乗るし、ノイズをどのようにして除去するかを考えないとそれっぽく認識させることは難しいです。
もう1つはパッドはすごいということ。
複数の操作を同時か、ほぼ同時に近いタイミングで連続的に行うことが出来ます。
特に2作目で、両手それぞれを使って別々にアクションさせるということが如何に大変か身にしみてわかりました。
それにもかかわらず、パッドは3~4の操作は同時に行うことが出来ますし、しかも正確です。
直感的にわかりにくい部分があることは否定しませんが、それを押さえ込める程度の利点は存在するわけです。
で、これらの反省点を踏まえて、
- NUIの不正確な点を逆に利用する
- パッドを利用することを考慮する
まあ、NUIを使ったゲームを作成している人にとっては何を今更って話なのかもしれませんが、NUIをゲームに用いる場合の注意点を知るいいきっかけになりました。
では、続きからで、ゲーム内で工夫したかな、という点を書いていきたいと思います。
まず最初にランダム配置の手法ですが、これはBlueprintにある以下の命令を利用しています。
この命令はナビゲーションメッシュで移動可能な範囲内でランダムな位置を生成する命令のようです。
バウンディングボックス内のランダム位置を取得してしまうと移動不可能な座標(壁の中とか)を取得する恐れがありますが、この命令を利用するとAIの移動範囲内のみ取得できるので便利です。
ただ、若干分布が偏るような印象がありますね。
第1引数を指定すると特定のナビゲーションメッシュ内でのみ取得できるような気がしますが、この場合はナビゲーションメッシュがアセットとして存在していないとダメなのかな?
Nav Mesh Bounds Volumeは接続できませんでした。
ブルーマンの生成は各レベルで通常とスペシャルが決まった数だけ作成されます。
ライフルでブルーマンを倒した場合、連鎖等で死亡したブルーマンと同じ数のブルーマンが生成され直します。
なので、ライフルを打てるようになった時には必ず通常とスペシャルのブルーマンが規定数存在していることになります。
次にカメラの移動ですが、これはLeap Motionで手の方向をチェックしています。
ただし、手の方向は手のひらのボーンの前方ベクトルではなく、手のひら、人差し指、中指、薬指の4本のボーンの前方方向を平均したものを用いています。
手のひらだけ、指1本だけでは不意にカメラがおかしな方向に向いたりしてしまったので、平均を取ることでおかしな動きを減らしました。
Player Pawnはゲーム中では固定されて動かず、Player Pawnが持っているカメラコンポーネントを回転させるようにしました。
これはPlayer Pawnの初期配置をデフォルトとして、縦方向と横方向に特定角度以上回らないように制限をかけるのが主な目的でした。
以下はその処理イベントです。
[CalcForwardVector]というマクロが特定角度に制限されたベクトルを計算して返す命令です。
この命令には若干バグがあり、大きく角度制限を突破しようとした時に変なカメラ動作を行うことがあります。
この問題は制限角度を小さくしたら出なくなったので、修正せずにパラメータで修正を行いました。
計算式のどこに問題がありそうかはなんとなくわかってはいたんですがね…
若干わかりにくかったと思っているのは銃を撃てるようになるタイミングですね。
リロード時間は3秒程度にしていたんですが、コンボが完全に終了して死亡したブルーマンが全員削除された段階で銃が撃てるようになるようにしました。
そのため、死亡処理が行われている最中のブルーマンはIsDeadフラグが立てられ、ゲーム中のすべてのブルーマンを検索してIsDeadフラグが立っているブルーマンが0になったらリロード終了としました。
本来であればデスリスト何かを使った方が良かったんでしょうけどね…
以下の命令は[HasDeadmans]というマクロで、これが前述の処理を行っています。
ブルーマンのダメージ判定はコリジョンボリュームを各骨に合わせて配置しています。
これらのコリジョンボリュームには[RayCheck]というコリジョンプリセットを作成して設定しています。
銃弾を意味するレイとの衝突判定以外を無視したいがためにこのようなプリセットを作成しました。
当初はこれも普通に物理的衝突判定を持っていたのですが、ブルーマン死亡時の物理挙動ONで物理アセットと干渉しておかしな動きをしていました。
これを回避するために最初はブルーマン死亡時にこれらのコリジョンを削除するようにしてみました。
この際、メッシュの子としてコリジョンコンポーネントを追加していたので、[Get Child Components]命令を使ってみたのですが、なぜか正しいコンポーネントを取得できませんでした。
仕方ないのでTagをつけて、[Get Components by Tag]で列挙してみたのですが、こちらも何故か正常に動作せず。
どちらも5~6個程度列挙はするのですが、全部は列挙してくれないんですよね。ちなみに、全部で11個あります。
結局原因がわからなかったので全部削除命令を指定する形で対応しましたが、あれはUE4.7のバグだったのかなぁ…
AIはビヘイビアツリーを利用しましたが、まだまだ詳しいことがよくわかってなかったので凝ったことはしていません。
作成したビヘイビアツリーは3つで、通常のブルーマン、時々走るブルーマン、心臓撃ちで発火して走り回るブルーマンです。
前の2つは通常ブルーマンかスペシャルブルーマンかで選択されていましたが、発火時のビヘイビアは心臓撃ちを食らった瞬間、もしくは発火したブルーマンに触れた瞬間に[Run Behavior Tree]命令で処理を実行されます。
ただ、それだけだとそれまでのビヘイビアを続けてしまうようでしたので、[Restart Logic]命令でAIの行動をリセットしました。
ビヘイビアツリーを切り替えるときはこの命令を呼び出したほうがいいようです。
最後にダメージ処理ですが、ここは少しだけ工夫した点です。
と言ってもプログラマから見ればそれほど特殊なことはしていません。
ブルーマンはダメージを受けるとそのダメージ場所に合わせた処理が行われます。
ボディを撃たれると小さな範囲で爆発が発生し、周囲のブルーマンも巻き添えにしますし、心臓撃ちなら発火、頭を撃たれたらはじけて連鎖するようになっています。
何も考えずに実装するのであればダメージ処理でダメージを受けたコンポーネントごとに分岐させてやればいいのですが、ヘッドショット時の連鎖だけが微妙でした。
というのも、他の死亡演出と違い、ヘッドショット時は一旦GameStateが持っている遅延死亡リストにアクターが追加されます。
このリストは空でない場合に限り、1フレームに1つのアクターをリストから取り出し、ヘッドショット時の死亡処理を行うようにしていました。
ただの分岐でも十分対応できるのですが、ダメージ判定の種類が今後増えることも想定し、[Damager]というアクターを作成し、各種ダメージ処理はこの[Damager]を継承したアクターで行うようにしました。
遅延死亡するべきかどうかはその継承したアクターが自分自身についての情報を持っていて、[IsDelayDead]という変数で保持するようにしました。
このフラグが立っている場合、その[Damager]を持っているブルーマンは遅延死亡リストに入れられます。
また、[Damager]は[Dead End]命令を持っていて、これも各継承先のアクターが処理を実装するようにしてました。
こうすることで、分岐の際の処理は対応する[Damager]を作成するだけにとどまり、遅延死亡でなければ即時[Dead End]命令が実装され、遅延死亡であれば遅延死亡リストに追加されます。
分岐はスッキリするし、実際の処理も[Damager]アクターが担ってくれるので、ブルーマンのブループリントはかなりわかりやすくなったと思います。
反省点としては、[Actor Component]を用いるべきだったな、という点でしょう。
[Damager]なんかはアクターとして登録する理由はほとんどありません。
ただ、これを作った段階では[Actor Component]に気づかなかっただけなので、次回があれば[Actor Component]で実装するでしょう。
このような、アクターの性能をコンポーネントとして切り分けるのは大変重要なテクニックです。
特に動的にアクターの性能が変化する場合、それに合わせて分岐して処理を作成するというのはけっこう面倒です。
また、同じ性能を複数のBlueprintに設定したい場合もコンポーネントとして作成しておくと融通がききやすいです。
とまあ、グダグダとりとめもなく書いてしまいましたが、工夫したといえるのはこんなもんでしょうか。
…やっぱりそんなに複雑なことはしてませんね。
最後に『はじブル』の実行ファイルを公開しておきます。
Xbox360コントローラとLeap Motionが必要というのはちょっと敷居が高いとは思いますが、持っている方は遊んでみるのもいいかもです。
HajiBlue.zip
もしも疑問等がありましたらTwitterででも聞いていただければ答えますので。