[Unity] [ゲーム制作] 簡易的なギミックの実装方法

初投稿です。よろしくお願いします。

# 目次
1. [概要](#概要)
2. [実装概要](#実装概要)
3. [作業手順](#作業手順)
4. [実装方法](#実装方法)
5. [最後に](#最後に)

# 概要
2Dゲーム制作において、「キャラクターがスイッチを要求数踏むと扉が開く」の実装方法について記述します。また、以下のことが学べます：
* 上記ギミックの実装方法
* クラスを跨いだ変数の受け渡し

##### こんな感じのギミックが作れます：
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/ca435386-ccf6-3b9e-85d7-80dfb363f303.gif)
(カメラの動作は実装しません)
(権利表記：TheCogen-City AllStars_Unity-chan_PixelArtPack_ActionGames Vol.2 © UTJ/UCL)

# 実装概要
要点は「スイッチを押すと変数の値が増え、その値が一定以上になると扉が開く」なので、最低限以下をすればよい：
1. 扉に現在起動しているギミック数"ActiveSymbolCount"と発動のために必要なギミック数"requiredCount"という変数を持たせる。
2. スイッチを踏んだ時、スイッチ側でActiveSymbolCountを増加させる。
3. 要求数回スイッチを押す(ActiveSymbolCount >= requiredCount)と、扉が開くようにする。

この際に気を付けるべきことは、同一スイッチによる二重判定である。また、要求数としてsymbolリストというものを用いている。これは、スイッチを押すと光るオブジェクトを格納するリストであり、現在何個起動したかをわかりやすくするためのものである。

:::note warn
注意
2Dのコライダーを使用するので、それを発動できるように環境を整えておく必要がある(Rigidbody2Dと2Dのコライダーを持つSpriteを用意すればよい)。また、それのタグを"Player"として下さい。
:::

# 作業手順
[1. 扉とスイッチとシンボルのプレハブ作成](#1-扉とスイッチとシンボルのプレハブ作成)
[2. 扉と複数個のスイッチを含むプレハブの作成と配置](#2-扉と複数個のスイッチを含むプレハブの作成と配置)
[3. 扉とスイッチ用のスクリプトを作成](#3-扉とスイッチ用のスクリプトを作成)
[4. 実装確認](#4-実装確認)

# 実装方法
#### 1. 扉とスイッチとシンボルのプレハブ作成
* 扉(<font color="green">GimmickDoor</font>.prefab)の作成
1. Spriteを作成し、好みの画像を設定。
2. Box Collider 2D(Is Triggerをオフ)を付与。
3. 扉のOrder in Layerを-1とする。　(扉の上にシンボルを配置するため)
4. 扉をAssets下のフォルダに入れ、シーン上の物は消す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/db64a8a4-6a2a-7728-5c87-a8efe4374209.png)
(AudioSourceをつけていると、中央のマークが出ます。今回は実装しないので無視してください。)


* スイッチ(<font color="blue">GimmickSwitch</font>.prefab)の作成
1. 扉プレハブをコピペし、名前を付ける。
2. 画像をスイッチ用に差し替え、Box Collider 2DのIs Triggerをオンにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/5a5aeccc-1c37-a123-9469-e9c72fdc218c.png)


* シンボル(<font color="orange">Symbol</font>.prefab)の作成
1. Spriteを作成し、子としてPoint Light 2D(アクティブはオフ)を持たせる。
2. シンボルをAssets下のフォルダに入れ、シーン上の物は消す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/061f6c9b-2c78-1788-36b2-53c4b7db593e.png)


* 扉(<font color="green">GimmickDoor</font>.prefab)の子として4つのシンボル(<font color="orange">Symbol</font>.prefab)を持たせる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/a9fc76d1-c9aa-32d9-2f3c-5bea90c6b11f.png)


#### 2. 扉と複数個のスイッチを含むプレハブの作成と配置
1. 空のオブジェクト(<font color="red">GimmickDoorGroup</font>)を作成し、 1つの<font color="green">GimmickDoor</font>.prefabと4つの<font color="blue">GimmickSwitch</font>.prefabを下記のように子として持たせる。(今回は4つのスイッチを押すと起動するようにするので。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/2a92bf4f-e833-967c-c339-ebe02a7e29fe.png)
2. これをAssets下のフォルダに入れ、シーン上の物は消す。
3. シーン上に<font color="red">GimmickDoorGroup</font>.prefabを置き、扉やスイッチをいい感じに配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480241/a5fa252b-e270-74dd-ff3a-2b4078e5b01f.png)


#### 3. 扉とスイッチ用のスクリプトを作成
1. <font color="green">GimmickDoor</font>.csと<font color="blue">GimmickSwitch</font>.csを作成し、それぞれを<font color="green">GimmickDoor</font>.prefabと<font color="blue">GimmickSwitch</font>.prefabに付与。
2. 下記のようにスクリプトを記述。
3. <font color="green">GimmickDoor</font>.prefabを選択し、Gimmick Door (Script)コンポーネントのsymbolsのサイズを4にし、子のシンボルを代入。
4. <font color="red">GimmickDoorGroup</font>.prefab内の<font color="blue">GimmickSwitch</font>を選択し、Gimmick Switch (Script)コンポーネントの<font color="green">GimmickDoor</font>に、<font color="red">GimmickDoorGroup</font>.prefab内の<font color="green">GimmickDoor</font>を代入。

<details><summary><font color="green">GimmickDoor</font>.cs (tap)</summary>

```csharp:GimmickDoor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GimmickDoor : MonoBehaviour
{
    private const float WaitTime = 0.5f;
    private const int OpeningTime = 120;
    private const int OpenHeight = 7;

    public List<GameObject> symbols;

    private int requiredCount;
    private int prevActiveSymbolCount;

    public int ActiveSymbolCount { get; set; } // 外部で参照するのでプロパティを使う

    private void Start()
    {
        requiredCount = symbols.Count; // 要求数 = 代入したシンボル数
    }

    private void Update()
    {
        if (ActiveSymbolCount > requiredCount || prevActiveSymbolCount == ActiveSymbolCount) { return; } // 要求数以上押しても無効 または ActiveSymbolCountに変動があった場合のみ通す
        prevActiveSymbolCount = ActiveSymbolCount;

        ActiveSymbolAction(ActiveSymbolCount);
    }

    private void ActiveSymbolAction(int count)
    {
        symbols[count - 1].transform.GetChild(0).gameObject.SetActive(true); // シンボルを点灯
        if (count >= requiredCount)
        {
            StartCoroutine(nameof(OpenDoor));
        }
    }

    private IEnumerator OpenDoor()
    {
        yield return new WaitForSeconds(WaitTime);
        for (var i = 0; i < OpeningTime; i++)
        {
            transform.position += Vector3.up * OpenHeight * 1 / OpeningTime; // OpeningTime/60秒かけて扉が上昇
            yield return null;
        }
        DisableSymbols();
    }

    private void DisableSymbols()
    {
        foreach (var symbol in symbols)
        {
            symbol.transform.GetChild(0).gameObject.SetActive(false);
        }
    }
}

```

</details>

:::note info
ポイント：
* 要求数をシンボル数としているので、必ずすべてのシンボルが光った瞬間に開く
* 要求数以上押しても無効
* prevActiveSymbolCountを用いて、ActiveSymbolCountに変動があった場合のみActiveSymbolAction()を実行
:::

<details><summary><font color="blue">GimmickSwitch</font>.cs (tap)</summary>

```csharp:GimmickSwitch.cs
using UnityEngine;

public class GimmickSwitch : MonoBehaviour
{
    private const float PressedDiffPositionY = -0.3f;

    [Tooltip("対応する扉")]
    public GimmickDoor gimmickDoor;

    private bool isOn;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (gimmickDoor && !isOn && collision.gameObject.tag == "Player") // プレイヤーが踏むと、
        {
            isOn = true; // 二重判定回避
            transform.position += Vector3.up * PressedDiffPositionY; // 踏むと凹むように見えるようにする
            gimmickDoor.ActiveSymbolCount++; // 扉が開くためのフラグ
        }
    }
}
    
```

</details>

:::note info
ポイント：
* 扉側の変数ActiveSymbolCountをプレイヤーの接触に応じて増加
* isOn変数を用いることで二重判定を回避
:::


#### 4. 実装確認
最後に、プレーヤーを用いて4つのスイッチを押し、扉が開けば実装完了。([参考](#こんな感じのギミックが作れます))


# 最後に
どうでしたか、わかりやすかったでしょうか。外部へのセーブや位置を指定したシーン遷移などネタはあるので気が向いたら記述します。それでは。


宣伝：暇でしたら、私が作ったゲームで遊んでいただけると嬉しいです↓
https://www.freem.ne.jp/win/game/29100