Unityで数字順押しゲーム(タッチナンバー)を作る 3

前回までのあらすじ

  1. 数字ボタンがランダムな位置に表示されるようにしました。
  2. ボタンクリック時の判定を追加し、正しい順にクリックされている場合にボタンを削除するようにしました。

完成予定のゲームとソース

完成するゲームは以下のページで遊べます。

Unity WebGL Player | TouchNumber

ソースはGitHubのページにあります。

gamegame-game/TouchNumber
Unity Game, Touch Number. Contribute to gamegame-game/TouchNumber development by creating an account on GitHub.

今回の作業

  1. クリアまでの時間を計測して画面に表示
  2. スタートシーンを作成し、ゲームシーンとの遷移を作成
  3. ビルドして遊ぶ

ゲームシーンに計測時間を表示する

スタートからクリアまでの時間を計測するようにしたいと思います。クリア時間がこのゲームの結果(スコア?)になります。画面右上にゲーム開始からの時間を表示するようにします。

UIを作成する

まずは計測時間を表示するためのUIをシーン上に配置します。

Hierarchy -> Create -> UI -> Text からオブジェクトを作成し、名前を “Timer” とします。表示位置やフォントサイズ等は適宜設定してください。とりあえず以下の用に設定してみました。

実行画面でいい感じに表示されていればOKです。

一応以下のように設定しています。

  • Anchor Preset -> 右上
  • Position -> (-60, -30, 0)
  • Width, Height -> (120, 40)
  • Font Size  -> 32
  • Alignment -> 中央中段
  • Color -> #FFFFFF(白色)

TimerController を作成する

経過時間を表示するために、スクリプトを作成します。

Project -> Create -> Script C# からスクリプトを作成し、名前を “TimerController” とします。先程作成したUI(Timer)にコンポーネントとして貼り付けておきます。

TimerController.cs の実装

では実装していきます。

やることは単純で、

  • Startメソッド(オブジェクト生成)でTimerオブジェクトを取得
  • Updateメソッドが呼ばれるたびに、経過時間を計算してTimerオブジェクトのTextコンポーネントのテキストを上書き
  • ただし停止中の場合は経過時間の更新はしない

です。プロパティを外部から更新することで時間を止められるようにしています。

using UnityEngine;
using UnityEngine.UI;

public class TimerController : MonoBehaviour
{
    // 計測時間
    public float TimeCounte { get; private set; } = 0f;

    // 開始されたかどうかのフラグ
    public bool IsStarted { get; set; } = false;

    // 経過時間を表示するテキストオブジェクト
    public GameObject TimerText { get; private set; }

    void Start()
    {
        // テキストオブジェクトを取得しておく
        this.TimerText = GameObject.Find("Timer");

        // 計測開始
        this.IsStarted = true;
    }

    void Update()
    {
        if (this.IsStarted)
        {
            // タイマーが計測開始中の場合のみ
            // 経過時間を加え、テキストに表示
            this.TimeCounte += Time.deltaTime;
            this.TimerText.GetComponent<Text>().text = this.TimeCounte.ToString("F2");
        }
    }
}

ゲームを実行すると、右上のタイマーに経過時間が表示されるはずです。

スタートシーンを作成

ゲームを遊ぶためのシーンとは別に、スタートシーンを作成します。今のままだとゲーム起動と同時に時間の計測が始まるので、スタート画面にクリックしてからゲームが始まるようにします。

Project -> Create -> Scene で新しくシーンを作成します。名前を “StartScene” とします。

タイトル表示

Hierarchy -> Create -> UI -> Text でタイトル表示用のテキストを作成します。名前を “Title” とします。ただのタイトルなので適当に表示位置とか文言を表示させておきます。

雑ですが、まあこんな感じで良いでしょう。

GameSceneに遷移する処理の作成

Hierarchy -> Create -> Create Empty で空のオブジェクトを作成します。名前を “StartDirector” とします。同じ名前のスクリプトを作成し、コンポーネントとして貼り付けておきます。

実装は以下のとおりです。

using UnityEngine;
using UnityEngine.SceneManagement;

public class StartDirector : MonoBehaviour
{
    void Update()
    {
        // クリック時に遷移
        if (Input.GetMouseButtonDown(0))
        {
            SceneManager.LoadScene("GameScene");
        }
    }
}

これでクリック時に “GameScene” という名前のシーンに遷移することができます。起動し動くか確認しましょう。

クリックするとエラーが発生するときの対処

Scene 'GameScene' couldn't be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.
To add a scene to the build settings use the menu File->Build Settings...
UnityEngine.SceneManagement.SceneManager:LoadScene(String)
StartDirector:Update() (at Assets/StartDirector.cs:11)

遷移しようとすると上記のようなエラーが発生する場合があります。書いてあるのは「ビルド設定がされてないから遷移できないよ」みたいなことです。「File->Build Settings … から設定してね」とも書いてあるみたいなので言われたとおりにします。

File->Build Settings からビルド設定画面を開くことができるので、画面上部の枠にビルド対象とするシーンをドラッグ&ドロップで追加しましょう。プラットフォームはとりあえずなんでもいいです。

ビルドしたゲームを起動した時、先頭のシーンが一番最初のシーンとして読み込まれるので、”StartScene” を選択に並べ替えておきましょう。ドラッグ&ドロップで並べ替えられます。

