LoginSignup
1
2

More than 5 years have passed since last update.

【Unity】コンポーネントの初期化にAwakeを使うか自作メソッドを使うかで例外発生時の止まり方が違う

Last updated at Posted at 2019-04-20

Instantiateなどで生成したコンポーネントを、その場ですぐに初期化する場合、大きく分けて2つの方法があると思います。

  1. Awake() のように、Unityのメッセージで初期化する
  2. Init() のように、メソッドを呼んで初期化する

どちらにもそれぞれのメリットがあります。
例えばAwakeはいつ呼ばれるのか単純明快ですし、生成側とコンポーネントが依存しない造りにできます。
一方メソッドを作るのなら引数を自由に変えられますし、Unity独特の仕様に依存しない造りにできます。
パフォーマンスも、InstantiateやAddComponentそれそのものに比べたら差はないも同然です。

というわけで、引数もなくて同じ所で呼ぶなら、様式以外は大差ない――

――と思っていました。

サンプル

その違いは例外処理にありました。
次の2つのコードは、上で説明した2パターンを再現したものの組み合わせです。

パターン1(Awakeで初期化)

Manager.cs
public class Manager : MonoBehaviour {
    // ゲーム開始時にキャラクターを生成する
    void Awake() {
        Debug.Log("パターン1(Awakeで初期化)");

        var gameCharacter = new GameObject("GameCharacter");
        gameCharacter.AddComponent<GameCharacter>();

        Debug.Log("Game Start!");
    }
}
GameCharacter.cs
public class GameCharacter : MonoBehaviour {
    // だがそいつは初期化時にエラーを起こしてしまう死のキャラクターだった!
    void Awake() {
        Debug.Log("Initializing GameCharacter");
        throw new System.Exception("GameCharacter: Initialization failed");
    }
}

パターン2(Initメソッドで初期化)

Manager.cs
public class Manager : MonoBehaviour {
    // ゲーム開始時にキャラクターを生成する
    void Awake() {
        Debug.Log("パターン2(Initメソッドで初期化)");

        var gameCharacter = new GameObject("GameCharacter");
        gameCharacter.AddComponent<GameCharacter>().Init();

        Debug.Log("Game Start!");
    }
GameCharacter.cs
public class GameCharacter : MonoBehaviour {
    // だがそいつは初期化時にエラーを起こしてしまう死のキャラクターだった!
    public void Init() {
        Debug.Log("Initializing GameCharacter");
        throw new System.Exception("GameCharacter: Initialization failed");
    }
}

結果

pic1.png
Managerを配置すると、1ではGame Startに到達していますが、2では到達しませんでした。

検証

C#の仕様上は、2のような挙動をするはずです。
つまりは「1は例外が起きたはずなのに止まっていない」ということです。
きっとUnityがAwakeを呼ぶとき何かしているに違いありません。
例ではAddComponentですが、もちろんInstantiate中に呼ばれるAwakeでも同じです。

試しにtryで囲ってみました。この他の部分はさっきと同じです。

// パターン1 + try-catch
try {
    var gameCharacter = new GameObject("GameCharacter");
    gameCharacter.AddComponent<GameCharacter>();
} catch {
    Debug.LogWarning("キャラクター生成できなかったけどいいよね?");
}
// パターン2 + try-catch
try {
    var gameCharacter = new GameObject("GameCharacter");
    gameCharacter.AddComponent<GameCharacter>().Init();
} catch {
    Debug.LogWarning("キャラクター生成できなかったけどいいよね?");
}

pic2.png
なんと前者は例外をキャッチできません。囲ってない状態と全く同じです。
別に、初期化が遅れて実行されているわけではないということは、ログからもスタックトレースからもわかりますが……。

これは「全てのメッセージは最初からtry-catchで囲まれている」ということか、もしくは同様の何かでしょう。

まとめ

  • Unity上のC#の例外処理は、Unityがメッセージ呼び出しをするたびに区切られます。
  • スクリプトの途中でメッセージが呼ばれる場合、メッセージから奥で例外が起きても、手前の処理は止まらずに続行します。

プログラムを書くときにはわざわざ例外が起きたときの止まり方まで意識はしないでしょうが、もしリリースビルドで明らかに例外が起きているっぽい状態に出くわしたら、何が動いていて何が止まったかから原因を特定するような時には役立つ知識かもしれません。

それにしても、UpdateのようにPlayerLoopから呼ばれるメッセージは当然こういう仕組みなんだろうとは思っていましたが、Awakeなどが途中で呼び出される場合でも同じというのは意外でした。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2