Technology Blog

技術ブログ

2020.04.13

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


引き続き進めていきます。
前回は空中を浮遊する敵をとりあえず動かすところまでやっていきました。
今回はもう少しゲームっぽく進めるために『敵へのダメージ処理』『ダメージ時の敵の挙動』を作っていきます。

とりあえず今回の目標としては
『弾がヒットすると体力が減る』
『体力が減るたびに移動速度が上がる』
『体力が減るとランダムで移動方向が変わる』
という風に敵が動作するようにします。

1.敵の移動速度を簡単に変更できるようにする

敵へのダメージ処理の実装をしていく…前に少しだけ前回のBPを手直ししていきます。

前回のロジックは『現在の時間を基にスプライン上の現在位置を取得する』といったものです。
ただ、あのロジックではエネミーの移動速度はスプラインのDurationに依存してしまい、
『エネミーのパラメータ操作のためにレベル上のスプラインを操作する』
という非常に気持ちが悪いことになってしまいます
ということで、時間のカウントを行わずにスプライン上を移動できるようにロジックを修正していきます。

まずは上記3つの画像のようにTick内での移動処理を変更してください。
この時、新しくfloat型の変数CurrentDistanceも追加しています。
こちらの変数についてはロジックと一緒に説明します。

プレイをしてみて前回と変わらずUFOがスプライン上をグルグル回れば成功です。
なお、前回使った変数Timeと変数LoopTimeは今後一切使わないので削除してしまって大丈夫です。

※ロジック説明

Tick直後のこちらではCurrentDistanceの更新を行っています。
スプラインにおけるDistanceとは『スプラインを一本の線として見た場合の位置』という意味です。
例えば『長さ5のスプラインのDistance3はちょうど真ん中』、といった感じです。
(説明がわかりづらかったらスミマセン)

ここでは引数DeltaSecondsに500を乗算した値を毎フレーム加算しています。
500は移動速度で、つまり『移動速度の分だけDistanceを進める』という処理ですね。
なお500は仮の移動速度として設定しているだけです。
(このあとすぐに正しい値をセットします)

続いてのSetActorTransformは一見前回と同じように見えますが、引数に入れる値が変わっています
ここで使っているのはGetLocationAtDistanceAlongSplineとGetRotationAtDistanceAlongSplineです。長々しい名前でややこしいですがこれは『そのスプライン上における引数Distanceの位置/向き』ですね。相変わらずワールド座標が欲しいのでCoordinateSpaceはWorldを設定してください。

そして最後に変数CurrentDistanceとGetSplineLengthの比較を行っています。
GetSplineLengthで返ってくる値は『そのスプラインの長さ』、つまりDistanceの最大値です。
『Distanceが最大値をオーバーしていれば0に戻す』、前回までのLoopTime関係と同じ処理ですね。

2.敵のダメージ処理(基本部分)

最初から少し脱線してしまいましたが、いよいよ今回の本題です。
ここからは『敵のダメージ処理』を創っていきます。
ひとまず敵の体力を表すためにint型変数のHealthを追加します。
とりあえず初期値は3にでもしておきましょう。

そしてHitイベントを追加し、画像のようにします。
なにかがヒットするたびに体力(Health)が減少し、0以下になったらデストロイするという簡単な処理ですね。
試しに何発か弾を当ててみて、3発当たったら消えるようになっていたらOKです。

3.敵のダメージ処理(対象の判定)

ただ、これだけだと今後何かしら別のものにヒットした時でも体力が減ってしまい、
何もしていなかったのに気が付いたらやられている…なんてことにもなりかねません。
というわけでヒットした対象がプレイヤーの弾であるかチェックできるようにしましょう。

FirstPersonProjectileというプレイヤーの弾用のBPを開き、Tagsに判別用のタグを設定しましょう。
今回は『PlayerProjectile』としました。
(FirstPersonProjectileはFPSテンプレートに最初からあるBPです)

その後、BP_FlyingEnemyのHitイベントの最初で画像のようにActorHasTagを使い、
『ヒットした対象がPlayerProjectileタグを持っているか』をチェックするようにします。

この状態でプレイして、引き続き3回ヒットでデストロイしたのならOKです。

4.移動速度変化の処理

『敵を撃ち』『倒す』という基礎中の基礎はこれでできあがったわけですが、これだけでは正直寂しいです。
ということで、『ダメージを受けたら移動速度が上がり、ランダムで移動方向も変わる』という処理を追加します。

まずはfloat型変数のDefaultMoveSpeedとint型変数のMaxHealthを追加したうえで、
BP_FlyingEnemyのBeginPlayの処理直後を画像のようにつなげます。
MaxHealthはレベルからいじれるようにパブリックにしておきましょう。
ここでMaxHealthの値でHealthを上書きしています。
また、DefaultMoveSpeedにゲーム開始時点のエネミー移動速度(MaxFlySpeed)を格納しています。
この値は今後必要な時に使っていきます。

