【Unity2D】エリアに入ったときだけモンスターを動くようにする

Unity2D

Unity2D ARPG

基本的な仕掛けは出来上がってきたので次は応用してゲームを面白くしていきましょう。

ARPGといえばモンスターは欠かせませんね。

体験版ではモンスターのAIは至極単純なものにしようと思います。

というか時間がないのでAIというよりもスクリプト動作になります。

製品版ではモンスターの動き等はしっかりと作りこもうと思いますのでご了承をば……

最初のエリアから上へいき、次に右のエリアにいくと数匹のゴブリンがいるようにします。

モンスターの仕掛けといえばシンプルなところでエリア内のすべての敵を倒せというやつですね。

今回は手始めにエリア内に入ったらモンスターが稼働するようにしましょう。

モンスターの数をチェックする仕組み

モンスターのラッパーであるモンスターオブジェクトにスクリプトを取り付け、
モンスターの数を監視させておきます。

そしてモンスターの数が0になったら、押しオブジェクトの時の仕掛けと同じようにフラグを立て
該当する扉を開くように仕掛けます。

それではモンスターを管理するスクリプトを作りましょう。

State_EnemyManagerというクラスを作りましょう。

using System.Collections;
using UnityEngine;

public class State_EnemyManage : MonoBehaviour
{

    // フロア内モンスター数カウント保持
    protected int _enemyCount = 0;
    public int EnemyCount
    {
        get { return _enemyCount; }
        protected set { _enemyCount = value; }
    }

    // エリア保持変数
    [SerializeField]
    private int _area;

    // Start is called before the first frame update
    protected virtual void Start()
    {
        // 初回時にカウントしておく
        checkAreaMonsters();
    }

    // Update is called once per frame
    protected virtual void Update()
    {
    }

    // フロア内モンスターカウント
    int checkAreaMonsters()
    {
        foreach (Transform child in transform)
        {
            EnemyCount += 1;
        }

        return EnemyCount;
    }

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

このスクリプトがアタッチされたら子オブジェクトとなっているモンスターの数を数えるようにします。

あとからモンスターが増殖するということも考えておかなければいけませんね。

生成されたらモンスターの数を再計算するようにすればよさそうです。

今回の体験版では途中からモンスターを増やすということはしないので一旦後回しにしましょう。

モンスターの移動はプレイヤーのいるエリアのみにする

オブジェクトにも初期値があるように、モンスターにも初期値を持たせて
プレイヤーがそのエリアを抜けると位置をリセットするのが望ましいです。

そうじゃないと入口に集結したモンスターに手も足も出なくなってクソゲー化してしまいます。

スクロールが完了したらモンスターも位置リセットをしましょう。

子オブジェクトは親スクリプトを継承したものをアタッチします。

State_EnemyActorというクラスを作ってください。

モンスターの挙動は一旦「SmallEnemyCollision」にまとめていたのですが、
これからはモンスター用のスクリプトも個別に作っていきます。

State_EnemyActorは名前の通り、個別のモンスター制御になります。

using System.Collections;
using UnityEngine;

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

    // 移動可能かのフラグ
    private bool _moveFlag = false;

    override void Start() {}

    override void Update() {}

}

ステータスは本来Enumとかでやったほうがいいんでしょうね。

これもコミトレ終わってからしっかり組みなおそうと思います。

とりあえず判定できればヨシですね。

プレハブになるのでpublicにしてUnityエディタ側で調整できるようにしておきます。

親のほうでSerializeFieldの「_area」変数を作ったと思うので、
そこにUnityエディタ側でマップのナンバーを入れておきましょう。

プレイヤーがそのエリアに来たら動くフラグをONにするようにします。

State_EnemyManagerに以下の変数と関数を追加してください。

    // エリア稼働フラグ
    private bool _activeArea;

    // プレイヤーステータス確保用変数
    protected State_Player _State;

    // Start is called before the first frame update
    protected virtual void Start()
    {
        // 初回時にカウントしておく
        checkAreaMonsters();
        // プレイヤーステータスを取得
        _State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();

    }

    // Update is called once per frame
    protected virtual void Update()
    {
        isMoveMonsters();
    }

    // モンスターを動かすか止める
    private void isMoveMonsters()
    {
        // プレイヤーが同じエリアに来たら
        if (_State.NowAreaNo == _area)
        {
            // エリアがアクティブじゃないなら
            if (!_activeArea)
            {
                // 1回だけ発動させたいのでエリアアクティブフラグを立てる
                _activeArea = true;
                // 全子オブジェクトを回す(アクティブフラグにより1度だけforeachをする
                foreach (Transform child in transform)
                {
                    // プレイヤーがエリアにきたら稼働させる
                    child.GetComponent<State_EnemyActor>().MoveFlag = true;
                }
            }
        }
        else
        {
            // アクティブフラグが立ってたら折ってモンスターをストップさせて初期位置に戻す
            if (_activeArea)
            {
                _activeArea = false;
                // 全子オブジェクトを回す(アクティブフラグにより1度だけforeachをする
                foreach (Transform child in transform)
                {
                    // プレイヤーがエリアにきたら稼働させる
                    child.GetComponent<State_EnemyActor>().MoveFlag = false;
                }
            }
        }
    }
}

専用のアクティブフラグを持たせることで1回作動したら何度も同じ処理をさせないように制御しています。

foreachは処理的に重たいですから基本的に1フレーム内に何回もやらないほうがよいですね。

次にEnemyActorのMoveFlagがTrueになったら動かしたいので、Enemy用のMoveスクリプトを作りましょう。

Move_Enemyを作ってください。

基本はマネージャーオブジェクトにつけていましたが、Enemyだけは例外でプレハブにアタッチしてください。

そうしないと値渡しがとても大変なことになりますし、Unityのコンポーネント志向の良さもつぶしてしまいます。

本来であればきっちりとやるべきではあるのですが、とりあえずは動かしましょう。

何度も言いますが最適化は最適化前が来ないとできないのでとにかく組み立てるのが大事です。

using System.Collections;
using UnityEngine;

public class Move_Enemy : Move_Manager
{
    // Move_Enemy用の変数(子クラスでの作成だとusignエラーが出る)
    protected Transform _Player;
    protected State_EnemyActor _Enemy;

    // コンストラクタ
    void Start()
    {
        _Player = GameObject.FindGameObjectsWithTag("Player")[0].transform;
        _Enemy = this.GetComponent<State_EnemyActor>();
    }

    // 更新処理
    protected virtual void Update()
    {
        if (_Enemy.MoveFlag)
        {
            moveMonster();
        }
    }

    // モンスターの移動
    private void moveMonster()
    {
        Vector3 pv = _Player.position;
        Vector3 ev = transform.position;

        float p_vX = pv.x - ev.x;
        float p_vY = pv.y - ev.y;

        float vx = 0;
        float vy = 0;

        // 減算した結果がマイナスであればXは加算処理
        if (p_vX < 0)
        {
            vx = -10f;
        }
        else
        {
            vx = 10f;
        }

        // 減算した結果がマイナスであればYは加算処理
        if (p_vY < 0)
        {
            vy = -10f;
        }
        else
        {
            vy = 10f;
        }

        transform.Translate(vx / 50, vy / 50, 0);
    }
}

ほとんどSmallEnemyCollisionの移植ですね。

各モンスターが持つことになるスクリプトなので、なるべく軽めにしておきたいところです。

あとはエリアに入ったら動くようになります。

スクロール終わってから移動するように改良したいのですが、あとにして次に進みましょう。

次回はモンスターを複数出してすべて倒したら扉を開くロジックを作りましょう。

それでは!