Unity 2Dアクションの作り方【シーン切り替え】

前回までで、キャラクターの移動制御、アニメーション、敵を制作しました。今回はシーンの切り替えを作っていきたいと思います。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください

この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。

<シーン間を移動しよう>

SceneManager.LoadScene

さて、まだ未完成ですが、今まで2つのシーンを作ってきました。

現在、タイトルシーンとステージ1のシーンがあります。今回はタイトル→ステージ1へのシーン移動を行なっていこうと思います。

と、言ってもタイトルシーンを作った時、ボタンを押したら次のシーンへいく手前のところまで行っていたので後は少し手を加えてあげればシーン移行が可能になります。

↓の記事でタイトルシーンの解説をしているので、忘れてしまった人やこの記事から入った人は参考にしてみてください。

現状タイトルシーンにくっついているスクリプトはボタンを押したら、PressStartのメソッドが呼ばれるようになっています。

ここに次のシーンへいく命令を書いていきます。

次のシーンへ行く命令を書くと↓のようになります。

クリックすると展開します。
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.SceneManagement;

 public class Title : MonoBehaviour
 {
     private bool firstPush = false;

     public void PressStart()
     {
          Debug.Log("Press Start!");
          if (!firstPush)
          {
              Debug.Log("Go Next Scene!");
              SceneManager.LoadScene("stage1");
              firstPush = true;
          }
     }
 }

ポイントとしては

 using UnityEngine.SceneManagement;

これを書くことによってUnityのシーンに関連する命令を使う事ができるようになります。

そして、

 SceneManager.LoadScene("行きたいシーンの名前");

これで指定した名前のシーンへ行く事ができます。

ボタン側からメソッドを呼ぶのを忘れずに

この状態で実行すると

おっと、エラーが発生しています。

Scene ‘シーン名’ couldn’t be loaded because …

これは、単にシーンの名前を間違っているか、シーンの設定が足りてないので起きているエラーになります。

シーン名はstage1で合っているのでシーンの設定が足りていない事になります。

自分だけかもしれませんが、このシーンの設定はよく忘れるのでこのエラーが出たらサッと直しましょう。

File>Build Settings…から

この画面のAdd Open Scenesをクリックします。

すると現在開いているシーンが表示されたかと思います。これで現在開いているシーンを設定に登録する事ができました。

また、プロジェクトウィンドウからシーンファイルをドラッグ&ドロップすることでも登録でき、間違えて登録してしまった場合は、右クリックしてRemove Selectionを押せば登録を消すことができます。

これをtitle,stage1両方行いましょう。

これで両シーン登録する事ができました。

ここで右側にある数字に注目してください。0と1と書いてあると思います。

1以降は別にどうでもいいのですが(命名規則等でコントロールする場合を除く)0はちょっと重要になってきます。

0に割り振ってあるシーンはゲーム開始時一番最初に開くシーンになります。

タイトルシーンを一番最初に開いて欲しいのにstage1が0になってしまっています。

ドラッグ&ドロップで場所を移動できるのでtitleを0番目に持ってきましょう。

はい。これでOKです。この状態でシーンを再生してみましょう。

はい、これでシーンの移動が可能になりました。

覚えておきたい

・using UnityEngine.SceneManagement;
・SceneManager.LoadScene(“行きたいシーンの名前”);
・シーンを移動するにはシーンを設定に登録しておく必要があるよ!

<シーンの移動とは>

さて、今までシーンとは(一般的には)場面場面だよと解説してきました。

よってシーン移動というのは場面を切り替えるという事になります。

しかしながら、あくまでシーンというものは「オブジェクトとそのパラメータを記録し保持しているもの」なので、シーン移動というのは正確には「前のオブジェクトとそのパラメータ」をメモリ上から解放し、「次のオブジェクトとそのパラメータ」をロードするという事になります。

今はまだちゃんと理解する必要はありませんが、シーンの移動によってアンロード&ロードをしていると思ってください。

スポンサーリンク

<シーンの切り替えにフェードを入れよう>

Spriteを使ってフェードを実現しよう

さて、先ほどシーンの切り替えに成功しましたが、パッと変わってしまって、なんだかゲームっぽくありません。すごく淡白な感じがします。

シーン切り替え時にフェードを入れて演出したいところなのですが、色々難しい部分がたくさんあるので、最も簡単(だと思う)方法でフェードを実装していきたいと思います。

今回は簡易的なフェードですが、本格的なフェードはまた解説する機会があればしたいと思います。

タイトルシーンのCanvasの子にImageを作成してください。

Imageの大きさを背景と同じにしてください。

そして、適当な色の画像を用意します。自分は↓のような画像を用意しました。

