2020/04/03

UE4で1人称視点の3DSTGを作ってみる(第一回)

blog-post-41-content

お久しぶりです。 さて、突然ですが今回からは数回にかけて 簡単な3DSTGのようなものを作っていこう と思います。 とりあえず初回である今回は 『空中を浮遊する敵』 を作るところからやっていきます。

使用するUE4のバージョンは 4.23.1 です。 (最新ではないですが、現時点で最新バージョンの4.24でも問題なくこの手順で実装できます)


1.実装前の準備

まずはプロジェクトを作成します。
名前は自由ですが今回は 『BLOG_3DSTG』 と名付けています。
テンプレートにはFirstPersonを使用し、プロジェクトは『ブループリント』を選択します。
(C++でも同様の実装は問題なく可能です)

blog-post-41-content1

さて、このFirstPersonテンプレートでは簡易的なFPS系ゲームのプレイヤーの実装が一通りされています。 今回の記事では敵の移動までを行うため、プレイヤー部分の実装はテンプレートをそのまま使います。 (プレイヤーは一切触りません)

ただし、この FirstPersonテンプレートではあまり敵らしいデザインのメッシュが存在しません。 ということで…。


2.よそのテンプレートからデータを持ってくる

blog-post-41-content2

ハイ、よそのテンプレートからメッシュを持ってきます。
画像のUFOを敵のメッシュとして使っていきます。

このUFOはFirstPersonと同じ UE4のテンプレート に含まれています。
今回は TwinStickテンプレート から引っ張ってきます。
Flyingなど、UFOのメッシュが持ってこれるのであればなんでもいい です。 自前でメッシュが用意できる場合はそちらを使った方がいいでしょう。

blog-post-41-content3

一度作成したプロジェクトフォルダをエクスプローラから開き、
その中から UFOメッシュが含まれるディレクトリをそのまま『1』で作ったプロジェクトにコピー しましょう。
エクスプローラからプロジェクトを操作する前に プロジェクトファイルはどちらも閉じておきましょう。
(そもそも本来はエクスプローラからプロジェクトをいじるべきではないのですが…)

これで『2』で作ったプロジェクトの役目は終わりですので消してしまって大丈夫です。


3.敵BPの作成

さて、ここからが本番になります。
まずは 敵キャラ用のBP を作っていきましょう。
『Charactor』を継承して『BP_FlyingEnemy』を作ります。
作成したBP_FlyingEnemyを開き、Meshの子としてStaticMeshコンポーネントを追加し 、ここに先ほどコピーしたUFOを設定します。 (UFOはスタティックメッシュであるため、デフォルトのメッシュ=スケルタルメッシュに設定できない)

このまま置いておくと実際のコリジョンと見かけのコリジョンがズレてしまうため、CapsuleComponentのサイズを調整します。 CapsuleComponent>CapsuleHalfHeightを34に設定 してください。

blog-post-41-content4

これで敵BPそのものの作成は完了です。 (実装はもう少ししてから行います)


4.スプラインBPの作成

続いては 『敵の移動に使うスプラインBP』 を作成します。
空中に浮かぶ敵の移動処理の実装には様々な方法がありますが、
今回はこのスプラインを使う方法で進めていきます。

まずは Actorを継承して『BP_EnemyMoveSpline』を作成 します。
BP_EnemyMoveSplineを開き、親としてSplineMesh、その子としてSplineコンポーネントを追加 します。

blog-post-41-content5

これでスプラインBPの作成も完了です。


5.スプラインの配置と設定

blog-post-41-content6

では今度はレベルに『4』で作成したBP_EnemyMoveSplineを配置・設定していきましょう。
ドラッグ&ドロップにて BP_EnemyMoveSplineを配置し、スプラインのポイントを上の画像のようにレベル上でUの字になるよう にしましょう。
なお、スプラインは右クリックにてポイントを追加できるほか、選択中の左右にあるマーカーから選択中のポイントの向きの微調整が可能です。

blog-post-41-content7

次に配置した BP_Spline>Spline>ClosedLoopにチェックを入れます。
こうすることで スプラインの始点と終点が繋がります。
繋がった際にスプラインが壁にめり込んでしまうこともあるので、
その場合はスプラインの位置や向きを修正しましょう。

そして最後に BP_Spline>Spline>Durationの値を20に変更 します。
これは簡単にいうと 『始点から終点までに到達するまでにかかる時間』 です。


6.移動ロジックの実装1

ここからはBPをいじっていきます。
『3』で作成したBP_FlyingEnemyを開きます。

blog-post-41-content8

まずは BeginPlayでSetMovementModeを行い移動モードをFlyingに変更 します。
何もせず空中にBP_FlyingEnemyを配置してプレイするとわかりますが、
移動モードを変更してやらないとUFOはすぐにストーンと墜落してしまいます。

GIF Description

これはスポーン直後だと空中にいるため、移動モードがFallingになり落下している のが原因です。
コレに対応するために 移動モードをFlyingに変更 する必要があります。
(移動モードを変更させたくない場合は重力を無効化してやる方法もあるがケースバイケース)

ちなみに 移動モードでよく出てくるのは以下の3種 です。
———-
Walking…通常移動、基本的に地上にいる時の状態
Falling…落下中、足元に地面がない時は基本的にコレ
Flying…空中移動、手動で設定する必要あり、重力の影響を受けない
———-

