【Unity2D】エリア内のモンスターを全て倒すと開く扉の作り方

Unity2D

Unity2D ARPG

モンスターの管理ができるようになったので、
今度はエリア内のモンスターをせん滅をトリガーにした仕掛けを作ってみましょう。

今回は宝箱を出現させて鍵を取得して扉を開けて進む部屋にしていきたいと思います。

モンスターの数が0になったら通知する仕組みを作る

State_EnemyManageでモンスターのカウントができるようになったので、0になったらトリガー発動といった感じにしたいです。

毎フレーム監視しているとforeacthが動いてしまうので処理的にも無駄になってしまいます。

なのでモンスターが減ったときにカウント数をチェックして自前の数値が0になったら、
せん滅フラグを立てるようにしましょう。

本来であればチェックも兼ねて0になったらもう一度foreachを回してモンスターが残ってないかのチェックも兼ねると確実です。

今回はやりませんが。

SmallEnemyCollisionで作っていたハンマーを当てた時のノックバックを、
State_EnemyActorに移植してHPを経るようにしてい見ます。

using System.Collections;
using UnityEngine;

public class State_EnemyActor : State_EnemyManage
{
    // 自身のリジッドボディ格納用
    private Rigidbody2D rb;

    // モンスターのステータス
    public int[] EnemyStatus = {
        1, // エネミーのHP
        1, // 行動タイプ
        1, // 速度
        0, // 不死フラグ
        1 // 復活フラグ
    };

    // モンスターの初期配置
    [SerializeField]
    private Vecotr3 _initPos = Vector3.zero;

    // 移動可能かのフラグ
    private bool _moveFlag = false;
    public bool MoveFlag
    {
        get { return _moveFlag; }
        set { _moveFlag = value; }
    }

    protected override void Start()
    {
        // 自身のリジッドボディを取得
        rb = GetComponent<Rigidbody2D>();
    }

    protected override void Update()
    {
    }

    // 衝突判定
    private void OnTriggerEnter2D(Collider2D other)
    {
        // 衝突判定が武器タグでHPが0より大きければ
        if (other.tag == "Weapon" && EnemyStatus[0] > 0)
        {
            // 武器ステートスクリプトを取得して変数forceを利用してノックバックさせる
            rb.AddForce(other.GetComponent<_ObjectStateWeapon>().force);
            // ヒットしたら自身のHPを減らす
            EnemyStatus[0]--;
            // モンスターのHPをチェックする
            _checkEnemyState();

        }
    }

    // モンスターのステータスをチェック
    private void _checkEnemyState()
    {
        // 自身のHPをチェック
        if (EnemyStatus[0] <= 0)
        {
            // HPが0になったら

            // ムーブフラグを停止
            _moveFlag = false;
            // 衝突判定コンポーネントを無効化
            GetComponent<BoxCollider2D>().enabled = false;
            // 姿を透明にする
            GetComponent<SpriteRenderer>().color = new Color(0, 0, 0, 0);
            // 一応初期配置に戻しておく
            transform.localPosition = _initPos;
            // 最後に親の関数を呼んでエリアモンスターの総数を減らす
            countDownMonster();
        }
    }

}

ハンマーが衝突判定に入ったら、HPがある場合のみノックバックさせてHPを減らしステータスチェックを開始。

ステータスチェックでは自身のHPを見て0であれば移動フラグ・衝突判定コンポーネントを停止し姿を透明にする。

そして念のためにモンスターを初期配置に移動させておきます。

最後に親の関数であるモンスターの総数を減らす関数を読んでいます。

一旦親の関数であるcountDownMonsterで総数チェックをしてみましょう。

    // モンスターカウントを減らす
    protected void countDownMonster()
    {
        // モンスターが0より多ければ作動
        if (EnemyCount > 0)
        {
            EnemyCount -= 1;
        }

        if (EnemyCount <= 0)
        {
            Debug.Log("モンスターせん滅");
        }

    }

カウントして0になったらモンスターが0になった合図を一旦表示してみます。

Unity2D モンスターせん滅

ハンマーを当てるとモンスターが消えてモンスターの数が0になるのでせん滅したことになりました。

宝箱イベントを作る

これでせん滅フラグを取得することができるようになったので、これをもとに宝箱を出現させてみます。

まずは画像はなんでもいいので宝箱イベントを作りましょう。

ハンマーイベントをコピーして宝箱ように設定します。

よく使うものはプレハブ化してしまうといいかもですね。

宝箱とかスイッチとか。

宝箱イベントはハンマー取得イベントとほぼ同じです。

メッセージを出してアイテムを入手。