テクスチャタイプをスプライトに変更してImageに突っ込みます。すると画面が真っ黒になったかと思います。

ここでImageのImage Typeという項目を変更します。Filledにしてください。

次にFill Methodという項目をHorizontalもしくはVerticalに変更します。

この状態でFill Amountという項目をいじると画像が行ったり来たりすると思います。

↓Verticalだと縦で動きます。Horizontalにすると横になります。

Imageについて詳しく知りたい方は↓の記事を参考にしてください。

今は手動でやってしまっているので、これをスクリプト制御するようにしたいと思います。

自分はFadeImageという名前でスクリプトを作成しました。

using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.UI;
 
 public class FadeImage : MonoBehaviour
 {
     private Image img = null;

     void Start()
     {
         img = GetComponent<Image>();
     }

     void Update()
     {
         
     }
 }

とりあえず、Imageのインスタンスを捕まえます。

UGUIをスクリプトに書く際には

 using UnityEngine.UI;

これを追加するといいと思います。

フェードインを実装するのに初期設定を行っていきます。

     void Start()
     {
         img = GetComponent<Image>();
         img.color = new Color(1, 1, 1, 1);
         img.fillAmount = 1;
         img.raycastTarget = true; 
     }

初期状態はフェードインをして欲しいので画面全体に表示されている状態にします。

img.raycastTarget = true;

これは当たり判定をオンにしています。フェードが完了するまでボタンを押せなくするためです。フェードが完了したらオフにします。

フェードインを実装しよう

次はフェードインを実装するために、Updateにアルファ値を下げる処理とfillAmountをいじる処理を書きます。

クリックすると展開します
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.UI;
 
 public class FadeImage : MonoBehaviour
 {
     private Image img = null;
     private float timer = 0.0f;
     private int frameCount = 0;
     private bool fadeIn = false;
 
     void Start()
     {
         img = GetComponent<Image>();
         img.color = new Color(1, 1, 1, 1);
         img.fillAmount = 1;
         img.raycastTarget = true;
         fadeIn = true;
     }
 
     void Update()
     {
         if(frameCount > 2)
         {
             if (fadeIn)
             {
                  //フェードイン中 
                  if (timer < 1)
                  {
                      img.color = new Color(1, 1, 1, 1 - timer);
                      img.fillAmount = 1 - timer;
                  }
                  //フェードイン完了 
                  else
                  {
                      img.color = new Color(1, 1, 1, 0);
                      img.fillAmount = 0;
                      img.raycastTarget = false;
           timer = 0.0f;
                      fadeIn = false;
                  }
                  timer += Time.deltaTime;
              }
          }
          ++frameCount;
      }
 }

まず、frameCountという変数で、そのシーンに移動した時に2フレーム待ってからフェードを開始するように設定しています。

これは何故かというと、シーンの移動というのは重い処理なので、移動した時、ゲーム内時間がmaximun allowed time step分進みがちです。また、2フレーム目も重い場合が多いです

maximun allowed time stepは↓の記事を参考にしてください

つまり、シーン移動時2フレームは時間が一気に飛ぶことが多いので、フェードが一気にガクン、ガクンと進んでしまいます。そのため、2フレーム待っているわけです

そしてfadeInのフラグがたったらフェードインするようにします。

この処理は、ちょっと時間を待ってから1秒かけて画像のアルファ値とfillAmountを下げる処理を書いています。

1が最大値なので、そこから1秒かけて引いている感じです。

new Color(1, 1, 1, 1 – timer)はRGBAで、Aがアルファ値で、0に近づくにつれて透明になっていくので、時間でアルファ値が下がっていく形ですね。

ちなみに、このカラーはImageに設定した画像とは別物なパラメーターで、元々の画像に、この色で乗算します。

RGBの部分は0,0,0が黒で1,1,1が白なので、これは白色をかけています。この解説では黒色のフェードを実装していますが、白をかけて大丈夫なのでしょうか?

実は乗算なので元々の画像が黒色だと何かけても0です。そのため、別に1,1,1じゃなくても、どんな値でも結果は変わりません。しかし、何故1,1,1にしているかというと、1を掛け算すると値は変わらないので、白をかけるということは「元々の画像の色を使用しますよ」という意味になります。

そのため、黒色の画像を使った場合、どんな色をかけても結果は変わらないのですが、元々の画像の色を使うという意味で1,1,1にしています。

フェードインが完了したら後処理で値をきちんとします。これはTime.deltaTimeの値がマチマチになるのできっちり1秒になるとは限らないからです

最後の値はきっちりしてfadeInのフラグをfalseにしましょう。

この状態で再生するとフェードインします。

