2020/04/13

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

blog-post-42-content

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

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


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

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

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

blog-post-42-content1

blog-post-42-content2

blog-post-42-content3

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

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


※ロジック説明

blog-post-42-content4

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

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

blog-post-42-content5

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

blog-post-42-content6

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


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

blog-post-42-content7

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

blog-post-42-content8

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


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

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

blog-post-42-content9

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

blog-post-42-content10

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

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


4.移動速度変化の処理

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

blog-post-42-content11

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

blog-post-42-content12

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

blog-post-42-content13

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


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

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

blog-post-42-content14

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

blog-post-42-content15

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

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

blog-post-42-content16

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

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


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

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

blog-post-42-content17

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

blog-post-42-content18

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

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


※ロジック説明

blog-post-42-content19

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

blog-post-42-content20

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


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

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

blog-post-42-content21

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

blog-post-42-content22

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


※ロジック説明

blog-post-42-content23

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

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