今回はゲームの機能を作成するのではなく、AwakeとStartの違いと現状のプログラミングの仕方の問題点を見ていきます。
通常のクラスはMonoBehaviourを継承してクラスを作成していますが、MonoBehaviourで用意されているメソッドでAwakeとStartというものがあります。
この二つは同じタイミングで実行されるような気がしますが、実は違います。
わたしの場合あまり気にせずすべてStartメソッドで変数の初期化、ゲームオブジェクトの取得を行っていました。
なぜそうしているかと言うと、購入した本の中で他に設定しているゲームオブジェクトを取得する際は、Awakeだと初期化処理がまだ行われておらず取得出来ない可能性があるので、Startメソッド内でやると良いと書いてあったからです。
AwakeとStartの違いをマニュアルで調べる
Unityのマニュアルを見てみると、
・AwakeはStartメソッドの前およびプレハブのインスタンス化直後(ゲームオブジェクトが有効である事)
・Startは最初のUpdateフレームが呼び出される前
となっています。
Awakeの方が先に呼び出されるんですね、ならフィールドの初期化はAwakeの方が良さそうです。
Awakeでは他のスクリプトやゲームオブジェクトの参照を取得するのはいいですが、参照先のAwakeの処理が終わってない可能性がある為、値の取得はやらない方がいいみたいです。
AwakeとStartの違いをテストスクリプトを作成し検証する
Unityのチュートリアルでもありますが、AwakeとStartの実行順序確認の為、以下のスクリプトを用意して、試してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | using UnityEngine; using System.Collections; public class AwakeStartTest : MonoBehaviour { void Awake() { Debug.Log ("Awake"); } void Start () { Debug.Log ("Start"); } } |
ちゃんとAwakeが先に表示されます。
このAwakeなんですが、実はスクリプトが非アクティブでも実行されます。
上のように設定したスクリプトのチェックを外し非アクティブ(enabledがfalseの状態)にしておきます。
Unityの実行ボタンを押して確認してみます。
上のようにスクリプトを非アクティブにしていてもAwakeメソッドは呼ばれます。
ただ、上のようにスクリプトを設定しているゲームオブジェクトのチェックを外して非アクティブ(activeSelfがfalse)にしていた場合は、スクリプトがアクティブ・非アクティブに関わらずゲームオブジェクトがアクティブにならないとAwakeメソッドは呼ばれません。
ゲーム実行中にゲームオブジェクトをアクティブにするとスクリプトのアクティブの状態にかかわらずAwakeメソッドが呼ばれます。
コンポーネント取得の際の注意
スクリプトを取りつけたゲームオブジェクトがあり、そのスクリプトのStartメソッド内でコンポーネントの取得をしているとします。
しかし、UIなど最初から表示したくないゲームオブジェクトはインスペクタでチェックを外して配置している事もあります。
そんな時に外部のスクリプトからそのUIをアクティブにし、メソッド内でStartメソッド内で取得するはずのコンポーネントにアクセスしようとするとエラーになる可能性があります。
試しにMainCameraにTest2というスクリプトを作り取りつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class Test2 : MonoBehaviour { public GameObject directionalLight; // Update is called once per frame void Update () { if(Input.GetButtonDown("Fire1")) { directionalLight.SetActive (true); directionalLight.GetComponent<Test>().GetComponentTest(); } } } |
マウスの左ボタンを押したらインスペクタで設定したゲームオブジェクトをアクティブにし、そのゲームオブジェクトに設定されているGetComponentTestメソッドを呼びます。
Directional LightゲームオブジェクトにTestスクリプトを作り取りつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using UnityEngine; using System.Collections; public class Test : MonoBehaviour { private Light myLight; void Awake() { Debug.Log ("Awake"); } void Start() { Debug.Log ("Start"); myLight = GetComponent <Light> (); } public void GetComponentTest() { Debug.Log("Test"); Debug.Log(myLight.name); } } |
Startメソッド内でLightコンポーネントの取得をします。
GetComponentTestメソッドでは取得したコンポーネントの名前を出力しています。
これでサンプルが出来ました。
まずはDirectional Lightをアクティブの状態でUnityを実行しマウスの左ボタンを押します。
最初にAwake、次にStartが実行された後に画面内でマウスの左ボタンを押すとライトの名前が表示されます。
次にDirectional Lightを非アクティブにしてUnityを実行します。
Directional Lightが非アクティブの為、最初は何も表示されません。
その後、画面内でマウスの左ボタンを押すとメソッドが実行されますが、Startメソッドの前にGetComponentTestメソッドが呼ばれてしまいlightフィールドにはNullが設定されています。
このようにアクティブにした直後にそのスクリプトのメソッド内でStartメソッドで取得するはずのコンポーネントを使おうと思うと取得出来ていない場合があります(今はエラーが出ないかも?)。
その為、最初に非アクティブにしたゲームオブジェクトのスクリプトではその中で使うコンポーネントを実行するメソッド内で再度取得するようにした方がいいかもしれません。
OnEnableメソッドを使う
先ほどのスクリプトだとNull参照になってしまいますが、Directional Lightゲームオブジェクトに設定したTestスクリプトにOnEnableメソッドを作成し、そこで自身のLightコンポーネントを取得するようにすればエラーが発生しません。
OnEnableはMonoBehaviourクラスで定義されているメソッドでゲームオブジェクトがアクティブになった時に実行されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using UnityEngine; using System.Collections; public class Test : MonoBehaviour { private Light myLight; void Awake() { Debug.Log ("Awake"); } void Start() { Debug.Log ("Start"); myLight = GetComponent <Light> (); } public void GetComponentTest() { Debug.Log("Test"); Debug.Log(myLight.name); } void OnEnable() { myLight = GetComponent <Light> (); } } |
↑のようにしておくとDirectional Lightがアクティブになった時にLightコンポーネントを取得するのでエラーになりません。
OnEnableに関しては
↑も参照してみてください。