今のままではフェードインを他のスクリプトから呼ぶ事ができないので、呼べるように変更します。また、うまく使いまわせるようにメソッド化していきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class FadeImage : MonoBehaviour
{
     [Header("最初からフェードインが完了しているかどうか")] public bool firstFadeInComp;
     private Image img = null;
     private int frameCount = 0;
     private float timer = 0.0f;
     private bool fadeIn = false;
     private bool compFadeIn = false;

     /// <summary>
     /// フェードインを開始する
     /// </summary>
     public void StartFadeIn()
     {
          if(fadeIn)
          {
               return;
          }
          fadeIn = true;
          compFadeIn = false;
          timer = 0.0f;
          img.color = new Color(1, 1, 1, 1);
          img.fillAmount = 1;
          img.raycastTarget = true;
     }

     /// <summary>
     /// フェードインが完了したかどうか
     /// </summary>
     /// <returns></returns>
     public bool IsFadeInComplete()
     {
          return compFadeIn;
     }

     void Start()
     {
          img = GetComponent<Image>();
          if (firstFadeInComp)
          {
               FadeInComplete();
          }
          else
          {
               StartFadeIn();
          }
     }

     void Update()
     {
          //シーン移行時の処理の重さでTime.deltaTimeが大きくなってしまうから2フレーム待つ
          if(frameCount > 2)
          {
               if (fadeIn)
               {
                    FadeInUpdate();
               }
          }
          ++frameCount;
     }

     //フェードイン中
     private void FadeInUpdate()
     {
          if (timer < 1f)
          {
               img.color = new Color(1, 1, 1, 1 - timer);
               img.fillAmount = 1 - timer;
          }
          else
          {
               FadeInComplete();
          }
          timer += Time.deltaTime;
     }

     //フェードイン完了
     private void FadeInComplete()
     {
          img.color = new Color(1, 1, 1, 0);
          img.fillAmount = 0;
          img.raycastTarget = false;
          timer = 0.0f;
          fadeIn = false;
          compFadeIn = true;
     }
}

メソッド化して外から呼べるようにします。

/// <summary>
/// フェードインを開始する
/// </summary>
public void StartFadeIn()
{
    if (fadeIn)
    {
        return;
    }
    fadeIn = true;
    compFadeIn = false;
    timer = 0.0f; 
    img.color = new Color(1, 1, 1, 1);
    img.fillAmount = 1;
    img.raycastTarget = true;
}

フェードインに必要な各種フラグを初期化します。Startに書いてあった部分をこちらに移し、Startからフェードインを呼ぶようにします。

何度も呼ばれると困るのでfadeInがtrueの時フェードインしないようにreturnで返します。

そして、↓のフラグで他のスクリプトがフェードインを完了したことを認知できるようにします。

/// <summary>
/// フェードインが完了したかどうか
/// </summary>
/// <returns></returns>
public bool IsFadeInComplete()
{
     return compFadeIn;
}

また、タイトルシーンのように、シーンを開始した時にフェードインをしてほしくないシーンもあると思うので、インスペクターで設定できるようにしています。

[Header("最初からフェードインが完了しているかどうか")] public bool firstFadeInComp;

Imageを透明にする時の注意点

さて、フェードインを実装しましたが、Imageのアルファ値をいじって透明にしています。

ここで注意点があるのですが、実は透明であるということは非常に重いのです。

よく思い出してみてください。完全な透明だとイメージしづらいですが、うっすら透明のオブジェクトが画面に乗っている場合どうなるでしょうか?

うっすら透明のオブジェクトがあった場合、後ろのオブジェクトの色にうっすら透明の色が乗って描画されると思います。

透明なオブジェクトが存在した場合、後ろのオブジェクトを描画した後、透明なオブジェクトの色を重ねる処理が走ります。

要は、透明なオブジェクトが存在すると同じ場所が2回描画されます。透明なオブジェクトが増えると同じ場所が何回も描画されることになります。そのため非常に重いのです。

とはいえ、フェードさせるのに透明な状態を使う必要がある為、透明は使わなければなりません。

しかしながら、「フェードしていない時」透明なオブジェクトがずっと存在していると意味もなく重くなってしまいます。

そこで、Canvas RendererのCull Transparent Meshにチェックを入れましょう。

これにチェックが入っていると完全に透明になっているオブジェクトは無視されるようになります。

KeyPoint

・長い間透明な状態になるUGUIがある場合、Cull Transparent Meshにチェックを入れよう!

フェードアウトを実装しよう

では、フェードアウトの方も作りましょう。

フェードインと同じようにすればOKです。

