ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

20220329162023

Unity - 2D:残基、ゲームクリアなどの処理 -

初めに

 GameManagerなどを作り、残機やゲームクリアなどゲームシステムを構築していきます。

GameManagerを作る

 GameManagerは、現在のステージの進行状況や残機、セーブデータ、スコアなどシーンをまたいで扱いたいものを保持させることが多いです。
 今回は、現在のステージ、残機を追加します。GameManagerを次のように記述しましょう。

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

public class GameManager : SingletonMonoBehaviour<GameManager>
{
    //現在のステージ
    public int stageNum;
    //残機
    public int remain = 2;

    public void Awake()
    {
        if (this != Instance)
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }

    //以下に処理を書く
}

 現在のステージと残機を記述しました。ステージクリアをしたら、stageNumを変えましょう。また、プレイヤーがミスをしたら残機数であるremainを減らしましょう。
 注意すべき点はシングルトンになっている点です。シングルトンについては、以前の記事をご覧ください。
 GameManagerのオブジェクトはほぼ全シーンで使うため、プレハブ化しておきましょう。プレハブについての説明は次の記事をご覧ください。
fineworks-fine.hatenablog.com

ゴールを作る

 ゴールの処理を作ります。今回はゴールとなるオブジェクトを作り、そのオブジェクトに触れたらゴールとしたいと思います。

  • ゴールとなる画像を用意する

 まず、ゴールとなる画像を用意します。私は次のドット絵をゴールとしました。


  • ゴールのSpriteを生成し、BoxCollider2Dをつける


 
 今回は、ゴールをすり抜けられるようにするため、Is Trigger にチェックを入れてください。

 Spriteの使い方やBoxCollider2Dの使い方については、以下の記事をご覧ください。
fineworks-fine.hatenablog.com
fineworks-fine.hatenablog.com

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

public class GoalScript : MonoBehaviour
{
    public GameObject GameManager;
    private GameManager GMScript;

    void Start()
    {
        GMScript = GameManager.GetComponent<GameManager>();
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Player"))
        {
          GMScript.stageNum++;
          Debug.Log("ゴールした.NextStageNum = " + GMScript.stageNum); // ログを表示する
        }
    }
}

 CompareTagはオブジェクトのtagを比較して判別するものです。今回であれば、collisionで取得したゲームオブジェクトのtagが "Player" かどうかを判定しています。もし、ゴールにPlayerが触れれば、ゲームマネージャーのstageNumを1増やすという処理になっています。

  • Goalをプレハブ化する

 Goalも何回も使うため、プレハブ化しておきます。

  • プレハブ化したGoalのスクリプトにGameManagerをアタッチする

 これでゴールのオブジェクトは完成です。プレイヤーキャラのタグをPlayerに変えて実行してみましょう。

実行結果

 ログを見ると、stageNumが増えているので、うまくいっていることが分かります。

ステージクリアの処理を作る

 ゴールしたら、ステージクリアを表示し、次のステージへ移る処理を作りましょう。

ステージクリアテキストを表示させる

  • ステージクリアのTextを作成し、Canvasごとプレハブ化する

 Hierarchyで右クリック→UI→TextでTextを生成できます。また、Component→Effects→UI→Outlineで文字の輪郭に色を付けることができます。

  • GameManager、GoalScriptをそれぞれ次のように修正する

GameManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;  //Textを使うため追加

public class GameManager : SingletonMonoBehaviour<GameManager>
{
    //現在のステージ
    public int stageNum;
    //StageClaer時に生成するCanvas
    public GameObject StageClearCanvas;

    //残機
    private int remain = 2;

    public void Awake()
    {
        if (this != Instance)
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }

    //以下に処理を書く
    void Start()
    {
      Debug.Log("始まったよ");
    }

    //ステージクリア時に呼び出される関数
    public void StageClaer()
    {
      //表示されるTextの取得
      Text stageclearText = StageClearCanvas.transform.Find("Text").GetComponent<Text>();
      //表示されるTextを入力
      stageclearText.text = "Stage" + stageNum + "\nClear";

      //Canvasごとクリア時のTextを生成する
      GameObject _clearcanvas = Instantiate(StageClearCanvas, new Vector3(0, 0, 0), Quaternion.identity);

      //stageNumを1増やす
      stageNum++;
    }
}

GoalScript

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

