サイトアイコン Unity+AssetStoreおすすめ情報

Unity: Start / Update の不思議、その正体とは?

Unityで新しくスクリプトを追加すると生成される、Start / Update について不思議に思った事はないでしょうか?こいつらは、MonoBehaviour のメソッドをオーバーライドしていると思っている人多いますが実は違います。

Unityを知らない方は、ぜひ こちらの記事 をご参照ください。

不思議な現象

不思議1:親クラスには、Awake / Start / Update は存在しないので継承しているわけではない

それではまず、本命の MonoBehaviour を見ていきましょう。ふむふむ、Start / Update はいないみたい、でもまだまだ親クラスはいる次は Behaviour

namespace UnityEngine
{
    public class MonoBehaviour : Behaviour
    {
        public MonoBehaviour();
        public bool useGUILayout { get; set; }
        public bool runInEditMode { get; set; }
        public static void print(object message);
        public void CancelInvoke(string methodName);
        public void CancelInvoke();
        public void Invoke(string methodName, float time);
        public void InvokeRepeating(string methodName, float time, float repeatRate);
        public bool IsInvoking(string methodName);
        public bool IsInvoking();
        public Coroutine StartCoroutine(string methodName);
        public Coroutine StartCoroutine(IEnumerator routine);
        public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value);
        public Coroutine StartCoroutine_Auto(IEnumerator routine);
        public void StopAllCoroutines();
        public void StopCoroutine(IEnumerator routine);
        public void StopCoroutine(Coroutine routine);
        public void StopCoroutine(string methodName);
    }
}

どうみても Behaviour には、Start / Update はいません、負けじと次の Componet を見てみましょう。これは次こそはいそうですね・・・

namespace UnityEngine
{
    public class Behaviour : Component
    {
        public Behaviour();
        public bool enabled { get; set; }
        public bool isActiveAndEnabled { get; }
    }
}

結構たくさんあるみたいだけど、Component にも Start / Update はいませんでした、次は Objectお前だ!!