クリックすると展開する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class FadeImage : MonoBehaviour
{
    [Header("最初からフェードインが完了しているかどうか")] public bool firstFadeInComp;

    private Image img = null;
    private int frameCount = 0;
    private float timer = 0.0f;
    private bool fadeIn = false;
    private bool fadeOut = false;
    private bool compFadeIn = false;
    private bool compFadeOut = false;

    /// <summary>
    /// フェードインを開始する
    /// </summary>
    public void StartFadeIn()
    {
         if(fadeIn || fadeOut)
         {
              return;
         }
         fadeIn = true;
         compFadeIn = false;
         timer = 0.0f;
         img.color = new Color(1, 1, 1, 1);
         img.fillAmount = 1;
         img.raycastTarget = true;
    }

    /// <summary>
    /// フェードインが完了したかどうか
    /// </summary>
    /// <returns></returns>
    public bool IsFadeInComplete()
    {
         return compFadeIn;
    }

    /// <summary>
    /// フェードアウトを開始する
    /// </summary>
    public void StartFadeOut()
    {
         if (fadeIn || fadeOut)
         {
              return;
         }
         fadeOut = true;
         compFadeOut = false;
         timer = 0.0f;
         img.color = new Color(1, 1, 1, 0);
         img.fillAmount = 0;
         img.raycastTarget = true;
    }

    /// <summary>
    /// フェードアウトを完了したかどうか
    /// </summary>
    /// <returns></returns>
    public bool IsFadeOutComplete()
    {
         return compFadeOut;
    }

    void Start()
    {
         img = GetComponent<Image>();
         if (firstFadeInComp)
         {
              FadeInComplete();
         }
         else
         {
              StartFadeIn();
         }
    }

    void Update()
    {
         //シーン移行時の処理の重さでTime.deltaTimeが大きくなってしまうから2フレーム待つ
         if(frameCount > 2)
         {
               if (fadeIn)
               {
                    FadeInUpdate();
               }
               else if (fadeOut)
               {
                    FadeOutUpdate();
               }
         }
         ++frameCount;
     }

     //フェードイン中
     private void FadeInUpdate()
     {
         if (timer < 1f)
         {
              img.color = new Color(1, 1, 1, 1 - timer);
              img.fillAmount = 1 - timer;
         }
         else
         {
               FadeInComplete();
         }
         timer += Time.deltaTime;
    }

    //フェードアウト中
    private void FadeOutUpdate()
    {
         if(timer < 1f)
         {
              img.color = new Color(1, 1, 1, timer);
              img.fillAmount = timer;
         }
         else
         {
              FadeOutComplete();
         }
         timer += Time.deltaTime;
    }

    //フェードイン完了
    private void FadeInComplete()
    {
         img.color = new Color(1, 1, 1, 0);
         img.fillAmount = 0;
         img.raycastTarget = false;
         timer = 0.0f;
         fadeIn = false;
         compFadeIn = true;
    }

    //フェードアウト完了
    private void FadeOutComplete()
    {
         img.color = new Color(1, 1, 1, 1);
         img.fillAmount = 1;
         img.raycastTarget = false;
         timer = 0.0f;
         fadeOut = false;
         compFadeOut = true;
    }
}

これをボタンを押した時に起動するようにしましょう。

↓タイトルの方のスクリプトにpress startを押したらフェードアウトし、フェードアウトが完了したらシーンを移動するようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Title : MonoBehaviour
{
     [Header("フェード")] public FadeImage fade;

     private bool firstPush = false;
     private bool goNextScene = false;

     //スタートボタンを押されたら呼ばれる
     public void PressStart()
     {
          Debug.Log("Press Start!");
          if (!firstPush)
          {
              Debug.Log("Go Next Scene!");
              fade.StartFadeOut();
              firstPush = true;
          }
     }

     private void Update()
     {
          if (!goNextScene && fade.IsFadeOutComplete())
          {
               SceneManager.LoadScene("stage1");
               goNextScene = true;
          }
    }
}

インスペクターでインスタンスを渡すのを忘れないようにしましょう。

各シーンにフェードを配置しよう

さて、このフェードの設定をいちいちするのはだるいのでプレハブ化しましょう。

プレハブについてよくわからない方は↓の記事を参考にしてください。

Prefab用のフォルダを作っておくと便利かと思います

これをステージシーンにも配置しましょう。配置する際に、ステージシーンにもタイトルと同じ設定のキャンバスを用意します。

一つ注意点として、カメラが映す距離を変更している場合、Plane Distanceがその範囲内に入るようにしてください。

そして作ったプレハブをCanvas配下に設置すればOKです。

タイトルシーンに戻って再生すると

これでシーンのフェードを作る事ができました。

何かうまくいかない事があった場合は↓の記事を参考にしてみてください

最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。

また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。