【開発メモ】敵キャラクターの行動ロジック修正 攻撃編

というわけで、今回は敵キャラクターの攻撃ロジックの修正を行います!
前回から間が空いてしまいましたが、移動ロジックを簡単に作ったので、その続きになります。

【開発メモ】敵キャラクターの行動ロジック修正 移動編

shot2ss20160415224147751

敵キャラクターの種類によって攻撃方法は異なりますが、今回はオーソドックスに「近接攻撃」と「遠距離攻撃」の2つを持ったキャラを想定します。
前回までの過程で、プレイヤーを発見するとその方向へ移動する制御まではできているので、プレイヤーとの距離に応じてどちらかを選択するようにします。

攻撃方法の判定

プレイヤーを発見した敵は移動を行い、キャラクター毎に持っている攻撃手段の射程内に入った際に攻撃を行います。
「プレイヤーが射程内にいるか」はレイキャストで判定します。
攻撃手段として2つ用意されているので、それぞれでレイキャストを行います。

Raycastのイメージは以下の矢印のような感じで、赤が近距離、青が遠距離の攻撃です。
プレイヤーキャラの判定(黄色枠)に当たった場合、その攻撃を実行します。

shot2ss20160415224814713

しかし、単純にRaycast()を行うだけでは他のオブジェクトに当たって消えてしまい、プレイヤーキャラにはほとんど当たってくれません。

対処法として以下の2つが思い付きました。
・RaycastAll()を使い、当たったオブジェクトからプレイヤーを判定する
・レイヤーマスクを使い、Raycastがプレイヤーにしか当たらないようにする

効率的にはレイヤーマスクでRaycastの判定対象を絞ったほうがよさそうです。
が、個人的にレイヤーマスクは苦手なので、素直にRaycastAll()の結果をループして判定する方法で実装しました。

以下はEnemyクラスのUpdate()の一部です。
前回やった索敵・移動部分は省略してあります。

【Enemyクラス】

protected virtual void Update () {
    if (animator && canAction) {
        stateInfo = animator.GetCurrentAnimatorStateInfo(0);

        if (stateInfo.IsTag("Attack")) {
            isAttack = true;
        } else {
            isAttack = false;
        }

        // アクション範囲内にプレイヤーがいる場合
        if (enemyActionRange.isDetectPlayer) {
            actionInterval += 1f;

            // 攻撃アクション
            if (defaultActionInterval < actionInterval && !isAttack) {
                doAttackAction();
            }

        } else {
            animator.SetBool("Move", false);
        }
    }
}

【各敵キャラクター用クラス】

protected override void doAttackAction() {
    Ray attackRay = new Ray(transform.position, transform.forward);

    // 攻撃射程内にプレイヤーがいるか判定
    if (ObjectUtility.judgeRaycastHitsTag(Physics.RaycastAll(attackRay, attackRange[0]), "Player")) {
        isAttack = true;
        motionChange("Attack1", true, true);

        createHitManager(attackHitManager[0], hitOffset[0]);
    }
}

【ObjectUtilityクラス】

  
using UnityEngine;
using System.Collections;

public static class ObjectUtility {
    public static bool judgeRaycastHitsTag(RaycastHit[] hits, string tag) {
        foreach (RaycastHit hit in hits) {
            if (TagUtility.getParentTagName(hit.collider.gameObject) == tag) {
                return true;
            }
        }

        return false;
    }
}

索敵範囲は「EnemyActionRange」というクラスを付けた判定で行っています。
プレイヤーを発見した場合、isDetectPlayerがtrueになります。

doAttackAction()で攻撃射程内かの判定と、実際の攻撃処理を行います。
敵キャラクターごとに内容が異なるため、各敵キャラクター用クラスでEnemyクラスのdoAttackAction()をオーバーライドして実装します。

まずRaycast用のRayを作成します。
今回は単純に「現在位置」から「前方への位置」を指定しています。
ここで作成しなくても、RaycastAll()のオーバーロードでoriginとdistanceを指定できるので、直接指定してしまってもOKです。

次に攻撃射程の判定です。
プログラムソース上で上から順番に評価されていくので、優先させたい攻撃アクションの評価ほど上に記載します。
上の例では、
・attackRange[0] = 10 (近接攻撃の射程)
・attackRange[1] = 30 (遠距離攻撃の射程)
と設定しているので、まず近接攻撃可能な距離にいるか判定され、次に遠距離攻撃が可能かが判定されます。

ObjectUtility.judgeRaycastHitsTag()は自作した静的なメソッドで、RaycastHitの配列と文字列を渡すことで、RaycastAll()の結果から「特定のタグのオブジェクトに当たったか」を判定します。
処理自体はforeachで回して判定しているだけですが、使う機会が多そうなのでユーティリティ化しておきます。

プレイヤーが攻撃射程内にいる場合はモーションを切り替え、攻撃判定を生成します。
攻撃判定については割愛しますが、以下の記事が参考になると思われます。

【再編集】Unity開発メモまとめ 「攻撃処理系」

攻撃間隔とリセット処理

攻撃を行った後、一定時間は移動・攻撃を行わないようにします。
一定時間後に再度プレイヤーを索敵→移動・攻撃を行います。

このあたりを自重せずに実装すると、「1フレーム毎に遠距離攻撃を連射」という凶悪な敵ができてしまったりするので、ゲーム的な体裁を保つ意味でも重要な間です。
特殊な敵やボスならともかく、雑魚的が連続で行動してくる必要もありません。
アクションゲーム故、敵が複数同時にいることも多いので尚更ですね。

上で載せたソースで言うと、「actionInterval」という変数で攻撃間隔を制御します。
1フレーム経過ごとにactionIntervalを加算しています。
合わせて「defaultActionInterval」というフィールドを用意しておき、attackIntervalがdefaultActionInterval以上になった場合のみdoAttackAction()を実行します。
攻撃実行後はattackIntervalを0にリセットし、再度defaultActionInterval以上になるまで攻撃は行いません。

以下はdefaultActionIntervalに120(=2秒)を指定した場合の挙動です。

20160415_1

1敵キャラクターとしてみると妥当な攻撃間隔でしょうか。
「短めの間隔で小刻みに攻撃」「長めの間隔で強力な攻撃」といった敵も作れそうです。

まとめ

そんなわけで、敵キャラクターの攻撃ロジックを考えてみました。
前回の移動ロジックと合わせて、最低限「敵」の動きになったと思われます!

実装していくと無駄な処理や「これもっとスマートにできそう」といった箇所が目についてきます。
ゲームの形にすらなっていない今はともかく、ある程度作ってきたらパフォーマンスも気にする必要がありそうです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*