yotiky Tech Blog

とあるエンジニアの備忘録

エントリーポイントを探してUnityの森を彷徨う

Unity 基礎シリーズ 目次

Unityの開発を始めてまず探したくなるのがエントリーポイント、アプリケーションが始まる場所である。
サンプルアプリ程度の短いコード量ならサクッと処理を追えるだろうと思うわけだ。だがしかし、これが一向に見つからない。
あるはずのものがなくてちゃぶ台をひっくり返す。冷静さを取り戻し再度Unityと向き合うも何度探してもわからない。
お前は一体どこから走り始めたんだ、、、やがてUnityをそっと閉じる。

目次

エントリーポイントが見つからない理由

Unityはゲームループで動くエンジンである。
エンジンとMonoBehaviourを継承したスクリプトプラグインアーキテクチャのような仕組みになっており、規定の名前のイベント関数を実装することで各々のタイミングでエンジンがキックしてくれる。 f:id:yotiky:20181129031839p:plainf:id:yotiky:20181129031841p:plain

開発者が書くスクリプトは、基本的にはこのMonoBehaviourを継承したクラスになり先の仕組みで動く。イベント関数で一番早いのはAwakeだが、実行タイミングはシーンがロードされ、各GameObject(Component)のインスタンスが生成し終わった後くらいに走り出すと言ったところ。アプリケーションのエントリーポイント、ゲームループが始まるよりも前の処理はエンジン側が隠し持ち、開発者が触れる表層ではゲームループ以降となる。

f:id:yotiky:20181129031935p:plain

では、「どの順でAwakeが呼ばれ、どのGameObject(Component)が最初になるのか」だが、UnityではComponentの実行順は保証されていない。恐らくInstanceID順で実行はされるのだが、このIDは保存されておらず編集やUnityの再起動などで簡単に変わってしまう。(詳しくは「複数のComponentのイベント関数の実行順」を参照)

このような構成のため、Unityを触り始めた人がエントリーポイントを探しても簡単には見つからない、理解できないということになる。

エントリーポイントを作る

最近書くアプリケーションでは、エントリーポイントを意図的に作るようにしている。『MVP4U』1記事でEngineの初期化(こちらはアプリ側のEngine)で軽く触れたが、単一のエントリーポイントを定義し、そこを起点にScript側から波及的に初期化を呼び出す設計だ。 f:id:yotiky:20181129032034p:plain

ここではScenePresenterとしているが、これがエントリーポイントとなる。
ScenePresenter以外の全Presenter(以降Presenter)は、ScenePresenter(以降EntryPoint)に登録されている。
PresenterにはInitializeメソッドが定義されておりここに初期化処理を集約する。AwakeやStartといったイベント関数は持たない。
View側も同様にInitializeを用意し、Presenterの初期化の中でViewのそれを呼び出す。Viewの先にあるComponentについてもInitializeに初期化を定義し、PresenterのInitializeを起点として波及的に呼び出されるようになっている。

    // EntryPoint.cs
    public class EntryPoint : MonoBehaviour
    {
        public PresenterBase[] presenters;

        void Start()
        {
            foreach (var p in presenters)
            {
                p.InitializeOnStart();
            }
        }
    }
    // SamplePresenter.cs
    public class SamplePresenter : MonoBehaviour
    {
        private SampleView view;

        public void InitializeOnStart()
        {
            view = GetComponent<FloorView>();
            view.Initialize();
            // その他必要な初期化処理
        }
    }
    // SampleView.cs
    public class SampleView : MonoBehaviour
    {
        public HogeComponent hoge;

        public void Initialize()
        {
            hoge.Initialize();
        }

イベント関数以外のメソッドの実行に関しては「Scriptのイベント関数の実行順と実行可否」の最後に書いたが、GameObject/Componentの有効/無効に関係なく、Awakeが呼ばれて無くてもメソッドの呼び出しは可能である。これは初期化を行うためにGameObjectをアクティブにしたり、編集中にHierarchy上での状態を気にする必要がないことも意味する。
さらにEntryPointはStart(もしくはAwake)となるため、この時点でGameObject/Componentのインスタンス生成はすべて完了しており、Initializeの波及が問題なく実行される。

注意点としては、Initializeで実行順序を担保するので、AwakeやStartなどと初期化を混同しないことだろうか。意図的に分ける場合は除くが。

余談だがこの実装の場合、UniRxのメソッドチェーンは多くのケースでInitializeの中で宣言的に定義することができる。


  1. Unity向けにMVPを解釈したアーキテクチャ(MVP for Unity)