この記事で述べていること
- Unityでシーンを読み込むたびに、静的フィールドの初期化メソッドを一度だけ呼び出したい。
- そんなときは静的コンストラクタで
SceneManager.sceneLoaded
に初期化メソッドを追加すればよい。 - この方法に辿り着く前に検討した方法と、採用しなかった理由はこういうものです。
- もっといいやり方あったら情報ください。
使用バージョン
Unity 2018.4.5f1 Personal
説明
Unityでは静的フィールドの中身はシーンが変わっても保持されます。
しかし、時には「シーンを読み込むたびに初期化したい!」「でも静的フィールドにはしておきたい!」と思うときもあると思います。少なくとも私はありました。
そんなときは、以下の方法を使えばシーンを読み込むたびに静的フィールドの初期化メソッドを一度だけ呼び出すことができます。
1.まず、以下のようにクラスの静的コンストラクタを用意します。
これは、静的フィールドを初めて参照した際、参照する直前に呼び出されるコンストラクタです。これを使えば静的フィールドが参照される前に一度だけ、処理を行うことができます。
public class Example{
private static int staticField_;
static Example(){
}
}
2.次に初期化用のメソッドを用意し、そのメソッドを静的コンストラクタ内でSceneManager.sceneLoaded
に追加します。
これはUnityAPI側で用意されているイベントで、シーンが読み込まれた際にここに追加されたメソッドが呼び出されます。
第一引数には読み込まれたシーン、第二引数にはシーンの読み込みモードが入ります。この引数は追加するイベントに合わせて付ける必要があるため、省くことはできないことに注意してください。
また、SceneManagerクラスはUnityEngine.SceneManagement
名前空間にあるため、インポートする必要があることにも注意してください。
using UnityEngine.SceneManagement;
public class Example{
private static int staticField_;
static Example(){
SceneManager.sceneLoaded += init;
}
private static void init(Scene loadingScene, LoadSceneMode loadSceneMode){
staticField_ = 0;
}
}
実行タイミング
「シーンを読み込んだ際に呼び出される」と書きましたが、その実行タイミングは
1.Awake
2.sceneLoadedに追加したメソッド(ここで実行される)
3.Start
となっているらしいです。つまりこの方法でも、自他のスクリプトのAwakeで静的フィールドを参照するようなコードを書くと、未初期化の値を参照してしまう恐れがあるので、その点は注意してください。
こうしなかった理由
これを考えつく前に検討した方法と、そうしなかった理由です。
1.わざわざ初期化メソッドを追加しなくても、静的コンストラクタで初期化すればいいんじゃないの?
静的コンストラクタは、初めて静的フィールドを参照した際に一度だけ実行されます。
「ならそれで初期化すればよくない?」と思われるかもしれませんが、Unityでは静的フィールドの値が保持される関係で、最初の一回のみ「初めて参照した」と判断されるらしいです。つまるところこれで初期化してしまうと、最初の一回以外はいくらシーンを切り替えようがゲームを終了しない限りはもう二度と初期化されません。
2. Awakeで初期化すればいいんじゃない?
このスクリプトが1つしか張り付いてない・確実にシーンにそのスクリプトが張り付いているオブジェクトがアクティブで存在している保証があるならまあそれでもいいんですが…いくつか以下のようなデメリットが出る場合があるので採用しませんでした。
デメリット1:Awakeは各インスタンス毎に実行されるため、必然的に複数張り付いていればそれだけ初期化メソッドが実行されます。 初期化が重ければ、それだけシーンのロードが重くなってしまいます。
デメリット2:「なら静的フィールドのフラグを作って一度だけ実行されるように管理すれば…」と思われるかもしれませんが、それだけの理由でフィールドが1つ増えてしまうことになりますし、そのフラグをStartなりでfalseにしてやる手間が増える(シーンの最初で再度初期化を走らせるため)ので、好ましくありません。
デメリット3:Awakeは「オブジェクトがアクティブのとき、インスタンスがロードされたタイミング」に実行されます。つまり、何か理由があって「オブジェクトを最初は非アクティブにしておき、後でアクティブにする」ということをしていた場合、初期化メソッドが走るタイミングはシーンの最初ではなく、そのタイミングになってしまいます。(オブジェクトがアクティブならスクリプトが非アクティブでもAwakeは実行されます)
デメリット4:そのスクリプトが張り付いているオブジェクトがなければ、必然Awakeも実行されません。つまりそのスクリプトが張り付いているオブジェクトが存在しないシーンでその静的フィールドを参照した場合、未初期化の値が取得されてしまいます。
最後に
これ本当に一番いい方法なんでしょうか?もし「違うよ」って人がいましたら連絡ください。
参考サイト
- 【Unity】シーンの切り替えを検出するイベント
https://tech.pjin.jp/blog/2018/10/24/unity_scene-manager_event/#10 - OnLevelWasLoadedが非推奨になったからSceneManager.sceneLoadedで置き換えてみる【Unity】
https://kan-kikuchi.hatenablog.com/entry/sceneLoaded - UnityのメソッドAwakeとStartの違い
https://gametukurikata.com/program/awakestart