PavilionDV7の雑多なやつ

Qiitaから移行しました。UE4に関する記事から興味のあることまで色々書きます。

【UE4】第13回ぷちコン応募作品を解説する

はじめに

いまさらですが2020年4月に公開した第13回ぷちコン応募作品の解説をします。ただし「どういう面白さを狙ったのか」といったゲーム性についての解説はせず、ゲームの仕組みについて解説となります。

応募した作品

https://www.youtube.com/watch?v=RmTpK6tRiZE&feature=emb_title

プロジェクトはこちら

使用バージョン:UE4.24.3

https://1drv.ms/u/s!Au-8FqgREBKZilmreWACqwg8CWWL

注意

自分はエディタを英語設定で使っているため紹介するプロパティは全て英語となります。

プレイヤー

カメラは固定のままカーソル方向へ向く

カメラは常にプレイヤーの右斜め上から見下ろす形にしたかったためいくつかのプロパティを変更しました。これは言い訳ですが、ここの回転についての設定はいつも混乱するため「カメラは固定のままカーソル方向へ向く」ためには不要な設定も含まれていると思います。

  • プレイヤー側
    • Use Controller Rotation Yaw をオフ
    • Use Controller Desired Rotation をオン
  • Character Movement Component側
    • Orient Rotation to Movement をオン
  • Spring Arm Component側
    • Inherit Pitch, Yaw, Roll をオフ

挙げた設定の中で大切なものが Spring Arm Component 側の Inherit Pitch, Yaw, Roll のオフ設定です。これらをオフにすることでプレイヤーの回転に合わせてカメラが回転することを防げます。挙げてはいませんが Spring Arm Component の Do Collision Test についてもオフとしています。

f:id:PavilionDV7:20200508214604p:plain


実際のプレイヤーの回転は BP_PlayerController にて実装されています。

f:id:PavilionDV7:20200508214609p:plain

Get Hit Result Under Cursor by Channel Controllerクラスで呼び出せる処理なので使用する際は注意してください。

ダッシュ移動

ダッシュ移動は Launch Character を呼び出すことで実装しています。

f:id:PavilionDV7:20200508214612p:plain

ただし Launch Character は Charcter クラスでのみ呼び出せるので Pawn クラスからプレイヤーを作成した場合はこの方法は使えません。

攻撃

攻撃に必要な「攻撃時にコリジョンを有効化」「衝突検知」「与ダメージ」といった処理は全て BP_Weapon クラスが担当します。プレイヤーは武器に対して「攻撃アニメーションを再生して!」と指示を出すだけで攻撃処理は完了します。

f:id:PavilionDV7:20200508214617p:plain

武器の使用についてはエネミー側も共通です。

武器攻撃の流れ

武器に対しアニメーション再生を指示したあと処理がどのように流れているかを見ていきます。

プレイヤー攻撃時に再生される Anim Montage は PunchCombo_Player_Montage です。開くといくつかのセクションと大量の Anim Notify が設定されています。

f:id:PavilionDV7:20200508214622p:plain

特に攻撃時に重要なものが NS_WeaponAttack です。(「NS」は Notify State の略) NS_WeaponAttack は攻撃アニメーション中で「攻撃ヒット判定を有効化する範囲」を指定するためにあります。中身は Notify State の開始通知イベント「Received_NotifyBegin」、Notify State の終了通知イベント「Received_NotifyEnd」をオーバーライドしています。

Received_NotifyBegin や Received_NotifyEnd ではモンタージュの再生者を取得し現在所持する武器を取得、攻撃開始を意味する「Begin Weapon Attack」、攻撃終了を意味する「End Weapon Attack」を呼んでいます。(Received_NotifyBegin でCurrent WeaponのIs Validチェックが抜けていますが、これは自分がサボったためです)

武器クラスの Begin Weapon Attack と Begin Weapon End は以下の通りになります。

f:id:PavilionDV7:20200508214626p:plain


武器攻撃の流れをまとめると以下の通りになります。

f:id:PavilionDV7:20200508214631p:plain

敵キャラクター

継承関係

敵キャラクターの継承関係は以下の通りになります。

f:id:PavilionDV7:20200508214634p:plain