そして画像のような関数GetCurrentMoveSpeedを作成します。
『現在の移動速度を返す』ための関数であり、float型を返す純粋関数にしておきましょう。
ここではMaxHealth/Healthの結果をDefaultMoveSpeedに乗算しています。
つまり『現在の体力が最大値から離れるほど移動速度が上昇する』という実装になっています。

では作成した関数GetCurrentMoveSpeedを今まで仮の移動速度を直接設定していた箇所
つまりTickの一番最初に繋げます。
この状態でプレイしてみると、敵が被弾するたびに目に見えて移動速度が上がっていきます。
これにて体力減少による移動速度変化が実装できました。

5.移動方向変化の処理(前準備まで)

ここからは移動方向を変える処理について実装していきます。
まぁ『移動方向を変える』といってもこのエネミーは必ずスプライン上を添うように移動するため、
『今とは逆方向に移動させる』というだけなのですが。

さて、まずはbool型の変数IsOrderMoveを追加します。
この変数は『現在スプラインを順路で移動しているか』という意味です。
この値がTrueならば今まで通りの方向へ、Falseならば逆方向へと進ませます。

そして、ダメージ用のイベントとしてOnHitDamageを追加し、画像のようにします。
新規イベントはBP上で右クリック→カスタムイベントの追加で作成できます。

一番最初にGetCurrentMoveSpeedの結果をMaxFlySpeedに設定していますが、
コレは今回は特に役に立ちません。今後の実装に必要になる箇所なので今のうちにやってます。
今回重要なのはそのあとのIsOrderMoveの変化ですね。
『TrueかFalseのどちらかをランダムで返す』関数のRandomBoolの値をそのまま入れてます。

これを被ダメージ時にHitイベントでDestroyに入らなかった場合に実行できるようにつなげます。

これでダメージ時にランダムでIsOrderMoveが変わるようになりました。
(ただし、今プレイしても逆方向にはまだ動きません)

6.移動方向変化処理(本番)

さて、移動方向の変化処理はここからが本番です。
これまでの作ってきた部分を一気に変えていきましょう。
(ロジックは最後にまとめて説明します)

まず最初の修正箇所はTickの頭部分です。
画像のように修正しましょう。

次の修正箇所はTickの終わり部分
移動のループを行っている箇所です。
ここも画像のように修正しちゃってください。

上記2か所を修正してからプレイを開始してみます。
敵が被弾した後、まれに逆方向に飛んでいくようになります。

※ロジック説明

まずCurrentDistanceの更新を行っている箇所ですが、
ここではIsOrderMoveを使ってCurrentDistanceを上書きする値を変えています
IsOrderMoveがTrueの時はCurrentDistanceに移動量を加算し、逆にFalseの時は減算します。
こうすることでDistanceを戻す(=逆走する)ことを可能にしています。

そして最後のループ処理の箇所では逆走時にもループできるように対応しています。
これまではCurrentDistanceが最大値になったときのみリセットを行っていましたが、
今後はCurrentDistanceが0以下になったときにもリセットを行います。
ここで注意なのはリセット後の値で、
加算中のリセット(最大値に到達)の場合は0、減算中のリセット(0に到達)の場合は最大値に上書きします。

7.移動方向変化処理(向き編)

確かにこれで逆方向にも敵は飛べるようになりました。
ただ見た目は明らかにおかしいですよね?
逆向きに進んでいる時は完全に背を向けたまま進んでしまっています。
当然、現在進んでいる方向に向いてくれないと違和感がありますので、そちらの修正です。
(所謂エイム中などはその限りではないが、今回は置いておく)

修正すべき箇所は移動のSetActorTransformのRotationの値の取得箇所です。
これまで使用していたGetRotationAtDistanceAlongSplineは、
そのDistance時点での向きを取得できますが、あくまで順路で進むこと前提の向きなので、
今回のように逆走をしようとするとおかしくなってしまうのです。

ということで、画像のように修正。
ロジックについてはおいおい説明しますが、
このように変更することで問題なく逆走時に正しい方向を向くようになります。

※ロジック説明

ここでは現在の位置と1秒後の位置を取得し、FindLookAtRotationで向くべき方向を取得しています。
FindLookAtRotationは『StartからTargetへの方向』を取得できます。
1秒後の位置はCurrentDistanceに1秒間の移動量をそのまま加算/減算した結果
GetLocationAtDistanceAlongSplineで使用して取得しています。

これにて、そこそこ敵がゲームらしい動きをするようになったと思います。
次回からはもう少しゲームらしさを上げるために『敵の攻撃』を作っていこうと思います。


関連ブログ