Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜室内を移動する〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
アクティブ操作があれば何でもできる

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

表示したい画面をアクティブにしてそれ以外の画面を非アクティブにするだけ
配列を使うと一つの変数に二つ以上のゲームオブジェクトをセットできる
これでMove関数一つで室内を左右に移動できるようになりました
今日はたいていの脱出ゲームでは実装されるであろう、視点変更をやりたいと思います。

やり方はいろいろあると思うのですが、本作での視点変更は前回やったアクティブ状態の切り替えで行なっています。簡単に言えば表示したい画面をアクティブにしてそれ以外の画面を非アクティブにするだけです。本作では室内を移動するくらいしか視点の変更がないのですが(あとはヒントの表示とか)、画面の一部分を拡大表示するような視点変更なども考え方は一緒です。

今回は前回に比べるとスクリプトのコードの量が多い上に「配列」やら「引数」やらの機能についても触れるので、プログラミングが初めてという方はめんどくせえと感じるかもしれませんが、そんなに難しいことはやっていない(と個人的には思っている)つもりなので大丈夫だと思います。説明自体はかなーりながーくなっちゃうと思いますが、そのへんはご容赦願いやす。

動画はこちらです。





基本的な概念

いきなり本題に入る前に、改めて本作の画面構成について触れておきます。

本作は部屋の中を四つの画面に分けています。ゲーム内では方角に関する表記は出てきませんが、ようは東西南北の四方向の画面があるという形です。

ヒエラルキー

このヒエラルキーの中のRoom1〜Room4がそれぞれの画面の親オブジェクト(Canvasオブジェクト)です。その子供にドアやら椅子やら、各画面内のオブジェクトがいます。

親子関係にあるオブジェクトは、親オブジェクトを非アクティブにすれば子供たちも全部非表示になります(クリックなども無効になる)。つまりRoom1〜Room4の親オブジェクトのアクティブ状態を操作するだけで、表示する画面を切り替えることができるという仕組みです。このやり方なら拡大画面などの視点変更も、別のCanvasオブジェクトを用意してアクティブ状態を切り替えるだけで実装できるというすんぽーです。Canvasを分けなくてもImageオブジェクトはヒエラルキーで下にあれば上のオブジェクトを隠すことができるので、画面いっぱいの大きさの拡大画面用の背景オブジェクトを用意しておいて、視点変更時にそいつをアクティブにしてメイン画面を隠すという方法もありかと思います。

本作では画面の上部に室内移動用の矢印があり、そいつをクリックすると画面が切り替わります。

矢印を矢印で指すとかこれもう分かんねえな

例えば今Room1がアクティブになっていたとして、そこからRoom2に移動したいのであれば、こんなスクリプトを書けばオーケーです。

public class Room : Monobehaviour {
  public GameObject current;
  public GameObject next;

  public void Move() {
    current.SetActive(false);
    next.SetActive(true);
  }
}

「current」と「next」はそれぞれRoom1とRoom2のオブジェクト用変数です。そしてMove関数の中にはcurrent(Room1)を非アクティブに、next(Room2)をアクティブにするコードが書かれています。矢印のクリックイベントでこのMove関数が発動するように設定すれば、クリックで画面が切り替わります。

Room1〜Room4のそれぞれの子供に矢印オブジェクトを用意するなら上記のコードだけでも室内の移動は完成させられます。同じスクリプトであっても別々のオブジェクトにセットすればそれぞれ独立したスクリプトとして機能するので(次回もうちょい詳しく話します)、例えばRoom2の右矢印のコンポーネントにRoomスクリプトをセットして、current変数にRoom2オブジェクト、next変数にRoom3オブジェクトをセットすれば、Move関数が発動した時にRoom2が非アクティブになってRoom3がアクティブになります。同じ要領でRoom4の左矢印にRoomスクリプトをくっつけてcurrent変数にRoom4オブジェクト、next変数にRoom3オブジェクトをセットすればMove関数発動時にRoom4が非アクティブになってRoom3がアクティブになります。ちょっと何言ってるか分からんかもしれんけど、ようは全部で8つの矢印オブジェクトを作って個別にアクティブ状態を切り替えるスクリプトをセットすればいけるよねって話です。

