【UE4】第12回ぷちコンを終えて

# はじめに
8月4日から9月16日までの間、[第12回ぷちコン](http://historia.co.jp/ue4petitcon12)が開催され、自分は次のようなゲームを提出しました。
[第12回ぷちコン応募作品「LAY ～ofuton ni dive～」](https://www.youtube.com/watch?v=8aemGDS9e3A)

残念ながらノミネートとはなりませんでしたが、審査結果発表会では紹介していただき非常に良い反応をいただくことが出来ました。
[第12回UE4ぷちコン審査結果発表会！](https://www.youtube.com/watch?v=SiZ1RcoxVe4)

今回の記事は、このぷちコンに提出したゲームについて紹介していきます。

# プロジェクトはこちら
今回は作成したゲームプロジェクトを配布してみようと思います。
動画中では有料のアセットや音源を配布しているサイトからダウンロードした素材を使っていますが、配布しているプロジェクトからは全て取り除いています。よって一部サウンドが鳴らない場面やそれに由来する警告が表示されますので適宜サウンドを設定して頂く必要があります。

エンジンバージョン 4.22.3
https://1drv.ms/u/s!Au-8FqgREBKZiGnhtZVF4eRizjiy?e=dSlRef

# 企画編
## 作るゲームを決める
テーマが「れい」ということで国語辞書サービスを利用して、とにかく「れい」と読む漢字や、「れい」がつく熟語を調べまくりました。このとき、他の参加者とゲーム内容が被ってしまうのを防ぐため「霊」という単語やモチーフは使わないように決めました。
[goo国語辞書 索引 れい](https://dictionary.goo.ne.jp/jn/index/れい/)

「れい」には「霊」や「例」とたくさんの漢字・熟語がありますが、ゲーム制作者にとってはお馴染みの技術である「Ray」もあります。そこで英語の方でも「れい」と読めそうな・発音しそうな単語を探してみたところ今回提出したゲームでも使用した [lay](https://ejje.weblio.jp/content/lay) にたどり着きました。この発見はかなり運が良かったと思います。「LAY」という単語のおかげで恐らく参加者の中では唯一の「オフトンダイブゲーム」を作ることが出来ました。

## 作るゲームを考えるコツ
自分は「作るゲームを考える」というフェーズが最も苦手です。ぷちコンでテーマが発表されるたび「あぁ、どうしよう」「どんなゲーム作ろう..」「何も思いつかない」「お、いいの思いついた！...いや、やっぱり面白くないなぁ」と独り言をぶつぶつつぶやいたり、布団でうーんと呻いたりしています。
そんな自分がゲームを考える際に念頭に置いていること、というのを紹介します。

### 動詞を決める
「動詞」というのは動作や状態を表す語です。つまり **ゲーム中にプレイヤーが行うこと（ゲーム中の目的）** と表す事も出来ます。ここの段階で他の人が選ばないような動詞を選ぶことができれば非常に独創的なゲームを作ることが出来るでしょう。
この動詞が決まれば、あとは無理矢理にでも「れい」をこじつけてしまえば、とりあえずぷちコンに提出するゲームは作ることが出来ます。

今回作ったゲームの「Lay」という単語は幸いにも「横たわる」「横になる」「置く」といった動詞でした。

### 「なにで/どうやって」動詞をするのか
動詞を決めることでゲームを考える作業は大きく楽になります。しかしまだゲームを作るには不十分な場合もあります。それは **なにで/どうやって** という部分です。わかりやすく言うと「動詞をするための手段」です。
例えば「霊を退治する」と考えたとします。テーマの「れい」に対しては「霊」が対応し、動詞は「退治する」です。では「どうやって退治をする」のでしょう？ここが決まらないとゲーム作りに着手したとしても「結局なにすりゃいいんだ」と途方に暮れます。

今回作ったゲームの動詞は「Lay」です。この言葉の「横になる」を「寝る」に変換し、「どうやって寝るのか」を考えました。その時、たまたま「さぁ、そろそろ寝るぞ～」と布団に横になった瞬間に「飛び込む＝オフトンダイブ」が頭に思い浮かびました。
これでテーマ「れい」に対しては「Lay」が対応し、動詞は「Lay＝横になる＝寝る」、どうやっては「飛び込んで」が対応しました。ゲーム制作に着手するには十分な材料が揃いました。

> この「なにで」といった **目的を達成するための手段** も独創的なゲームを作る際に大きく影響する部分だと思います。
例に出した「霊を退治する」といった目的もルイージマンションでは「掃除機を使って」目的を達成しますし、零では「カメラを使って」目的を達成します。
どちらもプレイヤーが行う「霊を退治する」といった目的は同じですが手段が異なるため、それぞれで独創的なゲームになっています。

# ゲーム編
## タイトル画面で揺れる家具たち
「テーマの「れい」を「Lay」としました！」
「はぁ？」
と言われるのが怖かったのでわかりやすく「れい」を取り入れるため、タイトル画面ではポルターガイスト現象が起きる演出を入れています。

ポルターガイスト現象の演出は全てシーケンサーで制御しています。
これは開発時の目標の１つに「積極的にシーケンサーを活用する」と決めていたためです。

<img width="512" alt="01_タイトルシーケンサー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/b9ec0b04-d186-cd6d-6ab1-698ea7bedb8d.png">

シーケンサーを使うことで各アクターの連携（ライトが消えると枕元にグレイマンの霊が出る/テレビに砂嵐が映る等）は非常に楽に出来ました。ですが、本棚がガタガタと揺れたり、お皿がふわふわ浮いたりといった動きは結構面倒でした。１つ１つ手打ちで位置を変えて向きを変えてとしたのですが、今思えばEventトラックがあるのでそちらでなんとか出来たんじゃ...と思わなくも無いです。カメラも定点なのですからレベルブループリントで動きをつけても、同じくらい楽だったかなとも思います。

## イントロシーン
タイトル画面からゲーム画面の間に位置するシーンも作ってみました。スタートボタンを押していきなりオフトンダイブしてもらうというのも急な話ですので「オフトンダイブするまでのいきさつ」を察せられるようにしつつ「そろそろあなたが操作するシーンに移りますよ」という予告的な意味も込めています。

このシーンもシーケンサーを使用しました。特に「字幕表示」は結構面倒でした。

<img width="512" alt="02_字幕.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/01449be6-3dce-3a5c-c3a5-6a740a215c04.png">

UE4にはビルトインされている[字幕表示機能](https://docs.unrealengine.com/ja/Engine/Audio/Dialogue/4/index.html)がありますが、「Sound Waveセットが入っていない限り字幕は表示されません」と注意書きがあったので、早々に諦めてWidgetを利用した字幕表示機能を作ることにしました。

---

ウィジェットの構造は以下の様になっています。

<img width="512" alt="03_字幕うぃじぇっと.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/0c808d22-948f-977d-64d0-f188c96852a4.png">

改行も考慮したサイズにしたTextBlockを字幕を表示したい部分（画面下）に配置しています。
ウィジェットのブループリントは以下のとおりです。

<img width="512" alt="04_字幕表示.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/867348ed-4d6d-a4cf-3f86-386f09ff6a99.png">

このカスタムイベントは外部から呼び出されます。呼び出されると引数で渡されたテキストをTextBlockにセットしテキストが表示されます。
引数のDurationは字幕の表示時間です。Duration分の時間をDelayで待機して空っぽのSetTextを呼び出して、字幕をクリアします。ここで字幕の表示時間を指定しなかった場合は新たに字幕がセットされるまで同じテキストを表示し続けます。

字幕表示のウィジェットはレベルブループリントではなく、字幕表示ウィジェット専用のアクターを用意しました。アクターのブループリントは次の２つのイベントで構成されます。
<img width="512" alt="05_BP_SubtitleWidget.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/0a30b3e7-cb93-84b7-6095-fe17ac00b266.png">

注目点としてはShowSubtitleイベントです。生成した字幕表示ウィジェットのShowSubtitleCustomイベントを呼び出します。
ShowSubtitleイベントでは２つの変数が登場します。String型のSubtitleTextとfloat型のSubtitleDurationです。この２つの変数はシーケンサーで値が入力されます。
わざわざ専用のアクターを作成したのは、この2つの変数をシーケンサーへ公開するためです。
このようなシーケンサーを使ったテキスト表示はEpic Games JapanさんのUnreal Japan Streamで解説されていました。
[Unreal Japan Stream | UE4.21 で大きく変わった Sequencer の Event Track の使い方を紹介！](https://www.youtube.com/watch?v=LgIJbXhXAfU)

シーケンサーのタイムラインは以下のようになっています。
<img width="512" alt="07_イントロのシーケンサー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/0ad38865-3bf2-c591-91ef-cea290bd6131.png">
字幕表示ウィジェット専用のアクターで定義した変数をタイムラインで制御しているのがわかります。
この方法の1つの注意点として「表示するイベントと字幕テキストの変更を同時に行わない」があります。同時に変更してしまうと直前に表示していた字幕が一瞬表示された後に新しい字幕へと置き換えられます。目に見えないくらい素早ければ大きな問題ではないのですが、こちらではガッツリ切り替わる瞬間が見えてしまいました。ですので、表示するイベントの手前（0.01秒くらい手前）で字幕テキストの設定を行っています。

## ゲームシーン
### プレイヤー
はじめにプレイヤーを構成するコンポーネントを紹介します。
<img width="512" alt="06_Player.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/9aeeecee-44b8-5ddc-ab74-e74dcb33fc49.png">

このゲームでは複雑な移動（ジャンプとかしゃがみとか泳ぐとか）を必要としない真っ直ぐ移動するためのシンプルなものなので、継承するクラスはPawnクラスを選択し移動コンポーネントとしてFloatingPawnMovementを選択しました。
他にはジャンプする際の「ジャンプする方向」を決めるためのArrow Componentやスコア計算に必要な各節点に配置したSphere Collisionで構成されます。

#### Tick
<img width="512" alt="08_Player_Tick.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/b1988a77-cf93-4a14-940d-a3fad4ae3c3a.png">

Tickは画像のようになっています。UMGで表示しているジャンプパワーのゲージに必要なパーセンテージはここで計算しておりUMG側はここから値を参照してProgress Barへとセットします。
射出角度を決める時、矢印が上下に動く仕組みもここで計算されています。三角関数を性質を少し知っていればこういったのを実装するのに便利です。

#### 射出時と射出後
<img width="512" alt="09_Player_Launch_Countdown.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/49d76bce-6bf2-b53b-cc77-ec2f1a299583.png">

Launchイベントはスペースキーが離されると呼び出されます。スペースを連打すると連続して射出してしまう問題を防ぐために、Gateノードを使いました。（Do Onceノードを使っても同じように出来ます。Gateノードを使ったのは当時はGateノードで組んだほうが処理の流れをイメージしやすかったためです）
Gateノードを通った後は射出中に不要な処理やコンポーネントを無効化しラグドール化します。ArrowComponentが止まっている角度に向かってAdd Impulseしてグレイマンを吹き飛ばします。ここでBegin Countdownイベントを呼び出していますが、これは結果判定ウィジェットを表示するまでのカウントダウンを開始する処理になります。
Delayノードを見るとわかりますが、この結果判定ウィジェットを呼び出すまでの待機時間は3秒で固定です。この部分を「グレイマンのPhysics Assetが全て（または一部）Sleep状態になったら結果表示」というようにできれば非常に柔軟な仕組みに出来たのですが、Sleep状態になったというデータが上手く取れなかったので固定時間にした次第です。([物理アセットのプロパティ リファレンス](https://docs.unrealengine.com/ja/Engine/Physics/PhysicsAssetEditor/Reference/index.html) ではWake/Sleep時にイベントを発行出来るようなオプションが有るのですが、有効にしてもSleep時にイベントが発行されないように見受けられました。まぁ自分が使い方を間違っているのでしょう...）

#### ベッドの接触判定
結果判定では各関節がベッドに触れているかどうかで評価を変えています。この「どの関節がベッドに触れているか」を知るために次のような処理を組みました。

<img width="512" alt="10_Player_ベッド接触判定.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/aa6eb582-64b7-4dce-d4b5-3f9b1b202fc0.png">

最初に各関節にアタッチされているコリジョンコンポーネントのリストから1つずつ要素を取り出し、Component Overlap Actors関数でオーバーラップしているアクターを取得します。この時、オーバーラップしているアクターの数が1以上であればベッドに接触しているとしています。続いてBody Parts Listという各関節のコンポーネント名が格納されているリストと現在取り出しているコリジョンコンポーネントの名前をFind Substringで検索します。該当する文字列が見つかれば「該当する関節はベッドに接触しているか」をオーバーラップしているアクター数で判断し結果をディクショナリ型変数Resに格納します。
これを繰り返して「各関節のベッドへの接触状況」が記録されたリストを作成します。最後に結果判定ウィジェットで参照するための構造体を作成して返してあげます。

今思えばScene Componentを継承したクラスに、それぞれ「ベッドに接触しているか」判定をさせればもう少し簡潔に出来たかなと思います。（Component Overlap Actorsの代わりにSphere Overlap Actorsを使えば同様のことが出来る）

### カメラ
カメラはプレイヤーのコンポーネントとしてアタッチされておらず、別のカメラアクターとしてレベルに配置し、プレイヤーに親子付しています。
コンポーネントで対応しなかった理由として「プレイヤーが射出されたときにカメラが荒ぶらないようにするため」「射出時に部屋全体が映るアングルにカメラを移動させる際にローカル座標とかの計算が面倒くさかった」「そもそもカメラの移動をプレイヤーにさせるのはおかしくないか。カメラアクター自身にやらせなさいよ」があります。

リビング兼ベッドルームに進入するとカメラは部屋全体を映すような位置に移動します。この仕組は非常に単純で、コリジョンコンポーネントを備えたアクター（Camera Modifier）がプレイヤーを検知したときカメラアクターに定位置に移動するように指示をします。次の画像はレベルに配置されたCamera Modifierです。Camera Destinationはカメラアクターが移動するべき定位置で、プレビュー用のカメラでCamera Destinationから見える景色を確認出来るようにしています。
<img width="512" alt="11_Camera_Modifier.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/22cebba2-f89a-35f8-315c-e743c4c2eb9f.png">

カメラが定位置に移動する処理は次のとおりです。
<img width="512" alt="12_Camera_定位置へ移動.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/1cbbc476-3b62-396a-96e3-b096267a68ae.png">
Detach From Actorで紐付けられたプレイヤーと切り離し、Time LineとEaseを使って滑らかに定位置へ移動するようにしています。


### 結果判定ウィジェット
結果判定ウィジェットの構成は次のようになっています。
<img width="512" alt="13_ResultWidget_構成.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/61adc733-7293-8e48-ea04-11433b34b5b2.png">
左側にあるグレイマンや白丸、赤丸は全て画像で用意しています。グレイマンの画像はSkeletal MeshのプレビューからカメラをFront、ライティングをUnlitにした状態をスクショしてGIMPで縁取りしました。白丸と赤丸も同様にGIMPで用意。赤丸は白丸よりも若干幅を大きくしています。同じ幅にしなかったのは同じ幅だと縁からほんの少し白丸が見えてしまい、見た目がよろしくなかったためです。
ウィジェットの描画範囲外にボーナス用のテキスト、評価用のテキストを配置しています。これは必要に応じてアニメーションを再生して画面に登場させます。

---

次の画像はConstructイベントの処理です。このウィジェットが生成されると同時に実行されます。
<img width="512" alt="14_ResultWidget_Construct.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/d14df8bb-f575-c463-fc17-166864f76107.png">
ここではほとんどがアニメーションの再生処理です。2段目の処理ではベッドに接触している関節に応じたアニメーションをまとめたリストを作成し順次再生しています。実は赤丸が白丸に重なるアニメーションは各関節ごとに用意されています。
<img width="512" alt="15_ResultWidget_Animations.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104467/3891dd40-4754-3e94-4f5e-49ade387596f.png">

3段目はオフトンボーナスのアニメーション再生処理。4段目ではベッドに接触している関節の数に応じたランクテキスト＆SEを準備して表示＆再生します。

#おわりに
振り返り記事というのは文量が多くなりがちなので避けていましたが、やはり振り返ってみると多くの改善点が見つかりますし改善方法もすんなり出てきたりするものですね。今後も機会があればこういった記事を書いていこうと思います。
普段はUE4を技術検証程度でしか使わないのでこういった気軽に参加出来るコンテストというのは非常にありがたいです。良い練習にもなります。
今後も積極的に参加して笑ってもらえるゲームを作りたいと思います。