今回は鍵を入手するのでその仕組みを作ります。

まずは文字を定義しておきましょう。

ハンマーもそのうちアイテム側において、アイテム名のほうから文字列を取得するようにしたほうがいいですね。

一応アイテム名のデータベースを作っておきました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Message_db : MonoBehaviour
{
    private static List<string> _message = new List<string> {
        // 0個目はダミーメッセージ
        "ダミーです",
        "ハンマーが壁に掛けられている",
        " ハンマーを取る\n ハンマーを取らない",
        "宝箱には ちいさなカギ が入っていた!"
    };
    // ゲッター
    public List<string> Message { get { return Message_db._message; } }



    private static List<string> _itemName = new List<string>
    {
        // 0個目はダミーメッセージ
        "ダミーアイテム",
        "ちいさなカギ",
        "ハンマー"
    };
    // ゲッター
    public List<string> Message { get { return Message_db._itemName; } }
}

本来は文字列を解読して変数になる部分をスクリプト側で置換してあげるのがいいかもしれませんが、
体験版は文字が限られているのでリテラルで一旦しのぎます。

Player_Stateにカギを保持する変数を作って扱えるようにしましょう。

プレイヤーのステータスに組み込んでしまうほうがいいのですが、
わかりやすいように別管理にします。

しかしこうやって別に分けるとダンジョン別にカギを管理しやすいので、
鍵に関しては別にしてしまうのもいいかもしれません。

初代ゼルダの伝説は鍵が全ダンジョン共通で保持して別のダンジョンで使えるという仕組みがありました。

容量の問題で苦肉の策だったんでしょうね。

これはこれで自由度が高まって面白いのですが会話イベントとかがあると破綻しかねません。

初代ゼルダは会話がほとんどないので成立してたわけです。

そしてUnityでPCであるいま容量はそこまで気にしなくても大丈夫です。

心置きなく変数を分けましょう。

    // プレイヤー所持キー
    private int[] _playerKeyStatus = {
        0, // ステージ1のちいさなカギ数
        0, // ステージ1のボス部屋カギ数
    };
    public int[] getPlayerKeyStatusList { get { return _playerKeyStatus; } }
    public int getPlayerKeyStatus(int item) { return _playerKeyStatus[item]; }
    // キーの増減
    public void setPlayerKey(int val, int item) {
        // 鍵が0個以下であれば減算制御
        if(_playerKeyStatus[item] <= 0 && val <= 0) {
            // 保持キーが0且つ減算であれば0に補正
            _playerKeyStatus[item] = 0;
        } else {
            // 問題なければvalを加算
            _playerKeyStatus[item] += val;
        }
    }

ステータス取得と同じで全鍵状態取得と個別の状態取得。

そして鍵の加減算処理を作りました。

念のため保持キーがマイナスにならないように補正しています。

そして鍵増減イベントを作って値を操作しましょう。

Event_Managerのイベント処理関数に8番目を付け加えましょう。

case 8:
    // プレイヤー鍵状態変更
    _State.setPlayerKey(ev[1], ev[2]);
    break;

ハンマー取得イベントはプレイヤーのステータスを変更したので、
鍵専用の増減イベントを作りました。

これでイベントを「8,0,1」とすればエリア1の鍵を1つ追加するということになります。

以上のことから宝箱オブジェクトのイベント一覧は以下のようになります。

  • 5,3,0 // 宝箱メッセージ(メッセージ送り待機
  • 8,1,0 // 鍵を増やす
  • 0,0,0 // イベント終了

これで宝箱から鍵をゲットできます。

余裕があれば宝箱が開いたグラフィックを用意して、
対象イベントのグラフィックを変更するイベントを用意しないとですね。

ゲットした鍵があれば扉を開く仕組み

鍵をゲットできたのであとは鍵付きの扉を調べて開けるだけですね。
1回開けた扉は基本的に開けっ放しでいいでしょう。

扉付きの鍵を実装します。

ゼルダの場合は体当たりで鍵が開きますのでそれにならってみましょう。

製品版では調べるよにするかはちょっと考えておきます。

まずは扉を配置しましょう。

1つ前の部屋の扉をコピーして鍵付きと分かりやすいようにカラーを緑に変えてみました。

この辺は好きに調整してください。

タグをDoorから「SmallKeyDoor」にします。

あとはプレイヤーの衝突判定にこのタグを持つオブジェクトがぶつかっていて、
尚且つ小さなカギを持っている状態であれば扉を開く仕組みにしてみましょう。

_ObjectCollisionをいじります。

using System.Collections;
using UnityEngine;