でも本作では矢印は画面の分だけ用意しているわけではなく、FloorというCanvasオブエジェクトの中に左右の矢印が一つずつあるだけです。つまり上記のコードだけでは不十分で、クリックするたびに非アクティブにしたいオブジェクトとアクティブにしたいオブジェクトを変えなければいけません。

そんな時に役に立つのが配列という機能です。



配列を使う

先ほどのcurrentやnextおよび前回作ったwaterという変数には一つのゲームオブジェクトしかセットできません。でも配列という機能を使うと一つの変数に二つ以上のゲームオブジェクトをセットすることができます。

変数を配列にする方法は簡単で、GameObjectの後ろにかっこをつけるだけです。

public GameObject[] rooms;

これでrooms変数が配列になりました。

インスペクターで変数にオブジェクトをセットする時、変数が配列になっていると「Size」という入力項目が出てきます。

サイズ

ここにはこの変数にいくつのオブジェクトをセットするか、その数を入力します。今回はRoom1〜Room4の4つのオブジェクトをこの変数で扱いたいのでSizeは4となります。数字を入力するとその数の分だけオブジェクトの入力欄が出てくるので、waterに水オブジェクトをセットした時のようにそれぞれのCanvasオブジェクトをドラッグ&ドロップすれば配列にオブジェクトがセットされます。

ゲームオブジェクトをセットしてターンエンド

これでrooms変数でRoom1〜Room4を扱える状態になりました。変数の中身はこんな感じになっています。

rooms[0] ・・・ Room1(Element 0)
rooms[1] ・・・ Room2(Element 1)
rooms[2] ・・・ Room3(Element 2)
rooms[3] ・・・ Room4(Element 3) 

配列は原則的に0番目から順番に値が格納されます。Roomオブジェクトは1から番号を振っているのでちょいこんがらがりそうですが、0が1で1が2で2が3で3が4でお前が俺で俺がお前で……という感じです。慣れないうちは先頭が1番目ではなく0番目という感覚に惑わされて頭が痛くなるかもしれませんが、大丈夫です。痛いのは最初だけですから……ふふ。

このrooms[0]〜rooms[3]の扱い方は通常の変数と同じです。なのでさっきのRoom1を非アクティブにしてRoom2をアクティブにする動きをこの配列を使って書き直すとこうなります。

public class Room : Monobehaviour {
  public GameObject[] rooms;

  public void Move() {
    rooms[0].SetActive(false);
    rooms[1].SetActive(true);
  }
}

さっきのroom1やroom2変数を使う場合は変数の中身を変えなければ操作するオブジェクトを変えられませんでしたが、これならかっこの中の数字を変えれば操作対象のオブジェクトが変わります。ということは矢印をクリックした時に何とかしてかっこの数字を変えれば室内を自由に移動できるようになるわけですね。会社の帳簿の数字を不正に書き換えるよりははるかに簡単な作業だし、誰にも怒られない。

ではどうやって数字を変えようかという話ですが、ここでもう一つ変数を追加してみます。

public class Room : Monobehaviour {
  public GameObject[] rooms;
  private int now = 0;

  public void Move() {
    rooms[0].SetActive(false);
    rooms[1].SetActive(true);
  }
}

「now」という変数を追加しました。今回は変数名の前に「int」という文字をつけています。このintはintegerの略で、この変数には整数を入れますという宣言です。intを宣言した変数には整数以外の値を入れることはできません。アルファベットも日本語もダメだし、もちろんゲームオブジェクトもセットできません。逆にrooms変数にも数字や文字を入れることはできません。前回もちょろっと言いましたが、プログラミングでは変数にどんな値を入れるかを定義する「型」という概念があります。プログラミング言語にもよるのですが、C#ではこの型を厳密に決めなくてはいけません。

それから型の前にpublicではなくprivateという文字を書きましたが、この違いはまた今度説明します。publicと書いても動かないわけではないです。

このnow変数はかっこの中の数字を書き換えるために使用します。説明は後回しにして先にコードを書いちゃいましょうか。

