Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜文字をそろえるパスワード〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
文字をそろえるパスワード

この記事を三行にまとめると

パターンとしてはオーソドックスなタイプじゃないかと思います
アルファベットも同じです
汎用的に使えるような処理を考えたつもりなんだけど……
今日からはアイテムと同じくらい脱出ゲームには欠かせないと思われる、パスワード作りに入っていきたいと思います。ある意味これもいわゆる一つの山場かもね。

今回は文字をそろえるパスワードを作ってみたいと思います。本作では漢字をそろえるパスワードを実装していますが、アルファベットでも扱う文字が違うってだけでやり方は一緒なので、パターンとしてはオーソドックスなタイプじゃないかと思います。

動画はこちらです。





文字を順番に変える仕組み

まずはボタンを押した時にボタンの中の文字が順番に変わっていく仕組みから作ります。

最初に必要になるのは変えたい文字の一覧です。今回は10個の漢字を使います。

public class Shutter : MonoBehaviour {
  private string chars = "加馬墨奈哥地陀拉西瓜";
}

一覧は一つの変数に全部突っ込んじゃってOKです。アルファベットでも同じです。

private string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

それから各ボタンのテキストを扱うための変数も必要ですが、ここでは配列を使って全部のボタンのテキストを一つの変数で扱うようにします。

public class Shutter : MonoBehaviour {
  private string chars = "加馬墨奈哥地陀拉西瓜";
  public Text[] texts;
}

この変数の中に10個のボタンのテキストコンポーネントを入れておきます。

インスペクター的にはこんな感じ。

ボタンズ

Button1〜Button10の子テキストをセットしています。スクリプトで子オブジェクトをまとめて取得したりタグ付けして取得しても良いです。自分のやりやすい方法でOK。

続いて文字を変えるための関数を作りますが、やりたいことは「n番目のボタンを押した時にchars変数のm番目の文字をセットする」という動きです。

こんな感じのコードでその動きを実現できます。

public class Shutter : MonoBehaviour {
  private string chars = "加馬墨奈哥地陀拉西瓜";
  public Text[] texts;
  private int[] nows = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  public void ChangeText(int n) {
    nows[n] += 1;

    if(nows[n] >= chars.Length) {
      nows[n] = 0;
    }

    texts[n].text = chars[nows[n]].ToString();
  }
}

内容としては「ChangeText関数が発動した時、texts配列変数のn番目にセットされているボタンのテキストをchars変数の現在表示されている文字の一つ隣の文字に入れ替える」という処理を行なっています。一つずつ見ていきましょう。

「texts[n].text」はn番目のボタンのテキストです。n変数は引数で渡しています。このnを指定することでどのボタンのテキストを扱えば良いか分かるようになっています。Button1なら0、Button2なら1・・・Button10なら9みたいな。

n番目のボタン

「chars[nows[n]].ToString()」はcharsのm番目の文字を指定しています。

「texts[0]」とか「texts[1]」のように書けば配列の0番目や1番目の要素を扱えますが、実は配列じゃなくてもこれと同じ書き方が可能です。例えば「chars[0]」だとcharsの一番目の文字(加)にアクセスできます。chars[1]〜chars[9]に関しても同じ。これでcharsの中の任意の一文字を取ってきてテキストに突っ込むことができます。ただしchars[0]で文字を取ってくると型がcharという型になります。textに入れる値はstring型じゃないとエラーになってしまうので、「ToString()」を使って型をcharからstringに変換しています。

「nows[n]」はn番目のボタンにcharsの何番目の文字がセットされているか知るための配列です。

前に室内の移動を実装した時、roomsという配列変数に画面ごとの親オブジェクトを入れて、now変数を使って何番目の画面が表示されているかを把握するという仕組みを作りました。考え方としてはあれと同じで、ボタンごとに何番目の文字が表示されているかをこの変数を使って把握します。

例えば0番目のボタン(Button1)の文字が「加(chars[0])」になっているとしたら、nows[0]に0が入っていれば良いわけです。1番目のボタン(Button2)の文字が「奈(chars[3])」になっているならnows[1]の中身は3。こんな感じでそれぞれのボタンの現在の状態を知ることができます。

またこの設定により、現在表示されている文字の一つ隣の文字を入れるためにはnows[n]の値を+1すれば良いことになります。なので「nows[n] += 1」という処理を入れて、n番目のボタンをクリックした時、nows[n]が0なら+1してchars[1]の文字(馬)をtextに入れる、さらにクリックしたらchars[2]の文字(墨)をtextに入れる……というような動きにしています。

ただし配列と同じで、nows[n]の値をずっと+1し続けるといずれアウトオブレンジだぞと言われてエラーになってしまうので、if文を使ってnows[n]が10になったら0に戻すという処理も行なっています。「nows[n] >= 10」と書いても良いんですけど「chars.Length」でchars変数の文字数を取得できるので、こう書いといた方が使い回せて便利です。charsの中身をアルファベットの一覧に変えたら自動的に「nows[n] >= 26」の条件式になるしね。

ちなみにこのコードだと文字の変化は一方向ですが、ボタンによって+1する場合と-1する場合の処理を分岐させておけば、A、B、C……と文字を変える場合とC、B、A……と文字を変える場合の両方向にも対応できます。

