Cluster GAMEJAM 2020 in WINTER 『Entropy In The Room』制作記
Cluster GAMEJAM 2020 in Winter参加者のみなさま、お疲れさまでした。48時間を全力で楽しむことができましたでしょうか。わたしはとても楽しかったです。
これまでUnityやCluster Creator Kitを触ったことがなかったのですが、先人クリエイターたちの知恵を借りつつ、なんとか提出まで走りきれてよかったです。
コンテストへの初参加という体験は今回限りなので、ワールド作成の試行錯誤の中で自分なりに考えたことをまとめておこうと思います。
NOTE:
手探りで編み出したノウハウなので、ベストプラクティスではない箇所が多々あると思います。
あとマルチプレイのテストを忘れていたため、複数人プレイ時に思わぬ動作をするかもしれないです。
どんなワールド?
https://cluster.mu/w/1e380639-e9c0-4401-a3e6-4175486345cc
子供部屋を散らかすゲームです。制限時間180秒以内にできるだけ多くのものを掴み、そして離してください。
掴めるアイテムは全部で90個。同じアイテムで複数回掴む->離すを繰り返しても1点しか入りません。なので最大90点です。
コツ:
わざわざアイテムを離さなくても、アイテムを掴んだ状態で他のアイテムを掴むことで、もともと持っていたアイテムを離したことになります。これを利用すると一人プレイでも安定して90点取れます。
アイテムを手に持った状態で片付け用の扉とインタラクトすると、手元のアイテムのみリセットされません(バグ)。これを利用するとクローゼットの上に隠すように置いてあるドーナツを先に処理でき、スコアがより安定します。
いつでも投稿できる状態を維持する
このゲームは特性上部屋の内装が非常に大事です。しかし、家具やアイテムの作成・配置は後回しにして、それ以外の重要な要素(ゲーム初期化処理、タイマー、UIなど)の作成および後述するThrowableオブジェクトの整備を真っ先に片付けました。これには以下のメリットがありました。
- ワールド投稿締め切りに確実に間に合わせられる。家具や掴めるアイテムが少なくても、ゲームとして成り立っていれば投稿はできる。実際、アイテムを100個用意して100点満点としたかったところを90個に妥協している。
- 時間をかければかけるだけ成果物がよくなるフェイズを作業がダレてくる中盤~終盤にもってくることができる。二日目はロジックで悩んだりせずにひたすら家具を配置し続けることができた。
Throwableオブジェクト
掴めるアイテムは全てThrowableオブジェクトというラッパーの中にPrefabが入った構造となっています。
Throwableオブジェクトが担う役割は以下のとおりです。
- GrabbableItemの付与
- 離したときに前方に弾き飛ばすギミック
- 最初に離したときだけスコアを加算するロジック
- 残り時間が0秒のときのスコア加算を無視する&ゲームリセット時に再び加算するように戻すロジック
- ゲームリセット時にアイテムをもとの位置にRespawnするギミック
これらを各Prefabに逐一設定するのは大変ですが、このThrowableオブジェクトをAssets化することで、
- Throwableオブジェクトを配置
- 子オブジェクトにPrefabを配置
- 子のBoxCollider付与とサイズ調整
- Inspector画像に赤で示した3箇所を変更
という定型化した4ステップに圧縮することができました。これにより、作業に疲れてくる終盤でもほとんどミスすることなくたくさんのアイテムを配置できました。 作業の効率化の他にも、アイテムの形状に関わるComponentとゲームロジックに関わるComponentを分離できるというメリットもあります。
ゲーム内の工夫した処理
ものを手から離したときに弾き飛ばすロジック
OnReleaseItemTriggerでAddInstantForceItemGimmickをトリガーします。座標系をThrowableの子オブジェクトとし、Z方向に10(ここはお好み)を指定することで、アイテムが手から離れた瞬間に弾き飛ぶようになります。
散らかしたアイテムの初期化
ゲームでは、入り口のドアをインタラクトすると散らかしたアイテムがもとの位置に戻るようになっています。現状、アイテムを指定した座標に移動させるギミックやアイテムをRespawnさせるギミックといったものはなさそうだったため、WarpItemGimmickでDespawnHeightにワープさせることで擬似的にRespawnを実現しています。
リザルト画面の表示/非表示
(cluster公式のクリエイターコミュニティ(Discord) にて質問した結果解決したものです。応対して下さったnatsu_sanさんとvinsさんに感謝!)
残り時間が0秒のときだけUIにリザルト画面を表示したかったのですが、setTextGimmickはオブジェクトにつき1つしかアサインできないため、「あるトリガーで『RESULT:{0}』をセットし、別のトリガーで空文字列をセット」といった方法はとれませんでした。そこで、SetGameObjectActiveGimmickを使用してリザルト用のSafeArea自体の表示/非表示を切り替えることで同様の機能を実現させました。
その他ちょっとした小技
BoxColliderをオブジェクトに追加すると、通常はオブジェクトをちょうど含む大きさのボックスが生成されます。ところが、子オブジェクトをもつオブジェクトにBoxColliderを追加すると、ボックスの大きさが自動調節されず、各辺の長さが1のボックスが生成されてしまいます。この場合は、子オブジェクトの中からいいかんじのものをを見つけて、それにBoxColliderを追加&CopyComponentで親にペーストすると自力で調整する手間を省くことができます。
一部アイテムを壁にかけたり吊るしたりしてるように見えますが、実は透明な台に乗せているだけです。
至らなかったところ
Globalの扱いが雑
たとえばGlobalのキーを操作できるオブジェクトは一箇所にまとめてしまい、各アイテムはGlobalトリガーをSignalや定数で投げるだけ、みたいにした方が、ゲームシステムに関わる処理の記述が分散しなくてよさげな気がしました。たとえば時間管理やスコア管理などはこうした方がよかったかなぁと反省しています。
既知のバグ
一部のアイテムが投げるともとの位置に戻ってしまいます。薄いアイテムや小さいアイテムでよく見られます。原因はよくわかりませんでしたが、点数は加算されてるし、まぁいいか、と放置してあります。
その他
- ものに応じてmassを設定し弾き飛ばしやすさを調整したかったのですが、アイテム数を優先した結果割愛となりました。
- 当初は、ものを移動させた距離の総和をとり、これをスコアとする予定でした(その方が大胆に散らかしたくなるはず!)。しかし実現方法が思いつかず断念。
次に参加するときは今回得たノウハウを活かしつつも、まだ触ったことのない機能や要素をどんどん使ってみたいなと思います!