【Unity2D】ゴブリンの移動処理を作る

Unity2D

Unity2D ARPG

そろそろモンスターの動きをしっかりと作っていきます。

移動タイプをステータスに持たせそれを見て処理を変更するといった感じになります。

ゴブリンはゼルダの伝説神々のトライフォースに出てくる見張り兵(襲ってこないタイプ)を元に動きを組んでみます。

プレイ中はあまりまじまじと観察しなかったので気づかなかったのですが、
意外と規則性のある動きだったことに気付きました。

ゼルダ神トラの見張り兵(襲ってこない)のルーチンを見てみる

神々のトライフォースをやったことがある前提で書きます。

襲ってこない兵士のほうのルーチンは以下になります。

  • 進行方向に前進
  • 一定歩数(秒数?)で止まる
  • 以下右右左左を繰り返す
  • 顔だけ右を向く(1回目)
  • 向いた方向に方向転換し、直進(1回目)
  • 顔だけ右を向く(2回目)
  • 向いた方向に方向転換し、直進(2回目)
  • 顔だけ左を向く(1回目)
  • 向いた方向に方向転換し、直進(1回目)
  • 顔だけ左を向く(2回目)
  • 向いた方向に方向転換し、直進(2回目)
  • ループ最初に戻る

といった感じです。

小さいころは見つけたらすぐ切りかかってたので、
モンスターのルーチンなんてまったく意識してないので以外なことに気付きました。

この襲ってこない兵士、一応リンクを視界にとらえたら徘徊モードより
ほんの少しだけスピードアップして突進してきます。

シンプルなルーチンながらもちゃんと見張りしてますね。

それでも追尾してくる兵士とは違ってやる気が感じられません。

アルバイト兵でしょうか(笑)

リンクを発見して突進してくるときは方向転換するときよりも
長く直進してくるような動きでした。

このルーチンを一旦トレースして全く同じだと芸がないので
方向転換はランダムにしてみましょう。

直進する動きを作る

まずはシンプルなところからゴブリンを進行方向に直進する動きを作ります。

そのためには方向のステータスが必要になりますね。

進行方向の横に方向を変更する関数

必要な要素は向いている方向と方向に進むための移動処理ですね。

方向はState_EnemyManagerで「_direction」として定義し
プロパティで「Direction」でgetできるようにしています。

protectedで宣言しているのでsetは基本的にこのクラスの親子だけ操作可能なので必要ありません。

それでは乱数で方向を強制的に変えるメソッドを作りましょう。

基本的に進行方向に対して後ろに行くことはないので、
向いてる方向の左右のどちらかを向くように2択になっています。

    // 強制方向変更
    public void setDirectionAbs(int direct)
    {
        // 0-1の乱数を取得
        int rand = UnityEngine.Random.Range(0, 2);

        // 上下に向いてたら
        if (direct < 2)
        {
            if (rand == 1)
            {
                // 1なら左に向く
                _direction = 2;
            }
            else
            {
                // 0なら右に向く
                _direction = 3;
            }
        }
        // 左右に向いてたら
        else
        {
            if (rand == 1)
            {
                // 1なら下に向く
                _direction = 0;
            }
            else
            {
                // 0なら上に向く
                _direction = 1;
            }
        }
    }

下0上1左2右3なので2未満であれば上下と判定できます。

それ以外は左右と判定できるので結構シンプルにかけますね。

上下向いてるときは左右のどちらか。

左右向いてるときは上下のどちらかに方向を強制変更するメソッドです。

これで進行方向に対して横に向けるようにできました。

進行方向に直進する関数

あとは向いている方向に対して直進するだけです。

今までは最短追尾移動を使っていましたが、方向を見て加算する情報を変更するようにしてみましょう。

Move_Enemyクラスの移動関数を変更します。

    // 更新処理
    protected virtual void Update()
    {
        // プレイヤーが同じエリアにいて移動フラグがONになったら
        if (_Enemy.MoveFlag)
        {
            moveKoblin();
        }
    }
    // ゴブリンの移動ルーチン
    private void moveKoblin()
    {

        // 毎フレーム加算座標をリセットしておく
        float vx = 0;
        float vy = 0;

        // 向いてる方向に速度を加算する
        switch (_Enemy.Direction)
        {
            case 0:
                // 下に加速
                vy = -30f;
                break;
            case 1:
                // 上に加速
                vy = 30f;
                break;
            case 2:
                // 左に加速
                vx = -30f;
                break;
            case 3:
                // 右に加速
                vx = 30f;
                break;
        }

        // 上下左右に速度が入っていれば
        if (vx != 0 || vy != 0)
        {
            // モンスターを移動状態にする
            _Enemy.ActState = 1;
        }


        // 実際の移動処理
        transform.Translate(vx / 50, vy / 50, 0);

        // ぶつかったら方向を変える
        if (_Enemy.ColHit)
        {
            _Enemy.setDirectionAbs(_Enemy.Direction);
            _Enemy.ActState = 0;
            _Enemy.ColHit = false;
        }
    }


    private void OnCollisionStay2D(Collision2D other)
    {
        switch (_Enemy.EnemyStatus[1])
        {
            // コブリンの場合
            case 1:
                _Enemy.ColHit = true;
                Debug.Log("ぶつかってる");
                break;
            default:
                break;
        }
    }

