Unityで簡単な2D脱出ゲームを作ってウェブサイトで公開してみよう 〜アイテムの使用〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
アイテムの使用

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

そろそろ耳がオクトパな頃かもしれませんが
あながち嘘じゃない気がしてきたでしょ?
否定されること山のごとしでしょうな
前回はアイテムを選択できるところまでやったので、今回はその選択したアイテムを使用するスクリプトを作っていきましょう。

動画はこちらです。





基本はアクティブ操作

本シリーズの記事を初めから読んでくれている人にとってはもうそろそろ耳がオクトパな頃かもしれませんが、アイテムの使用も基本はやっぱりアクティブ状態の操作です。

本作でアイテムを使用する部分を一覧にするとこんな感じです。

  1. トングで暖炉の奥のパネルを取る
  2. キャビネットの横に椅子を置く
  3. カッターでダンボールを開ける
  4. リモコンでディスプレイをつける
  5. ペットボトルの水で暖炉の火を消す
  6. ドライバーでパネルのカバーをはずす
  7. パスワードにパネルをセットする

この全ての動きをアクティブ操作で行なっています。どんな手順でアクティブ操作を行なっているかに関して多少の違いがあるものの、とりあえずアクティブさんの活動っぷりはアクティブすぎますね。アクティブすごすぎぃ。

一番上のトングでパネルを取るところは、言葉で言うと「トングが選択状態ならパネルのアイテム取得の処理を行う」というものになります。アイテム取得に関しては前回GetItemという関数を作りましたが、やることはあれにトングが選択状態か判定するif文をつけるだけです。

public class Brazil : MonoBehaviour {
  public GameObject get;
  public GameObject item;
  public Dialog hint;
  public GameObject detail;
  public GameObject selected;
  public GameObject tongs_selected;

  public void GetItem() {
    if(tongs_selected.activeSelf) {
      get.SetActive(false);
      item.SetActive(true);
      hint.OpenDialog(detail);
    }
  }
}

トングが選択状態かどうかはtongs_selected(トングの選択状態を示す画像オブジェクト)がアクティブかどうかで判定できます。アクティブなら無事にパネルをゲットできると。何で暖炉の火の奥にパネルがあんねんっていうツッコミはなしで。何でって訊かれたら他に良いパターンが思いつかなかったとしか答えられん。まあ脱出ゲームってそういうもんですからね。「何でそこにそんな仕掛けが!?」って突っ込んだら負けなのよ。「何で探偵の周りではやたらめったら殺人事件が起きるの!?」って疑問と同じなのよ。

椅子を置く、ダンボールを開ける、ディスプレイをつける、カバーをはずす方の処理はもっと簡単です。アイテムが選択状態であれば椅子やダンボールのアクティブ状態を切り替えるだけで良い。

//椅子を置く
public class Chair : MonoBehaviour {
  public GameObject selected;
  public GameObject set;

  public void SetChair() {
    if(selected.activeSelf) {
      set.SetActive(true);
    }
  }
}

//ダンボールを開ける
public class Cutter : MonoBehaviour {
  public GameObject selected;
  public GameObject open;

  public void OpenCardboard() {
    if(selected.activeSelf) {
      open.SetActive(true);
    }
  }
}

//ディスプレイをつける
public class Controller : MonoBehaviour {
  public GameObject selected;
  public GameObject display;

  public void OnDisplay() {
    if(selected.activeSelf) {
      display.SetActive(true);
    }
  }
}

//カバーをはずす
public class Driver : MonoBehaviour {
  public GameObject selected;
  public GameObject cover;

  public void RemoveCover() {
    if(selected.activeSelf) {
      cover.SetActive(false);
    }
  }
}

ほぼ一緒ですね。いずれも特定のクリックポイントでクリックされた時、対象のアイテムが選択状態であれば該当するオブジェクトのアクティブ状態を切り替えているだけです。

