読者です 読者をやめる 読者になる 読者になる

テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

一時的にLoadLevelする際にGameObjectが破棄されないようにする

Unity スクリプト リソース・メモリ管理 最適化・デバッグ

フラグの管理についてアレコレ考えていたら、ゲームオブジェクトを次のシーンへ受け渡す面白いアイディアが浮かんだ。

ゲームオブジェクトを次シーンまで保たせる

Unityでゲームを作っている場合、LoadLevelによるシーン遷移をよく利用する。LoadLevelはメモリをスッキリして参照関連や変数を上手い感じにリセットしてくれるが、DontDestroyOnLoad以外のオブジェクトを全て破棄する。

これが若干厄介で、例えばゲーム画面→結果画面のようにゲーム画面の内容を表示したい場合、以下の事を行う必要があった。
  • staticなクラスもしくはフィールド(=変数)に値を保存しておく
  • DontDestroyOnLoadでGameObjectを破棄を防ぐ

ただどちらもリセット処理が必要となり、staticな値の場合はリセット処理の定義、DontDestroyOnLoadなら以降利用される前に探し出して破棄する必要があった。

Unityで2週目・3周目に1週目と違う挙動がある場合、大抵この初期化が完全ではないケースが多い。そして、この辺りを管理するのは非常に面倒くさい。



このDontDestroyOnLoadの方だが、実はある程度自動化が可能だ。例えば以下のようにイベントを記入すると次のシーンのAwake後までは生存してくれる。

using UnityEngine;
using System.Collections;

public class ScoreController : MonoBehaviour
{
    public int score = 0;

    // ----------ここから  -------

    bool isStarted = false;
    void Start()
    {
        isStarted = true;
        DontDestroyOnLoad(gameObject);
    }

    void OnLevelWasLoaded(int level)
    {
        ifisStarted )
            Destroy (gameObject);
    }
    // ---------  ここまで -------
}

これは単純に、OnLevelWasLoaded(シーンがロードされたタイミングで呼ばれる)がAwakeの後・Startの前に呼ばれる事を利用している。つまり、Awakeが完了後にStartを一度経由していれば破棄されるという事だ。

なので、次のシーンのAwakeでスコアを回収可能なら、次のシーンで値を継続して利用することが出来る。

ちなみに実際に使用する場合は、LoadLevelの直前でDontDestroyOnLoad属性を付与する事をお勧めする。Startでこの属性を付けても良いのだが、必ず次のシーンで破棄されるようになり、どのシーンからでも初められるの法則が守りにくくなる。



とまあコレはコレで便利なのだが、Awakeで値を回収しなければいけないのは正直面倒だ。次のシーンはAwakeで値を回収する処理+それ用の変数が必要になってしまう。

なので、DontDestroyOnLoad属性を付与せずシーン移行時にオブジェクトを破棄しないようにした。これで、次の次のシーンではオブジェクトが破棄されるようになり、リセットが簡単になる。

DontDestroyParent.cs


やってる事は単純で、シーンロードの直前にDontDestroyOnLoad属性のあるオブジェクトの子としてオブジェクトを配置してるだけ。DontDestroyOnLoad属性を持つオブジェクトの子オブジェクトは、親同様破棄されないので、これでシーンのクリーンナップを乗り切り親離れする。

やり方は簡単、DontDestroyParentをプロジェクトへ配置後、LoadLevelを実行する直前で以下の処理を実行する。

this.DontDestroyOnNextLoad(コンポーネント);

これでDontDestroyOnLoad属性を付与せず次のシーンまでオブジェクトを保持出来る。
下は↑を使った例。SingletonMonobehaviourを使用。