using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] public class KamikazeAI : MonoBehaviour { [Header("References")] public Transform player; [Header("Detection")] public float detectRange = 15f; private bool canSeePlayer = false; [Header("Movement & Random Patrol")] public float patrolSpeed = 2.5f; public float chaseSpeed = 7f; public float patrolRadius = 12f; // Bán kính của khu vực tuần tra ngẫu nhiên public float patrolWaitTime = 2f; // Thời gian đứng nghỉ trước khi đổi sang điểm ngẫu nhiên mới private Vector3 startPosition; // Tâm của khu vực tuần tra (Vị trí ban đầu) private float currentWaitTime; private NavMeshAgent agent; private bool isExploding = false; public Node behaviorTreeRoot; public GameObject explosionEffectPrefab; private void Start() { agent = GetComponent(); // Lưu lại vị trí xuất phát để làm tâm, NPC sẽ chỉ đi loay hoay quanh khu vực này startPosition = transform.position; InitBehaviorTree(); } private void Update() { if (isExploding) return; if (player == null) FindPlayer(); else CheckVision(); behaviorTreeRoot?.Evaluate(); } private void FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) player = playerObj.transform; } private void CheckVision() { if (Vector3.Distance(transform.position, player.position) <= detectRange) canSeePlayer = true; else canSeePlayer = false; } private void InitBehaviorTree() { var explodeSequence = new Sequence(new List { new TaskNode(CheckIsCloseEnoughToExplode), new TaskNode(ActionTriggerExplosion) }); var chaseSequence = new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChase) }); // Hành động tuần tra ngẫu nhiên var patrolNode = new TaskNode(ActionRandomPatrol); behaviorTreeRoot = new Selector(new List { explodeSequence, chaseSequence, patrolNode }); } #region CONDITIONS private NodeState CheckCanSeePlayer() { return canSeePlayer ? NodeState.Success : NodeState.Failure; } private NodeState CheckIsCloseEnoughToExplode() { if (player == null) return NodeState.Failure; float dist = Vector3.Distance(transform.position, player.position); return dist <= 3f ? NodeState.Success : NodeState.Failure; } #endregion #region ACTIONS // HÀM TUẦN TRA NGẪU NHIÊN MỚI private NodeState ActionRandomPatrol() { Debug.Log("Wandering randomly..."); agent.isStopped = false; agent.speed = patrolSpeed; // Kiểm tra xem NPC đã đi đến điểm ngẫu nhiên hiện tại chưa if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { currentWaitTime += Time.deltaTime; // Đứng đợi hết thời gian quy định rồi mới tìm đường mới if (currentWaitTime >= patrolWaitTime) { // 1. Lấy một điểm ngẫu nhiên trong không gian hình cầu dựa trên bán kính Vector3 randomDirection = Random.insideUnitSphere * patrolRadius; randomDirection += startPosition; // Cộng với tâm ban đầu để giới hạn khu vực NavMeshHit hit; // 2. Ép tọa độ ngẫu nhiên đó phải nằm TRÊN bề mặt xanh của NavMesh (tránh kẹt tường) // Số 1 ở cuối là Area Mask (thường là Walkable) if (NavMesh.SamplePosition(randomDirection, out hit, patrolRadius, 1)) { agent.SetDestination(hit.position); } currentWaitTime = 0f; // Reset thời gian chờ } } return NodeState.Running; } private NodeState ActionChase() { if (player == null) return NodeState.Failure; Debug.Log("Kamikaze is rushing you!"); agent.isStopped = false; agent.speed = chaseSpeed; agent.SetDestination(player.position); return NodeState.Running; } private NodeState ActionTriggerExplosion() { StartCoroutine(ExplosionRoutine()); return NodeState.Success; } #endregion #region EXPLOSION LOGIC private IEnumerator ExplosionRoutine() { isExploding = true; agent.isStopped = true; agent.velocity = Vector3.zero; Debug.Log("BOMB ARMED!"); yield return new WaitForSeconds(1.5f); if (player != null) { float distToPlayer = Vector3.Distance(transform.position, player.position); if (distToPlayer <= 4f) { Debug.Log("BOOM! Player took damage!"); } } if (explosionEffectPrefab != null) { Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity); } Destroy(gameObject); } #endregion // Vẽ vùng giới hạn tuần tra màu xanh lá cây trên Scene để bạn dễ căn chỉnh độ rộng private void OnDrawGizmosSelected() { Gizmos.color = Color.green; // Nếu game đang chạy thì vẽ quanh tâm startPosition, nếu chưa chạy thì vẽ quanh vị trí hiện tại Vector3 center = Application.isPlaying ? startPosition : transform.position; Gizmos.DrawWireSphere(center, patrolRadius); } }