作ってて気付いた、小ネタです。
Unityを触りはじめてから、最初はプロトタイプ的なものを作って、次の段階にはスタート画面からゲーム本体、ゲームオーバーやポーズなど、画面全体を切り替える実装に進むパターンが多いです、そこで何を使ったら良いか、考えてみましょう。
画面を切り替える方法の幾つかを比較
Unityで、例えばスタート画面からゲーム本体など、画面の切り替えをしたいとき、幾つか方法があります。
●方法1:Sceneを一つのままGameObjectなどを表示、非表示で制御
Scene中にGameObjectを置いた状態でgameObject.SetActive (false);
の状態からtrue
に切り替えるかenabled=false
からtrue
にする、画角の外においた状態から画角内に座標を変える、などの方法をとります。
- 読み込み時間などを考慮するとこれが良いと推奨されてきた方法
- ◯ 読み込みを始めに済ませておくと表示に時間はかからない
- ◯ データが残っているのでもとに戻すのも容易、ガベージコレクション回避に繋がる
- ✘ データを全部Scene内で定義するので分離できない(Prefab化して別個にすることは可)
とりあえず実験的にやる場合には、手間がかかりません。
●方法2:Prefabで用意、Object.Instantiate
で配置
表示したいものをPrefabで別個に用意して、表示したい時にObject.Instantiate
で画面上に登場させます。
- ◯座標も設定しておけば実装は簡単
- ◯ファイルで独立するので別個に作って統合は可能(GUIDに注意)
- △エディタでは、実行しないとInstantiateした状態の確認ができない
- △読み込みに少し時間を要する
- △メモリを後から消費するのでガベージコレクションなどで不利になる可能性
Sceneがごちゃごちゃしないのが良いですが、重ねて読み込むとメモリ消費が増えます。要らない他を破棄すればいいとなると、ガベージコレクションの誘引にもなるので、そこまでするならSceneごと読み込むほうが良いです。
●方法3:別個にSceneを用意してLoadする
エディタで画面を別個のSceneとして作っておき、それをSceneManager
(5.3以降)で読み込ませんる方法です。旧バージョンだとApplication.LoadLevel
などで実装していました。
- ◯Sceneが独立するのでファイル単位で別データになり分担を分けやすい
- ◯Singleならスッキリ全部、切り替わる
- △Scene間で共有したいデータは
Static
等で破棄されない構造にする必要がある - ✘読み込みと初期化で時間がかかる場合がある
こういう感じになります。
画面切り替えの選択
こうしてみると、以下のルールが明らかになります。
- 既に配置状態のものは時間がかからないが最初からメモリを食う可能性がある
- 後から読み込む、要らないものは破棄にすると時間が掛かるがメモリを消費しない
ということです。これは他の処理でも同様です。あとは別個に分担するなどでデータがファイルで別れたほうが良いか、等で選ぶと良いでしょう。以下は一例です。
適したパターンの比較
切り替えの種類 | 適した方法の例 |
---|---|
画面に在るものに重ねたい、出したり隠したりが多い | 常駐させて表示・非表示の切り替え |
たまにしか出さない、それによって動作が緩慢になっても気にならない(ポーズ画面など) | Prefabで用意 |
画面が全面的に切り替わり、元の表示が要らない(スタート画面など) | Sceneを切り替え |
これが必ずしも正解かは分からないので、そのゲームにあった方法を選択してください。
プロトタイピングする順番
表示の状態を試す場合は以下が良いと思いますが、予め、設計の方針が決まっている場合はそれに従いましょう。
- 先ずは単一Sceneで作って表示・非表示の切り替え
- Sceneが肥大化したらPrefab分けて
Instantiate
、何度か使う場合は破棄しないでメモリに置いたままにする - 画面が全部、切り替わってその際のタイムラグがプレーヤーのストレスが少ないならSceneで分ける
例えば、ゲーム中にキャラクターのステータスを出すなら元からある画面に重ねるし共有する値が多いのでCanvasに作っておいて表示・非表示を切り替える。アクションゲームでのゲームオーバーからリスタートでメニュー画面へ遷移なら、プレイヤーはゲームオーバーの時点で少し間が空いてもそこで休憩する意思が働くし画面が全体で切り替わるので、Scene全体で切り替える、等です。
Scene切り替えで何を使うべきか
Sceneの読み込みには、以下の2つが在り、違いを理解すると良いプレイヤー体験を提供できます。
-
SceneManager.LoadScene
- 実行すると読み込み状態に入る
- 読み込み中にも入力などが受け付けられバッファリングされる
- https://docs.unity3d.com/jp/current/ScriptReference/SceneManagement.SceneManager.LoadScene.html
-
SceneManager.LoadSceneAsync
- 実行すると非同期にバックグラウンドで読み込む
- 表示中のSceneから変わらず、読み込みが完了後に切り替わり、表示される
- https://docs.unity3d.com/jp/current/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html
モードをLoadSceneMode.Single
にすると、上記の何れでも入れ替え動作になります。
ここで、どちらが良いかというと、以下の理由に因りSceneManager.LoadSceneAsync
を勧めます。
-
LoadSceneMode.Single
は画面が固まったような状態になる - 読み込みで固まっていてもタップなどの入力が効き、読み込まれた後のScene側で対処していない場合は、イベントが入ってくることがある
これが厄介で、画面の固まった状態で連続タップしてると次に読み込まれたシーンの操作をしてしまう状態になります。もちろん、画面が固まったような状態はプレイヤーに見せるすべきではありませんから、このパターンに限らず「今、何が起こっているか」を分かる状態にしておくためにも、SceneManager.LoadSceneAsync
が重宝されます。これを使った時の難点は非同期で読み込むので影で読み込んでる間はメモリを多く消費し、ガベージコレクションの誘引などを考えると、以下に該当する場合は適しています。
[SceneManager.LoadSceneAsync
の適したパターン]
- 現在のSceneのオブジェクトがほぼ次の表示で要らなくなる
- LoadするSceneのデータもその後、
SceneManager.LoadSceneAsync
で置き換えられる
#まとめ
データの読み込みや破棄なので、また使うであろうデータを破棄してから再び読み込むと、メモリの取得と破棄により無駄な処理が入り、更には分断化したためにガベージコレクションが起きます。ただし、それだけを考えるとメモリをどんどん消費し、結果的にそのためのガベージコレクションがあり得ます。プロファイラで様子を見ながら試しましょう。
最優先すべきはプレイヤーの体験を最良にする方法です
##参考
メモリ管理については以下を参照してください。
[Unity マニュアル/自動メモリ管理を理解する
https://docs.unity3d.com/jp/current/Manual/UnderstandingAutomaticMemoryManagement.html]
(https://docs.unity3d.com/jp/current/Manual/UnderstandingAutomaticMemoryManagement.html)
初心者以上にとても参考になる記事
こちらの記載が管理システムにも研究されていてとても参考になります。
https://madnesslabo.net/utage/?page_id=11109#i
こちらにもいろいろ書いています。参考にしてみてください。
[Unityチュートリアルindex
http://qiita.com/JunShimura/items/f87c599b3738b804f605]
(http://qiita.com/JunShimura/items/f87c599b3738b804f605
)