Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜色と角度のパスワード〜

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

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

オブジェクトを回転させるパスワードにはこんなのもあるよね
どんな色かも分からんしまず何て読むのかも分からんけどね
ネーミングセンス、たったの5か……ゴミめ
今回はオブジェクトの色や角度を使ったパスワードを作ってみたいと思います。考え方は画像を使ったパスワードと同じなので、そっちの記事や動画を見た方なら今回何をやろうとしているかもだいたい分かると思います。

画像を使ったパスワード

この時に正解判定を行う関数などを親スクリプトに作りましたが、今回のパスワードもこの親スクリプトを継承して作ります。

動画はこちらです。



それから動画にはないんですが、記事の最後の方で本作にはないけどオブジェクトを回転させるパスワードにはこんなのもあるよねってのを一つ紹介してるので、何かの参考になれば嬉しいです。



色の判定

まずは色の方から作っていきましょう。

内容的には画像やテキストとほとんど同じで、一覧用の配列や各ボタンの現状を把握するためのnowsなどを用意し、関数でnows[n]を+1して色を順番に切り替えられるようにします。

てなことで先にコードをまとめて書いちゃいますね。

public class CabinetLow : Password {
  private Color[] colors = {
    new Color(1, 1, 1),
    new Color(1, 0, 0),
    new Color(0, 0, 1),
  };
  
  void Start() {
    nows = new int[9];
  }

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

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

    images[n].color = colors[nows[n]];
  }
}

「colors」は切り替えに使用する色の配列です。白、赤、青の三色が配列に入っています。今回はシンプルな色なんでスクリプトで直接セットしましたが、変数をpublicにすればインスペクターのカラーパレットで色をセットできるので、原色以外の、数値の設定がややこしそうな一斤染め色とか空五倍子色とかシャルトルーズイエローとかバンダイクブラウンも思いのままです。空五倍子色とか、どんな色かも分からんしまず何て読むのかも分からんけどね(うつぶしいろと読みます)

「nows」の使い方は今までと同じです。変数自体は親スクリプト(Password)で作成しているので、ここではStart関数で初期値だけ入れています。「int[9]」というのは配列の要素を9個作成して、そこにデフォルトの値(int型の場合は0)を入れるという書き方です。インスペクターでSizeに9を入れて全部の値に0を入れた場合と同じですね。あるいはこれ(↓)と同です。

nows = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0};

「images[n]」はn番目のボタンのImageコンポーネントです。画像の時と一緒ですね。入れ替える項目がspriteかcolorかの違いってだけです。

これも正解の判定はnowsを使えば良いので、こないだ作ったCheckAnswer関数をそのまま使えばオッケーです。

public class Password : MonoBehaviour {
  protected int[] nows;

  public void CheckAnswer(string right) {
    string answer = "";
    foreach(int now in nows) {
      answer += now.ToString();
    }

    if(answer == right) {
      //パスワード解除成功
    }
  }
}

もちろん色そのもので正解を判定することもできますが、一つ一つ正しい色が入っているか判定するよりはnowsの値で判定した方が楽かなと思います。



角度の判定

角度の切り替えと正解判定も画像や色と同じです。何度も同じことを繰り返し言い続けて恐縮ですが、まあ大事なことは一度しか言わないよりは良いだろってことで、ここは一つ。

public class CabinetLow : Password {
  public RectTransform[] parents;
  private int[] degrees = {0, -90};
  private int[] nows2 = new int[3];
  
  public void ChangeDegree(int n) {
    nows2[n] += 1;

    if(nows2[n] >= degrees.Length) {
      nows2[n] = 0;
    }

    parents[n].rotation = Quaternion.Euler(0, 0, degrees[nows2[n]]);
  }
}

「degrees」は切り替えに使用する角度の配列です。本作では0度と-90度(270度)を交互に入れ替えるので配列の中身も0と-90の二つだけです。