数字の場合も同じやり方で実装できますが、数字に関してはわざわざchars変数やnows変数を用意しなくてもテキストオブジェクトの中身をそのまま+1してセットし直せば良いので、もうちょい違う書き方もできますかね。

public class Shutter : MonoBehaviour {
  public Text[] texts;

  public void ChangeText(int n) {
    int i = int.Parse(texts[n].text) + 1;

    if(i >= 10) {
      i = 0;
    }

    texts[n].text = i.ToString();
  }
}

こんな感じ。テキストオブジェクトの中身はたとえ数字であっても型は文字列型なので、足し算するためにはint型に直す必要があります。文字列型に+すると文字の連結になっちゃいます(詳細は後述)。int型に直したらそれを+1してもう一度文字列型に直してオブジェクトにセットするというのが上記のやり方です。もちろんchars変数に「0123456789」を入れて漢字やアルファベットと同じようにやっても全然オーケーよ。

また今回の書き方だと処理の流れとしては「nowsの値を+1してからテキストをセット」となるので、一番初めにセットされる文字はchars変数の二番目(chars[1])になります。漢字の場合は何が最初に表示されてもあまり違和感ないと思うんですけど、アルファベットや数字だったら最初に表示される文字は「A」とか「0」にしたいと思います。そういう時はあらかじめボタンの中に先頭の文字を入れておくか、+1してからテキストにセットするのではなく、テキストにセットしてからnowsの値を+1すれば解決できます。あるいはnowsの初期値を変えておくとかですかね。アルファベットの場合、初期値を26以上の値にしておけば最初にボタンが押された時にif文の判定で0に戻されるので、最初のクリックで先頭の文字をセットできます。他にも良いやり方があるかもしれない。



答え合わせ

文字を順番に変える処理はできたので、次は入力された文字が正解と一致しているかを判定する処理を作りましょう。

やりたいことは「ボタンのテキストの文字をまとめて取得して一つの文字列を作り、それが正解の文字列と同じか判定する」という内容です。

これをコードに書いてみると……

public class Shutter : MonoBehaviour {
  private string chars = "加馬墨奈哥地陀拉西瓜";
  public Text[] texts;
  private int[] nows = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  public void CheckAnswer() {
    string answer = "";
    foreach(Text text in texts) {
      answer += text.text;
    }

    if(answer == "加奈陀墨西哥瓜地馬拉") {
      //パスワード解除成功
    }
  }
}

解除成功の部分をはしょっちゃいましたが、ここは重要じゃないのでスルーしてください。

「answer」は入力内容を一つの文字列としてまとめるための変数です。

texts変数をforeachで回すことにより、それぞれのボタンのテキストを順番に取得できます。その順番に取得した文字を一つずつanswer変数にくっつけていけば、最終的に入力内容が一つの文字列になります。

先ほども言いましたがC#では数字にプラスを使う場合と数字じゃない文字にプラスを使う場合では動きが異なります。数字にプラスを使うと通常の足し算が行われますが、数字以外の文字に使うと文字の連結になります。

int answer = 1 + 1 + 1; //3になる
string answer = "A" + "B" + "C"; //ABCになる

ここで言う数字と数字以外は変数の型に依存するので、たとえ文字が数字でも型がstring型なら足し算ではなく連結になります。

string answer = "1" + "1" + "1"; //111になる

逆に言えば数字をそろえるパスワードも答え合わせの時は文字列型にしておく必要があるってことですね。数字の足し算にならないように。

10個の文字を全部連結したら、あとはそれと正解の文字列を比較するだけです。同じならif文の結果がtrueになるので解除成功の処理に進めると、そんな感じです。



解除後のパスワード画面

基本的に一度パスワードの解除に成功したらそれ以降はパスワード画面を出す必要がなくなります。ドアに直接パスワード画面がついてる場合なんかは、解除した後はドアをクリックした時にパスワードが出てくるのを避けたいですよね。

そういう時は解除フラグを一つ用意しておけばオッケーです。

public class Shutter : MonoBehaviour {
  private bool locked = true;

  public void OpenPassword() {
    if(locked) {
      //パスワード画面を開く
    }
  }

  public void CheckAnswer() {
    string answer = "";
    foreach(Text text in texts) {
      answer += text.text;
    }

    if(answer == "加奈陀墨西哥瓜地馬拉") {
      locked = false;
    }
  }
}

こんな感じでlockedみたいな変数を用意し、trueならまだロックがかかっている(解除されていない)という判定にしておいて、解除成功時にfalseにする処理を入れておけば、lockedがtrueの時だけパスワード画面を出すという処理を実装できます。アイテム使用の時に使ったused変数と考え方は一緒ですね。






個人的には本作でしか使用できないパスワードではなくて、同じようなパスワードを実装する際に汎用的に使えるような処理を考えたつもりなんだけど……どうですかね?

この後画像を使ったパスワードとか色で判定するパスワードなども実装しますが、根本の考え方は今回と一緒です。配列を使ってn番目のボタンの内容を変えて、foreachで入力内容を取ってきて正解と一致するか判定するっていう。もっと良いやり方もあるかもしれませんが、このやり方もそんなに難しくて扱いづらいものではないんじゃないかなあ……と、そう思いたい。

というわけで次回は画像を使ったパスワードを実装します。テキストではなくボタンの画像を変えるパスワードです。

それじゃあ次回もよろしくお願いしんぱしー。



本シリーズの記事の一覧はこちら
Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜エピローグ〜
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください