第16回UE4ぷちコンに参加した振り返りの中で、改善したい点をまとめました。
前回の記事はこちらになります。作品の概要などの紹介となります。
今回は、作品を制作中にこだわった点、引っ掛かった点、気になった点、改善したい点等をまとめてみました。
ただし、この記事が全て正解という事ではなく、UE4歴1年生が一つのゲームを完成させていく四苦八苦っぷりを楽しんでいただければと思います。
基本的な構造の設計
以前、記事にさせて頂いた通りですが、UE4でのキャストの挙動について理解した為、不要なキャストを使わない事を目標に、全体が見通せる設計になるように努力しました。
基本的には以下の点を考慮しながら設計を行いました。
- Actor/Pawnはそれぞれ単独でも動作すること
- ステージ全体の管理はGameModeクラスに一任すること
- ゲーム中の状態変化(例えばスコア加算・スポーン処理・死亡処理・ゲームスピード調整)は発生元からGameModeにブループリントインターフェースで情報を知らせること
- UMGの変更(スコア表記の更新・残機数の更新など)は必ずGameModeから行うこと。(UMGへの更新依頼もブループリントインターフェースを利用すること&Actor等から直接UMGを操作しないこと)
要約すると 「ゲーム中のActorはルールに従って自由に動いてね!ただし、何かあったらGameModeに教えてね!そしたら、ゲームの状態とUMGを変更してあげるから!」という感じです。
個人的には、この考え方で作っていれば、複雑に絡み合うような構造にはならないと思いますが、如何でしょうか?
定期的にスポーンする処理の書き方に悩む
今回の作品では、データテーブルでステージ毎の車両スポーン情報を管理しています。
例えば、上記の画面の2行目にあるように"ゲーム開始から5秒後にゲート2から車両3をスポーンして5秒後に発進する"という情報を保持していて、それに従って車両が登場するのですが、"ゲーム開始から〇秒後に"という部分の実装方法に悩みました。
GameModeのTickでゲーム開始から時間を加算していき、その秒数で車両を登場させるべきか、車両スポーン用イベント内でDelayとループを駆使して実現すべきか悩みましたが、処理負荷的にTickは使うべきではないのかな?と思った為、Delayを使った処理で作成しました。(Delayもあまり使わない方が良いという話しを聞いた事もありますが・・・)
ゲーム開始と同時に車両スポーン用のイベントをコールし、直近の車両登場時間までDelayで待機後に車両を登場させ、次に登場する車両の時間の差分を改めてDelayする・・・の繰り返しで車両を登場させていたのですが、ゲーム完成後に、この仕組みでは問題があることが判明しました。
このゲームでは、車両が道を外れた場合にカットシーンが入るのですが、カットシーン再生中は内部で持っているゲームの進行スピード係数(車両の基本速度に掛けて使用)を0にして、アニメーションしている車両以外の速度を0にしています。(ゲームの世界の時間を止めているわけではなく、車両の速度のみ調整しています)
しかし、車両スポーン用の処理はDelayで待機しているだけなので、カットシーン再生中(つまり係数が0の時)でも容赦なく時間が進んでいきます。
これが原因で、同じゲートから5秒間隔で車両が登場する場合、1台目が登場した直後に、どこかでクラッシュが発生してカットシーンが再生されると、1台目はゲートから出現した直後に停車し、カットシーン再生中に2台目の車がスポーンされて、ゲート内で1台目と2台目がクラッシュしてしまう不具合となってしまいました。
結局、Tickによる秒数のインクリメント方式に変更したのですが、Tickを使わずに定期的(または、特定の秒数ごと)にイベントを発生させる方法が無いか模索中です。
タイヤが曲がらない!!回らない!!
今回使用した車両のアセットはスケルタルメッシュで作られていました。
このゲームを作るまで、スケルタルメッシュとして扱ってきたメッシュは全て人の形をしており、マーケットプレイスでも「Epic skeleton : Yes!」となっているものしか自由に扱えませんでした。(ちなみに、アニメーションデータはMixamoからグレイマンのボーンに対応したアニメーションに変換するという方法しかできません・・・)
「そもそも、車のボーンって何!?」と思い、ボーンの構造を眺めてみると、どうも車両を中心としてタイヤが動く構造になっているようで、サスペンション的な動きができるのかな?と思ったのですが、タイヤを回転させたり、ハンドルをきった時のようにタイヤを曲げる事ができませんでした。
UE4上で色々と試行錯誤していたのですが、解決できず・・・
結局は今回のゲームの視点なら、タイヤの回転・曲がりが実現出来ていなくても発覚しにくいという事で実装を諦めてしまいました。
(唯一、オープニング画面でハチロクっぽい車のタイヤが回転していないのがバレバレですが、ロゴでタイヤの部分を隠し、カメラを揺らすことでサスペンション感を出すことで誤魔化しています)
この問題は作品提出後に下記情報を見つけることで解決することができましたが、期限までに間に合わなかったことが悔やまれます。
画像ファイルを多用しすぎ
UMG内や、ステージ選択画面で画像ファイルを多用しています。
例えば、車両がゲートから登場する際に画面の端からスライド表示される画像は、車両毎にスクリーンショットを撮って、それを加工した画像です。
つまり、ゲームに登場する車両を増やしたければ、画像ファイルまで作る必要があります。
車両のメッシュデータがあるのに、それぞれの画像も用意するなんて無駄の極みですが、メッシュを動的にレンダリングしてUMGで表示するという方法が分かりませんでした。
唯一思いついた方法は、今回の作品でもライブカメラとして使っている技術で、カメラで撮影した内容をテクスチャとしてオブジェクトに張り付ける方法を応用して、画面に車両画像を表示する案でしたが、UMGでアニメーション表示している画像に重ねて表示させる事が出来なかった為に諦めました。
また、ステージ選択画面では、ステージのサムネイル画像を表示していますが、これもスクリーンショットを加工したものです。
もっと言えば、ステージ毎にスクリーンショットした画像に見えて、実は全く関係ないステージの画像が張り付けてあります。(ミンナニハナイショダヨ)
各ステージはそれぞれレベルになっていて、レベル内にブロックが配置されているのだから、ステージ選択画面でも動的にステージ(レベル)を撮影した画像を使えれば良かったのですが、ステージ選択レベルから、各ステージのレベルを操作することが出来ませんでした。
そもそも、レベル間でそのような操作ができるのでしょうか?(別レベルのカメラが表示している画像を使えますか?)
これは、なんか無理っぽいと判断し調べていなかったのですが、やり方があれば実装したい機能となります。
ライフサイクルを理解していなかった・・・
UE4に限らず、新しいフレームワーク等を触るとき、必ずイベントのライフサイクルを理解する必要があります。
UE4にもライフサイクル(ゲームフロー)という概念があるのですが、それを理解せずに突き進んでいました。
レベルブループリントのBeginPlayとゲームモードのBeginPlay、どちらが先に呼ばれるか?等、根本的な事を理解しないまま突き進むと、完成間近で重大な不具合に気づく可能性があるので、このあたりは真面目に理解しておいた方が良いと思います。
ユーザー入力は誰が受け取るべき?カメラは誰が持つべき?
今回作成したパズルゲームのように、明確な主人公が画面上にいない(強いて言えば、ブロックを移動させる為のセレクタが主人公?)場合、ユーザーの入力を誰が受け取るべきかで悩みました。
今までの経験では、グレイマンのBPでユーザー入力を受け取って、グレイマンのコンポーネントのカメラ視点でゲームが進んでいましたが、固定画面のパズルゲームの場合、ユーザー入力とカメラをどこで定義すべきかという事に悩みました。
結果として、Characterクラスを一つ作り、レベル上の0,0,0に配置、その中にコンポーネントとして追加したカメラを上空に配置し、入力イベントの処理もCharacterクラス内に記述しました。
結果的に、固定カメラもユーザー入力も実現できたのですが、これが新たな不具合を生むことになります。
ブロックがレベル上の0,0,0に配置されると、そこに配置してあるユーザー入力・カメラBP(Character)のCapsuleコンポーネント(サイズは0×0になっている)と衝突するらしく、カメラが勝手に動き出すという謎現象が・・・
Characterクラスの位置をずらすことで解決しましたが、このような場合のカメラ・ユーザー入力はどのように定義するのが正しいのか・・・
アセットのインポートで悩む
今回の作品では、複数のアセットを利用させて頂きました。
アセットをインポートすると、コンテンツのルートに追加されていきますが、これがディレクトリ構造を汚していく原因に・・・
そこで、ルートディレクトリに適当なディレクトリを作って、アセットをインポート後に、そのディレクトリに移動させると、たまに移動できてないファイルがあったり無かったり・・・(アセット内のファイル数が多いと、うまく移動できない場合が多いような・・・)
ちょっと挙動が怪しいので、今回はルートディレクトリに配置されていくアセットディレクトリを容認していたのですが、このままだと使うアセット分だけルートディレクトリが汚れてしまうので、大量のアセットを使う場合は考慮が必要かもしれません。
また、取り込んだアセット分だけパッケージサイズが大きくなるので、そもそも本番用プロジェクトと、アセット取込用プロジェクトを作って、アセット用プロジェクトから本番プロジェクトに使うアセットだけを移行させるべきなのか!?
パッケージ化で1GB!!
最終的に、実行ファイルを公開しようとしたところ、Windows版のパッケージ化で総容量1GBのゲームとなりました。
あの有名なスーパーマリオブラザーズが40KBで、私が1カ月で作ったゲームが1GBだなんて・・・恥ずかしくて誰にも言えません。
使っていないアセットを一つ一つ探し出して、最終的には500MBまで減らすことが出来ましたが、ゲームの内容的には100MB程度なんじゃないの?という感じです。
これも、アセットを直接本番プロジェクトに取り込んでしまったことが原因かもしれません・・・
たまに、「ファミコンのゲームのメモリを見ていたら、隠しキャラクターが見つかった」なんて情報があったりしますが、このゲームは隠しキャラクターどころか不要なデータ満載って感じです。
今回初めてのパッケージ化だったので、このような問題がありましたが、これからはアセットの中を精査し、使用するアセットだけを取り込むようにしていこうと思います。
最後に
今回、UE4ぷちコンに参加したことで、解説動画や、サンプルプロジェクトをいじっているだけでは分からなかった問題に遭遇することができました。
そんな状態でも、ゲームとして動くものが出力できるUnrealEngineって凄いと思いながらも、自分の勉強不足を痛感しました。
一通りのゲーム作りを体験したことで、洗い出せた問題点を考慮しながら、今後の作品作りに生かしていければと思います。