それから本作は色と角度の両方の判定を同時に行う関係上、二つのnowsが必要になります。でもnowsは色の方に使ってしまっているので、nows2という変数を作って現在の角度を設定しています。で、ChangeDegree関数ではこのnows2[n]を+1してn番目のオブジェクトの角度を変えています。角度は前回紹介したQuaternion.Eulerを使って入れ替えています。

「parents」ってのは回転させるオブジェクトの配列です。角度を変えたいのでRectTransformコンポーネントを扱うための配列にしてあります。

本作でこの色と角度のパスワードを実装しているところはこんな風になってます。

3つの国旗を作るパスワード

蘭とか仏という字の下に縦長のボタンが3つずつ並んでいますが、parentsはこの3つでひと塊りになっているボタンの親オブジェクトです。これはオランダ、フランス、ロシアの国旗を作るというパスワードで、どれもトリコロールと呼ばれる三色旗なんですが、横に色が分かれているのはフランスだけでオランダとロシアは縦に色が分かれているので、その角度も入力してもらうという作りになっています。

正解判定はnows2を使って他と同じように判定します。ただ本作ではそういう仕様にしていますが、角度に関しては画像や色と違って数字を扱うだけなので、角度そのものを使って判定しても良いんじゃないかなとも思います。それについては後ほど。



色と角度の正解を同時に判定する

先ほど色の正解判定はCheckAnswerを使えば良い、角度の正解判定も他と同じようにやれば良いと言いましたが、それは色や角度を使ったパスワードが独立して存在している場合の話で、本作では色と角度を同時に判定して両方正解だった時だけパスワードを解除する動きにしなければならないので、本当はこの説明だと不十分です。

やり方はいろいろあります。例えばCheckAnswerと同じ内容の処理をnowsとnows2に個別に行う関数を一つ用意するというやり方。

public class CabinetLow : Password {
  public override void CheckAnswer(string right) {
    string answer = "";
    foreach(int now in nows) {
      answer += now.ToString();
    }

    string answer2 = "";
    foreach(int now in nows2) {
      answer2 += now.ToString();
    }

    if(answer == "102201021" && answer2 == "101") {
      //パスワード解除成功
    }
  }
}

流れとしてはanswerとanswer2という二つの変数を作って、それぞれにnowsとnows2の現在の状態を入れて、両方が正解の文字列と一致しているか判定しています。これでも別に問題はないと思います。

でも親スクリプトにCheckAnswerという共通で使用できる関数を作ってあって色の方はそれを使えば良い状態になっているのなら、せっかくだからそっちを使いたいですよね。

そこでためしにこんな風にコードを書き換えてみます。

public class CabinetLow : Password {
  public override void CheckAnswer(string right) {
    string answer = "";
    foreach(int now in nows2) {
      answer += now.ToString();
    }

    if(answer == right) {
      base.CheckAnswer("102201021");
    }
  }
}

まず角度の正解を判定して、正解だったら色の判定を親のCheckAnswerで行うという内容に変えてみました。これでも動作的には問題ないと思います。

ちなみに色と角度の正解用の文字列を両方引数で指定せず色の方は直接文字列を関数の中でセットしていますが、これを両方引数で指定しちゃダメなのかと言われると、実はダメなんです。

public class CabinetLow : Password {
  public override void CheckAnswer(string right, string right2) {
    string answer = "";
    foreach(int now in nows2) {
      answer += now.ToString();
    }

    if(answer == right2) {
      base.CheckAnswer(right);
    }
  }
}

確かに関数には引数を複数設定することができます。でもインスペクターでセットできる関数は引数が一つ以下のものに限られます。引数が複数あるとスコープがpublicでもインスペクターで選べなくなるので、この関数をインスペクターで使うためには引数は一つ以下にしておかなきゃいけない。

ともあれこれで一応親のCheckAnswerを使った処理に書き換えることはできました。でもnows2の方も使っている変数が違うだけで処理内容自体は同じです。だからもっと共通化させることもできそうな気がしますよね。

そこで親のCheckAnswerをこんな風に改造してみます。

public class Password : MonoBehaviour {
  public virtual void CheckAnswer(string right) {
    bool check = CheckRight(nows, right);
  if(check) {
      Unlock();
    }
  }

  protected bool CheckRight(int[] buttons, string right) {
    string answer = "";
    foreach(int button in buttons) {
      answer += button.ToString();
    }

    if(answer == right) {
      return true;
    } else {
      return false;
    }  
  }

  protected void Unlock() {
    //パスワード解除
  }
}

「CheckRight」と「Unlock」という二つの関数を新たに作りました。CheckRightは正解の判定を行う関数でUnlockはパスワードの解除処理(ドアを開けたりとかlocked変数をfalseにしたりとか)を行う関数です。CheckRightはインスペクターから呼び出すことはないので引数は2つでも大丈夫です。

そしてCheckAnswer関数はこのCheckRight関数をUnlock関数を呼び出すだけにしました。CheckRight関数で正解判定を行い、正解だったらUnlock関数を呼び出せるようにしています。

CheckRight関数の中に「return true」とか「return false」というコードがありますが、これは「戻り値」と言って関数の外の変数に渡したりできる値です。上記ではCheckAnswer関数のcheck変数にtrueやfalseを渡しています。

戻り値はtrueやfalseだけじゃなく、数字や文字列でも良いですし、変数や条件式でもオーケーです。ただし戻り値を設定する場合は関数名の横にある「void」を戻り値の内容に応じた型にする必要があります。voidというのは戻り値がない関数に対する宣言みたいなものなので、上記でもboolと書いていますが、整数ならint、文字列ならstringにしないとエラーになります。

//整数
public int Func() {
  return 114514;
}

//文字列
public string Func() {
  return "返り値って言う場合もある?";
}

//条件式
public bool Func() {
  return answer == right;
}

条件式の場合は条件が真ならtrue、偽ならfalseが戻り値になるので関数の宣言はboolになります。

また戻り値は変数に渡さずに直接if文の条件に使っても良いです。

public class Password : MonoBehaviour {
  public virtual void CheckAnswer(string right) {
  if(CheckRight(nows, right)) {
      Unlock();
    }
  }

  protected bool CheckRight(int[] buttons, string right) {
    string answer = "";
    foreach(int button in buttons) {
      answer += button.ToString();
    }

    return answer == right;
  }
}

これでCheckRightで正解判定だけを行える状態になったので、色と角度のそれぞれにCheckRight関数を使って、両方がtrueだったらUnlock関数を呼び出すという処理を書くことができます。

public class Password : MonoBehaviour {
  public virtual void CheckAnswer(string right) {
  if(CheckRight(nows, right)) {
      Unlock();
    }
  }

  protected bool CheckRight(int[] buttons, string right) {
    string answer = "";
    foreach(int button in buttons) {
      answer += button.ToString();
    }

    return answer == right;
  }

  protected void Unlock() {
    //パスワード解除
  }
}

public class CabinetLow : Password {
  public void CheckAnswers() {
    if(CheckRight(nows, "102201021") && CheckRight(nows2, "101")) {
      Unlock();
    }
  }
}

動画ではcheck1とcheck2という変数に戻り値を入れてif文を書いていますが、こっちでは関数を直接if文に入れても良いよという説明を先にしてしまったので、ちょっと書き方を変えてます。この書き方でも正常に動作します。

CheckRightの引数の名前をbuttonsとしちゃったけど、考えたら角度のnows2はボタンの状態を把握する変数じゃないからちょっと微妙な感じがしますね……変数とか関数の名前のつけ方ってネーミングセンスが問われますよね。僕が持ち合わせていない108のセンスの一つですな。少し前に「混昔物語」という小説を出版したんですけど(さりげなくダイマ)、その時も最初は違うタイトルで出版社に原稿送ったら「ネーミングセンス、たったの5か……ゴミめ」と言われてしまって、編集さんにアドバイスをいただきながら直したくらいのレベルですからね。



角度で直接正解を判定する

本作にはないんですが、画像をくるくる回して角度をそろえるようなパスワードも脱出ゲームでは結構見かける気がします。例えばこんなの。