例えばダンボールは閉じた画像オブジェクトと開いた画像オブエジェクトの両方を用意しておき、閉じたダンボールの方にクリックでOpenCardboard関数が発動するイベントをつけておいて、カッターが選択状態だったら開いたダンボールの画像をアクティブにするという流れです。ドアなんかもそうですが僕は基本的に開閉の動きをする箇所は閉じた画像と開いた画像を用意して、開いた画像がアクティブになったら閉じた画像が奥に隠れるようにしています。自分的にはそれが一番楽なもんで。これも何度も言ってるのでそろそろしつこいと思われそうですが、UIオブジェクトはヒエラルキーで下にある方が手前になる仕様なので、開いた画像オブジェクトを下に配置して同じ位置に重ねておけば、開いた画像のアクティブ状態を操作するだけで開閉の動きが実装できます。

ダンボールのヒエラルキー

カバーの場合は逆にパスワードパネルの上にカバーを重ねておいて、クリックしたらカバー自身が非アクティブになる動きになっています。これもヒエラルキーでカバーの方を下にしておけばパスワードパネルは奥に隠れるので、カバーを非アクティブにするだけでパネルが現れる動きになります。

あみだのところのヒエラルキー

ちょっと混乱させてしまうかもしれませんが、InputオブジェクトがパスワードパネルでカバーがPanelオブジェクトです。Panelが非アクティブになれば自然とInputが現れる感じです。Amidaはパスワードパネル内に表示しているあみだくじの画像です。

パスワードにパネルをセットするってのも基本はやっぱりアクティブの操作なんですが、こいつは若干他と違う動きをするので、またパスワードを実装する時に詳しくやります。



追加の条件が必要な場合

ペットボトルの水で火を消すところは上記のやり方だけでは実現できません。火を消す前に「ペットボトルに水を入れる」という手順が必要だからです。つまり火を消すためには「ペットボトルが選択状態」で、なおかつ「中に水が入っている」という条件を満たさないといけません。

とはいえ水が入っているかどうかも結局はアクティブ状態の判定を行うだけなので、なーんも難しいことはござんせん。

public class Petbottle : MonoBehaviour {
  public GameObject selected;
  public GameObject fire;
  public GameObject full;

  public void FireExtnguish() {
    if(selected.activeSelf && full.activeSelf) {
      fire.SetActive(false);
    }
  }
}

fullというのは水が入った状態のペットボトルの画像オブジェクトです。アイテムが選択状態でなおかつこいつがアクティブなら火が消えます。

考え方としてはドアやダンボールと一緒です。空のペットボトルの画像オブジェクトの手前に水の入ったペットボトルの画像オブジェクトが来るようにしておけば、アクティブになった時に空のペットボトルは隠れます。

ペットボトルのヒエラルキー

Fullが水の入ったペットボトルの画像オブジェクトです。

ちなみにこのFullオブジェクトをアクティブにする処理(ペットボトルに水を入れる処理)もアクティブ状態の操作です。アクティブ操作ばっかりやないかい。

public class Petbottle : MonoBehaviour {
  public GameObject selected;
  public GameObject fire;
  public GameObject full;

  public void FireExtnguish() {
    if(selected.activeSelf && full.activeSelf) {
      fire.SetActive(false);
    }
  }

  public void GetWater() {
    if(selected.activeSelf) {
      full.SetActive(true);
    }
  }
}

本当は詳細画面の方のテキストを変えたりとかもGetWater関数の中で行なっているんですが、たいして重要な話ではないのでここでは省略。

こんな感じでアイテムの状態変化が必要な場合も、アクティブ状態の操作や判定を使えばたいていは何とかなります。ライターを使用する条件として火がついていることとか、布で汚れを拭く条件として水で濡らしてあることとかね。複数のアイテムを組み合わせて一つのアイテムにする場合とかも、一つのアイテムになった状態の画像オブジェクトを使えば、そいつがアクティブかどうか判定するだけでいける。

ここまでくるとアクティブを制する者が脱出ゲームを制するってのもあながち嘘じゃない気がしてきたでしょ?



使用済みアイテムの扱い

アイテムによっては何度も使える場合と一度しか使えない場合があると思います。本作も国旗のパネル以外は一度しか使えないアイテムとしています。

僕の場合は一覧のアイテムがアクティブじゃないと選択できない処理にしているので、使用時にアイテムを非アクティブにしてしまえば以降は選択ができなくなり、その延長で使用も不可な状態になります。