まず毎フレーム加速度をXYともに0に初期化しておきます。

その後現状の向きを取得してどの方向に加速度を入れるかを判断します。

上下左右どこかに加速している状態だったら状態を歩き状態に変更します。

そして現在の加速度をもとにモンスターを移動させるといった感じですね。

その後はぶつかっているかを確認し、ぶつかっていたら先ほど作った向き変更関数を呼び出します。

そして止まっている状態に変更し、当たった判定も解除しておきましょう。

これで基本的なルーチンは出来上がりです。

一度実行してみましょう。

なんかイマイチな挙動ですね……

ツイートにある動画をみてもらうとわかるのですが、
壁沿いに行くと方向を変えた瞬間まだ壁に当たっているのか連続で方向転換をしてしまいます。

衝突判定を調べる場所が悪いのかもしれません。

少し判定位置を変えてみましょう。

    // コブリンの移動ルーチン
    private void moveKoblin()
    {

        // 毎フレーム加算座標をリセットしておく
        float vx = 0;
        float vy = 0;

        // ぶつかっていたら方向を変える
        if (_Enemy.ColHit)
        {
            _Enemy.setDirectionAbs(_Enemy.Direction);
            _Enemy.ActState = 0;
            _Enemy.ColHit = false;
        }
        else
        {
            // 向いてる方向に速度を加算する
            switch (_Enemy.Direction)
            {
                case 0:
                    // 下に加速
                    vy = -30f;
                    break;
                case 1:
                    // 上に加速
                    vy = 30f;
                    break;
                case 2:
                    // 左に加速
                    vx = -30f;
                    break;
                case 3:
                    // 右に加速
                    vx = 30f;
                    break;
            }

            // 上下左右に速度が入っていれば
            if (vx != 0 || vy != 0)
            {
                // モンスターを移動状態にする
                _Enemy.ActState = 1;
            }

            // 実際の移動処理
            transform.Translate(vx / 50, vy / 50, 0);
        }
    }

上記のコードをやってみるとわかるのですが悪化しました(笑)

そもそも

 private void OnCollisionStay2D(Collision2D other)

にしてしまったのが悪かったのかと思って

触れた瞬間に発動する

 private void OnCollisionEnter2D(Collision2D other)

を使ったところ、良い感じに動いてくれたのですが
今度は狭い通路に行くと詰まってしまうことが確認しました。

難しいですね……

Game側のウィンドウではなくてScene側のウィンドウとぶつかりログをみていると
角っこで2連続壁にぶつかるとStayの状態になってしまうようでEnter側が発動しなくなっています。

なのでやはりEnterじゃなくてStayに戻すべきですね。

Stay側の問題は壁側に進んでいるときに同じ場所を動きがちというのが問題点だったので、
従来の予定通り一定時間を進むかぶつかるかで強制的に方向変換させることでいろんな場所へ行くようにさせてみましょう。

「一定時間経過後に何かをする」はコルーチンを使うといい感じですね。

早速コードを加えてみましょう。

    // ぶつかったときの処理
    private void _hitCol()
    {
        switch (_Enemy.EnemyStatus[1])
        {
            // コブリンの場合
            case 1:
                _Enemy.ColHit = true;
                break;
            default:
                break;
        }
        StopCoroutine("checkWalk3sTime");
    }

    IEnumerator checkWalk3sTime()
    {
        // 3秒間処理を止める
        yield return new WaitForSeconds(3);

        // 無事3秒経過したら強制的にぶつかったことにして向きを変える
        _hitCol();

    }

先ほどの衝突時の処理を関数化しておいてコルーチン側で3秒後に呼び出します。

_hitColでStopCoroutineを呼び出しているのは壁にぶつかったとき
強制的にコルーチンを止めて方向転換させたいからです。

ぶつからないときは3秒後に方向転換するって感じですね。

エリアに入ったときモンスターが動き出すのでその時に1回だけStartCoroutineを呼び出します。

    // コルーチン唯一性スイッチ
    private bool _coroutineStartFlag = false;

    // コブリンの移動ルーチン
    private void moveKoblin()
    {
        // 初回だけコルーチンを開始
        if (!_coroutineStartFlag)
        {
            StartCoroutine("checkWalk3sTime");
            _coroutineStartFlag = true;
        }

        // 以下処理省略
    }

これでいい感じに動いてくれるのですが……

Unity2D モンスターの挙動

エリア外に出てしまっていますね……

エリア移動用の判定しかないためスルーできてしまいます。

モンスターだけ壁と認定する透明オブジェクトを置いておくのがよさそうですね。

プレイヤーは素通りできるものが望ましいです。

しかし基本的にエリアとエリアの間は扉があるので、扉を開けたらオブジェクトを動かさずに
透明にしてプレイヤーだけ通れるようにしたほうがよさそうです。

今回はこれぐらいにしておきましょう。

次回はモンスターが外に出ないように判定の修正を行います。

それでは!