Files
VRProject/Assets/Scripts/EnemyAI.cs
oogushiyuuga 4141e2d189 敵AI Ver.1
2026-05-26 17:04:24 +09:00

186 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEngine.AI;
using System.Collections;
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyAI : MonoBehaviour
{
// 敵の状態を定義する列挙型FSM
public enum AIState { Wander, Chase }
[Header("現在の状態")]
[SerializeField] private AIState currentState = AIState.Wander;
[Header("索敵設定")]
[Tooltip("プレイヤーを検知する距離")]
[SerializeField] private float detectionRange = 10f;
[Tooltip("プレイヤーを見失う距離(検知距離より広く設定するのがコツ)")]
[SerializeField] private float loseRange = 15f;
[Tooltip("プレイヤーのタグ名")]
[SerializeField] private string playerTag = "Player";
[Header("徘徊Wander設定")]
[SerializeField] private float wanderRadius = 8f;
[SerializeField] private float minWaitTime = 0.1f;
[SerializeField] private float maxWaitTime = 1f;
[Tooltip("目的地に向けて移動を開始してから、何秒で諦めるか")]
[SerializeField] private float wanderTimeout = 7f;
private NavMeshAgent agent;
private Transform playerTransform;
private bool isWandering = false;
void Start()
{
agent = GetComponent<NavMeshAgent>();
// 負荷軽減のため、AIの思考ループをコルーチンで開始毎フレーム Update で処理しない)
StartCoroutine(AILoop());
}
// AIの意思決定を行うメインループ0.2秒ごとに実行して負荷を劇的に下げる)
private IEnumerator AILoop()
{
while (true)
{
// 1. プレイヤーを探す(まだ見つけていない場合のみ)
if (playerTransform == null)
{
FindPlayer();
}
// 2. 現在のステート(状態)に応じて処理を分岐
switch (currentState)
{
case AIState.Wander:
WanderBehavior();
break;
case AIState.Chase:
ChaseBehavior();
break;
}
// 毎フレームではなく、0.2秒1秒に5回だけ実行するQuest 3向けの最適化
yield return new WaitForSeconds(0.2f);
}
}
// シーン内からプレイヤー(タグ付き)を探すメソッド
private void FindPlayer()
{
GameObject playerObj = GameObject.FindGameObjectWithTag(playerTag);
if (playerObj != null)
{
playerTransform = playerObj.transform;
}
}
// ─── 【徘徊状態】の行動 ───
private void WanderBehavior()
{
if (playerTransform != null)
{
// プレイヤーとの距離を計算
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
// 索敵範囲内に入ったら「追跡状態」へ遷移
if (distanceToPlayer <= detectionRange)
{
currentState = AIState.Chase;
isWandering = false;
agent.ResetPath(); // 徘徊の目的地をリセット
Debug.Log($"👁️ {gameObject.name}: プレイヤーを発見! 追跡します。");
return;
}
}
// 徘徊の移動ロジック
if (!isWandering)
{
StartCoroutine(WanderMoveRoutine());
}
}
private IEnumerator WanderMoveRoutine()
{
isWandering = true;
// ランダムな目的地を取得して移動開始
Vector3 newPos = GetRandomNavMeshPoint(transform.position, wanderRadius);
agent.SetDestination(newPos);
float elapsedTime = 0f; // 経過時間を計るカウンター
// 目的地に到着するまで待つ(ただしタイムアウト時間を超えたらループを抜ける)
while (agent.remainingDistance > agent.stoppingDistance)
{
// 0.5秒ごとにチェック
yield return new WaitForSeconds(0.5f);
elapsedTime += 0.5f;
// 【スタック対策】設定した制限時間を超えた場合
if (elapsedTime >= wanderTimeout)
{
Debug.Log($"⚠️ {gameObject.name}: 目的地({newPos})に到達できないため諦めました。");
agent.ResetPath(); // 動けない経路(バグ目的地)を一旦クリアする
break; // whileループを強制終了して次の目的地設定へ進める
}
}
// 到着後(または諦めた後)、ランダムな時間だけその場で待機して次の徘徊へ
yield return new WaitForSeconds(Random.Range(minWaitTime, maxWaitTime));
isWandering = false;
}
// ─── 【追跡状態】の行動 ───
private void ChaseBehavior()
{
if (playerTransform == null)
{
currentState = AIState.Wander;
return;
}
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
// 見失う距離loseRangeを超えたら「徘徊状態」に戻る
if (distanceToPlayer > loseRange)
{
currentState = AIState.Wander;
agent.ResetPath();
Debug.Log($"❓ {gameObject.name}: プレイヤーを見失った。徘徊に戻ります。");
return;
}
// プレイヤーの位置を目的地に設定0.2秒ごとに更新されるため、滑らかに追従します)
agent.SetDestination(playerTransform.position);
}
// NavMesh上のランダムな座標を取得する補助メソッド
private Vector3 GetRandomNavMeshPoint(Vector3 center, float radius)
{
Vector3 randomDirection = Random.insideUnitSphere * radius;
randomDirection += center;
NavMeshHit hit;
if (NavMesh.SamplePosition(randomDirection, out hit, radius, NavMesh.AllAreas))
{
return hit.position;
}
return center;
}
// 【視覚的なデバッグ用】エディタ上で索敵範囲を線で表示する
private void OnDrawGizmosSelected()
{
// 索敵範囲(赤)
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, detectionRange);
// 見失う範囲(黄)
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, loseRange);
}
}