BP_Enemy_ZAKOを継承したLV1、LV2がありますがこれらは処理そのものの違いは全くありません。体力値や体力バーのカラーが異なるだけです。

BP_Enemy_ZAKO と BP_Enemy_ZAKO_LV1 との関係ははっきり言って失敗です。UE4にはパラメータをまとめた DataTable というアセットがあるので、パラメータが異なるだけならDataTableを用意してスポーン時に参照するDataTableの行を指定すれば済む話でした。

[UE4] CSVデータを扱う方法 DataTable編|株式会社ヒストリア

ザコ敵BehaviorTree

BT_Enemy_ZAKO_LV1 を例に取ります。

プレイヤーの周りをウロチョロする

赤文字で書かれている要素がスコアが高くなる要因となります。

敵が固まって動かないよう味方の移動先から離れた位置のスコアを高くし、最適な位置から動かなくなるのを防ぐため自身の移動先から離れた位置のスコアを高くしています。Dot を使うことでプレイヤーの目の前や真横を通って移動せず自身のすぐ左手側、右手側に移動するように制御しています。

f:id:PavilionDV7:20200508214637p:plain

これらは以下の資料を参考にしました。

初心者がEQSやってみた

スクウェア・エニックスにおける UNREAL ENGINE 4 を用いた人工知能技術の開発事例


ザコ敵のバリエーションの数だけ Behavior Tree アセットが存在していますがこれは、攻撃の頻度を調整するために Cooldown ノードや TimeLimit ノードを使用しているのですが秒数については Blackboard のプロパティによる指定が出来なかったためです。

複数の敵が同時に攻撃しないようにする

Blackboard に定義された変数の中で CurrentAttackingEnemy というものがあります。これは 「私は今プレイヤーを攻撃しています!」 を表す枠のようなものです。敵自身が攻撃可能になると CurrentAttackingEnemy を参照し攻撃可能枠が残っているか確認します。枠が残っていれば先に処理を進め「Increase」と書かれているノードで使用枠数を1つ増やします。その後いくつかの処理を経て「Decrease」と書かれているノードで枠を返却。つまり使用枠数を1つ減らします。

f:id:PavilionDV7:20200508214642p:plain


通常であれば Blackboard はインスタンスごとに独立のため敵キャラクターAが Blackboard の変数を書き換えても敵キャラクターBの変数には一切の影響がありません。しかし CurrentAttackingEnemy というのは同一の Blackboard を利用する敵全体で共有されます。

Blackboard の変数を共有化するには定義した変数の Instance Synced にチェックを入れるだけで完了します。

f:id:PavilionDV7:20200508214645p:plain

Instance Synced オプションはあまり弄る機会は無いと思いますが覚えておいて損はないと思います。

ボス敵

BP_Enemy_Boss1 の中身で最も特徴的なものが特殊攻撃についての処理です。

特殊攻撃は Behavior Tree から Special Attack イベントが呼ばれることで発動します。このときに列挙体で最大で3つの特殊攻撃の内、どれを発動するかを指定します。

特殊攻撃その1 - 突進攻撃

突進攻撃は予兆モーション、突進モーション(ポーズ)、突進後モーションの3つに別れており、それらを1つの Anim Montage にまとめています。

f:id:PavilionDV7:20200508214648p:plain

PunchCombo と同様に各種通知が突進攻撃の要です。

少し攻撃を避けづらくするために NS_FocusTarget の通知開始イベントで AI Controller の Set Focus ノードを使ってプレイヤーキャラクターの方向を常に見るようにします。

NS_MoveForward は通知が呼ばれている間、 Add Actor World Offset で向いている方向に強制移動します。この通知と NS_FocusTarget が組み合わさることで追尾するような突進攻撃が出来ます。

NS_Invincible は名前が意味する通りこの通知中は無敵になります。

NS_ChangePlayRate は再生中の Anim Montage の再生レートを設定します。これは0.5秒ほどの突進攻撃モーションを任意の秒数に引き伸ばすための策です。 本当は秒数が指定できればもっと調整が楽に出来たのですが、それでもアニメーションを数秒増やすだけでDCCツールを行き来する必要がなくなったのでこの方法を取って正解でした。

特殊攻撃その2 - つかみ攻撃

