Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜シーンの切り替え〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
シーンの切り替え

この記事を三行にまとめると

アクティブ操作での画面切り替えよりも扱いが難しいという印象
シーンをまたいで値を持ち回る変数はstaticにしておくと良い
DontDestroyOnLoadについてはすみません
今回はシーンの切り替えで表示する画面を切り替えてみたいと思います。僕個人としてはアクティブ操作での画面切り替えよりも扱いが難しいという印象なのですが、でも使えて損はない機能だと思うので見ておきましょう。

動画はこちらです。





シーンの作成

今まで一度も触れていませんでしたが、これまで作ってきたゲーム画面は全て一つのシーンの中に実装しています。デフォルトでは「SampleScene」という名前がついています。ヒエラルキーの上の方を見ると名前が書いてある。

なのでシーンを切り替えるにはまず新しいシーンを作成する必要があります。

シーンの作成はプロジェクトウインドウで行います。プロジェクトウインドウの中には「Scene」という名前のフォルダがあり、SampleSceneもそこに入っているので、同じ場所に作るのが良いでしょう。メニューの一覧にも「Scene」があるので、それを選択すれば新しいシーンが追加されます。

シーンの新規作成



ビルドの設定

シーンを複数作った場合、ビルド設定で切り替えるシーンを追加しておかないと「Scene 〇〇 coudn’t be loaded〜」みたいなエラーが出て切り替えることができません。ビルドの設定画面は画面左上メニューから「File → Build Settings…」で開くことができます。

なんでSettingsの後ろに三点リーダがついてんだろうね?

画面を開くと上部に「Scenes in Build」という項目があるのですが、ここに入っていないシーンは切り替えることができません。シーンの追加はプロジェクトウインドウからシーンをドラッグ&ドロップするだけで追加できます。

またシーンを追加すると0から順に番号が振られていくのですが、ゲームをビルドして公開する時はこの番号が0のシーンが初期画面になります。なのでスタート画面などを作る場合はスタート画面のシーンを0にしておく必要があります。もし初期画面にしたいシーンが0になってなくても、シーンを選択してドラッグすれば簡単に順番を入れ替えられるのでご安心を。

これでシーンを切り替える準備ができました。



スクリプト

シーンの切り替えはスクリプトで行います。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Scene : MonoBehaviour {
  public string scene;

  public void ChangeScene() {
    SceneManager.LoadScene(scene);
  }
}

「SceneManager.LoadScene」という関数でシーン名を指定するとその名前のシーンに切り替わります。この関数を使うには「using UnityEngine.SceneManagement」が必要です。

これをクリックイベントなどにセットしておけば任意のタイミングでシーンを切り替えることができます。



変数の保持

シーンの切り替えで一番気をつけなきゃいけないのは、ロードされたシーンは初期状態であるということです。

例えば部屋ごとにシーンを作って部屋を移動する際にシーンを切り替えるような作りになっている場合、アイテムを取ったりドアの鍵を開けたりして別の部屋に移動した後、前の部屋に戻ってくるとまたアイテムを一から取り直しになります。鍵も開け直さなないといけない。それまでの状態が保持されないので、シーンを切り替えるたびに一からスタートという状態になってしまいます。

もちろんこれを防ぐ方法もあります。変数の値の保持とオブジェクトの状態の保持でやり方が違うのですが、僕はオブジェクトの状態を保持する方は上手く扱えないので、すみませんがここではこんな感じのやり方があるという紹介だけさせてもらいます。

変数の値の保持は簡単です。前に子供たちのスクリプトの間で値を共有するためにstaticをつけるというのを紹介しましたが、あれと同じでstaticを宣言した変数はシーンを切り替えても中身が前の状態のままです。

public class Password : MonoBehaviour {
  protected static bool locked = true;

  protected void Unlock() {
    locked = false;
  }
}

本作ではlockedという変数でパスワードが解除済みかを判定しているのですが、staticじゃない場合はシーンを切り替えると常に中身がtrueに戻ってしまいます。でも上記のようにstaticにしておくとUnlock関数でfalseにした後はシーンを切り替えてもfalseのままです。

この状態の保持は初期化を防ぐ以外に複数のシーンで値を共有することも可能なので、シーンをまたいで値を持ち回る変数はstaticにしておくと良いでしょう。ただし前述の通り、staticな変数は子供たちのスクリプト間でも値が共有されます。本作だと全てのパスワードは親スクリプトであるPasswordを継承しているわけですが、値が共有されるということは、どれか一つのパスワードを解除してlockedがfalseになった時点で他のパスワードのlockedも全てfalseになることを意味します。つまり解除してないはずのパスワードにも解除フラグが立ってしまうことになります。なのでstaticにする変数は他と値を共有しても大丈夫かどうかという点だけ確認しといた方が良いですね。



オブジェクトの保持

一方のオブジェクトの保持についてですが、「DontDestroyOnLoad」という関数を使うと状態を保持できるそうです。

public class Scene : MonoBehaviour{
  private GameObject floor;

  void Awake() {
    floor = GameObject.Find("Floor");
    DontDestroyOnLoad(floor);
  }
}

上記では「Floor」というゲームオブジェクトを指定していますが、こんな感じで保持したいオブジェクトを指定するとシーンが変わってもFloorオブジェクトやその子供たちの状態が保持されます。

