シーンを追加する

ページ更新日 :
ページ作成日 :

検証環境

Windows
  • Windows 11
Unity エディター
  • 2020.3.25f1
入力システムパッケージ
  • 1.2.0

この Tips の前提設定

この Tips の説明の前提として以下の設定を事前に行っています。

シーンの追加について

Unity では同時に複数のシーンを表示させることができます。 単純にシーンを重ねて表示させたいときや、別のテクスチャーに描画内容を書き出したい場合に使用します。

今回はゲームを実行中にゲーム画面を残したまま手前にメニュー画面を表示する、という場面を想定してのシーンの重ね表示を例に説明したいと思います。

シーンを複数表示する場合以下の点に注意する必要があります。

  • EventSystem は複数存在してはならない
  • Audio Listener は複数存在してはならない
  • カメラが複数になる場合の扱いに注意する

シーンを用意する

今回は2つのシーンを用意します。最初から開くシーンを SampleSceneParent, 追加で表示するシーンを SampleSceneChild とします。 2つのシーンを用意する方法は前の以下の Tips と同じですのでそちらを参考にしてください。

今回は以下のようなレイアウトで作成します。親のシーンでボタンをクリックしたら子のシーンを追加する以外は適当で問題ありません。 後で確認する現象を分かりやすくするためにそれぞれ Canvas とスプライトを配置しています。

SampleSceneParent

SampleSceneChild

複数のシーンを表示した場合にオブジェクトの前後関係はシーン単位では関係なく各オブジェクトの「レイヤーの順序」に依存します。 初期値はすべて 0 なので SampleSceneChildCanvas とスプライトは「1」に設定しておきます。

ビルド設定で2つのシーンをセットすることを忘れないで下さい。

シーンの追加処理

シーンの切り替えと同じようにコードで処理をします。 今回もボタンをクリックしたときに処理しますがシーンの切り替えではなく追加として処理します。

スクリプトファイルの名前は任意ですがここでは SceneAdd とします。

スクリプトは以下のようにします。

using UnityEngine;
using UnityEngine.SceneManagement;  // 追加

public class SceneAdd : MonoBehaviour
{
  /// <summary>ボタンをクリックしたときに呼ばれます。</summary>
  public void OnClick()
  {
    // 指定したシーンを追加します
    SceneManager.LoadScene("SampleSceneChild", LoadSceneMode.Additive);
  }
}

シーンの切り替えの時は LoadSceneModeSingle にしていましたが今回は Additive としています。 違いはこれだけです。

スクリプトを保存したら SampleSceneParentEventSystem にアタッチします。

ボタンに OnClick メソッドを設定します。

ゲームを実行して動作を確認してみてください。 ボタンをクリックしたら子のシーンが表示されると思います。

また、ヒエラルキーを見てみると2つのシーンが存在していることを確認できると思います。

しかし、シーンが追加されたという割には親のシーンの Canvas の内容が表示されていないように見えると思います。 逆に親シーンのスプライトについてはレイヤーの順序にそって裏側に表示されていることを確認できます。

また、コンソールを見てみると絶え間なくログが出力されていることを確認できると思います。

内容は以下の2点です。

  • There are 2 event systems in the scene. Please ensure there is always exactly one event system in the scene
  • There are 2 audio listeners in the scene. Please ensure there is always exactly one audio listener in the scene.

複数のシーンを使用する場合はこの2点とカメラの対応を行う必要があります。

EventSystem のエラーを修正する

ログの内容は以下となっています。

There are 2 event systems in the scene. Please ensure there is always exactly one event system in the scene

(シーンには2つのイベントシステムがあります。 シーン内に常に1つのイベントシステムがあることを確認してください)

文章のとおり EventSystem を一つにすれば解決します。

SampleSceneParent は必ず存在するという想定としますので、SampleSceneChild を開いたら EventSystem を削除します。

当たり前ですが、SampleSceneChild を単独で実行した場合はボタンなどの UI が動作しなくなるので注意してください。

実行すると EventSystem のログが消えていることを確認できます。

