← 기사 목록
日本語https://zenn.dev/topics/ai/feed

【Unity】敵AIのターゲットを動的に切り替える方法

추출된 키워드

14
Unity·5敵AI·5ターゲット·5defaultTarget·4currentTarget·4OverlapSphereNonAlloc·4GameObject·3Targetable·3Collider·3Physics.OverlapSphereNonAlloc·3Coroutine·2レイヤーマスク·2Update·2sqrMagnitude·2

원문

3,655
【Unity】敵AIのターゲットを動的に切り替える方法

📘

【Unity】敵AIのターゲットを動的に切り替える方法

はじめに

敵が常にプレイヤーだけを狙う実装だと、
味方ユニットや障害物を配置しても無視されてしまい、
ゲーム性が単調になりやすいです。

今回は、
「通常はプレイヤーを狙う」
「近くに味方ユニットがいればそちらを狙う」
「撃破後は元の目標に戻る」
というターゲット切り替え処理を実装します。

やりたいこと

  • 基本ターゲットを持つ
  • 一定範囲内の別ターゲットを検知する
  • 条件に合えばターゲットを切り替える
  • 切り替え先が倒されたら元のターゲットへ戻す

例えば以下のような状況を想定します。

  • 敵は通常時はプレイヤーを追跡
  • 味方ユニットが近くにいる場合はそちらを攻撃
  • 味方ユニット撃破後は再びプレイヤーを追跡

このような仕組みを作ることで、
敵の行動に変化を付けることができます。

実装方針

  • defaultTarget
    を保持する
  • currentTarget
    を現在の攻撃対象にする
  • 一定範囲内の候補を探す
  • 候補があれば
    currentTarget
    を差し替える
  • 候補が無効になったら
    defaultTarget
    に戻す

実装例

今回は以下の流れでターゲットを切り替えています。

  • 現在のターゲットが有効か確認
  • 無効ならデフォルトターゲットへ戻す
  • 周囲のターゲット候補を検索
  • より優先度の高い対象が見つかれば切り替える

周囲のターゲット候補は

OverlapSphereNonAlloc
を利用して周囲の対象を取得し、
最も距離の近いオブジェクトを選択しています。
    [SerializeField] private GameObject defaultTarget;
    [SerializeField] private GameObject currentTarget;

    private void Update()
    {
        // 現在のターゲットが有効か判定
        if (!IsValidCurrentTarget())
        {
            currentTarget = defaultTarget;
            // 関連コンポーネントへのターゲット反映(必要に応じて実装)
            ApplyTarget(defaultTarget);
        }

        GameObject nearest = FindNearestTarget();
        GameObject desired = nearest ?? defaultTarget;
        if (desired != currentTarget)
        {
            currentTarget = desired;
            // 関連コンポーネントへのターゲット反映(必要に応じて実装)
            ApplyTarget(desired);
        }
    }

    // 周辺ターゲット取得処理
    private GameObject FindNearestTarget()
    {
        // ターゲット候補オブジェクト取得処理
        int count = Physics.OverlapSphereNonAlloc(
            transform.position,  // 検知の中心座標
            targetSwitchRadius,  // 検知対象半径
            scanResults,  // 検知結果
            targetableLayerMask,  // 検知対象のレイヤーマスク
            QueryTriggerInteraction.Collide);

        GameObject best = null;
        float bestSqrDistance = float.MaxValue;

        for (int i = 0; i < count; i++)
        {
            Collider hit = scanResults[i];
            if (hit == null) continue;

            // Targetable:検知対象になるゲームオブジェクト(敵or味方等)にアタッチ
            Targetable target = hit.GetComponent<Targetable>();
            if (target == null || !target.IsTargetable) continue;
 
            if (target.transform == null) continue;

            float sqrDistance = (target.transform.position - transform.position).sqrMagnitude;
            if (sqrDistance < bestSqrDistance)
            {
                bestSqrDistance = sqrDistance;
                best = target.gameObject;
            }
        }

        return best;
    }

なお、サンプルでは分かりやすさを優先して

Update()
内で探索していますが、
実際の実装する際は一定間隔で検索する方が負荷を抑えられます。

実装時の注意点

  • 毎フレーム探索すると重くなりやすい
    • Coroutineや一定間隔更新も検討する
  • 優先順位を決めておく
  • 倒された対象を参照し続けない
    • 対象がなくなった時の更新処理が必要

まとめ

敵のターゲットを固定せず状況に応じて切り替えられるようにすると、
ゲームらしい駆け引きを作りやすくなります。

今回のようにデフォルトターゲットを保持しておけば、
一時的なターゲット変更と復帰処理も比較的シンプルに実装できます。