呪いのマーケットプレイスはUnreal Engine 4を使用して制作しました。この記事では、このゲームがどのように作られているのかをピックアップしてご紹介します。
1. 2Dの構成
1.1. Widgetをどういう構造にしたか
Webサイトを2DUIシステムであるUMG(Widget)で再現するにあたって、サイトの構造をいくつかのブロックに分けて、それぞれのWidgetを以下のように作成しました。
- Webブラウザのヘッダー
- Webサイトのヘッダー
- Webサイトのナビゲーション
- Webサイトのコンテンツ
- Webサイトのフッター
画面上では以下のように分割されています。
Webサイトを複数のWidgetで構成
1.2. Webサイトの表示はどう再現したか
単純に座標やサイズを似せるだけであれば、本物のマーケットプレイスのサイトをGoogle Chromeのデベロッパーツールでmarginやpaddingを調べて、同じ値で配置するだけなので簡単です。
Google Chromeのデベロッパーツールを開いた状態
しかし、スマホ向けにレスポンシブ対応しようと思うと、難易度が一気にあがります。Grid Panelなどを駆使してレイアウト自体のレスポンシブ対応と、ブループリントによる挙動の対応が必要になります。UE4でWebサイトを作っているのとなんら変わりませんね。
ホントはスマホ対応するならばレスポンシブにも対応したかったのですが、HTML5アプリがiPhoneで動かないようなので対応する価値が大幅に下がってしまい諦めました。(後述)
1.3. Widgetブループリントの処理はどうなっているか
今回のゲームのようなWebサイトを模したゲームでは、クリック可能なボタン要素で画面が埋め尽くされることになります。想像するだけでその画面の物量はやばいことになりそうですね!
以下の画像はWebサイト内のHeader部分のみのWidgetです。
WBP_Headerの表示
まずはイベントグラフです。
WBP_Headerのイベントグラフ
処理ごとにノードをグループ化しています。
このゲームでの主な処理は以下の二つです。
- ボタンに対するマウス操作(赤のグループ)
- Webサイト上の全テキストの言語変更や、テキスト文字化け処理(右下の青のグループ)
まず 1. に挙げた、ボタンをマウスでクリックしたりホバーしたりといった、入力に対するイベント処理です。
ボタンをクリックし始めたとき(左側)と離したとき(右側)の処理
ボタンの上にマウスカーソルをホバーしたときの処理
続いて 2. に挙げた、テキストの表示内容を変更するイベント処理です。
テキストが文字化けしたときの処理
言語変更したときのテキスト更新処理
難しいことは何一つしていません。ただただ物量が死ぬほど多い作りになっています。もっと簡潔な作りにするにはどうしたらいいんでしょう…。テキストの要素を配列に入れて管理するとかでしょうか?
このスクショは先ほど述べた通りHeader部分のみです。他の部分も同じような惨状になっています。これがこのゲームの一番ホラーな部分ではないでしょうか。
1.4. Widgetブループリントの入力イベントに対する挙動はどこに置くか
ボタンをクリックした際に、基本的にそのWidgetはボタンに紐づくイベントをコールするだけで、Widget側がゲームの挙動を変えるような処理は行いません。
ゲームの進行はレベルブループリントが行います。また、ゲーム中のイベントを受け渡すためにBP_GameDataというアクターを用意して、レベルブループリントとWidgetの双方がこのアクタを参照しています。
図に表すと以下のような関係です。
ゲームの情報を持つ BP_GameData を皆が見て各々が自分の挙動を変更する
BP_GameDataはレベルに配置されているので、レベルブループリントからは当然参照することができます。各Widgetはレベルブループリントが生成するので、その際にBP_GameDataの参照を渡しておきます。
こういう構成ってどうなんでしょう…、大丈夫なのかな…。Tick処理(Update関数)でゲームを管理する作りではないので大きく間違ってはないと思うのですが…。
設計に関するノウハウが足りてないなと痛感した次第です。
2. ローカライズ対応
2.1. UE4 標準のローカライズ機能を調査
今後のことも視野に入れてエンジン標準のローカライズ機能を調べてみました。が、、、
docs.unrealengine.com
テキスト ローカライゼーション
https://docs.unrealengine.com/ja/Gameplay/Localization/Formatting/index.html
このページでは、テキスト リテラル、テキスト フォーマット、テキスト生成、文字列テーブル、その他のトピックを含む、テキストのローカライズに関する必要な情報をまとめています。
難しくてなんのこっちゃわかりませんでしたε-(´∀`; )
今回やりたいこととしては、言語を選択したらテキストがその言語のものに切り替わる。という程度なのですが、設定が複雑過ぎて僕には扱いきれませんでした…。聞きなれない用語も多かったです。
ということで諦めて自前で用意してみます。
2.2. 言語を切り替える
やっていることは非常にシンプルで、GameModeブループリントにEnum型の言語情報の変数を持たせます。言語を切り替えたら、GameModeに作成しておいたイベントをコールして、各Widgetがそのイベントをもとに自分のUIの言語を変更します。
Language変数が変わったら各Widgetが自分で更新する
画面上のテキストを一斉に更新するため、一瞬かなり重い処理が走るはずです。このゲームは静止画面なので目立ちませんが、リアルタイム性のあるゲームの場合、数フレームに分散して切り替えるなどの対策が必要かと思います。
2.3. 言語毎のテキストの管理
大量のテキストはCSVファイルで管理しています。もしUE4を始めたばかりの方で、こういった大量のデータを管理する方法を用意していない方は、早いうちに自分なりの外部ファイルでの管理方法を用意しておいた方が、あとあと楽になります。
私が公開しているプロジェクトデータにもCSVファイルを生成するVBAマクロ付きのエクセルが含まれてますので良かったら参考にしてみてください。
言語毎のテキストを外部ファイルで管理
3. 各演出の実装方法
3.1. 砂嵐が表示される演出
砂嵐はマテリアルで作成することができます。以下の記事を参考に作成しました。
砂嵐を表現するマテリアル
3.2. 青い鳥が飛んでいく演出
青い鳥は、Widgetの階層としては結構下の方に存在しています。というのも、スクロールによってフッターが動いた場合に青い鳥も一緒に動かすために、フッターの子階層に配置されているからです。
ところがこのまま青い鳥の座標を動かして空を飛ばすと、他のいろいろな画像の裏に隠れてしまいます。
青い鳥は下の階層にいるので後ろに隠れてしまう
そこで、以下の記事を参考に、階層の下の方にいる青い鳥の座標を、別で用意した上の階層にいる青い鳥に渡します。
普段は階層の下の方に埋もれている「埋もれ青い鳥」の座標を「空飛び青い鳥」に教えてあげて、同じ位置に表示します。空を飛ぶときに「空飛び青い鳥」は座標を教えてもらうのをやめて、自分の力で羽ばたいていきます。こうすることで他の画像の裏に隠れてしまうことはありません。
3.3. 画面全体がバグる演出
マーケットプレイスで購入しました。自作で作る方法を知りたかった方はスミマセン…。最近ショーケースで紹介されていて「これで勝つる!」と思いました。
Sci-Fi and Glitch Post-Process
www.unrealengine.com
Sci-Fi and Glitch Post-Process:Fabrice Piquet (Froyok):Visual Effects - UE4...
https://www.unrealengine.com/marketplace/ja/slug/sci-fi-and-glitch-post-process
Pack of 16 Post-Process to simulate glitch and monitors.
3.4. ブラウン管ノイズの演出
こちらもマーケットプレイスで購入しました。自作する方法を期待された方はスミマセン…。
Animated CRT TV – VCR Effects
www.unrealengine.com
Animated CRT TV - VCR Effects:CemTezcan:Materials - UE4 マーケットプレイス
https://www.unrealengine.com/marketplace/ja/slug/animated-crt-tv-vcr-effects
Animated CRT TV and VCR effects that can be used as material and post process effects.
3.5. モニタから出てくる演出
このゲームのラストシーンで、ブルーマンが画面から飛び出してこちらに迫ってきます。
クライマックスのブルーマンが飛び出してくるシーン
このシーンは、以下のような3つWidgetを重ねて表現しています。
3D空間を写しつつ、白い板のWidgetを上に覆っている
3Dの部分(黒い部分)にはWidgetは存在しない
ブルーマンのみ描画する。白い背景は実際は透過している
重要なのは3番目のブルーマンが飛び出てくるWidgetです。このWidgetはカスタムデプスという機能を使って、ブルーマンだけを描画しています(ブルーマン以外はマスクで透明にします)。このWidgetをいちばん手前に重ねて表示すると、Webサイトより手前に飛び出してきたように見えます。
ただし、この3番目のWidgetは、そのまま表示すると飛び出してくる前から常にブルーマンが見えてしまいます。
まだスタンバイ中です!
こうならないように、カメラからブルーマンまでの距離をチェックして、画面から飛び出てくる距離まで来たらWidgetに表示し始めるようにしています。
3.6. バットエンド時の色反転の演出
3D空間の色を反転させるのであればポストプロセスで簡単に対応できるのですが、Widgetの色を反転させるには別の方法で実現しなければなりません。
そこで、おかずさんが以前公開されていた、WidgetRendererの機能を使った手法で、WidgetをRenderTarget(テクスチャ)に書き出すことにしました。
前述の画面全体がバグるポストプロセスも、この機能を利用してWidgetにエフェクトを適用しています。
この方法で書き出したテクスチャに対して色反転をすれば解決です。ありがとうおかずさん!…ところがこのやり方をしたことで、後々大変な目に合うのでした。詳しくは次のHTML5の項目にて…。
4. HTML5で出力する
4.1. パッケージサイズを減らす!
UE4のHTML5出力は、何も設定してやらないと不要なデータも含まれるためにパッケージサイズが大きくなってしまいます。まずはalweiさんの記事を参考に、プラグインを極限まで切ったりしてサイズを小さくしました。
また、プロジェクト設定->HTML5の項目にある「Compress files during shipping packaging」のチェックを入れると、出力される実行ファイル達が圧縮されてサイズが小さくなります。
プロジェクト設定 -> HTML5 -> Compress files during shipping packaging
この状態でパッケージ化すると、圧縮されたファイルと圧縮前のファイルの2種類が出力されます。
ただし、デフォルトだとhtmlファイル内では圧縮前のファイルを読み込もうとするので、拡張子をcssから、cssgzに書き換える必要があります。
htmlファイル内で二箇所書き換える
この対応により、ファイルサイズが134MBから99MBになりました。
追記
ゲーム公開後、一部の人からゲームがプレーできないという問い合わせをいただき、調べたところ原因はこの圧縮でした。結局現在は圧縮をかけずに公開しています…。
4.2. Widgetをキャッシュ化して処理を軽くする!
Invalidation BoxというWidgetを使用すると、動かない子Widgetをキャッシュ化して更新処理を行わず、負荷を大幅に軽減できる!ということを知りました。
今回のゲームはほとんど動かないWidgetなので効果は絶大のはず!早速ほとんどのWidgetをInvalidation Boxの子にしました。気になるその結果は…!?
ほとんどのWidgetをキャッシュ化した結果
あまり数値に変動が無いように見えますがどうなんでしょう…。PC上で実行するのと、実際にブラウザ上で実行するのでは処理性能が変わりそうな気もするので、これだけではなんとも判断できませんでした…。何か設定できてないのかもしれません。公開しているUE4プロジェクトで何か間違っている箇所がありましたらご指摘ください!(人任せ)
4.3. WidgetをRenderTargetに書き出したら大変なことに…
前述した通り、Widgetに色反転などのエフェクトをかけるために、RenderTextureに書き出すようにしたところ、問題が発生しました。
一つのWidgetをViewportとRenderTargetへ2回描画をするせいで、Widgetブループリントの処理も同一フレーム内で2回実行されることになってしまったのです。
例えばボタンをクリックし続けるという処理をする場合、同じWidgetクラスでPressとPressじゃない状態が同一フレーム内で混在してしまい、クリックし続けるという処理ができなくなってしまいました。
この問題を解決するには、
- クリック入力処理などのロジックを持ちつつ、Viewportに描画するWidget
- 全く同じ描画内容だがロジックを一切持たない、RenderTargetに描画するWidget
の2つを用意する必要がありそうです。
その結果、ほぼ全てのWidgetをViewport描画用とRenderTarget描画用の2種類用意する羽目になってしまいました…。Widgetの数が2倍に…。
もしかしたらこんな力技をしなくても解決する方法があるのかもしれません。そのような知見がありましたらぜひ教えてください!ヽ(;▽;)ノ
4.4. HTML5アプリがiPhoneでは動かない
もともとこのゲームはTwitterなどでRTを見た人が、手元のスマホでそのままアクセスして遊ぶことを想定した設計をしていました。ところが制作終盤に、iPhoneではHTML5アプリが動かないということを知ってしまい、このゲームを作る意義の半分を失ってしまいました…。
調べてみると、ブラウザゲームが主流になってしまうとストア収益が減ってしまうから某社が阻止しているなどといった噂は見つかりますが、実際にアプリ動かすための解決策はありませんでした。
HTML5アプリをターゲットにした制作は先行きが見えないのでお勧めできません…。
5. まとめ
ということで、「呪いのマーケットプレイス」の制作ノウハウをご紹介させていただきました。見返してみるとうまくいかなかった箇所も多くて、とてもいい勉強になりました(^^;
皆様にひとつでも参考になる情報があれば幸いです!
また、「呪いのマーケットプレイス」のUE4プロジェクトは以下のリンク先からダウンロードすることができます!
マーケットプレイスで購入したアセットや、ブルーマンなどのエンジンコンテンツは共有することができないため、別の簡易的なアセットに差し替えています。
今後もそらまめゲームスの活動にご期待ください^_^