PavilionDV7の雑多なやつ

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

【UE5】アンリアルクエスト3 振り返り 1日目 ~ 2日目

【UE5】アンリアルクエスト3 振り返り 1日目 ~ 3日目

はじめに

2022-05-23 ~ 2022-5-27 の5日間に渡って開催されたアンリアルクエスト3に参加した。

この記事では主に各クエストに対する自分なりの実装を紹介・解説する。時々反省もする。「これが正解だ!」というものではなく、スマートではない実装もあるので「ははぁ、こういう実装をしたのか..」みたいな感じで読んでくれると嬉しい。

3日目 ~ 4日目
5日目 ~ おまけ

プロジェクトのダウンロード

マケプレアセット(音楽やアニメーション)を除いたプロジェクトデータは以下のリンクからダウンロード可能。

UEバージョン:UE 5.0.2

https://1drv.ms/u/s!Au-8FqgREBKZjwVYFuCn0tHMAUB7?e=mZKaxf

1日目 玉

発表当初は「なるだけ簡単にセットアップ出来るようにして、いろいろなオブジェクトを壊せるようにしよう。」と考え「基底クラスに必要な処理を組み、継承してパラメータを変更する」という方法ではなく「壊されるオブジェクトに要求される機能を個々でコンポーネントとして処理を組んで、壊したくなったオブジェクトに必要なコンポーネントをAdd Componentしていく方式」とした。Health(体力)を管理・計算する「BP_Health_Component」、簡単な上下運動をする「BP_SineMove_Component」を作った。

初級 玉が当たったら物が壊れる

BP_Health_Componentは「Health」という体力を表す変数を持つ。「Take Damageイベント」はHealth変数の値を減少させるイベント。

「体力が0以下になったかどうか」を判断する「Is Dead関数」もこのコンポーネントに実装している。関数で実装しているのは「返り値」があるから。カスタムイベントは「返り値」を返すことができない。

このコンポーネントを持つアクターは「Apply Damage」の処理が実行されると、BP_Health_Componentの「Take Damageイベント」を呼び出し体力を減らす。続いて「Is Dead関数」を呼び出し自分の体力が0以下になったかを確認する。TRUEと判断されればDestroy Actorで自身を消去する。という手順になる。

ただDestroy Actorで消滅させるだけでは寂しかったので、早々に破壊エフェクトを作った。破壊エフェクトは「キラー7」というゲームの敵死亡エフェクトを参考に一度メッシュの形状に合わせてにエフェクトを生成し、個々の粒子を散らしている。トレイラー内でエフェクトが発生している映像がある。

store.steampowered.com

中級 動く的に玉を当てる

上下運動はBP_SineMove_Componentによって実行されている。実装はこんな感じ。

sin関数を使っているのは滑らかな曲線が欲しかったから。

Frequencyはサインカーブの周期。1.0で1往復がおおよそ6秒程。2.0ならおおよそ1往復3秒ほど。0.5ならおおよそ1往復12秒ほどになる。MoveAmountは移動量で片道どれくらい移動するかを表す。この2つの変数はインスタンス編集可能にしているので、レベルに配置した後でも動きを調整しやすくしている。

これらのパラメータを調整する時は「2秒くらいで往復して欲しいから、1.0なら大体1往復6秒くらいだから...うんぬんかんぬん」とは全く考えてない。「あれ?思ったより遅い...数値増やすか」という感じでなんとなく調整している。

SineMove_Componentという名前からいろんな方向に自由な距離と時間で動かせそうだが、実装の通りZ軸に沿って動くだけ。このコンポーネントを実装したときは「今のところ動かすオブジェクトは1つだけだし、上下に動かせれば十分だから必要になったらもう少し自由に動かせるように修正しよう」と考えていた。結局は修正なんてしなかったし、何ならこのコンポーネントは1つのアクターでのみ使われるだけで、全く再利用されずじまいだった。

もう少し汎用的な往復運動を実行するコンポーネントを作るとしたら次のような実装にしたほうがいいかもしれない。

MoveDirにX=0,Y=0,Z=1と指定すればワールド座標系のZ軸に往復運動するし、XとYに1を指定すれば斜めに往復運動する。

ここではsinを使って滑らかに移動させたが、最終日に放送された解説編では「InteroToMovementコンポーネント」なるものを使って移動させていた。それを知っていれば、このような面倒な計算とかしなくてよかったのではとも思う。

上級 玉を投げる軌跡を表示しよう

軌跡は「VRテンプレート」に同梱されている「NS_TeleportTrace」というNiagaraアセットをそのまま移行し、パラメータの調整とマテリアルの変更をしただけ。つまりパーティクルで描画している。

軌跡の曲線は発射物の軌道を求める「Predict Projectile Path by Object Type」によって求められた頂点配列をNiagaraシステムに定義したユーザー変数に渡すことで実現している。(この仕組みもVRテンプレートをそのままコピペした)


軌跡パーティクルは投げる方向に模様が動くアニメーションをしている。これはリボンレンダラーに適用するマテリアル上でアニメーションさせている。ベースは次のチュートリアル動画を参考にした。

youtu.be

ここから「投げる方向」というのをわかりやすくするために少しノードを加えた。

2日目 アクション

初級 2段ジャンプで輪をくぐる

2段ジャンプはCharacterクラスの「Jump Max Count」を「2」に指定すればOK。

UE4 Characterの多段ジャンプについて - PaperSloth’s diary