public class Room : Monobehaviour {
  public GameObject[] rooms;
  private int now = 0;

  public void Move() {
    rooms[now].SetActive(false);
    now += 1;
    rooms[now].SetActive(true);
  }
}

かっこの中を数字からnow変数に変えました。最初nowには0が入っているので、rooms[now]はrooms[0]と同じです。つまりMove関数が発動すると「rooms[now].SetActive(false)」でRoom1(rooms[0])が非アクティブになります。その後「now += 1」でnowを+1しています。つまりrooms[now]がrooms[1]になるので、「rooms[now].SetActive(true)」でRoom2(rooms[1])がアクティブになります。

now変数の値はMove関数が発動すると毎回0からスタートするわけではなく、変更した値はそのままの状態で残っています。なのでもう一度矢印をクリックしてMove関数を呼び出すと今度はRoom2(rooms[1])が非アクティブになり、+1された後にRoom3(rooms[2])がアクティブになります。あとはこの繰り返しでMove関数が発動するたびにnowの値は増えていき、非アクティブになるオブエジェクトとアクティブになるオブジェクトが変化します。

一応これで視点が順番に切り替わる処理にはなりますが、これでもやはり内容としては不十分です。動きとしては室内を一周してきたら元の画面に戻ってきてほしいので、Room4(rooms[3])が非アクティブになったらRoom1(rooms[0])がアクティブになるという処理が必要です。しかしnowを+1するだけだと、rooms[3]を非アクティブにした後はrooms[4]をアクティブにしようとしてしまいます。これでは望む動きになりません。

ついでに言うと配列は存在しない数字を指定することができません。今回で言うと、rooms[4]とかrooms[10]とかrooms[100]とかに対してSetActiveを実行しようとすると以下のようなエラーが出ます。

IndexOutOfRangeException: Array index is out of range.

アウトオブレンジとか言ってますね。つまりレンジ外からロングライフルで狙撃するなんて汚ねーぞと、そう言っています(言ってない)。まあようするに存在しない番目の値は参照しようがないからお手上げ侍だぞということです。

いずれにしろnowが3になったら次は0に戻ってほしいので、その処理を関数の中に入れる必要があります。

public class Room : Monobehaviour {
  public GameObject[] rooms;
  private int now = 0;

  public void Move() {
    rooms[now].SetActive(false);
    now += 1;

    if(now > 3) {
      now = 0;
    }

    rooms[now].SetActive(true);
  }
}

if文を一つ追加しました。内容としては「nowが3より大きくなったら0を入れる」というものです。前回はactiveSelfをif文の中で使いましたが、あれはactiveSelfの中に直接trueやfalseが入っていたので、それがそのまま分岐の条件となっていました。今回のように式を書いた場合は、その式が条件を満たしているかどうかでtrueかfalseかが変わります。上記の場合は「nowが3より大きい」が条件なので、nowが3以下の時はfalse、4以上の時はtrueとなります。つまりnowが4になった時点で条件を満たして0を代入するので、その後のrooms[now]はrooms[0]になります。まあ今回の場合、4以上とは言っても仕組み的にnowが5とか6になることはないので、「now == 4」という条件を書いても同じ結果になります。

ちなみに「値が等しい」という条件判定を行いたい場合はイコールを二つ書く必要があります。これはC#に限らず他のプログラミング言語でもそうなんですが、イコールが一つだけだと値を代入するという動きになります。上記のコードでも「now = 0」でnow変数に0を入れていますね。さすがのC#さんも全く同じコードで二つの違う処理を行うという器用な真似はできないので、値の代入はイコール一つ、条件判定はイコール二つというふうに書き方が分かれています。キーボードで同じキーを押した時に、コンピュータがこっちの入力したい内容を自動で判別して違う文字を出してくれたりはしないのと同じことです。

これでrooms[now]がrooms[4]になってしまう状態は回避できたので、部屋の中をグルグルと回り続けることができるようになります。



引数を使う

上記のコードだとエラーは出なくなりましたが、一方向にしか移動ができません。でも室内の移動は右回りと左回りの2パターンあります。