public class Cutter : MonoBehaviour {
  public GemeObject item;
  public GameObject selected;
  public GameObject open;

  public void OpenCardboard() {
    if(selected.activeSelf) {
      open.SetActive(true);
      item.SetActive(false);
      selected.SetActive(false);
    }
  }
}

例えばこうですね。ダンボールを開けた時にカッターとカッターの選択状態を非アクティブにしています。

別にこれでも問題ないとは思いますが、本作では使用したアイテムは非アクティブではなく画像を半透明にしてそのまま一覧に残しているので、そっちのやり方も紹介したいと思います。

簡単に言えばフラグ用の変数を作って、そのフラグで使用済みかどうか判定しています。

public class Cutter : MonoBehaviour {
  public GemeObject item;
  public GameObject selected;
  public GameObject open;
  private bool used = false;

  public void OpenCardboard() {
    if(selected.activeSelf) {
      open.SetActive(true);
      used = true;
      selected.SetActive(false);
      item.color = new Color(1, 1, 1, 0.4f);
    }
  }
}

usedというのが使用済み判定フラグの変数です。初期値はfalse。アイテム使用時にこいつをtrueにして使用済みになったと見なしています。何かアクティブ操作以外の説明を久しぶりにした気がしますね。半透明化はアイテムの不透明度を調整して行なっています。

このusedは前回作ったアイテム選択用の関数であるSelectItemの中で使います。

public class Cutter : MonoBehaviour {
  public void OpenCardboard() {
    if(selected.activeSelf) {
      open.SetActive(true);
      used = true;
      selected.SetActive(false);
    }
  }

  public void SelectItem() {
    if(selected.activeSelf) {
      hint.OpenDialog(detail);
    else if(item.activeSelf && !used) {
      GameObject[] selects = GameObject.FindGameObjectsWithTag("Selected");
      foreach(GameObject select in selects) {
        select.SetActive(false);
      }

      selected.SetActive(true);
    }  
  }
}

SelectItemではアイテムが取得済み(itemがアクティブ)ならアイテムを選択状態にするという処理を行なっていますが、これをアイテムが取得済みでなおかつ使用前であれば選択状態にするという内容に変えます。else ifの条件に「!used」を追加したのがそれです。

変数の前のビックリマークは否定の意味を持つ記号です。通常、if文は条件がtrueであれば中の処理を行いますが、否定を使うと条件がfalseの時に中の処理を行います。used変数はアイテムが使用されるまでfalseなので、これでitemがアクティブでusedがfalseなら選択状態になるという動きが実現できます。

一度usedがtrueになってしまえばそのアイテムはもう選択ができなくなるので、何度も使用することができなくなります。逆に言えば、このusedをfalseにすれば再びそのアイテムは使用できる状態になるので、何度も使えるアイテムに対してもこのフラグは利用できます。テーブルの上にアイテムを置いたらusedをtrueにして、再び手に取ったらfalseに戻すとかね。そうすればテーブルの上にある時は選択できなくなるけど、手に取ったらまた選択できるようになります。本作でもパスワードにパネルをセットする部分ではこのusedをfalseに戻す処理を行なっているので、その時に改めてお話したいと思います。

それにしても何でビックリマークを否定に用いたんだろうね。if文さんもtrueの時に真って判定すれば良いと基本は思ってるはずから、そんなところにいきなり否定がきたら「え!? t……trueじゃない!?」みたいにビックリするかもってのが由来なんだろうか。まあ、実際にそういう由来にしようと提案したら「もっと真面目に考えろ!」って否定されること山のごとしでしょうな。






今回はこれくらいにしときましょう。動画ではカーソルの変更についてもやっていますが、直接アイテムの使用に関わる処理ではないのでこっちでは省略します。

次回はゲーム作りというよりもプログラミングに関する話になります。上記のコードを見て気づいたかもしれませんが、椅子を置いたりダンボールを開けたりってのはほとんどコードの中身が一緒です。こういう時はわざわざ別々のファイルに同じ処理をコピペするんじゃなくて、一箇所にコードをまとめた方が効率が良いです。後々の修正も楽だし。次回はそういう類の話をしたいと思います。

それじゃあ次もよろしくお願いししおどし。



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