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

前回までのあらすじ

  1. Unityプロジェクトを作成し、ボタンを配置
  2. スクリプトでボタンのサイズのテキストを設定できるようにした
  3. スクリプトのボタンに追加し、Prefabを作成した

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

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

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. 正しい順でクリックされたボタンを削除する処理

では引き続きやっていきます。

ボタンを自動で生成するスクリプトを作成

まずは、Prefabからボタンを生成するスクリプトを作成します。Inspectorから Create -> Empty Create で空のGameObjectを作成します。名前を NumberButtonGenerator とします。文字通りボタンを生成するスクリプトをコンポーネントとして持つだけのオブジェクトです。

続けて、Project から Create -> Script C# を選択し、NumberButtonScript.cs を作成します。作成したら先程用意した Generator にスクリプトをコンポーネントとして追加します。

こんな感じです。

NumberButtonGenerator.cs の実装

このスクリプトではボタンを生成し、画面上に配置する処理を実装します。

 

using System;
using System.Linq;
using UnityEngine;

public class NumberButtonGenerator : MonoBehaviour
{
    // ボタンのプレファブ
    public GameObject NumberButtonPrefab;
    
    void Start()
    {
        // 5 * 5 でボタンを生成してみる
        this.GenerateNumberButtons(5, 5);
    }

    public void GenerateNumberButtons(int rowCount, int colCount)
    {
        var canvas = GameObject.Find("Canvas");

        // 番号をシャッフル
        var numbers = Enumerable.Range(1, rowCount * colCount)
            .OrderBy(i => Guid.NewGuid()).ToArray();
        Debug.Log(string.Join(", ", numbers));

        // 一度ボタン等のオブジェクト全削除
        var buttons = GameObject.FindGameObjectsWithTag("NumberButton");
        foreach (var item in buttons) Destroy(item);

        // ボタンの位置調整用
        float offsetCountX = (colCount - 1) / 2.0f;
        float offsetCountY = (rowCount - 1) / 2.0f;

        int index = 0;
        for (int y = 0; y < rowCount; y++)
        {
            for (int x = 0; x < colCount; x++)
            {
                // ボタンに設定される番号
                int number = numbers[index++];

                // ボタンを生成
                var button = Instantiate(this.NumberButtonPrefab) as GameObject;

                // ボタンの情報を設定
                var controller = button.GetComponent<NumberButtonController>();
                controller.SetButtonInfos(number, number.ToString());

                // 画面中央に配置されるようにする
                button.transform.SetParent(canvas.GetComponent<RectTransform>());

                // (0, 0. 0) からボタンサイズ*個数分ずらして配置すると、中央から右上に偏る
                //button.transform.localPosition = new Vector3(
                //    controller.Width * x,
                //    controller.Heigh * y,
                //    0);

                // したがって、表示する行数列数から中央に来るように調整した位置とする
                button.transform.localPosition = new Vector3(
                    controller.Width * x - controller.Width * offsetCountX,
                    controller.Heigh * y - controller.Heigh * offsetCountY,
                    0);
            }
        }
    }
}

NumberButtonGenerator.cs の説明

NumberButtonPrefab

NumberButtonPrefab はPrefabです。ボタンのPrefab自体はInspectorから設定します。

GenerateNumberButtons メソッド

画面上にボタンを生成して配置するためのメソッドです。ボタンはグリッド状に並べます。引数で渡された行数、列数の形にします。もちろん数字の並びはランダムです。

Canvasオブジェクトを取得し、その上に配置していきます。

配列をシャッフルする方法

配列をシャッフルするには、OrderBy(i => Guid.NewGuid()) とします。一番お手軽な方法だと思います。Guid.NewGuid() は 128bitの乱数を生成するメソッドです。

Enumerable.Range()で連番の配列を生成し、乱数をキーに並べ替えている(つまり結果的にシャッフルしている)ということです。

一応シャッフルされているか、ログを吐いて確認しています。確認できたら消してください。

ボタンの全削除、破棄

一応ボタンを生成する前に、すでに存在するボタンがあればそれを削除するようにします。タグでボタンを取得し、全部破棄しています。

ボタンの生成と配置

Instantiate() で Prefabからオブジェクトを生成します。生成したオブジェクトから GetComponent<NumberButtonController>() でコントローラーを取得します。前回作成したスクリプトです。