つかみ攻撃については Anim Montage よりも BP 側の処理が重要です。

f:id:PavilionDV7:20200508214652p:plain

つかみ攻撃のアニメーションは掴み、掴み失敗、投げ飛ばしの3つのアニメーションがありますが、それぞれが独立して Montage 化されています。これはつかみ攻撃の成否によってアニメーションが分岐するためです。


プレイヤーはボスに捕まると持ち上げられた後に投げ飛ばされます。この捕まって持ち上げられる動作はプレイヤー側で実行されています。この処理をボス側ではなくプレイヤー側で行ったのは、初期段階で Attach to Actor や Attach to Component をボス側で呼び出してもカメラがグルグル回ってしまったりと大変なことになり、改善したところ殆どがプレイヤー側で実行してしまっても良い処理が出来上がったためです。

f:id:PavilionDV7:20200508214656p:plain

プレイヤーが捕まったとき、まずは Player Controller の Tick を停止して移動や回転を止めます。Set Timer by Event でほぼ毎フレーム SetActorLocation でプレイヤーを捕まえたキャラクター(つまりボス1)の指定ソケット位置へと補正し続けます。これで捕まって持ち上げられるような動きが実現できます。

f:id:PavilionDV7:20200508214701p:plain

Behavior Tree - Weighted Random

Weighted Random ノードですが以下のブログを参考に実装しました。

[UE4] Behavior Tree の Composite Node を自作する – rarilog

記事の内容は少し古いためコピペして発生した警告を消すために少し修正しています。更に配列を利用していくつも子を追加出来るようにしました。結局終わってみれば2つまでしか子ノードをつなげていないのであまり意味はありませんでした。

ゲームの進行

エネミースポナー

スポーンの動作に必要なデータは以下の通りです。

f:id:PavilionDV7:20200508214710p:plain

スポーン処理本体である Spawn Enemies イベントは以下の通りです。

f:id:PavilionDV7:20200508214714p:plain

For Each Loop with Delay マクロは自作のマクロで1サイクルごとに指定秒数の遅延が挟まれます。この遅延はスポーンとして使われます。続いて、分岐してスポーン時のトランスフォームを指定していますが、これはスポーンするべき位置が指定されているか否かによってトランスフォームの取得方法が異なるためです。

敵が全て倒されたことを知る

スポーン終了後はアクターが削除されたときに呼ばれるイベントディスパッチャ「On Destroyed」を実装します。敵キャラクターが倒されると On Destroyed が通知されスポナーの Spawned Enemies がデクリメントされます。これはスポーンした敵の数を表しており、この値が0になったときスポナーの残りウェーブ数が0であればスポナーは全てのスポーン処理を実行し終えたとして On All Wave Finished イベントディスパッチを呼びます。On All Wave Finished イベントディスパッチは GameMode にて実装されており通知を受け取った GameMode はプレイヤーを次のエリアへ強制移動させ再び敵のスポーン処理を指示します。

f:id:PavilionDV7:20200508214718p:plain

エリアシーケンス

エリアの強制移動は AI の移動系ノードである Move To Location or Actor を利用しています。プレイヤーそのままの状態ではナビゲーションを使った移動は使えないため一度 AI Controller に切り替えて移動をするという仕組みになっています。このとき Player Controller を Unpossess してしまうとプレイヤーカメラがおかしいことになるため観戦者クラスである Spectator Pawn もスポーンし、プレイヤーカメラを Spectator Pawn が持つカメラへ切り替えることで視覚的には一切の違和感なくプレイヤーと AI による制御を切り替えています。

f:id:PavilionDV7:20200508214721p:plain

f:id:PavilionDV7:20200508214726p:plain

おわりに

この記事を書くために久しぶりにプロジェクトを開いたのですが、やはり無駄な箇所や設計が失敗しているような箇所が多々見受けられました。言い訳のような言葉が多くある見苦しい記事になってしまいましたが今後の開発に必ず活かせる事なので良しとしましょう。

プロジェクトは公開されているので是非ダウンロードして遊んだり中身を解析してみることをおすすめします。先程も書いたように無駄な部分も多々ありますが何かしらゲーム開発に活かせる仕組みはあるはずです。