public class _ObjectCollision : MonoBehaviour
{
    // プレイヤーステートスクリプトの取得
    private State_Player _State;

    // 鍵ドア開けカウント
    private int _doorHitCount = 0;

    // Start is called before the first frame update
    void Start()
    {
        _State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnTriggerEnter2D(Collider2D other)
    {

        // 移動モード変数
        int sceneNo = 0;

        if (_State.GameSceneState == 0)
        {

            // タグにより移動モードを決定
            switch (other.tag)
            {
                case "AreaChangeV":
                    // 移動前のエリアを保持しておく
                    _State.BeforeAreaNo = _State.NowAreaNo;
                    // 

                    // 上下判定
                    if (transform.position.y > other.transform.position.y)
                    {
                        // プレイヤーが上にいる場合は下スクロール
                        sceneNo = 1;
                        // エリアを+4する
                        _State.NowAreaNo += 4;
                    }
                    else
                    {
                        // プレイヤーが下にいる場合は下スクロール
                        sceneNo = 2;
                        // エリアを-4する
                        _State.NowAreaNo -= 4;
                    }
                    // モードのステートを設定
                    _State.setSceneControl(sceneNo);
                    // スクロールフラグをONにする
                    _State.IsAreaScroll = true;
                    break;
                case "AreaChangeH":
                    // 移動前のエリアを保持しておく
                    _State.BeforeAreaNo = _State.NowAreaNo;

                    // 左右
                    if (transform.position.x < other.transform.position.x)
                    {
                        // プレイヤーが左にいる場合右はスクロール
                        sceneNo = 3;
                        // エリアを+1する
                        _State.NowAreaNo += 1;
                    }
                    else
                    {
                        // プレイヤーが右にいる場合左はスクロール
                        sceneNo = 4;
                        // エリアを-1する
                        _State.NowAreaNo -= 1;
                    }
                    // モードのステートを設定
                    _State.setSceneControl(sceneNo);
                    // スクロールフラグをONにする
                    _State.IsAreaScroll = true;
                    break;
                default:
                    break;
            }
        }

    }

    private void OnTriggerExit2D(Collider2D other)
    {
        switch (other.tag)
        {
            case "AreaChangeH":
            case "AreaChangeV":
                // エリアスクロール中は判定から抜けたらステートを強制0にする
                if (_State.GameSceneState >= 1 || _State.GameSceneState <= 4)
                {
                    _State.ActState = 0;
                }
                break;
        }
    }



    // コリジョンが触れ続けている場合
    private void OnCollisionStay2D(Collision2D other)
    {
        switch (other.collider.tag)
        {
            // 小さな鍵扉の場合
            case "SmallKeyDoor":
                // 255までインクリメントする参考演算子(オーバーフロー対策)
                _doorHitCount += _doorHitCount < 256 ? 1 : 0;
                // ドアにぶつかった時間が40フレームより大きく 且つ 小さな鍵を1つ以上持ち 且つ 歩き状態であれば
                if (_doorHitCount > 40 && _State.getPlayerKeyStatus(0) > 0 && _State.ActState == 1)
                {
                    // 扉を一旦どっかにやる
                    other.transform.localPosition = Vector3.zero;
                }
                else if (_State.ActState == 0)
                {
                    // 動いてなければドアぶつかりカウンターをリセット
                    _doorHitCount = 0;
                }
                break;
        }
    }

    // コリジョンが離れたら
    private void OnCollisionExit2D(Collision2D other)
    {
        switch (other.collider.tag)
        {
            // 小さな鍵扉の場合
            case "SmallKeyDoor":
                // ドアぶつかりカウントをリセット
                _doorHitCount = 0;
                break;
        }
    }
}

だいぶ改良しました。

エリアスクロールはOnTrigger~で扉はOnCollision~を使っています。

OnCollision~の引数はコライダそのものを取得しているわけじゃないところに気を付けてください。

直接other.tagとはできないので注意が必要です。

鍵をもってドアに一定フレーム数ぶつかっていると開くようにしました。

OnCollisionStay2Dだと隣接しているだけで判定されてしまうので、
歩いている状態という条件も合わせないと勝手に鍵を使われてしまいます。

ぶつかりながら一定時間歩くことによって任意で鍵を使わせることでユーザビリティを確保しています。

扉の横通って勝手に使われたらここで使いたかったんじゃなかったんだ!ってなってしまいますからね。

壁にそって歩き続けてもカウントが達する前に扉から離れているので余程変なことをしていなければ大丈夫でしょう。

次回は特定の床を特定の順番で踏んだら開くスイッチ床を作りましょう。

それでは!