この状態で空中に配置してプレイすると UFOが墜落せずに浮き続ける ようになります。


7.移動ロジックの実装2

ここから本格的な移動ロジックを実装します。
まずは変数として BP_EnemyMoveSplineのリファレンス を持たせ、インスタンス編集を可能にしましょう。
これは レベルに配置してから設定するパラメータで、『移動の参考にするスプライン』 とでも思っていただきたいです。
名前は『UseSpline』 にしておきましょう。

blog-post-41-content9

次に float型の変数『ElapsedTime』 を作成します。
ここには プレイを始めてからの経過時間 を格納します。
スプライン上の移動には現在の時間が必要不可欠なので変数 として持っておきます。

blog-post-41-content10

そして、上記の変数などを使用して画像のよう にTickからノードを繋ぎましょう。 (ロジックの説明はこの後すぐにします)

この状態でプレイしてみると UFOがスプライン上をぐるっと回り、1周したところで停止する はずです。


※ロジック説明

blog-post-41-content11

一番最初の部分では変数 ElapsedTimeに現在時間のカウント を行っています。
Tickノードの 引数DeltaTimeは『そのフレームでの経過時間』 で、それを毎フレーム加算しているというワケです。

blog-post-41-content12

そしてこの処理が敵の移動を行っている部分です。
『GetLocationAtTime』と『GetRotationAtTime』はスプラインが持つメソッド で、
『引数に指定された秒数でのスプライン上の位置』と『引数に指定された秒数でのスプライン上の向き』 を表しています。
ここで現在の秒数、つまり 変数ElapsedTimeを引数として使う ことで、現在いるべき位置と向きを取得しています。
なお、ここではワールド座標を取得したいので GetLocationAtTimeもGetRotationAtTimeもCoordinateSpaceをWorldに設定 しておきましょう。

最後に取得した位置と向きをセットしてやればOKです。
画像ではSetActorTransformでまとめて設定してますが、
個別にSetActorLocation・SetActorRotationを行っても問題ありません。


8.移動ロジックの追加(ループ処理の追加)

ただ、この状態でプレイした場合、確かに UFOはスプライン上を移動しますが、1周したところで止まってしまいます。
敵として扱うなら 1周したら2周・3周としてほしい ですよね?

ということでここからは 移動ロジックに周回させるための処理 を入れていきます。
まず、周回ができない原因 ですが、ズバリ画像のこの部分です。

blog-post-41-content13

『GetLocationAtTimeとGetRotationAtTimeにElapsedTimeを引数として使い現在位置・向きを取得できる』 と説明しましたが、
これはあくまで1周目だけの話です。2周目3周目となるとそうはいきません。

確かに上記メソッドは引数の時間における現在座標を返してくれますが、 この 引数の時間が1周にかかる時間、つまり『5』で設定したDurationTimeを上回ってしまう と ずっと同じ座標(ループの終点座標)を返し続けてしまうのです。

これを回避するため、『スプラインのDurationTimeをオーバーしたら経過時間をリセットする』 というロジックを追加します。

まずは float型の変数『LoopTime』 を用意します。
ここには 『1周にかかるループ時間』 を格納しますが、ここの値は自動で設定されるようにしていきましょう。

blog-post-41-content14

画像のように BeginPlayの移動モード変更処理の直後 にノードを追加します。

今度は 位置変更処理の直後を画像のように実装 します。
(ロジックはどちらもあとあと説明します)

blog-post-41-content15

このように実装を行ってからプレイをすれば、1周を終えたUFOが止まることなく2周目3周目とグルグル回る ようになるはずです。


※ロジック解説

blog-post-41-content17

BeginPlay直後の処理は 『設定されているスプラインを取得し、そのスプラインのDurationTimeをLoopTimeとして設定する』 というものです。 これで スプライン側のパラメータによって簡単にLoopTimeに変化 を持たせられるようになりました。

blog-post-41-content18

そしてこちらは 『ElapsedTimeがLoopTimeをオーバーしていないかチェックする処理』 です。 LoopTimeをElapsedTimeがオーバーしている(=1周が終わった)場合は即座にElapsedTimeをリセット しています。 ここで何周もスプライン上を移動できるようにしています。


9.ひとまず移動までできました

さて、これにて 空中エネミーの超簡単な移動ロジック を作ることができました。
ただ、これだけじゃただグルグル回るUFOを見るだけなので、
次回はもう少しゲーム風にするために UFOに攻撃が当たるようにしたり、
UFOの移動速度を変えられるようにしたい と思います。


オマケ.少しだけロジック部分の修正

説明のしやすさを優先したため、今回の実装は実際に使おうとした場合多少問題があります。 その代表例がここですね。

blog-post-41-content19

ここでもしもSplineが設定されていなかった場合、予期せぬエラーが発生する ことになります。 ということで、基本的には以下の画像のように リファレンスは検証済みゲットを行ってから実装に使うとよい でしょう。 検証済みゲットはリファレンスに対し右クリック→検証済みゲットとすることで可能 です。

blog-post-41-content20

今回以降は検証済みゲットもフルに使っていきます。