Technology Blog
技術ブログ
2020.05.19
UE4で1人称視点の3DSTGを作ってみる(第四回)
お久しぶりです、今回も進めていきましょう。
前回までの実装で『敵を配置して動かす』『敵に攻撃させる』『敵を倒す』といった部分を創っていきました。
今回はこれまでに比べるとやや地味ですが、『敵の出現処理』を創っていきましょう。要約すると『敵のスポーン処理』ということです。
今までは敵を直接レベルに配置して手動でパラメータなどを設定していましたが、これではレベルデザイナーさんに不親切ですよね?
(この記事は一人で書いているのであまり関係ないかもしれませんが、いると仮定しましょう)
簡単なスポーンシステムを実装することにより、レベルデザイナーさんなどをはじめ、様々な人がエネミーの配置をできるようになるハズです。
とりあえず今回作るスポーンシステムですが
『スポーン用のアクタを配置する』
『スポーン用アクタには各種パラメータを設定できるようにする』
『敵はスプラインの途中からもスポーンできるようにする』
『スポーンした時点での移動方向を指定できるようにする』
『敵が1体死ぬごとに次の敵をスポーンする』
という仕様で進めていきます。
1.移動方向を指定する列挙型を作成
スポーン用アクタを創るところから始めていきたいところですが、
現時点での敵の実装はあまりその仕様に適していません。
ということでひとまず前回までで作った敵を修正していきます。
まずはその前に『スポーン時点での移動方向』を表す列挙型を作成しましょう。
コンテンツブラウザの新規追加→ブループリント→列挙型
と選択すれば新しい列挙型を作成できます。
今回はこの列挙型に『Enum_MoveDirection』と名付けています。
Enum_MoveDirectionを開くと列挙型の中身を編集できます。
画像のように3つの値を用意しましょう。
それぞれの値は
—–
Random…スポーン時に順路/逆路のどちらへ移動かランダムで決める
Forward…スポーン時点では必ず順路で移動する
Backward…スポーン時点では必ず逆路で移動する
—–
という意味となっています。
なお、『説明』と『EnumDescription』はプログラム実行上では何の意味もありませんが、あった方があとで実装内容を確認するときに苦労しなくて済みます。
2.敵の初期化処理を実装
ここからは敵のBP(BP_FlyingEnemy)を触ります。
まず、『クラスのデフォルト』を選択し、詳細ウィンドウに出てきた『StartWithTickEnabled』をfalseにします。
『StartWithTickEnabled』は『スポーンした時点からTickの処理を実行するか』というパラメータです。コレをfalseにすることにより、BP_FlyingEnemyは手動でTickを有効化するまでTick処理が実行されなくなります。
続いては『Initialize』という関数を作成します。
この関数はその名の通り、エネミーの初期化に使用します。
画像のように実装してください。
なお引数は下記の4つ、返り値は必要ありません。
—–
UseSpline…BP_EnemyMoveSpline、移動に使用するスプライン
MoveDirection…Enum_MoveDirection、最初の移動方向
Health…int型、エネミーの体力
StartSplinePoint…int型、スポーンするスプラインポイントの番号
—–
Initializeの実装が終わったら『イベントグラフ』に戻り、BeginPlayから続く処理を全て削除します。
(ここの処理はInitializeと同じため、今後は不要になりました)
※ロジック解説(Initialize)
最初の処理は先ほど削除したBeginPlay以降の処理と全く同じです。つまり『移動モードの変更』『最大体力・移動速度の設定』ですね。
移動速度の設定の後は移動に使用する変数UseSplineを引数の物で上書きしています。こうすることでInitializeを行った箇所から移動に使用するスプラインを設定できるようにしています。
変数IsOrderMoveを設定している箇所ではEnum_MoveDirectionによる選択(Select)を行っています。引数MoveDirectionからノードを引っ張り『Select』と入力すればワイルドカードのノードを作成できるはずです。
SelectノードではIndexに指定された値(今回はMoveDirection)に応じて異なる値を返すことができます。画像のようにした場合は『Random時はRandomBoolの結果、ForwardならTrue、Backwardならfalse』を返します。こうすることでスポーン時の移動方向を設定しているわけです。
変数IsOrderMoveの設定後はSequenceノードに入ります。
Then0以降の処理では体力の設定をしています。
体力の設定自体はInitializeに入ってすぐに行っていましたが、ここでは『もしも引数Healthに何かしらの値が設定されている場合はその値で上書きする』という処理にしています。
Then1から先はちょっと難しいですが、ここでは『エネミーの初期位置の設定と初期のスプライン上のDistanceの取得』を行っています。
ここで変数CurrentDistanceに入っているのは『GetDistanceAlongSplineAtSplinePoint』の結果ですが、このノードは『指定されたスプラインポイントがスプライン上のどの位置にあるかをDistanceで返す』という処理です。ちなみにスプラインポイントとはスプラインの生成時に追加した画像のようなスプライン上の白い点を指します。
更に指定されているスプラインポイントを基にした『GetLocationAtSplinePoint』が返すロケーションにSetActorTransformで移動しています。ここまでで『スプラインポイントの位置から移動を開始する』という処理になっています。
そして、最後に『SetActorTickEnabled』を行っています。こちらは『Tickを有効化/無効化する』というノードで、コレを使うことにより、初期化済みのエネミーだけがTick処理を行うようにしています。
なお、ややこしいですがThen0まで伸びているint型は引数Health、Then1まで伸びているint型は引数StartSplinePointです。
ちなみに、これまで編集可能にしていた変数UseSplineと変数MaxHealthは今後Initializeから設定するようになったため、編集不可能な設定にしておきます。
(気にならないのであればそのままでも大丈夫です)
3.スポーンパラメータ用の構造体を作成する
まだ本題には入りません。
今度はスポーン時のパラメータを設定するための構造体を用意しましょう。
コンテンツブラウザの新規追加→ブループリント→構造体
と選択することで新しい構造体を作成できます。
構造体の名前は『Struct_SpawnParameter』にします。
Struct_SpawnParameterを開き、構造体の変数を追加していきます。
今回は画像のようにしてください。
それぞれの変数の用途は以下の通りです。
—–
EnemyClass…BP_FlyingEnemy、スポーンさせるエネミーのクラス
UseSpline…BP_EnemyMoveSpline、移動に使うスプライン
SplinePointIndex…int型、スポーンするスプラインポイント番号
MoveDirection…Enum_MoveDirection、スポーン時点での移動方向
EnemyHealth…int型、エネミーの体力
—–
なお、EnemyClassにはクラスを指定できるようにしています。
変数にクラスを指定するためには、変数の型設定の際にリストから『Class Reference』を選択すれば大丈夫です。
4.スポーン用マネージャを実装する(変数の追加まで)
ここからが今回の本題です。
エネミーをスポーンさせるためのマネージャを作っていきます。
Actorを継承して実装していきます。
クラス名は『BP_SpawnManager』とでもしておきましょう。
クラスの作成後は変数を用意していきます。
今のところは以下の2つだけあれば大丈夫です。
—–
SpawnParameter…Struct_SpawnParameterの配列、スポーンパラメータのリスト、編集可能
CurrentIndex…int型、現在何番目のエネミーまでスポーンしたか
—–
基本的にレベル上に配置したスポーンマネージャのSpawnParameterに各種スポーン情報を設定し、それに合わせて随時エネミーをスポーンしていくといった使い方をします。
SpawnParameterのみ外部から変更するパラメータなので、編集可能に設定しておきましょう。また、変数を配列にする場合は『変数の型』の横にあるアイコンをクリックすると出てくるメニューから画像のアイコンを選択すればOKです。
5.スポーン用マネージャを実装する(スポーン関数の実装)
スポーン用の関数として新しく『SpawnWithAdvanceNumber』という関数を作成し、bool型のローカル変数SpawnSuccessと同じくbool型ローカル変数SpawnAllMemberを追加します。ローカル変数は関数を開いている時のみ追加が可能です。
画像のようにノードを繋いでください。
これは『エネミーのスポーンを行いつつ、現在のスポーン番号を一つずらす』という実装です。
返り値は以下の通りです。
—–
SpawnedEnemy…BP_FlyingEnemy、スポーンしたエネミー
Success…bool型、スポーンに成功したか否か
AllMember…bool型、SpawnParameterに設定されているエネミーが全てスポーンしたか
—–
※ロジック説明
関数に入った直後にまずSpawnParameterのCurrentIndex番目のスポーン情報を取得しています。この結果をブレイク(分解)し、以降の処理に使っていってます。
その後はメインとなるエネミーのスポーン処理を行っています。
スポーンさせるクラスには先ほど取得したEnemyClass、スポーン位置には『GetLocationAndTangentAtSplinePoint』の結果を設定しています。『GetLocationAndTangentAtSplinePoint』はPointIndexに指定されたスプラインポイントの座標を取得する際に使います。今回はPointIndexにSpawnParameterのSplinePointIndexを設定しています。
スポーンが終わったらSequenceノードに入っています。
Then0以降の処理ではスポーンが正しく成功したか否かを『IsValid』ノードでチェック後、ローカル変数SpawnSuccessをTrueにしています。ここ以降の処理はスポーンに成功していないと行われません。
その後は『2』で追加したエネミーの初期化関数Initializeを実行しています。引数に指定されるパラメータは全てSpawnParameterから取得したものです。ここでスポーンしたエネミーの初期化を行っているわけです。
エネミーの初期化後はエネミーのスポーン番号、つまり変数CurrentIndexを加算しています。こうすることで次にスポーンされるエネミーを一つ先の対象にしています。変数CurrentIndexがSpawnParameterのLengthをオーバー、つまりSpawnParameterに設定されている全てのエネミーのスポーンができた場合は、変数CurrentIndexを0にリセットしたうえでローカル変数SpawnAllMemberをTrueにしています。
Then1以降は単なるリターンノードで、返り値を返しているだけです。スポーンさせたエネミーとここまでローカル変数だった2つのパラメータを設定しましょう。
6.スポーン用マネージャを実装する(イベントグラフ)
『エネミーをスポーンする関数』そのものはこれで完成ですが、これだけではまだ動作しません。関数を実行する箇所が必要になります。
ということで最後にイベントグラフをいじっていきます。
まずは画像のようにBeginPlayから関数SpawnWithAdvanceNumberを繋ぎます。これで最初の1体はスポーンできるようになります。
しかし、これだけでは2回目以降のスポーンができません。エネミーの死亡に反応して次のスポーンを行う作りにする必要があります。
ということで今度は画像のようにします。スポーンに成功している場合、スポーンさせたエネミーのOnDestroyedに新たなイベントをバインドさせています。OnDestroyedは対象のデストロイ時に呼び出されるイベントディスパッチャーですので、バインド先のイベントはエネミーの体力が0になるたびに呼び出されることとなります。
バインド先のイベントは今回『OnEnemyDestroy』と名付けました。バインド元のノードから『カスタムイベントの作成』を行うと最初から必要なパラメータが引数に設定されるので便利です。
OnEnemyDestroyイベント内での処理は見ての通りBeginPlayと全く同じ内容となっています。エネミーが死亡したら新たなエネミーをスポーン&バインド、そのエネミーが死亡したらまた新たなエネミーを…という無限ループになります。
7.レベルへの配置と設定
さて、ここまでで今回の実装は完全に完了ですが、
最後にレベル上で動作確認をしましょう。
BP_SpawnManagerをレベル上にドラッグ&ドロップをして配置します。配置する場所はどこでも大丈夫です。
そして、SpawnParameterにスポーンさせたい分だけスポーン情報を設定します。今回は画像のようにしました。なお、以前に配置したエネミーは消しておきましょう。
この状態でプレイしてみると、敵を1体倒すごとに新たな敵が異なる位置にスポーンし、3・6・9番目に出てくる敵のみ3回攻撃しないと倒せなくなっているはずです。
そこまで行けていれば、今回の実装は全てOKです。
敵がスポーンできずにエラーを吐かれた場合はスプラインポイントが壁や床に埋まっていないか確認してください。
さて、これにて今回の実装は完了です。
次回はゲームにはほぼ必須ともいえるスコア関係の処理を作っていきましょう。