また SampleSceneParentEventSystem は存在しているので子のシーンのボタンは動作させることができます。

AudioListeners のエラーを修正する

ログの内容は以下のようになっています。

There are 2 audio listeners in the scene. Please ensure there is always exactly one audio listener in the scene.

(シーンには2人のオーディオリスナーがいます。 シーンには常にオーディオリスナーが1人だけいることを確認してください。)

Main Camera のコンポーネントを見ると確かに Audio Listener が存在していることを確認できるので SampleSceneChild の Audio Listener を削除すれば良いのですが、 次の項目のカメラ制御で解決できるのでそちらで対応します。

もし、SampleSceneChild のカメラを残すのであればこちらの Audio Listener を削除することになります。

カメラを変更して2つのシーンを表示する

子のシーンを追加したときに子のシーンのキャンバスしか表示されない原因についてですが、 これはキャンバスに設定している親のシーンと子のシーンの2つのカメラが存在することになってしまい片方のカメラしか表示されていないためです。

これを解決するには「両方のシーンのキャンバスを親のシーンのカメラで表示する」ことによって解決できます。 処理内容としては子のシーンが追加された後に子のシーンのキャンバスのカメラを親のシーンのカメラに置き換えるようにします。

ではスクリプトを作成します。名前は任意ですがここでは ChildSceneCamera とします。

スクリプトは以下のようにします。Canvas にアタッチする前提の処理となっています。

using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ChildSceneCamera : MonoBehaviour
{
  /// <summary>シーンに追加されたタイミングで処理されます。</summary>
  void Awake()
  {
    // 親のシーンのキャンバスを取得
    var parentScene = SceneManager.GetSceneByName("SampleSceneParent");
    var parentCanvas = parentScene.GetRootGameObjects().First(obj => obj.GetComponent<Canvas>() != null).GetComponent<Canvas>();

    // 子のシーンのキャンバスを取得
    var childCanvas = GetComponent<Canvas>();

    // 子のシーンのカメラを削除
    Object.Destroy(childCanvas.worldCamera.gameObject);

    // 子のシーンのキャンバスのカメラを親のシーンのカメラに置き換えます
    childCanvas.worldCamera = parentCanvas.worldCamera;
  }
}

Awake メソッドはオブジェクトがシーンに存在する最初のタイミングで実行されます。Start メソッドよりも先に実行されます。 頻繁に使うことが多いので覚えておいて損はないでしょう。 今回は子のシーンが追加されたタイミングで実行されることを想定しています。

/// <summary>シーンに追加されたタイミングで処理されます。</summary>
void Awake()
{
  // 処理
}

カメラを置き換えるために親のシーンのキャンバスと子のシーンのキャンバスを取得します。 やり方はいろいろありますが本題ではないのでとりあえずこのようにすれば取得できると思ってもらって良いでしょう。

// 親のシーンのキャンバスを取得
var parentScene = SceneManager.GetSceneByName("SampleSceneParent");
var parentCanvas = parentScene.GetRootGameObjects().First(obj => obj.GetComponent<Canvas>() != null).GetComponent<Canvas>();

// 子のシーンのキャンバスを取得
var childCanvas = GetComponent<Canvas>();

最初に子のシーンのカメラを Object.Destroy で破棄します。なくなっても親のシーンのカメラでカバーできるので問題ありません。 削除対象を Canvas.worldCamera から取得していますが、 あくまでもこの時点でシーンが持っているカメラと同じものがセットされており簡単に取得できるという理由なだけですので、 実際に削除する場合はシーンからカメラを取得したほうがよいでしょう。

// 子のシーンのカメラを削除
Object.Destroy(childCanvas.worldCamera.gameObject);

最後に子のシーンの Canvas.worldCamera を親が持っているカメラに置き換えています。

// 子のシーンのキャンバスのカメラを親のシーンのカメラに置き換えます
childCanvas.worldCamera = parentCanvas.worldCamera;

スクリプトを保存したら SampleSceneChildCanvas にアタッチします。

実行して子のシーンを追加した結果が以下の図のようになればOKです。 ログも出ていないことを確認してください。