ゲームを実行すると遷移することが確認できます。

結果を表示してスタート画面に戻る

では最後の仕上げです。ゲームをクリアした場合に結果を表示するようにしましょう。そしてクリックしてスタート画面に戻るということにします。

結果表示用のUIテキスト作成

結果を表示するためのUIを作成し、Prefabにしておきます。手順は省略。

クリア時にこれを生成し表示します。”GameDirector” スクリプトにクリア判定の処理を作成します。

GameDirector.cs の編集

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameDirector : MonoBehaviour
{
    // 結果表示用UIのPreafab
    public GameObject ResultTextPrefab;

    // 生成するボタンの数
    public static int ButtonRowCount = 1;
    public static int ButtonColCount = 1;
    public static int ButtonAllCount = ButtonRowCount * ButtonColCount;

    // 次の数字
    public int NextNumber { get; set; } = 1;

    // クリアの判定
    public bool IsCleared { get { return ButtonAllCount < this.NextNumber; } }

    // クリックした値の正誤判定
    public bool CheckNumber(int number)
    {
        return number == this.NextNumber;
    }

    // 次の値を更新する
    public void ChangeNextValue()
    {
        this.NextNumber++;
    }

    // 開始時にボタンを生成する
    void Start()
    {
        // オブジェクトからスクリプトコンポーネントを取得
        // 生成メソッドを呼び出す
        GameObject.Find("NumberButtonGenerator")
            .GetComponent<NumberButtonGenerator>()
            .GenerateNumberButtons(ButtonRowCount, ButtonColCount);
    }

    void Update()
    {
        if (this.IsCleared && Input.GetMouseButtonDown(0))
        {
            // クリア時にクリックされたらスタート画面へ遷移
            SceneManager.LoadScene("StartScene");
        }
    }

    // クリア処理
    public void ClearGameIfAllButtonClicked()
    {
        // 全部ボタンが消えている場合はクリアとする
        if (this.IsCleared)
        {
            // タイマーを止めて結果を表示
            var timerObject = GameObject.Find("Timer");
            var timerController = timerObject.GetComponent<TimerController>();
            timerController.IsStarted = false;
            var resultTime = timerController.TimeCounte;

            // 結果を表示
            var resultText = $"Result\n{resultTime.ToString("F2")}";
            var resultTextObject = Instantiate(this.ResultTextPrefab) as GameObject;
            resultTextObject.GetComponent<Text>().text = resultText;

            // キャンバスの子として表示
            resultTextObject.GetComponent<Transform>().SetParent(
                GameObject.Find("Canvas").GetComponent<Transform>()
                );
            resultTextObject.transform.localPosition = new Vector3(0, 0, 0);

            // 右上の数字は非表示にする
            timerObject.SetActive(false);
        }
    }
}

コードの説明

前回から結構変わっています。

まず8行目は先ほど作成したPrefabを設定します。これに結果を表示します。

次に19行目ですが、これはゲームがクリアしたかどうかを判定するプロパティです。次の数字がボタンの総数より大きければ全ボタンクリックし終わった=クリアと判断するようにしています。

そして43行目、Updateメソッドを追加してクリア済でクリックがあったときにスタートシーンに戻るようにしています。これで何度も繰り返しプレイできるようになります。

最後に53行目、新しく作成した ClearGameIfAllButtonClicked メソッドです。これはクリアされた場合にのみ処理を実行します。基本的に難しいことはしていません。

ClearGameIfAllButtonClicked

まず、タイマーを止める必要があります。コントローラーオブジェクトを探し、スクリプトを取得し、ストップします。そのあと計測された時間を取得します。

次に結果の表示です。Prefab から オブジェクトを生成し、Textコンポーネントに結果の文字列を設定します。キャンバスを親として表示します。このあたりの手順はボタン生成の処理と考え方は同じです。

最後に結果を大きく表示するので右上の時間表示はいらないかなと思ったので消してます。オブジェクトに対して、SetActiveメソッド で False を設定することで画面から消えるみたいなので非表示にしています。

クリア処理を呼び出すようにする

クリア処理を作成しましたが、これを呼び出すようにしなければなりません。NumberButtonController.cs にあるクリック時の処理で呼び出すようにして完成です。

ただしボタンが押されるたびにクリアの判定が走りますが、まあいいでしょうたぶん。

// クリック時の処理
private void OnClick()
{
    if (this.GameDirector.CheckNumber(this.Number))
    {
        // 正しい番号の場合、数字を進めてボタンを消す
        this.GameDirector.ChangeNextValue();
        Destroy(gameObject);

        // クリア判定と処理を呼び出し
        this.GameDirector.ClearGameIfAllButtonClicked();
    }
}

完成 … !

ようやく完成です。簡単なゲームを作っていたはずがかなり大変でした。せっかくなので遊んでみます。

ゲームっぽくて感動です。

以下のページで遊べるので良かったらどうぞ。

Unity WebGL Player | TouchNumber

まとめ

ひとまずの完成を見ることができました。初めてのUnityということもあり、かなり手探りでの実装でしたので色々と手直しすべき点が多くありそうです。

もう少し開発に慣れてきたらリファクタリングというか、作り直してみようと思います。

以上、次は2Dのブロック崩しを作って見る予定で考えてます。

リンク

コメント