コントローラーからボタンのテキストと数字を設定します。

ボタンはCanvas上に配置するので、すでに取得しておいたCanvasを親として設定(SetParent)します。

表示位置はうまいこと画面中央グリッド状になるように配置しています。transform.positionだと、シーンの中央?になるので、localPositionを使います。そうすることで、Canvas基準の位置を設定できます。(0, 0, 0)が画面の中央になります。

localPositionの座標の計算が言葉とコードではわかりにくいですが、要するに以下図のようなことをしています。コメントアウトしている座標設定では青色のように偏るので、中央に来るようにずらしています。

おそらくもっとスマートなコードがかけるかと思いますが、とりあえずこれで中央に表示されています。あるいは、Layout系のコンポーネントを組み合わせることで座標を自分で計算しなくても、やりたいことを実現できるような気もするのですが、いろいろ試してもうまくいきませんでした。

動作確認

ここまでで起動すると画面に5*5のボタンが表示されるはずです。いい感じでランダムに並んでいます。クリックしたらコントローラーで設定している処理が実行されます。

 

ボタンクリック時の判定処理を作成

ランダムに表示するところまでできたのであと一息です。クリックされたボタンの値が正しいかどうか判定し、正しい場合にはボタンそのものを削除するという処理を追加してみます。

ゲームの開始や判定はGemeDirectorというスクリプトで管理するようにします。ということで作成します。

Project -> Create -> Script C# で GameDireactor.cs を作成します。

Hierarchy -> Create -> Create Empty で GamedDireactor を作成し、作成したスクリプトをドラッグ&ドロップで追加しておきます。

GamedDireactor.cs の実装

using UnityEngine;

public class GameDirector : MonoBehaviour
{
    // 生成するボタンの数
    public static int ButtonRowCount = 3;
    public static int ButtonColCount = 3;
    public static int ButtonAllCount = ButtonRowCount * ButtonColCount;

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

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

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

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

開始時に Generator からスクリプトを呼び出してボタンを作ってもらいます。生成するボタンの数は定数で定義しておくことにします。

あとは次に選ぶ数字をプロパティで保持し、これを使って数字が正しいかどうかを判定するメソッド(CheckNumberメソッド)を用意します。

次の数字を更新するメソッド(ChangeNextValuemメソッド)も用意しておきます。

ボタンのコントローラーからこれらの処理を呼び出すことで、ボタンの制御を行います。

NumberButtonController.cs の編集

クリック時の処理を作成します。コントローラースクリプトのクリック時のメソッドを以下のようにします。

using UnityEngine;
using UnityEngine.UI;

public class NumberButtonController : MonoBehaviour
{
    public GameDirector GameDirector { get; set; }

    // ボタンに表示されるテキストと数値
    public string Text { get; private set; }
    public int Number { get; private set; }

    // ボタンのサイズ
    public int Width = 40;
    public int Heigh = 40;

    void Start()
    {
        this.GameDirector = GameObject.Find("GameDirector").GetComponent();

        // ボタンクリック時の処理を追加
        this.GetComponent<button>().onClick.AddListener(OnClick);

        // ボタンのサイズを設定
        this.GetComponent().sizeDelta = new Vector2(this.Width, this.Heigh);
    }

    // ボタンの情報を設定する
    public void SetButtonInfos(int number, string text)
    {
        this.Text = text;
        this.Number = number;

        this.GetComponentsInChildren()[0].text = text;
    }

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

クリック時の判定処理

11行目がGemeDirectorクラスへの参照を確保するためのコードです。クリック時の処理(OnClick)で押された数値が正しいかどうかを判定し、正しければ次の数にすすめ、ボタン自体は削除しています。

こうすることで生成されたボタンがクリックされると、正しい場合はどんどん画面からボタンが消えていくようになります。実行すると以下のように簡単なゲームっぽく動かせます。

1から順に押すことでボタンが消えていくようになりました。段々とゲームっぽくなってきたのではないでしょうか。とりあえず今回はここまで。

まとめ

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

次回予定

  • スタート&リザルトシーンを作成します。
  • ゲームシーンに計測時間を表示します。
  • ゲーム終了後計測時間を結果として表示します。

以上。

リンク

コメント