例えば関数を「RightMove」「LeftMove」みたいに二つ作ってそれぞれの方向に関する移動の処理を書けば両方向への移動ができます。

public class Room : Monobehaviour {
  public GameObject[] rooms;
  private int now = 0;

  public void RightMove() {
    rooms[now].SetActive(false);
    now += 1;

    if(now > 3) {
      now = 0;
    }

    rooms[now].SetActive(true);
  }

  public void LeftMove() {
    rooms[now].SetActive(false);
    now -= 1;

    if(now < 0) {
      now = 3;
    }

    rooms[now].SetActive(true);
  }

}

nowを+1すればRoom1 → Room2 → Room3 → Room4と移動できるので、反対にnowを-1すればRoom4 → Room3 → Room2 → Room1と移動することになります。rooms[-1]となるのを防ぐために「nowが0を下回ったら3に戻す」という処理を入れておけばRoom1の次はRoom4に戻れます。

もちろんこれでも良いのですが、ここではあえてもう一歩踏み込んで、この二つの処理を一つの関数の中でやる方法も考えてみたいと思います。やりたいことは「左矢印をクリックした時はnowを-1して右矢印をクリックした時はnowを+1する」という処理です。

これも説明は後回しにしてコードから先に見ていきましょう。

public class Room : Monobehaviour {
  public GameObject[] rooms;
  private int now = 0;

  public void Move(string direction) {
    rooms[now].SetActive(false);

    if(direction == "right") {
      now += 1;
    } else {
      now -= 1;
    }

    if(now > 3) {
      now = 0;
    } else if(now < 0) {
      now = 3;
    }

    rooms[now].SetActive(true);
  }
}

こうなりました。nowを+1するか-1するか、その条件分岐のためのif文が追加されています。条件は「direction変数の値がrightだったらtrue、それ以外はfalse」となります。なのでこのif文は「direction変数の値がrightだったらnowを+1してそうじゃなかったら-1する」という動きをします。

じゃあそのdirection変数にどうやってrightや他の値を入れるんだって話ですが、Move()のかっこの中に「string direction」という単語が追加されているのが分かるでしょうか。これは「引数」と言って、関数が発動される時に一緒に値がセットされる変数です。stringはGameObjectやintと同じ型の一つで、この変数には文字列を入れるぞと宣言しています。文字列ってのは「あいうえお」とか「ABCDE」とかの一般的な文字の羅列です。

関数に引数を追加するとインスペクターのイベント設定のところに値の入力欄が出てきます。

動画ではここで夜神月の画像を出す予定でした

Move関数を呼び出す時にこの入力内容がdirection変数にセットされます。なので右矢印の方には「right」、左矢印の方には「left」と入力しておけば、クリックした時にどっちの矢印を押したかでnowの値が+1されたり-1されたりします。

それからnowが3を超えた時や0を下回った時に数字を入れ直すif文を一つにまとめていますが、上記のようにelseの後ろにも条件を書くことはできます。

このelse if文を使えば分岐させたい条件が3つ、4つと増えていった場合にも対応できます。

if(条件1) {

} else if(条件2) {

} else if(条件3) {

} else {

}

個人的には条件が増えてきた時はif文の代わりにswitch文というやつを使う方が好きなのですが、本作ではswitch文を使う箇所がないので、ここでもswitch文については触れません。すでにかなりの文量になっちゃってるしな……。

何はともあれこれでMove関数一つで室内を左右に移動できるようになりました。






今回はこんなとこですね。動画ではおまけとしてもう少しコードのつけ足しをやっていますが、あれをここでやる必要はないですからね……今回は配列と引数について紹介することが目的だったので、それは達成できたかなと。

次回はpublicとprivateの違いなど、今回説明しきれなかった部分をいくつか補足したいと思います。脱出ゲームの作り方というよりは完全にC#スクリプトに関する話なので、どちらかと言うとおまけ回みたいな感じにはなりますけど、でもUnityを扱う上では知っておいて損はない思う内容なので、良かったら次回も見てください。動画の方もよろしくお願いしっくすせんす。



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