2020/05/19

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

blog-post-44-content

お久しぶりです、今回も進めていきましょう。 前回までの実装で『敵を配置して動かす』『敵に攻撃させる』『敵を倒す』といった部分を創っていきました。

今回はこれまでに比べるとやや地味ですが、『敵の出現処理』 を創っていきましょう。要約すると 『敵のスポーン処理』 ということです。

今までは敵を直接レベルに配置して手動でパラメータなどを設定していましたが、これではレベルデザイナーさんに不親切ですよね? (この記事は一人で書いているのであまり関係ないかもしれませんが、いると仮定しましょう)

簡単なスポーンシステムを実装することにより、レベルデザイナーさんなどをはじめ、様々な人がエネミーの配置をできるようになるハズです。

とりあえず今回作るスポーンシステムですが
『スポーン用のアクタを配置する』
『スポーン用アクタには各種パラメータを設定できるようにする』
『敵はスプラインの途中からもスポーンできるようにする』
『スポーンした時点での移動方向を指定できるようにする』
『敵が1体死ぬごとに次の敵をスポーンする』
という仕様で進めていきます。


1.移動方向を指定する列挙型を作成

スポーン用アクタを創るところから始めていきたいところですが、
現時点での敵の実装はあまりその仕様に適していません。
ということでひとまず 前回までで作った敵を修正 していきます。

まずはその前に 『スポーン時点での移動方向』を表す列挙型 を作成しましょう。

blog-post-44-content1

コンテンツブラウザの新規追加→ブループリント→列挙型
と選択すれば新しい列挙型を作成できます。
今回はこの列挙型に『Enum_MoveDirection』と名付けています。

blog-post-44-content2

Enum_MoveDirectionを開くと列挙型の中身を編集できます。
画像のように3つの値を用意しましょう。

それぞれの値は

—–
Random…スポーン時に順路/逆路のどちらへ移動かランダムで決める
Forward…スポーン時点では必ず順路で移動する
Backward…スポーン時点では必ず逆路で移動する
—–

という意味となっています。

なお、『説明』と『EnumDescription』はプログラム実行上では何の意味もありませんが、
あった方があとで実装内容を確認するときに苦労しなくて済みます。


2.敵の初期化処理を実装

ここからは敵のBP(BP_FlyingEnemy)を触ります。

blog-post-44-content3

まず、『クラスのデフォルト』を選択し、詳細ウィンドウに出てきた 『StartWithTickEnabled』をfalseにします。

『StartWithTickEnabled』は『スポーンした時点からTickの処理を実行するか』というパラメータ です。コレをfalseにすることにより、BP_FlyingEnemyは手動でTickを有効化するまでTick処理が実行されなくなります。

続いては 『Initialize』という関数を作成 します。 この関数はその名の通り、エネミーの初期化に使用します。

blog-post-44-content4

blog-post-44-content5

blog-post-44-content6

画像のように実装してください。
なお引数は下記の4つ、返り値は必要ありません。

—–
UseSpline…BP_EnemyMoveSpline、移動に使用するスプライン
MoveDirection…Enum_MoveDirection、最初の移動方向
Health…int型、エネミーの体力
StartSplinePoint…int型、スポーンするスプラインポイントの番号
—–

blog-post-44-content7

Initializeの実装が終わったら 『イベントグラフ』に戻り、BeginPlayから続く処理を全て削除 します。 (ここの処理はInitializeと同じため、今後は不要になりました)


※ロジック解説(Initialize)

blog-post-44-content8

最初の処理は先ほど削除したBeginPlay以降の処理と全く同じです。つまり『移動モードの変更』『最大体力・移動速度の設定』 ですね。

blog-post-44-content9

移動速度の設定の後は移動に使用する変数UseSplineを引数の物で上書きしています。こうすることで Initializeを行った箇所から移動に使用するスプラインを設定できる ようにしています。

変数IsOrderMoveを設定している箇所では Enum_MoveDirectionによる選択(Select) を行っています。 引数MoveDirectionからノードを引っ張り『Select』と入力すればワイルドカードのノードを作成できる はずです。

SelectノードではIndexに指定された値(今回はMoveDirection)に応じて異なる値を返す ことができます。画像のようにした場合は『Random時はRandomBoolの結果、ForwardならTrue、Backwardならfalse』を返します。こうすることで スポーン時の移動方向を設定 しているわけです。

blog-post-44-content10

