Developer

【Unity実践】#22 ステージ管理の仕組みを作成【ランゲーム】
2022.02.08
Lv2

【Unity実践】#22 ステージ管理の仕組みを作成【ランゲーム】

今回の内容

今回はステージ管理の仕組みを作成します。
本連載中で最も難しい内容かもしれません。

※初めての方はこちらから
【第1回記事】導入とサンプルの紹介

仕様とデータの整理

ステージ管理の仕組みはゲームの仕様や開発プロジェクトによって様々です。
ここでは、仕様を決めて実装をしていきます。

<仕様>
・最初にロードするステージは名前が「Stage1」のシーン
・クリアごとに「Stage〇〇」の名前のシーンを順にロード(〇〇は数字が入る)
・全ステージクリア後は、ランダムにステージが読み込まれる
・現在何ステージ目かを「Level」という単位で管理する。(画面上部に表示)

次に、セーブデータとして管理する項目を整理します。
セーブデータと言いましたが、Unityの標準機能にある PlayerPrefs のことです。
上記仕様をもとに、以下の2つのパラメータを用意します。

・levelNum → 現在何ステージ目か
・stageNum → 読み込むステージの番号(Stage〇〇の〇〇に入る数字)

全ステージクリアするまでは stageNum と levelNum は一致しますが、
クリア後は stageNum はランダム、levelNum は引き続き1ずつ増加していきます。

前回までに作成した coinNum と併せて、PlayerPrefs は以下のような構成となります。

coinNum を除く2つのデータの役割を理解しておかないと、以降の実装が分からなくなるので、必ず頭に入れてから読み進めてください。

フロー図を考える

次に処理フローを考えていきます。
ここでは一例として以下に載せたものを使用します。

このフロー図に従って、次はいよいよ実装をしていきます。
余裕のある方は一度自分で実装してみて頂いても良いと思います。

実装してみる

GameManagerScript を編集していきます。

① プロパティを追加する

~ 省略 ~

public class GameManagerScript : MonoBehaviour
{
    public enum GAME_STATUS { Play, Clear, Pause, GameOver };
    public static GAME_STATUS status;

    public static int tempCoinNum;

    [SerializeField]
    Text coinNumText, resultCoinText, levelNumText;

    [SerializeField]
    GameObject clearUI, gameOverUI;

    int stageCoinNum;

    const string STAGE_NAME_PREFIX = "Stage";
    const int MAX_STAGE_NUM = 1;

    int levelNum; // 現在の進行数
    int stageNum; // 読み込むステージ番号

