脱出系ゲームの作り方3(3桁暗号パズル)

脱出系ゲームの作り方 Unityプログラミング

みなさん、こんにちは。前回はTextMeshProの導入について解説しました。ですから、みなさん、テキストUIが使えるようになったわけです。今回はそのテキストUIを使って3桁の暗号パズルを作ってみましょう!ご紹介する方法は筆者が独自に考えたものですので、間違いや非効率な説明もあるかもしれませんが、その点はご承知おきください。

使用するバージョン

Unity 6000.3.8f1

完成後のもの

今回、最終的にこのようなものを作ります。

プレイ画面

作り方(配置)

早速ですが、やっていきましょう。前回のプロジェクトを開いてください。Text (TMP)は使わないので削除しましょう。

テキストを削除

HierarchyのCanvasを選択し、その上で右クリックし、メニューからUI (Canvas) > Button – TextMeshProを選択しましょう。ボタンが生成されますので、Width=100、Height=100にしましょう。

ボタンを追加

Buttonの子要素のText (TMP)を選択し、インスペクターのFont Assetに、前回作成したNotoSansJP-VariableFont_wght SDFを指定しましょう。(すでに指定されている場合もあります。)

テキストを選択

Text Inputに0と書きましょう。Font Sizeを70にしましょう。HierarchyでButtonを選択して、その場で3回コピペしましょう。それから、以下のように配置しましょう。右下のButtonのテキストはOKに書き換えてください。

UIの外観

作り方(プログラミング)

さて、プログラミングのコードを書いていきましょう。やりたいことは次の通りです。

やりたいこと
  • 数字ボタンを押すたびに1足す。10まできたら0に戻す。
  • 正解は123にしたい。
  • 正解の時にOKボタンを押すとConsoleに「おめでとう!」と表示する。

まず、Assetsフォルダの中にScriptsというフォルダを作成してください。

Scriptsフォルダをつくる

Scriptsフォルダの中で右クリックし、Create > MonoBehaviour Scriptを選択して、クラスファイルを作成してください。ファイル名は「Puzzle1」にしてください。

ファイル名を誤記してしまっても場合でも大丈夫です。ファイル名を正しく書き直してください。そして、ファイルを開いてください。例えば、「Puzzle2」と誤記してしまった場合は、以下のようにpublic class Puzzle2 : MonoBehaviourのようになっていますので、Puzzle2をPuzzle1に書き換えてください。

using UnityEngine;

public class Puzzle2 : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

そして一旦、classの中身は以下のように空にしてください。

using UnityEngine;

public class Puzzle1 : MonoBehaviour
{
 
}

そして、以下のように追記してください。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class Puzzle1 : MonoBehaviour
{
    // 【1】TextMeshProのテキスト
    [SerializeField] TMP_Text[] Texts;

    // 【2】ボタン
    [SerializeField] Button[] NumberButtons;
    [SerializeField] Button OKButton;

    // 【3】正解の数字
    readonly int[] Corrects = { 1, 2, 3 };

    // 【4】現在の数字
    int[] Answers = { 0, 0, 0 };


    // 【5】初期設定(ボタンにメソッドを割り当てる)
    void Start()
    {
        for (int i = 0; i < NumberButtons.Length; ++i)
        {
            int id = i;
            NumberButtons[i].onClick.AddListener(() => NumberButton_Click(id));
        }

        OKButton.onClick.AddListener(OKButton_Click);
    }

    // 【6】廃棄処理(ボタンのメソッドを消す)
    void OnDestroy()
    {
        for (int i = 0; i < NumberButtons.Length; ++i)
        {
            NumberButtons[i].onClick.RemoveAllListeners();
        }

        OKButton.onClick.RemoveAllListeners();
    }


    // 【7】ボタン0
    public void NumberButton_Click(int id)
    {
        Answers[id] = (Answers[id] + 1) % 10;
        Texts[id].SetText("{0}", Answers[id]);
    }

    // 【7】OKボタン
    public void OKButton_Click()
    {
        bool result = true;

        for (int i = 0; i < Answers.Length; ++i)
        {
            if(Corrects[i] != Answers[i])
            {
                result = false;
                break;
            }
        }

        if (result)
        {
            Debug.Log("おめでとう!");
        }
        else
        {
            Debug.Log("ざんねん");
        }
    }

}

ちょっとコードが長いですが、5章で解説します。とりあえずは、動かしてみましょう。動かすためには、コードやオブジェクトを適切に割り当てないといけません。

ちゃんと割り当てよう

まず、Hierarchyの一番下の空白のところで右クリックして、メニューからCreate Emptyを選んで空のGameObjectを作成してください。名前をPuzzleに変更してください。

Puzzleオブジェクトをつくる

そのPuzzleを選択し、インスペクターのAdd ComponentからPuzzle1というクラスファイル(さっき作ったやつ)を追加してください。

クラスファイルを割り当てる

Puzzleのインスペクターは以下のようになっています。

Puzzle1のインスペクター

各項目に以下のようにオブジェクトを割り当てます。

  • Texts Element 0: 数字ボタンのテキスト(左)
  • Texts Element 1: 数字ボタンのテキスト(中)
  • Texts Element 2: 数字ボタンのテキスト(右)
  • Number Buttons Element 0: 数字ボタン本体(左)
  • Number Buttons Element 1: 数字ボタン本体(中)
  • Number Buttons Element 2: 数字ボタン本体(右)
  • OK Button: OKボタン本体