変数IsOrderMoveの設定後はSequenceノードに入ります。 Then0以降の処理では体力の設定をしています。 体力の設定自体はInitializeに入ってすぐに行っていましたが、ここでは 『もしも引数Healthに何かしらの値が設定されている場合はその値で上書きする』という処理にしています。

Then1から先はちょっと難しいですが、ここでは 『エネミーの初期位置の設定と初期のスプライン上のDistanceの取得』 を行っています。

blog-post-44-content11

ここで変数CurrentDistanceに入っているのは『GetDistanceAlongSplineAtSplinePoint』の結果ですが、このノードは 『指定されたスプラインポイントがスプライン上のどの位置にあるかをDistanceで返す』という処理 です。ちなみにスプラインポイントとはスプラインの生成時に追加した 画像のようなスプライン上の白い点 を指します。

更に指定されているスプラインポイントを基にした 『GetLocationAtSplinePoint』 が返すロケーションにSetActorTransformで移動しています。ここまでで 『スプラインポイントの位置から移動を開始する』という処理 になっています。

そして、最後に『SetActorTickEnabled』を行っています。こちらは 『Tickを有効化/無効化する』というノード で、コレを使うことにより、初期化済みのエネミーだけがTick処理を行う ようにしています。

なお、ややこしいですがThen0まで伸びているint型は引数Health、Then1まで伸びているint型は引数StartSplinePointです。

ちなみに、これまで編集可能にしていた変数UseSplineと変数MaxHealthは今後Initializeから設定するようになったため、編集不可能な設定にしておきます。 (気にならないのであればそのままでも大丈夫です)


3.スポーンパラメータ用の構造体を作成する

まだ本題には入りません。 今度は スポーン時のパラメータを設定するための構造体 を用意しましょう。

blog-post-44-content12

コンテンツブラウザの新規追加→ブループリント→構造体 と選択することで新しい構造体を作成できます。 構造体の名前は『Struct_SpawnParameter』 にします。

blog-post-44-content13

Struct_SpawnParameterを開き、構造体の変数を追加していきます。
今回は画像のようにしてください。
それぞれの変数の用途は以下の通りです。

—–
EnemyClass…BP_FlyingEnemy、スポーンさせるエネミーのクラス
UseSpline…BP_EnemyMoveSpline、移動に使うスプライン
SplinePointIndex…int型、スポーンするスプラインポイント番号
MoveDirection…Enum_MoveDirection、スポーン時点での移動方向
EnemyHealth…int型、エネミーの体力
—–

blog-post-44-content14

なお、EnemyClassにはクラスを指定できるようにしています。 変数にクラスを指定するためには、変数の型設定の際にリストから『Class Reference』を選択 すれば大丈夫です。


4.スポーン用マネージャを実装する(変数の追加まで)

ここからが今回の本題です。
エネミーをスポーンさせるためのマネージャを作っていきます。
Actorを継承して実装 していきます。
クラス名は『BP_SpawnManager』とでもしておきましょう。

クラスの作成後は変数を用意していきます。
今のところは以下の2つだけあれば大丈夫です。

blog-post-44-content15

—– SpawnParameter…Struct_SpawnParameterの配列、スポーンパラメータのリスト、編集可能
CurrentIndex…int型、現在何番目のエネミーまでスポーンしたか
—–

基本的にレ ベル上に配置したスポーンマネージャのSpawnParameterに各種スポーン情報を設定し、それに合わせて随時エネミーをスポーンしていく といった使い方をします。

blog-post-44-content16

SpawnParameterのみ外部から変更するパラメータなので、編集可能に設定しておきましょう。また、変数を配列にする場合は『変数の型』の横にあるアイコンをクリックすると出てくるメニューから画像のアイコンを選択 すればOKです。


5.スポーン用マネージャを実装する(スポーン関数の実装)

blog-post-44-content17

スポーン用の関数として新しく『SpawnWithAdvanceNumber』という関数を作成し、bool型のローカル変数SpawnSuccessと同じくbool型 ローカル変数SpawnAllMemberを追加します。ローカル変数は関数を開いている時のみ追加が可能 です。

blog-post-44-content18

blog-post-44-content19

blog-post-44-content20

画像のようにノードを繋いでください。

これは 『エネミーのスポーンを行いつつ、現在のスポーン番号を一つずらす』 という実装です。 返り値は以下の通りです。

blog-post-44-content21

