ほくそんめもろぐ

主に最近見た映画ログや自分のためのメモ

Unityでの単一シーン構造について調べてみた

先日、友人とUnityの話をしていると、「うちではシーンは1つしか使わないよ~」と言われました。 
僕はマルチシーンでの制作しかしていなかったので、かなり衝撃的な発言でした。
  
というわけで、その手法についていろいろ調べてみたのでまとめてみます。 
でも検索しても海外サイトでしか情報が出てこないんですよねえ

検索キーワードを絞り込んだ結果、どうやらシーン1つを使用する手法は、「Single Scene Architecture」というらしい。
和訳すると「単一シーン構造」 でしょうか?
 
どうやら、Unity Technologies社のBrett Bibby氏が2014年に紹介したものらしい。
いったいどういったものなのか見てみましょう。

単一シーン構造とは ?

これまで、通常Unityでゲームを開発するときは、実装したい機能、画面に応じてシーンを作成していました。
たとえば、SplashScene、TitleScene、StageSelectScene、GameScene、ResultSceneなどなど・・・

しかし、単一シーン構造では、1つのシーンのみしか使いません!
起動直後はそのシーンにあるのはMainCameraのみで、他のオブジェクトはありません。
必要なオブジェクトは、スクリプトから必要なときに、リソースファイル(XMLjson、prefabなど)を使って作成します。

もちろん、Unityのシーン管理を使っていないので、リソースの管理は自分自身で行う必要があります。

つまり、単一シーン構造の概念とは、最小限のオブジェクトのみから始まり、必要なときにスクリプトからリソースをロードするということです。

メリットは何?

1つは、シーン遷移ごとのローディングをなくすことができることです。

マルチシーンでの制作では、シーン遷移ごとにローディングが発生します。
ローディングアニメーションを作成するなどのアプローチはありますが、面倒な実装には変わりありません。

対してシングルシーンでは、静的なデータをタイトル画面中から読み込んだり、ゲーム中に随時読み込んだりする方法がとれるので、ゲーム起動時以外のローディングを可能な限り少なく、うまくすればなくすことだって可能です。


もう1つは、UnityのMonoが行うメモリ管理から逃れられるということです。

Unityアプリケーションは、実行時に2つの異なるメモリを使用します。
1つはシーン上のオブジェクトなど、Unity自体が生み出したオブジェクトを読み込むメモリ。
もう1つはスクリプトを使用して作成したオブジェクトを管理するメモリです。

そして、Unity自体が生み出したオブジェクトは、再利用されることを考えて、使われなくなってもしばらくはメモリをプールしておくようになっています。
このせいで、アクションゲームなどではメモリがすぐにいっぱいになってしまい、ガベージコレクションが働いてゲームが処理落ちしたりしてしまいます。

これまでのガベージコレクション対策としては、事前に使用しそうなオブジェクトをインスタンス化して、明示的にオブジェクトを再利用することでメモリプールをさせないようにするなどの方法がありました。

単一シーン構造にすると、必要なものはすべてスクリプトを通してに作成されるため、メモリプールをすることはありません。
その代わり、使わなくなったオブジェクトは自分で破棄する必要があります。

どうやって実装する?

全てのオブジェクトがリソースファイルから作成されるので、まずはリソースファイルを読み込み、その情報に応じてオブジェクトを作成する処理が必要そうです。
また逆に、ゲームの進行度やキャラのステータスをリソースファイルに保存する処理も必要ですね。
  
もちろん、リソースファイルにオブジェクトの座標、種類、コンポーネントを直に書き込む必要はありません。
まずはエディタでいつも通り画面を作成し、それらのオブジェクト群の情報をスクリプトでまとめてリソースファイルに書き込めばよいのです。

オブジェクト同士のやりとり

Find関数でヒエラルキーからオブジェクトを検索するのはいい考えではありません。
現在ゲームで表示されているものだけでなく、事前に読み込んでいるデータなどいろいろなデータが存在するからです。
Unityの機能以外の方法を使って、例えばシングルトンでマネージャーを作るなどしてオブジェクト間のアクセスをする方がよいです。

スクリーンの管理

(元のサイトでは「シーン」という名前でしたが、同じ名前の用語がいっぱいで混乱するので名前変えました。 もっと適切な名前があるかもです。)

ここでいうスクリーンとは、「メニュー画面」「タイトル画面」「リザルト画面」など、マルチシーン構造でシーンにするようなもののことを指します。

スクリーンごとの動作/遷移はスクリプトで管理します。
例えば、シーン間で遷移するオブジェクト(フェードイン、フェードアウト、いつ点灯するかなど)、ボタンの実装(このボタンが押されたときに何をするか)などです。
  
今回、これらの機能はすべてScreenControllerというクラスで行います。(名前は仮。元サイトではSceneControllerでした)

クラスの内容としては、

Awake()でリソースをロードし、
OnEnable()で現在のスクリーンから遷移を開始。
Update()でボタン入力を検知し、
OnDestroy()でリソースを削除する。

この流れをスクリプトとして作成します。

また、スクリーンの遷移を管理するシングルトンとして、ScreenManagerを用意します。
スクリーンを変更する必要があるときは、ScreenManagerから関数のSwitchScreen()を呼び出します。
例えば、SceneManager.SwitchScreen(Screen.hogeScreen)のようになります。
      
SwitchScreen内でhogeScreenControllerスクリプトをメインカメラに添付します。 Awake()が働き、リソースをロードします。
Awake内の処理が完了すると、onEnableが呼ばれます。そこで、ゲームはロードされた新しいスクリーンに移行します。
同時に、以前にアタッチされた前のSceneControllerスクリプトを破棄するようにします。

デバッグ

単一シーン構造は最小限の性質しかないため、デバッグや変更が困難というデメリットがあります。

簡単な解決策は、2つのプロジェクトを使用することです。
1つはゲームを設計するためのもので、もう1つはリリースバージョンの単一シーン構造プロジェクトです。

Unity3Dには優れたエディタ機能がありますから、開発プロジェクトの方では、洗練されていて使い勝手の良いエディタを使いまくりましょう。

そして完成したものをリソースファイルにうつし、単一シーンプロジェクトで読み込んで使用することで、両方のメリットを最大限に活用できます。

まとめ

単一シーン構造では、MainCameraオブジェクトとScreenManagerスクリプトが添付された単一のシーンで構成されます。
ScreenManagerはすべてのスクリーンを管理し、現在のスクリーンに従って、対応するScreenControllerスクリプトをMainCameraにアタッチします。
このScreenControllerは、リソースファイルをロードし、それを使用してシーンにオブジェクトを作成し、それらの管理を行います。

Unityで単一シーン構造を実装すると、いろんなメリットがありますが、デバッグや変更が困難です。
そのため、開発とビルドを分けた2つ以上のプロジェクトで行うと良いでしょう。

参考

alican.co
blog.hitnoodle.com

※記事中に間違い等ありましたらお知らせください。