UE4 多段ジャンプを実装しよう - UE4初心者が頑張ってるブログ


自分のゲームでは輪をブースターとして利用することにした。ブースターとしての機能は次のように実装するだけでOK。

輪の中央部分に配置したBox Collisionがプレイヤーを検知すると、Launch CharacterでBoost Dir方向に勢いよく飛ばす(瞬間的に押し出す)実装になっている。LaunchCharacterノードは対象がCharacterクラスまたはCharacterクラスを継承しているもののみが対象となるので、ActorやPawnでは機能しないことに注意。

どの方向にブーストされるかが分かるようなパーティクルと連続使用できないようにクールダウンを実装し、最終的には次のような実装になった。


ブースターのエフェクトはシンプルな構成になっている。

重要なものは「Shape Location」「Add Velocity」「Gravity Force」の3つ。

Shape Loationでは粒子の初期生成位置を決めていて、円の外周に沿って生成されるようにしている。Ring Radiusパラメータはブースターアクターにアタッチし、都度再生しながら数値を調整した。

Add Velocityは粒子のスポーン時に「物理的な力」を瞬間的に加える。「From Point」モードはパーティクルの中心から物理的な力を加える事を意味している。通常Velocity Speedに正の値を設定すると「中心から外側」に向かって粒子が飛ぶ。しかし負の値を入れることで「外側から中心へ向かう力」に変化する。

Gravity Forceは名前の通り「重力」を適用するが、力の方向や大きさは自由に設定出来る。初期値はX=0,Y=0,Z=-980で-Z方向(つまり地面)に向かって常に力が加わる。この値を正の値にするとZ方向(つまり空)方向に向かって力が加わることになる。Add Velocityで中心へと向かわせ、Gravity Forceで上方向に打ち上げることで「中心へと向かうながら上に移動する」ちょっとオシャレなエフェクトができた。

中級 空中ブランコ

実はゲーム中の空中ブランコは物理由来のものは一切使っていない。初めはPhysics ConstraintとRadial Forceを使い、物理の力で作っていたが、理想とする動きができなさそうと感じたので1日目の中級と同じように数学を利用してブランコっぽく動かすことにした。

ブランコの動きには「シグモイド関数」と呼ばれる関数を利用している。シグモイド関数が出力する値は0-1の範囲でS字カーブを描く。この特徴的なグラフがブランコのような振り子運動をそれっぽく再現するのに最適だと思った。

[活性化関数]シグモイド関数(Sigmoid function)とは?:AI・機械学習の用語辞典 - @IT

シグモイド関数への入力値にsin値を3.5~-3.5の範囲に調整している。シグモイド関数は入力値が-6の時おおよそ0を出力し、入力値が6の時おおよそ1を出力する。この通りに6~-6の範囲に調整すると、振り始めと振り終わりで「あれ?止まった?」というくらい値の変化が小さくなってしまうため、いろいろな値を試した結果3.5~-3.5の範囲に調整したほうが心地よかった。入力値によってシグモイド関数のグラフがどのように変化するかについては次のような計算サイトを使ってみると理解しやすいと思う。

シグモイド関数 - 高精度計算サイト

シグモイド関数の結果はLerpに繋がっており、Lerpは-15~-165の値を出力する。これはsinとcosによる円の動きを横から見た時、正の値を指定すると右回り、負の値を指定すると左回りになる。今回は下半分で振り子運動して欲しいので負の値を指定し左回りにおおよそ半回転するようにした。

「なんで0と-180にしなかったのか?」についてはシグモイド関数のパラメータの理由と同様、この値が実行して一番心地よい動きをしたから。Lerpの値は度数を取るsin(degree)とcos(degree)で使われる。ラジアン値を取るsin、cosを使わなかったのは「度数のほうがわかりやすいよなぁ」と思ったから。

上級 壁蹴り

プレイヤーキャラクターには壁を検知するためのBox Collisionコンポーネントを追加しており、コンポーネントが「壁蹴り可能な壁」に衝突するとフラグが立って壁蹴りのための処理が実行される。

壁蹴りフラグが立った時、Tickでは壁ズリ処理が実行される。画像の処理によって、壁に爪を立てて落下速度をゆっくりにしているように見せる事ができる。お手軽処理でプレイヤーに要求するアクションの難易度を下げることができたはず。

壁蹴りはプレイヤーに複雑な操作を必要とせず、ボタンを押すだけでポンポン実行されるようにしてみた。具体的には壁蹴り時は真上に飛ぶのではなく、斜め後ろに飛ぶようにし、それに合わせてプレイヤーの向きを反転(つまり後ろを向く)ようにした。

この実装はアクションの難易度を大きく下げることが出来るが、同時にレベルデザインについても制限がかかってしまう。斜め後ろにジャンプするため、1つの壁だけで高く登る事ができないので、ゲーム内では壁蹴りが必要な箇所は必ず「壁に挟まれた地形」となっている。

1つの壁だけで壁蹴りが出来るようにするためには、Character MovementコンポーネントAir Controlといった数値を大きくしたり、ジャンプの方向を真上に近い方向にすることで実現できるかもしれないが、必要以上に操作難易度を上げる必要は無いと判断しこのような実装になった。

おわりに

今回は2日目までの実装内容を紹介した。次回は3日目以降の処理内容を紹介する。

【UE5】アンリアルクエスト3 振り返り 3日目 ~ 4日目 - PavilionDV7の雑多なやつ