namespace UnityEngine
{
    public class Component : Object
    {
        public Component();
        public GameObject gameObject { get; }
        public string tag { get; set; }
        public Component rigidbody { get; }
        public Component rigidbody2D { get; }
        public Component camera { get; }
        public Component light { get; }
        public Component animation { get; }
        public Component constantForce { get; }
        public Component renderer { get; }
        public Component audio { get; }
        public Component guiText { get; }
        public Component networkView { get; }
        public Component guiElement { get; }
        public Component guiTexture { get; }
        public Component collider { get; }
        public Component collider2D { get; }
        public Component hingeJoint { get; }
        public Transform transform { get; }
        public Component particleEmitter { get; }
        public Component particleSystem { get; }
        public void BroadcastMessage(string methodName, SendMessageOptions options);
        public void BroadcastMessage(string methodName);
        public void BroadcastMessage(string methodName, object parameter);
        public void BroadcastMessage(string methodName,
        public bool CompareTag(string tag);
        public Component GetComponent(Type type);
        public T GetComponent<T>();
        public Component GetComponent(string type);
        public T GetComponentInChildren<T>();
        public Component GetComponentInChildren(Type t, bool includeInactive);
        public Component GetComponentInChildren(Type t);
        public T GetComponentInChildren<T>([DefaultValue("false")] bool includeInactive);
        public Component GetComponentInParent(Type t);
        public T GetComponentInParent<T>();
        public Component[] GetComponents(Type type);
        public T[] GetComponents<T>();
        public void GetComponents<T>(List<T> results);
        public void GetComponents(Type type, List<Component> results);
        public void GetComponentsInChildren<T>(List<T> results);
        public Component[] GetComponentsInChildren(Type t);
        public T[] GetComponentsInChildren<T>(bool includeInactive);
        public void GetComponentsInChildren<T>(bool includeInactive, List<T> result);
        public Component[] GetComponentsInChildren(Type t, bool includeInactive);
        public T[] GetComponentsInChildren<T>();
        public Component[] GetComponentsInParent(Type t);
        public T[] GetComponentsInParent<T>();
        public T[] GetComponentsInParent<T>(bool includeInactive);
        public void GetComponentsInParent<T>(bool includeInactive, List<T> results);
        public Component[] GetComponentsInParent(Type t,
        public void SendMessage(string methodName, SendMessageOptions options);
        public void SendMessage(string methodName, object value, SendMessageOptions options);
        public void SendMessage(string methodName);
        public void SendMessage(string methodName, object value);
        public void SendMessageUpwards(string methodName, SendMessageOptions options);
        public void SendMessageUpwards(string methodName, object value);
        public void SendMessageUpwards(string methodName,
        public void SendMessageUpwards(string methodName);
    }
}

はい、タイトルの通り、Object にも Start / Update などのメソッドは存在しませんでした。

namespace UnityEngine
{
    public class Object
    {
        public Object();
        public string name { get; set; }
        public HideFlags hideFlags { get; set; }
        public static void Destroy(Object obj);
        public static void Destroy(Object obj, [DefaultValue("0.0F")] float t);
        public static void DestroyImmediate(Object obj, [DefaultValue("false")] bool allowDestroyingAssets);
        public static void DestroyImmediate(Object obj);
        public static void DestroyObject(Object obj);
        public static void DestroyObject(Object obj, [DefaultValue("0.0F")] float t);
        public static void DontDestroyOnLoad(Object target);
        public static Object FindObjectOfType(Type type);
        public static T FindObjectOfType<T>() where T : Object;
        public static T[] FindObjectsOfType<T>() where T : Object;
        public static Object[] FindObjectsOfType(Type type);
        public static Object[] FindObjectsOfTypeAll(Type type);
        public static Object[] FindObjectsOfTypeIncludingAssets(Type type);
        public static Object[] FindSceneObjectsOfType(Type type);
        public static Object Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);
        public static Object Instantiate(Object original);
        public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);
        public static T Instantiate<T>(T original, Transform parent, bool worldPositionStays) where T : Object;
        public static T Instantiate<T>(T original, Transform parent) where T : Object;
        public static T Instantiate<T>(T original, Vector3 position, Quaternion rotation, Transform parent) where T : Object;
        public static T Instantiate<T>(T original, Vector3 position, Quaternion rotation) where T : Object;
        public static T Instantiate<T>(T original) where T : Object;
        public static Object Instantiate(Object original, Transform parent, bool instantiateInWorldSpace);
        public static Object Instantiate(Object original, Transform parent);
        public override bool Equals(object other);
        public override int GetHashCode();
        public int GetInstanceID();
        public override string ToString();
        public static bool operator ==(Object x, Object y);
        public static bool operator !=(Object x, Object y);
        public static implicit operator bool(Object exists);
    }
}

不思議2:private でも public でも呼び出される

private / public など関係無しにこいつらは呼び出されます。面白い特性をしていますね、以下確認してみます。

まずは、private で実行してみます。

using UnityEngine;

public class Test2 : MonoBehaviour
{
    private void Start()
    {
        Debug.Log("privateでの実行");
    }
}

正常にログが出力されたので動作している事が確認できます。

続いて、public にして実行してみます。

using UnityEngine;

public class Test2 : MonoBehaviour
{
    public void Start()
    {
        Debug.Log("publicでの実行");
    }
}

こちらも正常に動作しました。

上の結果から、private / public の I/F は無視されるとわかりますね。

不思議3:引数を持っていると呼び出されない

何が渡されるかまったくわかりませんが物は試しです!

Startメソッドに、「int arg1」の引数を設定して実行してみました

using UnityEngine;

public class Test2 : MonoBehaviour
{
    void Start(int arg1)
    {
        Debug.Log("引数付きでの実行");
    }
}

面白い結果が出ましたね、引数付きだとエラー(引数を取る事はできませんよ!)が発生して怒られました。

エラーで警告してくれる Unity のやさしさにびっくりしました、正直何も出力されずに無視されるのかと思っていました。

不思議4:どこからも参照されていない

続いては、Visual Studioの機能で全検索をかけてみて参照している箇所を探してみます。 Start 件数を選択して、右クリックから「すべての参照を検索」を実行してみます。

やっぱり!
どこからもこの関数が呼ばれていないようでした。

どうなっているのか?

自分の認識になっているので合っていない場合ご指摘ください!

まず、Unity は Hierarchy に存在する Script を検索して、Unityエンジンがこのスクリプトを管理しはじめます。

そして、Unityエンジンはそこに含まれている、Start / Update などのイベント関数Unityエンジンの呼び出しリストに登録します。
ここでは、Start呼び出しリストUpdate呼び出しリストとしましょう。

イベント関数のページにフローチャートが表示されているのですがここでイベント関数の呼び出し順序が記載されています。

例えば、Unity は実行開始時に Awake -> OnEnable -> Reset -> Start の順番にイベント関数の呼び出しを行います。
この Start のタイミングで、Start呼び出しリスト に含まれるすべてのスクリプトが呼び出されます。

Updateも同様に、タイミングがあり Update呼び出しリストに含まれるすべてのスクリプトが呼び出されます。

それでは次にどのようにこの関数が呼び出されているのでしょうか?
上の方での検証結果でもどこからも参照されていない事が確認できたのに不思議ですね。

System.Reflection を使っているのか?

Unityブログ「Update()を10000回呼ぶ」では System.Reflectionは使っていないとの事です。

System.Reflection を使うと同じような事ができます。のでその疑問は正しいです。

文字列で指定したメソッドを呼び出すには?

https://www.atmarkit.co.jp/ait/articles/0512/16/news110.html

今回の例でいえば、以下のようなソースコードで Unityエンジン からスクリプトのStartメソッドが呼ばれていると思われます。

以下のように記載すると、StartTestクラスのStartメソッドを動的に実行する事ができます。このように記載すると Visual Studio の全検索には引っかからない事になります。

StartTest sc = new StartTest();
Type t = sc.GetType();

MethodInfo mi = t.GetMethod("Start");

object o = mi.Invoke(sc, new object[] { } );

正解は?

Unityブログ「Update()を10000回呼ぶ」では 、 スクリプティングランタイム(MonoもしくはIL2CPP) +コンパイラレベルで独自にイベント関数は特別に呼び出せるようになっているような感じみたいです。

代わりに、任意の型のMonoBehaviourが初めてその基底のスクリプトにアクセスしたときに、スクリプティングランタイム(MonoもしくはIL2CPP)によって何かのマジックメソッドが定義されているかを調査され、この情報がキャッシュされます。もしMonoBehaviourが特定のメソッドを持っていたら所定のリストに追加されます。例えばスクリプトがUpdateメソッドを持っていたら「毎フレームUpdateを呼ぶべきスクリプトのリスト」に追加されるわけです。

最後に

Uniyが頑張ってチューニングしてくれてはいますが、Update関数を使いすぎるとパフォーマンスが悪くなるため、Update関数を1か所で定義して、それ以外の9,999のスクリプトはその1か所から呼び出しを行うようなパフォーマンスチューニングをする場合もあります。

この記事で、Start / Update の認識を深めてもらえてたら幸いです。
それでは。

モバイルバージョンを終了