Puzzle1のインスペクターの割り当て
ひとこと

ボタン側のインスペクターでメソッドを割り当てるという方法が一般的かもしれません。ですが、ゲームサイズが大きくなると、どのボタンに何のメソッドを割り当てたのかが非常にわかりにくくなります。ですので、筆者の場合は逆の方法を採用し、どのボタンに何のメソッドを割り当てたのかを1箇所で把握できるようにしています。

さて、プレイボタンを押してプレイしてみてください。数字を123に合わせてOKを押してみてください。

プレイ画面

どうでしょうか。Consoleビューに以下のように表示されましたか?表示されたら成功ですね!おめでとうございます!

コンソール

コードの解説

さて、コードの解説をしていきましょう。大変ですが、頑張りましょう。

【1】はインスペクターで割り当てるテキストUIです。publicとかprivateとかを書いていませんが、書いていないとprivateになります。外からアクセスされたくないのでprivateなのです。ですが、インスペクターにだけは表示したいというわがまま設定なので、[SerializeField]をつけることでそれを実現しています。

【2】はインスペクターで割り当てる数字ボタンとOKボタンです。

    // 【1】TextMeshProのテキスト
    [SerializeField] TMP_Text[] Texts;

    // 【2】ボタン
    [SerializeField] Button[] NumberButtons;
    [SerializeField] Button OKButton;

【3】は正解の数字です。配列に持たせています。

【4】は現在の数字です。こちらも配列にしています。テキストUIと同じ内容ですが、処理の都合上別途、配列として持たせています。要するに、正解の配列と現在の配列を比較することで、正解不正解を判定するという寸法です。

    // 【3】正解の数字
    readonly int[] Corrects = { 1, 2, 3 };

    // 【4】現在の数字
    int[] Answers = { 0, 0, 0 };

【5】はStart関数です。ゲームオブジェクトの生存期間中に1回のみ呼び出されます。Start関数の中で、数字ボタンとOKボタンをクリックした時に実行するメソッドを動的に割り当てています。(動的というのはプログラム上で行うという意味です。)

【6】はOnDestroy関数です。ゲームオブジェクトが削除された時に実行されます。OnDestroy関数の中で、各ボタンに割り当てたメソッドを削除しています。

    // 【5】初期設定(ボタンにメソッドを割り当てる)
    void Start()
    {
        for (int i = 0; i < NumberButtons.Length; ++i)
        {
            int id = i;
            NumberButtons[i].onClick.AddListener(() => NumberButton_Click(id));
        }

        OKButton.onClick.AddListener(OKButton_Click);
    }

    // 【6】廃棄処理(ボタンのメソッドを消す)
    void OnDestroy()
    {
        for (int i = 0; i < NumberButtons.Length; ++i)
        {
            NumberButtons[i].onClick.RemoveAllListeners();
        }

        OKButton.onClick.RemoveAllListeners();
    }

【7】は数字ボタンに割り当てるメソッドの本体です。引数idで、要素を指定します。0だと左、1だと中、2だと右ってことです。Answers[id] = (Answers[id] + 1) % 10は、Answers[id]に1足した結果を10で割った余りをAnswers[id]に入れるということです。要するに以下のようなことです。

Answers[id] = (Answers[id] + 1) % 10; // これは書き下すと以下のコードと同じ

int a = Answers[id] + 1;
int b = a % 10;
Answers[id] = b;

%というのは、割り算の余りを得るための演算子です。足す、引く、掛ける、割るとは別の第5の演算子ということです。

Texts[id].SetText(“{0}”, Answers[id]) はintをTextに表示させるためのメソッドです。第二引数のintの変数の値がテキスト表示されます。

    // 【7】数字ボタン
    public void NumberButton_Click(int id)
    {
        Answers[id] = (Answers[id] + 1) % 10;
        Texts[id].SetText("{0}", Answers[id]);
    }

【8】がOKボタンに割り当てるメソッドの本体です。この中で、正解の配列と現在の配列を比較しています。

最初に、比較結果を示すブーリアンをbool resultをTrueで初期化しています。次に、for文の中で、正解の配列と現在の配列を比較しています。先頭から要素同士を1つずつ比較していき、違いがあった場合はその場でresultにFalseを入れてbreakでFor文を抜けるようにしています。

最後に、resultの内容に応じて、結果をDebug.LogでConsole出力しています。

    // 【8】OKボタン
    public void OKButton_Click()
    {
        bool result = true;

        for (int i = 0; i < Answers.Length; ++i)
        {
            if(Corrects[i] != Answers[i])
            {
                result = false;
                break;
            }
        }

        if (result)
        {
            Debug.Log("おめでとう!");
        }
        else
        {
            Debug.Log("ざんねん");
        }
    }

解説は以上です。

おわりに

今回のプログラミングコードは3桁に限定されるものではありません。Texts、NumberButtons、Corrects、Answersの要素数を増やせば、いくらでも桁数を増やすことができます。ぜひ、ご活用していただければと思います。

なお、数字ボタンを押した時に数を増やすのではなく、減らしたいときは以下のように9を足せば良いでしょう。そうすれば、結果がマイナスになることを防止でき、かつ、同じ計算法が使えます。

    // 【7】数字ボタン
    public void NumberButton_Click(int id)
    {
        Answers[id] = (Answers[id] + 9) % 10;
        Texts[id].SetText("{0}", Answers[id]);
    }

コメント