    void Start()
  {

    ~ 省略 ~

11行目は、levelNumText のみ追加しています。
STAGE_NAME_PREFIX は読み込むシーン名の数字を抜いた文字列を設定します。
MAX_STAGE_NUM にはステージ数を設定します。

② ロード対象のシーンをロードするメソッドを作成
以下の2つのメソッドを追加してください。

    private string GetLoadSceneName()
    {
        return STAGE_NAME_PREFIX + stageNum;
    }

    public void LoadScene()
    {
        SceneManager.LoadScene(GetLoadSceneName());
    }

GetLoadSceneName は読み込むべきシーンの名前を取得するメソッドです。
実際の読み込みは LoadScene メソッドで行っており、ステージ名の取得に GetLoadSceneName を使用しています。

③ LoadNextSceneメソッドを変更
以前作成した、LoadNextSceneメソッドを下記の通り変更します。

    public void LoadNextScene()
    {
        PlayerPrefs.SetInt("levelNum", ++levelNum);

        stageNum = levelNum <= MAX_STAGE_NUM ? levelNum : Random.Range(1, MAX_STAGE_NUM + 1);
        PlayerPrefs.SetInt("stageNum", stageNum);

        LoadScene();
    }

これはクリア画面のNextボタンで呼び出していたメソッドです。
処理内容は、フロー図のクリア以降の流れに従っています。

※Random.Rangeの第二引数について
Random.Range(A, B) は「A以上B未満の乱数」を生成します。
そのため「MAX_STAGE_NUM + 1」とすることで、「1~MAX_STAGE_NUMの乱数」を生成します。
ただしこれは引数が整数の場合で、小数を指定した場合とは異なるので注意してください。
(公式リファレンス)Random.Range

④ Startメソッドを変更
最後に、Startメソッドを変更します。

    void Start()
    {
        // ステージ番号ロード
        stageNum = PlayerPrefs.GetInt("stageNum", 1);

        // 自分のシーンではない場合、ロードし直す
        if (!GetLoadSceneName().Equals(SceneManager.GetActiveScene().name))
        {
            LoadScene();
            return;
        }

        // レベル番号をロード
        levelNum = PlayerPrefs.GetInt("levelNum", 1);
        levelNumText.text = "Level " + levelNum;

        // ステージ内のコインの枚数を取得
        stageCoinNum = GameObject.FindGameObjectsWithTag("Coin").Length;

        // これまでの獲得コイン数をロード(初回は0)
        tempCoinNum = PlayerPrefs.GetInt("coinNum", 0);

        // ステータスをPlayに
        status = GAME_STATUS.Play;
    }

処理内容は、フロー図のゲームプレイ前の内容となっています。
この処理の目的は、ゲームを再起動した時に最初のシーン(Stage1)が読み込まれてしまう問題の対策です。
PlayerPrefs を参照して続きのシーンを再読み込みさせる形としています。

また、フロー図には無いですが levelNum を UI に反映する処理を記述しています。
levelNumText には以下のUIオブジェクトを忘れずに設定してください。
Canvas > GameUI > LevelNumText

ここまででステージ管理の仕組みは完成となります。

動作確認と完成版コード

テストプレイは以下の準備を済ませた上で行ってください。

<テストプレイ準備>
・Stage1を複製して少し変更を加えたシーンを複数用意
・複製した分だけ、MAX_STAGE_NUMの値を変更
・Build Settings を開いて、Stage〇〇を追加

※3つ目のBuild Settings の設定イメージは下記です。

設定方法は割愛しますので、以下の記事の「ビルドの設定」を参照してください。
【初心者Unity】シーンの切り替え

また、コードの最終形を以下に載せておきます。

完成ソース
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameManagerScript : MonoBehaviour
{
    public enum GAME_STATUS { Play, Clear, Pause, GameOver };
    public static GAME_STATUS status;

    public static int tempCoinNum;

    [SerializeField]
    Text coinNumText, resultCoinText, levelNumText;

    [SerializeField]
    GameObject clearUI, gameOverUI;

    int stageCoinNum;

    const string STAGE_NAME_PREFIX = "Stage";
    const int MAX_STAGE_NUM = 1;

    int levelNum; // 現在の進行数
    int stageNum; // 読み込むステージ番号

    void Start()
    {
        // ステージ番号ロード
        stageNum = PlayerPrefs.GetInt("stageNum", 1);

        // 自分のシーンではない場合、ロードし直す
        if (!GetLoadSceneName().Equals(SceneManager.GetActiveScene().name))
        {
            LoadScene();
            return;
        }

        // レベル番号をロード
        levelNum = PlayerPrefs.GetInt("levelNum", 1);
        levelNumText.text = "Level " + levelNum;

        // ステージ内のコインの枚数を取得
        stageCoinNum = GameObject.FindGameObjectsWithTag("Coin").Length;

        // これまでの獲得コイン数をロード(初回は0)
        tempCoinNum = PlayerPrefs.GetInt("coinNum", 0);

        // ステータスをPlayに
        status = GAME_STATUS.Play;
    }

    void Update()
    {
        if (status == GAME_STATUS.Clear)
        {
            // 現在のステージで獲得したコインの枚数
            int getCoinNum = tempCoinNum - PlayerPrefs.GetInt("coinNum", 0);

            resultCoinText.text = getCoinNum.ToString().PadLeft(3) + "/" + stageCoinNum;
            clearUI.SetActive(true);

            // コインを保存
            PlayerPrefs.SetInt("coinNum", tempCoinNum);

            enabled = false;
        }
        else if (status == GAME_STATUS.GameOver)
        {
            Invoke("ShowGameOverUI", 3f);
            enabled = false;
            return;
        }

        coinNumText.text = tempCoinNum.ToString();
    }

    public void LoadCurrentScene()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    private string GetLoadSceneName()
    {
        return STAGE_NAME_PREFIX + stageNum;
    }

    public void LoadScene()
    {
        SceneManager.LoadScene(GetLoadSceneName());
    }

    public void LoadNextScene()
    {
        PlayerPrefs.SetInt("levelNum", ++levelNum);

        stageNum = levelNum <= MAX_STAGE_NUM ? levelNum : Random.Range(1, MAX_STAGE_NUM + 1);
        PlayerPrefs.SetInt("stageNum", stageNum);

        LoadScene();
    }

    private void ShowGameOverUI()
    {
        gameOverUI.SetActive(true);
    }
}

おわりに

かなり難しい内容でしたが、ステージ管理の仕組みは多くのゲームで必要となります。
実装方法は様々ですので、自分で仕様を変えてカスタマイズしてみても良いかと思います。

 

 
関連リンク ➡ 「初心者のための」Unityゲーム制作 目次

© Unity Technologies Japan/UCL