—–
SpawnedEnemy…BP_FlyingEnemy、スポーンしたエネミー
Success…bool型、スポーンに成功したか否か
AllMember…bool型、SpawnParameterに設定されているエネミーが全てスポーンしたか
—–


※ロジック説明

blog-post-44-content22

関数に入った直後にまずSpawnParameterのCurrentIndex番目のスポーン情報を取得しています。この結果をブレイク(分解)し、以降の処理に使っていってます。

その後はメインとなるエネミーのスポーン処理を行っています。 スポーンさせるクラスには先ほど取得したEnemyClass、スポーン位置には『GetLocationAndTangentAtSplinePoint』の結果を設定しています。『GetLocationAndTangentAtSplinePoint』はPointIndexに指定されたスプラインポイントの座標を取得する 際に使います。今回はPointIndexにSpawnParameterのSplinePointIndexを設定しています。

blog-post-44-content23

スポーンが終わったらSequenceノードに入っています。
Then0以降の処理では スポーンが正しく成功したか否かを『IsValid』ノードでチェック 後、ローカル変数SpawnSuccessをTrueにしています。 ここ以降の処理はスポーンに成功していないと行われません。

その後は『2』で追加した エネミーの初期化関数Initializeを実行 しています。 引数に指定されるパラメータは全てSpawnParameterから取得したもの です。ここで スポーンしたエネミーの初期化 を行っているわけです。

blog-post-44-content24

エネミーの初期化後は エネミーのスポーン番号、つまり変数CurrentIndexを加算 しています。こうすることで 次にスポーンされるエネミーを一つ先の対象 にしています。変数CurrentIndexがSpawnParameterのLengthをオーバー、つまりSpawnParameterに設定されている全てのエネミーのスポーンができた場合は、変数CurrentIndexを0にリセットしたうえでローカル変数SpawnAllMemberをTrueにしています。

blog-post-44-content25

Then1以降は単なるリターンノードで、返り値を返しているだけです。 スポーンさせたエネミーとここまでローカル変数だった2つのパラメータを設定 しましょう。


6.スポーン用マネージャを実装する(イベントグラフ)

『エネミーをスポーンする関数』そのものはこれで完成ですが、これだけではまだ動作しません。関数を実行する箇所が必要になります。

ということで最後にイベントグラフをいじっていきます。

blog-post-44-content26

まずは画像のようにBeginPlayから関数SpawnWithAdvanceNumberを繋ぎます。これで 最初の1体はスポーンできる ようになります。

しかし、 これだけでは2回目以降のスポーンができません。 エネミーの死亡に反応して次のスポーンを行う作りにする必要があります。

blog-post-44-content27

ということで今度は画像のようにします。スポーンに成功している場合、スポーンさせたエネミーのOnDestroyedに新たなイベントをバインドさせています。OnDestroyedは対象のデストロイ時に呼び出されるイベントディスパッチャーですので、バインド先のイベントはエネミーの体力が0になるたびに呼び出される こととなります。

バインド先のイベントは今回『OnEnemyDestroy』と名付けました。バインド元のノードから『カスタムイベントの作成』を行うと最初から必要なパラメータが引数に設定される ので便利です。

OnEnemyDestroyイベント内での処理は見ての通りBeginPlayと全く同じ内容となっています。エネミーが死亡したら新たなエネミーをスポーン&バインド、そのエネミーが死亡したらまた新たなエネミーを…という無限ループになります。


7.レベルへの配置と設定

さて、ここまでで今回の実装は完全に完了ですが、
最後に レベル上で動作確認 をしましょう。

blog-post-44-content28

BP_SpawnManagerをレベル上にドラッグ&ドロップをして配置します。配置する場所はどこでも大丈夫です。

blog-post-44-content29

そして、SpawnParameterにスポーンさせたい分だけスポーン情報を設定 します。今回は画像のようにしました。なお、以前に配置したエネミーは消しておきましょう。

この状態でプレイしてみると、敵を1体倒すごとに新たな敵が異なる位置にスポーンし、3・6・9番目に出てくる敵のみ3回攻撃しないと倒せなくなっているはず です。

そこまで行けていれば、今回の実装は全てOKです。 敵がスポーンできずにエラーを吐かれた場合はスプラインポイントが壁や床に埋まっていないか確認してください。

さて、これにて今回の実装は完了です。
次回はゲームにはほぼ必須ともいえる スコア関係の処理 を作っていきましょう。