今まで
EQSを使ってInfluence Map
であったり
アニメーションに合わせてパーティクルの生成
など、**「ある仕様を実現するための方法」をUE4を用いてQiitaに投稿していましたが、「UE4(ゲームエンジン)なのにゲームほとんど作ったこと無い...」**ということに気づいてしまったので「とにかく何かゲームを作らないと...」と思い立ったことが今回解説するゲームを作り始めたきっかけです。
以下ゲームのプレイ動画です。
このゲームはこれで完成。今まで検証レベルでUE4を使っていたので、今回のような「ゲーム」を作るのはとても久しぶり。
— PavilionDV7 (@Dv7Pavilion) 2017年10月13日
次は何を作ろう。1週間で出来る規模がいいな。今回のもエタりそうだったから。#UE4 #EpicFriday pic.twitter.com/r2F6LJbEmB
以下ゲームへのリンクです。ダウンロードして展開してexeを開けば遊べます。
マウスとキーボードのみ対応しています。パッドは対応していません。
先程のEpicFridayで載せたゲームはこちらで公開しました。#UE4 #indiedev https://t.co/pmrCrqpi4F
— PavilionDV7 (@Dv7Pavilion) 2017年10月13日
#敵管理システム
敵のスポーニング、ボスが撃破された等のお知らせを通知したり受け取ったりするクラスとして、Actorクラスを継承したアクタを作成しました。
##敵の出現はウェーブ制
横スクロールや縦スクロールのシューティングでは「敵はあるタイミングで出現し、全てを倒さずともボスへ到達できる」というパターンが多いのですが、このゲームでは「1ウェーブ分の敵を決まった敵数のみフィールド上に出現させる」というちょっと変わったウェーブ制を採用しています。
あるウェーブに15体の敵が登録されておりフィールド上にいるべき敵数が3体だった場合、フィールド上には3体の敵がいる状態をキープするように敵を出現させます。
これを1ウェーブ分の敵を出し切るまで(この例だと15体)続けます。
敵のスポーニングは上の通りです。再帰処理にしてみました。
しかしこの処理方法では一度既定数スポーンさせた後は敵が一切出現しません。再帰処理は条件を満たし処理を終了させると外部から呼ばれない限り処理を開始することはありません。
そこで敵が撃破された時に呼び出されるイベントでFieldEnemyのデクリメントと同時に再度SpawnWaveを呼び出すことで解決しています。
敵は死亡時にSpawnerを検索し該当するイベントを呼び出します。
いちいち死亡する度にGet All Actors Of Classを呼ぶようにしているのですが、この理由は「スポーン時に引数としてSpawnerを渡してもアクセスエラーが出てしまった」からです。
出現した瞬間に撃破されると、なぜか引数として渡したはずのSpawnerの参照がNONEになっている事がありました。何故このようなことになったのか最終的な原因は不明ですが、とりあえず死亡時に検索するようにすればOKなのでこれでよしとしました。
Sequenceの下部分です。こちらではウェーブのインクリメントと全ウェーブを出し切った時にボスをスポーンさせています。
#ゲーム開始時とボス撃破時のカメラアニメーション
ステージを選択してゲームが始まる時、いきなりパッと始まるのは味気ないので以前から使ってみたかったシーケンサーを使ってカメラアニメーションを作ってみました。
上の画像はゲーム開始時のシーケンサー再生部分です。これはレベルブループリントで行っています。
ゲームプレイからシーケンスをトリガーする
を参考にしました。
ボスが撃破されるとボス撃破シーケンスが再生されるのですが、これは
EnemySpawnerで宣言されているOnBossDestroyedディスパッチャーをレベルブループリントで定義することで実現しています。
上の画像はEnemySpawnerでOnBossDestoyedディスパッチャーを呼び出している部分です。
DestroyedBossはボスブループリントから撃破時に呼び出されます。
#オフスクリーンインジケータ
このゲームでは敵が画面外に出た時には敵のいる方向にインジケーター(画像)を表示しています。これを**offscreen indicator(オフスクリーンインジケーター)**と呼ぶそうです。
先程のウィジェットブループリントのグラフです。OffScreenイベントやOnScreenイベントは、このウィジェットを生成する敵クラスから呼び出されます。
敵が画面外に出た場合にインジケーターを表示したいので、OffScreenイベントで可視性を「Visible」にしています。
OnScreenはOffScreenとは正反対の事をしています。画面内の場合はインジケーターを表示の表示は不要なので可視性を「Hidden」としています。
これは敵のベースクラスで記述しているインジケーターウィジェットの呼び出し部分です。
これもシンプルな実装だと思います。インジケーターウィジェットのUpdate Indicator関数とやっていることは同じです。
このような実装になる前に**「Was Recently Rendered」**を用いて「画面外にいるか」を判定していたのですが、この関数は指定した時間内にアクタがレンダリングされたかを真偽で返すので画面外に出てからインジケーターが表示されるまでのラグと画面内に入った時の表示までのラグがはっきり感じ取れてしまったのでラグの少ないこの方法を採用しました。
#スローモー
プレイヤー以外の時間を遅くするというアイテムを実装したのですが結構面倒くさかった印象です。というのもSet Global Time Dilationを用いてゲーム内の時間を遅くしているのですがこれによりSet Timer by Eventで指定した時間も影響を受けてしまいリアル時間とのズレが生じてしまいました。(SetGlobalTimeDilationに0.5を指定し、SetTimerByEventに5秒後を指定した場合Eventが呼び出されるのはリアル時間で10秒後となってしまう)
UE4小ネタ : 制限時間の実装方法を色々と
こちらではSetTimerByEventは影響を受けないと書かれていたのですが自分の勘違いの可能性もあります。
ここの問題の解決を含めてアイテムがどのような処理をしているか紹介します。
GetPlayerCharacterから伸びるOnBeginSlomoはプレイヤーのCustom Time Dilationを指定の速度にしています。
上の画像はプレイヤーのOnBeginSlomoとスローモーが解除された時に呼ばれるOnEndSlomo部分です。
##スローモー発動時のエフェクト
スローモーが発動した時画面には色収差(色ズレ)を起こすことで「プレイヤーの時間だけが加速している感」を出しています。(これは上手く表現できたと思います。出来てるよね...?)
このエフェクトはポストプロセスのChromatic Aberationを弄っています。
Scene Fringe (Chromatic Aberration)
そしてスローモーの発動時と非発動時のエフェクト切り替えはレベル上に配置したPost Process VolumeとCameraコンポーネントのBlend Weightを切り替えて実現しています。
プレイヤーのOnBeginSlomoとOnEndSlomoが呼び出されたタイミングでそれぞれWeightを1.0~0.0、0.0~1.0に変化させています。
#おわりに
「もしもこのゲームを誰かが作って自分がプレイしたときに、どういう事を聞きたい(知りたい)だろうか」というのを考えて書いてみました。主に「ある機能を実現するための手法」を紹介するような記事になりましたが、いかがでしたでしょうか?この記事を読んでいただいている方の参考になれば幸いです。
記事に書いてある事以外にも「これどうやってんの?」等の質問があれば
Twitterやコメントに書いていただければと思います。