public class GoalScript : MonoBehaviour
{
    public GameObject GameManager;
    private GameManager GMScript;

    void Start()
    {
        GMScript = GameManager.GetComponent<GameManager>();
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Player"))
        {
          //ステージクリア時の処理をGameManagerから呼び出す
          //stageNumを増やす処理もGameManagerに移した
          GMScript.StageClaer();
          Debug.Log("ゴールした.NextStageNum = " + GMScript.stageNum); // ログを表示する
        }
    }
}

 Textの使いかたやInstantiate関数を使ってプレハブを生成する方法については、次の記事にまとめております。

fineworks-fine.hatenablog.com
fineworks-fine.hatenablog.com

  • プレハブ化した(クリア時に表示するTextを子オブジェクトにもつ)CanvasをGameManagerにアタッチする

 ここまでで、ゴールするとTextが表示されるようになりました。

次のシーンへ移る

 クリア演出後、次のステージへ移るようにします。

  • Sceneの名前をStage1, Stage2, ...と変更する
  • SceneをScene in Buildに登録する

 File→Build Settings...をクリックすると、次の画面が開きます。シーンをドラッグ&ドロップし、登録します。


  • GameManagerをシーンが遷移できるように修正する

GameManagerを次のように追記します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;  //Textを使うため追加
using UnityEngine.SceneManagement;  //Sceneを切り替えるために追加

public class GameManager : SingletonMonoBehaviour<GameManager>
{
    //現在のステージ
    public int stageNum;
    //StageClaer時に生成するCanvas
    public GameObject StageClearCanvas;

    //残機
    private int remain = 2;

    public void Awake()
    {
        if (this != Instance)
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }

    //以下に処理を書く
    void Start()
    {
      Debug.Log("始まったよ");
    }

    //ステージクリア時に呼び出される関数
    public void StageClaer()
    {
      //表示されるTextの取得
      Text stageclearText = StageClearCanvas.transform.Find("Text").GetComponent<Text>();
      //表示されるTextを入力
      stageclearText.text = "Stage" + stageNum + "\nClear";

      //Canvasごとクリア時のTextを生成する
      GameObject _clearcanvas = Instantiate(StageClearCanvas, new Vector3(0, 0, 0), Quaternion.identity);

      //stageNumを1増やす
      stageNum++;

      //2秒後にChangeScene関数を実行
      Invoke(nameof(ChangeScene), 2.0f);  
    }

    //次のステージへシーンを移す関数
    public void ChangeScene()
    {
      SceneManager.LoadScene("Stage" + stageNum);
    }
}

 シーンを切り替える処理を追記しました。シーンの切り替えには "SceneManager" を使います。細かいことは次の記事をご覧ください。

fineworks-fine.hatenablog.com

 また、ゴールして一定時間後にシーンを移すようにInvokeを使っています。Invokeを使うことで一定秒数後に関数を呼び出すことができます。一定時間後に処理を実行する方法はほかにもコルーチンを使う方法があります。個人的にはコルーチンを使うほうが好みです。


実行して確認

 分かりにくいですが、プレイヤーキャラがスタート地点に現れ、次のシーンが始まったことが分かります。

バグを修正しよう

 このままでは、何回もゴールに触れることができるため、次のシーンに移るまでstageNumを増やし続けられます。そのため、1ステージにつき、1回しかゴールできないようにしましょう。
 いろいろな方法がとれますが、今回はGoalScriptにGoalしたかどうかの判定を追加することで対応します。

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

public class GoalScript : MonoBehaviour
{
    public GameObject GameManager;
    private GameManager GMScript;

    private bool Goaled = false;  //追加

    void Start()
    {
        GMScript = GameManager.GetComponent<GameManager>();
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Player") && Goaled == false)  //追加
        {
   Goaled = true;  //追加
          //ステージクリア時の処理をGameManagerから呼び出す
          //stageNumを増やす処理もGameManagerに移した
          GMScript.StageClaer();
          Debug.Log("ゴールした.NextStageNum = " + GMScript.stageNum); // ログを表示する
        }
    }
}

最後に

 これで残機、ゲームクリアの処理などが作れました。残機については、ステージから落下した場合や敵に触れた場合などにプレイヤーのスクリプトからGameManagerの変数を減らせばよいでしょう。これは落下処理や敵を作った際に追加すればよいと思います。