太陽のパズル

これは僕が前に作った別の脱出ゲーム(シルエットルームからの脱出2)で出したやつなんですけど、太陽の画像を3×3のボタンに分解して、9個のボタンをそれぞれくるくる回してそろえてもらうというパスワードです。本当は回すのは8個なんですけどその理由は後で説明します。

これは全てのボタンのRotation Zが0だと正解の状態になるという仕様になっていて、初期表示のそろえる前の状態(画像の左の方)はボタンごとに適当にRotation Zの値を変えてあります。

コードの中身はこんな感じ。

public class Sun : MonoBehaviour {
  public RectTransform[] buttons;
  private Vector3 rotate = new Vector3(0, 0, -90);

  public void ChangeDegree(int n) {
    buttons[n].Rotate(rotate);
  }

  public void CheckAnswer() {
    foreach (RectTransform button in buttons) {
      int degree = (int) button.localEulerAngles.z;

      if(degree != 0) {
        return;
      }
    }

    Unlock();
  }
}

ChengeDegreeでn番目のボタンの角度を変えていますが、こっちはボタンを押すたびに時計回りに90度ずつ回転してほしいので、Rotate関数を使って角度を変えています。現在のRotation Zがいくつであってもそこから90度回せば良いので、nows変数で各ボタンの現在の値を把握する必要はないです。

それからCheckAnswerで正解の判定をしていますが、先ほども言ったように正解かどうかは全部のボタンのRotation Zが0かどうかで決まります。つまり一つでも0じゃないボタンがあれば不正解なので、foreachで各ボタンのRotation Zを取得して、一つでも0以外のものがあった時点で処理を終了しています。returnは戻り値を渡すだけじゃなくて「ここで関数の処理を終了する」という役目も果たします。たとえforeachでループしている途中でもreturnが実行された時点でそれ以降の処理は行われなくなります。

上記の場合、degreeが全て0だった場合はreturnが発動しないので、その時は正解だと判断してパスワードを解除するUnlock関数を呼び出すようにしています。こんな感じで回転するオブジェクトのパスワードを作ったりもできます。

ただしこの太陽のパズルの場合、真ん中はどんなに回転させても見た目が変わらないんで、プレイヤーから見ると0度じゃなくても正しい状態になっているように見えます。だから実際には真ん中のボタンにはCheckDegree関数はセットしておらず、かつbuttons変数にも真ん中のボタンはセットしていません。真ん中以外の8個のRotation Zが0だったら解除されるという仕様です。ゲームの仕様上、一応真ん中もクリックはできますが、内部的にはクリックしても何も起こっていません。ただカチカチ言うだけ。






いろいろコードを書き換えたりreturnだの戻り値だの新しい用語を一気に出してしまったのでちょっとめんどうに感じてしまったかもしれませんが、それぞれの処理はさほど難しいことはやってないので、読んでも全くわけ分からんってことにはならないと思います。思いたい。思っても良いよね……?

あと補足というかこれは動画を作り終えた後に知ったんですけど、引数が二つ以上ある関数はスコープがpublicであってもインスペクターで選べなくなると言いましたが、戻り値を設定した関数もスコープに関係なくインスペクターからは呼び出せないみたいです。なのでインスペクターで使用したい関数はスコープがpublicでなおかつ戻り値がない(void)状態にしておく必要があるっぽい。

次回はアイテムを使ったパスワードを実行します。正解判定の仕組みはこれまでと同じなんですが、アイテムを使うってところが他とは違うんで、その辺を中心に説明できればと思っております。

それじゃあ次回もよろしくお願いしむしてぃ。



あ、ちなみに先ほど出てきた名前だけ聞いてもよく分からん色たちはこんな色だそうです。

・・・一斤染め色(245, 178, 178)
・・・空五倍子色(135, 119, 87)
・・・シャルトルーズイエロー(254, 242, 99)
・・・バンダイクブラウン(152, 94, 65)

カラーセラピーランドさんのサイトを参考にさせていただきました。あざっす。



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