実際にやってみたところ、確かに状態はリセットはされなかったのですが、思った通りの動きにならなかったりなぜかFloorというオブジェクトが大量に複製されたような状態になったりして、いまいち使いこなせませんでした。

検索するといっぱい情報が出てくるんで正しい設定をすればちゃんと使える機能だとは思うんですが……まだそんなにUnityの扱いに慣れてないうちは、行ったり来たりするような画面は本作のようにシーンではなくCanvasオブジェクトなどで切り分けて、アクティブ状態の操作で画面を切り替える方が良いように思います。そっちの方が楽だし、これといったデメリットもないと思うし……たぶん。

本作はスタート画面とクリア画面だけ別のシーンを作って切り替えています。この3つの画面は「スタート画面 → ゲーム画面(部屋) → クリア画面」と一方通行な上に保持しなきゃいけない変数もオブジェクトもないので、シーンの切り替え時に初期状態で画面を開いても問題ない作りになっています。



再帰処理

シーン切り替えの機能とは直接は関係ないのですが、本作ではシーン切り替え時に画面をゆっくりホワイトアウトする仕組みになっています。

これは時間差で関数を発動するInvokeと「再帰処理」という機能を使って実装しています。

public class Scene : MonoBehaviour {
  public string scene;
  public GameObject white;
  private Image image;
  private float alpha = 0;

  void Start() {
    image = white.GetComponent();
  }

  public void ChangeScene() {
    white.SetActive(true);
    WhiteOut();
  }

  public void WhiteOut() {
    if(alpha <= 1) {
      alpha += 0.02f;
      image.color = new Color(1, 1, 1, alpha);
      Invoke("WhiteOut", 0.03f);
    } else {
      SceneManager.LoadScene(scene);
    }
  }
}

まずChangeScene関数では直接シーンの切り替えを行わず、WhiteOutという関数を呼び出す処理に変更します。

white変数には画面いっぱいの真っ白な画像オブジェクトがセットされています。image変数はそのオブジェクトのImageコンポーネントです。

WhiteOut関数ではalpha変数の値を+0.02して、その値に画像オブジェクトの不透明度に入れています。最初にWhiteOut関数が呼び出されるとalphaの値は0.02になるので画像の不透明度も0.02になります。

次にInvokeを使って0.03秒後に自分自身(WhiteOut関数)がもう一度呼び出されるようにします。すると0.03秒後にもう一度alphaが+0.02されて画像の不透明度が0.04になります。そしたらまたInvokeによって0.03秒後にWhiteOut関数が呼び出されて画像の不透明度が0.06になります。これをalphaが1になるまで繰り返し、1になったところでシーンを切り替えます。

このように自分自身を何度も呼び出し続ける処理を再帰処理とか再帰呼び出しと言います。

for文などを使っても同じようにalphaを少しずつ加算していく処理は作れますが、forは時間差で発動するものではないので、よほど時間のかかるループ処理を実装しない限りは一瞬で処理が終わってしまいます。なので今回のような再帰処理には使えません。どうしてもfor文を使ってやりたいのであれば、一定時間処理を停止させる「Tread.Sleep」などを組み合わせて使うという方法もあるかもですが……感覚的にSleepは処理を止めたい時に使うという印象なので、こういう時は再起処理で良いんじゃないかな。

本作ではゆっくり不透明にする動きに再帰処理を使いましたが、同じ要領でオブジェクトの移動や回転も少しずつ動かすことが可能です。変化のスピードは変化量(alphaのような)やInvokeに設定する時間で調節できます。ただし設定時間は長くすればするほど動きが滑らかじゃなくなっていきます。あくまでも短い間隔で状態を変化させているという動きですからね。パラパラ漫画みたいなもんです。素早くページをめくれば絵が滑らかに動くけど、ゆっくりページをめくるとかくかくした動きになる。

動画でもクッションがゆっくり動く例を紹介してますが、ちょっとぎこちない動きをしていますね。動画では3秒かけて移動させていますが、あれくらいだったらもっと短い時間の方が良いですね。1秒とか。

もし同じようなことをやる場合は、時間や変化量は実際の動きを見ながら微調整すると良いでしょう。






ゲーム作りに限らずプログラミングで何かしらのシステムを開発する時、そんな頻繁ではないけど再帰処理は使うことがあるので、どっかで紹介はしたかったんですよね。前に知り合いのプログラマ見習いの人から「こういう動きを実現したいんだけど」って質問されたのがまさに再帰処理で解決できるものだったんですけど、その時は再帰処理の説明があまり上手にできなくてその人も全然ピンと来てなかった。あのリベンジをここで果たしかったです。今回は上手く伝わってると良いのですが……。

あとDontDestroyOnLoadについてはすみません。もしその使い方を知りたくて検索でここに来た人には無駄足を踏ませてしまうことになりますが、でも逆にこの記事をUnityに詳しい人が読んで「俺がDontDestroyOnLoadが使いづれーっていうその幻想をデストロイしてやんよ」と有益な情報を残していってくれるかもしれないので、その時が来ることを祈りましょう。

次回はアナリティクスの簡単な使い方を紹介します。そんながっつり使いこなすというよりはとりあえずアクセス数を取得してみようという程度の内容です。

それじゃあ次回もよろしくお願いしょうとくたいし。



本シリーズの記事の一覧はこちら
Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜エピローグ〜
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください