unity ゲームが完成に近づいていくと、どのシーンでも共通で欲しいデータ・処理をどうするか、という問題にあたります。
- 必要なゲームデータ
- シーン共通のフェードイン・アウトといった演出
- Editor でどのシーンから始めても、これらが常にいてほしい
どう実装していいのかわからないので、色々と試してみました。
Monobehaviour 消して static 変数にアクセス
Main.cs を任意の GameObject につけて使います。
(Main.cs)
public class Main
{
public static int DataA;
public static int DataB;
public static int DataC;
}
(Caller.cs)
public class Caller
{
public void Call()
{
Main.DataA = 1;
Debug.Log(Main.DataA);
}
}
簡単。データのやりとりだけならこれでもいいかも?
シーン跨いだらデータ消える
間違って2つの GameObject につけたらおかしくなりそう…?
全シーンの GameObject にくっつける必要があって大変
(結論)全然要件を満たせない
Singleton Monobehaviour
こちらより拝借しました。これも任意の GameObject につけて使います。
https://qiita.com/okuhiiro/items/3d69c602b8538c04a479
(SingletonMonoBehaviour.cs)
using System;
using UnityEngine;
public abstract class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance;
public static T Instance
{
get
{
if (instance == null)
{
Type t = typeof(T);
instance = (T)FindObjectOfType(t);
if (instance == null)
{
Debug.LogError($"No GameObject with attached {t}";
}
}
return instance;
}
}
virtual protected void Awake()
{
if (this != Instance)
{
Destroy(this);
Debug.LogError($"Multiple {typeof(T)} cannot exist. I deleted it from this. (Original = {Instance.gameObject.name})";
return;
}
DontDestroyOnLoad(this.gameObject);
}
}
(NewBehaviourScript .cs)
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : SingletonMonoBehaviour<NewBehaviourScript>
{
public int DataA;
override protected void Awake()
{
// Be sure to call
base.Awake();
}
}
(Caller.cs)
public class Caller
{
public void Call()
{
NewBehaviourScript.Instance.DataA = 1;
Debug.Log(NewBehaviourScript.Instance.DataA);
}
}
まぁまぁ簡単。シーン跨いだらデータ消える ← DontDestroyOnLoad になるため、消えない間違って2つの GameObject につけたらおかしくなりそう…? ← エラーで確認できる
NewBehaviourScript のあるシーンからしか開始できなくなる
ゲーム作成中は、色々なシーンからスタートしたいので、ちょっと使いづらいです。
一応、シーン起動時、例えばメインカメラに自動的に NewBehaviourScript をスクリプトでアタッチすれば大丈夫…? こんな感じで(実行はしていません)
(Initialize.cs)
public class Initialize : MonoBehaviour
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void afterSceneLoad()
{
if (NewBehaviourScript.Instance == null)
{
Camera.main.gameObject.AddComponent<>();
}
}
}
こうなってくると、実行までヒエラルキーに存在しないので、ヒエラルキーで構造を把握しにくくなりました。
また、データ管理ならともかく、シーン共通のフェードインアウトなんかはちょっと入れづらそう。
おすすめ:マスターシーンを作成し、どのシーンから起動しても呼び出されるようにする
この方法を愛用しています。1番応用が利くと思うので。
元ネタはテラシュールブログさんだったのですが、探しても見つからなかった…(いつもお世話になっております)。
手順はこんな感じです。
- 最初に起動したシーンがマスターシーンでなければ、一旦オブジェクトを全て Disabled
- マスターシーンを起動
- マスターシーンのオブジェクトを全て DontDestroyOnLoad に
⇒ これでシーン移動しても消えなくなります - (3) が完了したら、最初に起動したシーンを元に戻す
- マスターシーンの残骸(.unity)を消去
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
この属性がついたメソッドは特殊で、以下のような便利な特性を持っています。
- GameObjectについてなくてもコールされる
- 起動シーンのAwakeよりも早く実行される
これを使って、最初のシーンが起動するよりも先にマスターシーン(常駐)を起動、実行させる方法です。
(Main.cs)
/// <summary>
/// Master Scene
/// </summary>
public class Main : MonoBehaviour
{
static GameObject[] childSceneObjects = null;
/// <summary>
/// Program entry point
/// c の main() に相当するメソッド
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void main()
{
// launch check
string sceneName = SceneManager.GetActiveScene().name;
// if: launch different from 'Main'
if (sceneName != "Main")
{
// Disable child scene
setChildSceneActive(false);
// load 'Main' scene
SceneManager.LoadSceneAsync("Main", LoadSceneMode.Additive);
}
}
/// <summary>
/// Make the Main scene 'Don't destroy'.
/// Main シーンを常駐化する
/// </summary>
void Awake()
{
StartCoroutine(awaker());
}
/// <summary>
/// Create 'Don't destroy' scene (Main).
/// </summary>
IEnumerator awaker()
{
// 'Main' moved 'DontDestroyOnLoad'
UnityEngine.Object.DontDestroyOnLoad(this.gameObject);
// Enable child scene
if (childSceneObjects != null)
{
setChildSceneActive(true);
childSceneObjects = null;
}
Scene scene = SceneManager.GetSceneByName(eScene.Main.ToString());
// loaded になるまで unload できないので、ここで待つ
while (scene.isLoaded == false)
{
yield return null;
}
// delete source scene
yield return SceneManager.UnloadSceneAsync(scene);
}
/// <summary>
/// Activate child scene.
/// </summary>
static void setChildSceneActive(bool value)
{
if (childSceneObjects == null)
{
childSceneObjects = Object.FindObjectsOfType<Transform>()
.Select(t => t.root.gameObject)
.Distinct()
.Where(go => go.activeInHierarchy)
.ToArray();
}
foreach (GameObject go in childSceneObjects)
{
go.SetActive(value);
}
}
}
// Disable child scene
setChildSceneActive(false);
// load 'Main' scene
SceneManager.LoadSceneAsync("Main", LoadSceneMode.Additive);
- 最初に起動したシーンがマスターシーンでなければ、一旦オブジェクトを全てDisabled
- マスターシーンを起動
// 'Main' moved 'DontDestroyOnLoad' UnityEngine.Object.DontDestroyOnLoad(this.gameObject);
- マスターシーンのオブジェクトを全て DontDestroyOnLoad に
⇒ これでシーン移動しても消えなくなります
// Enable child scene
if (childSceneObjects != null)
{
setChildSceneActive(true);
childSceneObjects = null;
}
- 4(3) が完了したら、最初に起動したシーンを元に戻す
// loaded になるまで unload できないので、ここで待つ
while (scene.isLoaded == false)
{
yield return null;
}
// delete source scene
yield return SceneManager.UnloadSceneAsync(scene);
- マスターシーンの残骸(.unity)を消去
マスターシーンには全てのシーンで使うデータを色々と入れておきます。
DontDestroyOnLoad に移動した時、バラバラにならないように、Main というルート GameObject の中に全てを入れておきます。

Camera .. BgRoot を表示するため入れました
EventSystem .. 全てのシーンに入れてたら、必要なし
BgRoot .. アプリケーションで表示する背景です
VoiceClip .. 音声データ登録
DebugDisp .. デバッグ用の情報表示

Main に Main.cs をアタッチ
この状態になったら新しいシーンを作成し、実行します。
以下のように、どのシーンから開始しても DontDestroyOnLoad に Main シーンが作成&実行されます。




