[unity2019.LTS]お手軽シーン切り替え:前説

unity のシーン切り替えを使って、ゲームをお手軽に管理する方法を考えます。
ただシーンを切り替えるだけではなく、データ管理やリソース共有など、1つのアプリケーションで必要な部分も踏まえた使い方となっています。
個人製作や、インディーズ向けにはそこそこ使えるかもしれません❔

今回は概論、Resources.Load() の手軽さと問題点について触れ、次回その問題を解決できるような環境作成を例示します。

この動画は以下のフリーアセットを使用しています。問題があれば対処しますので、ご連絡ください。
Glowy Space - 2D Toon Parallax、Free Asset - 2D Handcrafted Art

unity では「シーン」という単位で表示されている画面を管理します。

ゲームだったら、タイトル画面、キャラ選択、ゲーム画面、等
アプリだったら、リスト一覧、設定画面、検索画面、等

シーンはそれぞれが独立しており、シーンが終わればそこで使っていた全てのオブジェクトが開放され、常にメモリが綺麗な状態になるよう設計されています。

これはとても便利な事ですが、反面、シーン共通でデータやリソースを管理できないデメリットも存在します。

  • 全ての画面に必要なデータ(設定情報とか、スコアとか)
  • 全ての画面に必要な画面演出(ロード演出とか)
  • どの画面でも使いたい絵素材(キャラクターの絵とか)

まずは簡単な管理方法を考える

それを管理するお手軽な方法として、以下のような形を考えてみます。

まず最初に Main Scene がいます。このシーンは「各シーンが必要な共通データ、リソース」を持っており、「ゲーム起動中は常に存在」します。
そしてこの Main Scene が必要に応じて各シーン(サブシーンと呼ぶことにします)を呼び出します。

メインシーンが SceneManager.Load() などでサブシーンを呼び出し、サブシーンの適当なクラスにメインシーンの情報を伝えれば、データの共有はすぐに出来そうです。
ただ、サブシーンのヒエラルキーで予め画像などを配置し、これがもし「どの画面でも使いたい絵素材(リソース)」だった場合、残念ながらその絵はサブシーンごとに格納されることになります。

これが嫌であれば、真っ先に思いつくのは Resources.LoadAsync() です。
シーン起動時に Resources.LoadAsync() で素材を読み込めば、「素材の共有化」が可能です。

これは一つの正しい解決法であり、小さめのアプリであればこれで管理している人も多いでしょう。
今回はもう一歩踏み込んで、この構造の問題を考えてみます。

Resources.LoadAsync() の問題点

Resources.LoadAsync() にキャッシュ機能はない
例えば TextMeshPro で日本語を表示して、どのシーンにも表示する必要があるとします。
この時、Resources.LoadAsync() だと、シーン解放時に一旦破棄する(のが一般的)ため、次のシーンでも日本語をロードすることになります。
これはかなり無駄で、ロード時間を長くする要因です。

Resources に素材をまとめることはできた。でも、シーンごとに毎回呼び出すんじゃあ…これを避けるためには、独自で Resources.Load のキャッシュ機能を実装する必要がありそうです。

Resources.LoadAsync() ではヒエラルキーに画像やモデルを置くことはできない
スクリプトで Resources.Load() を呼び出すため、ヒエラルキーで気軽に絵やモデルを置くことはできません。置いてしまった場合、例でいえば各シーンにも鳥さんが追加されてしまいます。

この制約のため、絵やモデルを見ながら Editor に配置するのが面倒です。
配置時に絵やモデルを載せ、調整が終わったら全て忘れずに破棄するか、実行時に位置を確かめるしか方法がないからです。
これではデザイナーさんが気軽に画面をレイアウトする、といった作業は難しそうです。

unity 以前はこれが当たり前だったため「それのなにが問題なの?」という方もいらっしゃるかと思いますが、個人的にはこれが開発期間を重たくしてしまう要因だと思っています。

シーン読み込みのカク付き制御
シーン読み込みのカク付きは、ヒエラルキー上に置いたオブジェクトの一斉展開と推察されます。
これについては完全な解決方法は難しいですが、「オブジェクトに使われるリソースを MainScene が起動時読み込んでおけば、緩和することは可能」かもしれません。

番外編:ゲーム機によっては開発ガイドラインにひっかかる(かもしれない)
Resources/ に突っ込めば起動が遅くなる > ゲーム機の許容起動時間に引っかかる
、といった部分がネックになる可能性があります。ゲーム機はスマフォやPCのようにロードは早くない事が多いです。

ロムだから早い、というファミコン理論は、最近の機器では通用しないことも。

インディーズゲームを世に出そう、という方であれば開発末期で泣きながら修正を強いられるかもしれません…。

問題解決策について

これらを、AssetBundle を使うことで解決してみようと思います。
Addressable Asset System でもおそらく可能だと思いますが、そこまで必要ないかな? と思うのと私があまり触れたことがないので😢 触れません。

AssetBundle の風当たりが強いのは、大きくこんな感じでしょうか。

  • Resources.Load より明らかに手続きが多い。非同期というのも直観的にわかりづらい
  • 課金ゲームなど、頻繁にアップデートが入るようなゲームで求められる複雑な管理が難しい

このうち、「Resources.Load よりは明らかに手続きが多い。非同期というのも直観的にわかりづらい」が今回の作成物(とその説明)で多少なりとも緩和されればいいなと思っています。
AssetBundle はコツさえ飲み込めば、インディーズゲームなど売り切りのものを作る場合、十分手軽で、力強い味方ではないかなーと。

次回、それらの問題を解決すべく、サンプル環境を提示します。