最近は麻雀 のバージョンアップを続けておりましたが、このSTAY HOME週間は唐突にこんなゲームを作っておりました。
【エアコミケ98】『エア搬入T.T.』プロモーション映像
ゲームはこっちから遊べます。
unityroom.com
連休ラストでもありますし、以前の1週間ゲームジャムのときのように振り返りをしてみようと思います。
きっかけ
耳にタコが出来るほどSTAY HOMEを呼びかけられて何かとテンションも下がりがちな連休()前・4月最終日の木曜深夜、こんな2つのツイートが流れてきました。
このツイートと動画と在庫の山を見ててふと、14年前の夏コミ(C70)で頒布したゲームのことを思い出しまして……まわるめいどさんをねみぎ nemigi.dna-softwares.com
ゲーム本体はまだおうちのPCにあるんですが残念ながら起動しなくなってました。さておきこのゲーム内のミニゲーム で「コミケ の島中や壁を模したコースを走って、指定したスペースに停める」 というものがありました。
ミニゲーム なのでカーブ一つ曲がってブレーキ程度のものだったんですが、「今だったらこれコミケ 会場フル再現できるのでは?」とふと閃きます。
当初のコミケ の日程は
5/1 前日設営
5/2 初日
5/3 2日目
5/4 3日目 (前日搬入はこの日が最後)
5/5 最終日
ということで、エアコミケ はこの日程で開催している体でアレコレツイートするから、ついでに他のユーザーも参加している体でツイートしてくれとのこと。
まあ、出かけることもできないなら腕試しとしてこの「車両搬入」をゲーム化してみようか、とチャレンジすることにしました。
リリースまでの道のりを ダラダラと日記的に書き連ねてみます。
-1日目 (4/30深夜)
モデル作成
テスト用レベルの作成
トラックの挙動決定
閃いた時間が23時とかだったので取りあえず眠くなるまでやってみるべーとせこせこモデル作成開始。
一応イベント主催 もやっておりますので親の顔より見た長机の寸法など調べるまでもありません。(え?今年の日程?……緊急事態宣言の先行き次第じゃないスかね……)
1800W×450D×700H、だいたいのイベントの長机はこの仕様ですので、パパッと作成します。
小物のモデリング ですが、今回はAsset Forgeというのを使いました。
assetforge.io
(図は適当に作ったダンジョンの一室的なもの。3~5分あればこのぐらいはすぐ)
要するにDoGA -L3みたいな感じでパーツくっつけてモデルつくるヤツなんですが、パーツの種類にバリエーションが多く、多彩なジャンルのモデルを作れます。SFに限らず現代・中世などのモデルも可。
FBXを直接エクスポート可能、かつUnity向け設定プリセットがありモデリング が終わったらすぐさまUnityに持ち込めるのも強みか。
机の次は搬入の主役?たる段ボール。ピコ手から大手まで積み具合を変えて複数、さっくり完成。
情景パーツだけじゃなくて主人公たるトラックの方も作らねばなりませんが、こちらは素材をそのまま使ってしまう方向に。
Asset Forge作者のKenney氏はゲーム向け素材集も出しており、この中にちょうど手頃な車両モデルがあったため使うことにしました。
Kenney Game Assets 3
買うだけ買って全然使えてなかったのでいい機会でした。プロトタイピングやゲームジャムではこういうモデルが超役に立ちます。
さて、Unity上で長机を並べるわけですが、1.8mをびっしり並べるというのは若干めんどくさいのでアセットに頼ることにします。
コライダーに沿ったり物理に従って良い感じにオブジェクトを撒いたりできるツールなんですが、配置機能の一つに等間隔配置があり、コレなら島作るのほぼ一瞬じゃん!(島の端っこの回転だけは手作業だけど)ってことで採用。
これで適当に島を並べてイベント会場っぽくして、テストフィールドを準備。
この段階で既に複数のサークルに荷物を配達するゲームということだけは固まっていたので、先に トラックの挙動を作ってしまおうと思いました。ここは自作で良い感じに出来る気がしなかったので、最初からアセット買う気満々でした。
アーケード挙動のアセット複数が候補に挙がりましたが、とにかく「壁ヒットの判定が車両形状通りちゃんと取れる」かどうかを調べて(カジュアル向けの車両挙動のアセットは球を転がすことで近似してるようで、特に側面の判定が見た目通りになってないことがありました)たどり着いたのが下のアセット。
無料版もあったんですが、5$ならいいかーってノリで購入。変なマネージャーやらへの依存もなくこちらで用意したモデルへの組込はあっさり完了。パラメーターの決定にはやや苦労しましたが最終的には遊びやすい形に収まってくれました。ジャンプとかブーストとかいう機能がありましたが当然いらないので削除。代わりに完全停止できるよう「ブレーキ」を実装。
この手の車両コントローラってブレーキからそのまま滑らかにバックに移行するヤツばっかりで「完全に止まる操作」って意外と入ってないこと多いですよね……
あと停車目標によさげなオブジェクトを準備。クレイジータクシー みたいに地面から枠が伸びてる感じがよかったんですが、自前ではしっくりこなかったためこちらもアセットに頼る方向へ。
抽象的な方がいいかな?ってことでフラットポリゴンなこれ(例によって購入済みだったのに使いどころが略)を採用。ちょうど都合良く「Checkpoint」なるパーティクルループがあったので有り難く使うことに。他のエフェクトもすべてこのアセットで揃えています。
以上の情景パーツを並べて下のスクショのようになったのが午前5時。
いける、と判断してスクショをツイートして、スタッフの知人に「BS東館の資料とか持ってないですかね?」とDM飛ばして就寝。
(ビッグサイト のWebサイトに図面があったんですが、気づいたのは寝て起きた後のことでした)
0日目(5/1)
本番用マップの作成
トラックのPrefabでテスト
エフェクト類準備
HUDのモック作成
ゲーム内情報を格納するコードの作成
起きたら知人が過去イベントの図面を見せてくれました。これで456ホールの寸法がわかったのでモデリング 可能に。
早速モデリング なんですが、マジメにやってたんじゃ終わるわけがないので「 ドア・お手洗い、壁面の看板、天井など全部無視」と、とにかく搬入に関係しないものは全部無視の方向で決定。
割り切ると決まれば簡単です。Adobe Illustrator で図面をトレスして壁と柱のパスを作り、SVG 出力してMetasequoia で読み込み。パスを押し出せばあっという間に東ホールのできあがりであります。
図面の上から壁・柱のパスを起こす(マゼンタのところ) SVG 出力してMetasequoia に送った状態(暗いところは床用のポリゴン)上に押し出せばごらんのとおり
これをUnityに持って行って、図面の寸法と一致するようにスケーリング調整。
で、サークル配置図を参考に島の数等を調べ、机を並べたのでひとまずTwitter に進捗をアップしたんですが、まだ違和感バリバリです。
短辺側の壁サークルと島が近すぎる気がするし、全体的に何かすごい違和感が……と思っていたらスタッフ経験者諸氏から「ホール出入口とシャッターの間の島間隔は他より広い」 と指摘が。より正確なサークル配置図も見せて貰って、そういえばそうだった、ってことで各所微調整して下図の通りに。
これで机配置を固定して、作っておいた車のPrefabとかをおいて実際に走らせてみました。
走りにくいということもなさそうなのでこのまま続行。停車目標へのインタラク ションから実装していきます。ここは特に新しいこともなし。デバッグ でわかりやすいように、停車目標とリンクしている荷物オブジェクトの間に線を引くようにしたぐらいでしょうか。
停車目標に止まると矢印の先にあるオブジェクトがPopする(エディタ上では荷物は表示したまま)
Unityのフォーラムの「Did you ever wish there was a Debug.DrawArrow()?(意訳:Debug.DrawArrow()とか欲しくね?) 」に書かれていたコードを使っています。なるほど便利だわ。オブジェクトのリンク漏れ・リンク間違いがすぐわかって良い感じです。
そのまま他のルールに関わるオブジェクトを作成。
障害物にぶつかったときのエフェクト。火花っぽい見た目で、ペナルティであると明確にわかる色に。これはPolygon Arsenalの中ではピッタリ合う感じのエフェクトがなかったので、近いイメージのPrefabから改変して作成。
ゴール地点のエフェクト。これはわかりやすいのがあったのでそのまま使用。
で、ゲームルールに目処が立ったところでHUDのモックを作成。モックと言っても値の更新の実装を後回しにするだけでUnity上で配置します。
デフォルトの画像リソース使いたくないマンなので先日セールだったこれを動員。
定価99$ですが先日のセールで半額でした。アイコンは微妙ですがUIパーツとカラースウォッチ が大変良い。WebでいうBootstrap的な基本配色にまとめられるので、なんとなくUIが引き締まります。
フォント類はmojimo-game。ゴール演出用のテロップなどもこのタイミングで作成しました。あと、レーダー。常時見られる地図がないと絶対難しい、ということでミニマップの導入は必須事項と思っておりました。
ミニマップはこちらのアセットを使用。取りあえず設定済みのprefabをCanvas に置いて、レーダーに表示させたいオブジェクトにコンポーネント 一つ貼ればもう準備OK……と導入がむちゃくちゃ楽で、外観の変更も簡単だったので助かりました。
UIを作るとゲーム中通して保持すべき変数が自然と浮かび上がるわけで、ここでゲーム通して参照される変数を入れるシングルトンクラス(ウチではGameEnvironmentと呼称しています)を作成。
ウチのUnity作品ではほぼ必ず存在する由緒正しき存在です。シングルトンに加えDontDestroyOnLoadを入れており、タイトルおよびゲーム中を通して同じオブジェクトを参照できるという形にしてあります。シーンを跨ぐ変数やシーン間でのパラメータ渡しなどに使用しています。
static変数よりはマシかなーと思ってやってるのですがどうなんですかね。
ここまで出来たところで取りあえず進捗ツイート用のGIFアニメを撮ったんですが、
どうせならこれは実際の搬入時間帯にツイートした方が面白いなと思ってツイートせず就寝。(身内のDiscordとFacebook には貼った)
1日目(5/2)
台車モデルの作成
ゲームシーケンスの作成
「ご注意」画面の作成
全体マップ画面&走行記録表示
タイトル画面
ツイート機能実装(途中)
開催!コミ!ケット!(300人のスパルタ人に任せた動画のアレ)ってなもんで盛り上がるハッシュタグ を眺めながら作業。Discordで「前日搬入あるある」として「他社の搬入車両」「邪魔なチラシ台車」などのネタを得たので、まずは台車から。適当にモノタロウのサイトで一般的な台車の寸法を調べ、Asset Forgeでさっくり作成。台車色々
流石に台車がトラックとぶつかって動かないハズはあるめえよってことで、ぶつけたら滑る物体としてRigidbodyを設定。
Kenney Game Assets 3からトラック以外の車両アセットも引っ張り出して台車と一緒に障害物として設置、チェックポイントを設定。
このあたりからゲームの情報を格納するシングルトンクラスやらArborのステートマシンやらを用意して、一繋がりのゲームにするべくスクリプト を書きます。(エア)搬入時間帯にUIも動き出したので一度MP4でキャプチャして投稿。
ついでにネタ半分の「ご注意」画面を作成しこれも投稿。
これがエアコミケ 公式にRTされてバズりました。反応も悪くなくモチベーションアップ。
ゲームとしては繋がったんですが、レーダーだけではまだチェックポイントがわかりにくいような気がしたため、開始前にブリーフィングを行うように決定。
さらに走行ルート自由というシステムなので、プレイヤーごとに走行ルートの違いをアピールできるよう、ゴールまたは時間切れの後にデブリ ーフィングとして走行したルートを表示するのを決定。
走行ルートの記録ですが難しい事はやっておりません。一定時間間隔でプレイヤーキャラのワールド座標と向きを記録。ほか、マップにプロットしたいイベントが発生したときも時刻と座標を記録するというだけです。
あとはその記録を最初から順番にプロットすればOK。
開始前、チェックポイントの位置を示すマップ ゴール後は走行ルートとクラッシュ箇所を表示
こういった流れの途中でタイトル画面も作成。ゲーム中UIにテイストを合わせつつさっくりと。
一般公開にあたっては何らかの形で記録を競い合う要素がないと面白くないと思っておりましたが、ランキング実装はだいぶ厳しいのでやるならツイートと思っておりました。
ちょっと調べたら画像付きツイートができるというので、imgurを使うタイプのサンプルを使ってみることに。
github.com
エディタ上ではツイートもアップロードも無事動いたのを確認できたのでWebGL 書き出してみたら……なんでかツイートダイアログがでない。(ログではNullReferenceだかを吐いて止まっている)
というところで意味がわからなくなって一旦ふて寝。
2日目(5/3)
身内向けテスト
ブリーフィング画面やゲーム画面での負荷低減策
ツイート機能の続き
効果音とBGMの選定
とりあえずツイート機能と効果音以外は無事動いたのでDiscordで身内テストを開始。
ツイートの方は結局原因がよくわからずじまいで、別の方のパッケージに切替えることに。
github.com
流石に心配になったので一度テストシーンを作成してWebGL 書き出しもチェック。こんどは大丈夫ということで本組込。
「prefab(ylib > UnityTweet > Resources > Prefabs > GO_TweetImgur)をtweet したいscene上に置く」という説明になっていますが、ほかのGameObjectの子に置くと正常に動きません。
ちょっと色気を出してGameEnvironmentの下に置いたらだめだったよ……
統合GPU のユーザーから重いと指摘があったので負荷低減策を検討。
Profilerにかけてみたところ
Batchesが2000とか3000とか
Setpassが数百回
机のMaterialが2個に分かれててバッチングが全然できてない
背景にライトマップとLightProbeを使っていたのだがLightProbeが細かすぎて参照するProbeが変わりすぎてバッチングができてない
という惨憺たる状態だったので
Staticの設定をマジメに実施しStatic BatchingやDynamic Batchingを入れた
Mesh Materializerでマテリアルを統合(本当はPro Drawcall Optimizerを使うつもりだったがいつの間にかDeprecatedにされてた……)
机のLightMapも焼く(Lightmapの計算時間とサイズが爆上がりするのでやりたくなかったけどやむなし)
と言う風にして一応軽量化。絵作りに使っていたPostprocessing Stackを全OFF出来た方が良かった気がするが、ひとまずは乗せるエフェクトを減らすオプションまで。
さらにブリーフィングの画面も重い。そりゃそうですよカリングのできないホール全域のリアルタイム描画ですからね……ただ、こっちは明確に軽くする手段があって、ブリーフィングのマップではオブジェクトは止まったままなのだから一度レンダーテクスチャに焼き付けてしまえばいい のです。
考え方としては「背景とステージ上の各要素(①)と、マップの記号(②)のレンダリング を分ける」「レンダリング を分けた上で、①は1回だけレンダリング してレンダーテクスチャに保存、あとは①のレンダー結果を敷いてその上に②をレンダリング 」といったところです。説明が長くなりそうなので取りあえずは概念図と結果だけ貼っておきます。
ともかくこれで劇的に軽くなりました。まだ詰めるなら机全部のメッシュ統合は当然考えるべきでしょうが上記の通り普段使いしてたPro Drawcall OptimizerがDeprecatedにされておりひとまず見送り。(さっき別のアセットでBakeを試みました。結果は巻末で)
夕方からはライトマップの設定調整。512x512のテクスチャが20枚越えというのがアレだったので2048x2048に変更してパラメタ調整して云々。最終的には2枚に収まってはくれましたが、詰め込み過ぎの影響か机のフチに謎の輝きが発生したりと、焼き直し回数が多くなってしまいました。
なんとかライトマップを倒してようやく効果音とBGMの選定を開始。細かく言うことはないんですけど、アセットストアの効果音集って実際に聞く為に一度インポートしないといけないわけですが、2GBとかのMEGA BUNDLE的なヤツだとダウンロードから展開まで数分、さらにインポートで30分とかかかって心が折れそうでした。効果音系のアセットは買ったらまず空プロジェクトなどに展開しておく のをお勧めします……。実物を聞きながら選定したいときにメチャクチャ時間無駄使いしてしまうので。
3日目(5/4) 公開まで
効果音組 込
時間ができてしまったのでステージ増加
公開
unityの効果音組 込で毎度困るのが
特定のGameObjectに紐付かないシステム系の音の取り扱い
シーンを跨いで鳴って欲しい音をどこで保持するか
じゃないかと個人的に思っています。
今回はメテオフォールで一躍時の人となって今は🦍 の人として有名(?)なEIKI`さんのスクリプト をベースに実装しました。
eiki.hatenablog.jp
BGMも複数鳴らしたかったので下記の通りのコードに変更。
using UnityEngine;
namespace DNASoftwares.ComikeCarryIn
{
public enum ESeID
{
_None,
Decide,
Cancel,
Select,
SignalCount,
SignalStart,
CheckPoint,
CheckPointLast,
Launch,
_Max
}
public enum EMusicID
{
_None,
Title,
Briefing,
Stage0,
Stage1,
Stage2,
Finish,
TimeOver,
Result,
_Max
}
public class AudioManager : SingletonMonoBehaviour<AudioManager>
{
const int cSEMax = (int ) ESeID._Max;
const int cBGMMax = (int ) EMusicID._Max;
AudioSource mBGM = null ;
AudioSource[] mSEList = new AudioSource[cSEMax];
AudioSource[] mBGMList = new AudioSource[cBGMMax];
[SerializeField] private bool _noBGM;
void Awake()
{
base .Awake();
DontDestroyOnLoad(gameObject);
var t = transform;
for (ESeID e = ESeID._None + 1 ; e < ESeID._Max; e++)
{
mSEList[(int ) e] = GetComponentSafe<AudioSource>(t, e.ToString());
}
for (EMusicID e = EMusicID._None + 1 ; e < EMusicID._Max; e++)
{
mBGMList[(int ) e] = GetComponentSafe<AudioSource>(t, e.ToString());
}
}
public void PlaySE(ESeID id)
{
if (mSEList[(int ) id] != null ) mSEList[(int ) id].Play();
}
public void PlayBGM(EMusicID id)
{
if (_noBGM) return ;
if (mBGM != null && mBGM.isPlaying) mBGM.Stop();
mBGM = mBGMList[(int ) id];
if (mBGM != null ) mBGM.Play();
}
public void StopBGM()
{
if (_noBGM) return ;
if (mBGM == null ) return ;
mBGM.Stop();
mBGM = null ;
}
private Type GetComponentSafe<Type>(Transform parent, string gameobject_name)
where Type : Component
{
Transform temp = parent.Find(gameobject_name);
if (!temp) return null ;
return temp.GetComponent<Type>();
}
}
}
効果音の選定は散々苦労しましたが実装はあっさり。
時間ができてしまったので難易度を下げた初級コースをちょいちょいと作成。もうちょっと説明を詳しく入れるとかしてもよかったんですけどそこまでは時間がなかった。
UI周りはArborであらかたなんとかなってしまうので助かってますが、モーダル的なものを実装するときはCanvas Groupの「Intractable」とか「Block Raycast」も変更したいところ、Arbor標準のBehaviorではここに手が届かないので取り急ぎ自作。
それでもなんとか3日目の搬入時間帯には間に合わせたのでした……。(動画撮影で手間取って予定から20分遅れたけど)
このあとPVを作ろうと思い始めてまた夜更かししてしまうわけですがそこは別記事にします。もうすでに長いし。
走り終わって
久しぶりの超短距離走 ゲーム開発でしたが、最初から最後までかなりモチベーション高く走りきることができました。
時間が無いことがわかりきっていた上、 「コミケ 会場をトラックで疾走して荷物を搬入する」という見せたい構図がはっきりしてたのでそれに関係の薄い要素を全部切り捨てる勇気が持てた
オレオレマネージャなどを主張しない使いやすいアセットに恵まれた
Twitter に投稿するとけっこう反応が貰えた
おかげさまで多くの方に遊んで頂けたようで、中にはこちらの想像を超えるプレイングで最速をたたき出すプレイヤーも……
壁サー前をバックで疾走するトラックを想像するとジワジワ来ます。
なお、
……もうちょっとだけ続くんじゃ。
おまけ:Mesh Bakerで机モデルを統合したら
左:ビフォー 右:アフター
Box Colliderが統合できないためGameObjectは減らせなかった(統合前モデルのMeshRendererだけ無効にした)のにこの数字。ウーン効果てきめん……あとでビルドしてアップデートします。