using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; // Cần thiết để dùng NavMesh using Hallucinate.Audio; [RequireComponent(typeof(NavMeshAgent))] // Tự động thêm component này nếu chưa có public class EnemyAI : MonoBehaviour { [Header("References")] public Transform player; [Header("Detection")] public float detectRange = 10f; public float moveSpeed = 3f; public float rotateSpeed = 50f; [Header("Patrol Area")] public float patrolRadius = 15f; // Bán kính khu vực tuần tra public float patrolWaitTime = 2f; // Thời gian đứng chờ trước khi đi điểm khác private Vector3 startPosition; private float currentWaitTime; [Header("Artifact")] public bool playerHasArtifact; [Header("Laser")] public GameObject laserPrefab; public Transform firePoint; public float minShootDelay = 1f; public float maxShootDelay = 3f; [Header("Audio")] public string alertSound = "Enemy_Alert"; public string shootSound = "Enemy_Shoot"; private bool hasSpottedPlayer; // Để chỉ kêu alert 1 lần [Header("Conversation")] public string npcName = "Guard"; public string persona = "You are a grumpy guard protecting gold."; public float talkRange = 4f; public float talkCooldown = 30f; private float lastTalkTime; private bool isTalking; private EnemyAI talkingPartner; private Hallucinate.UI.ChatBubble chatBubble; private float nextShootTime; private NavMeshAgent agent; public Node behaviorTreeRoot; private void Start() { agent = GetComponent(); chatBubble = GetComponentInChildren(true); agent.speed = moveSpeed; // Lưu lại vị trí ban đầu để làm tâm của khu vực tuần tra startPosition = transform.position; nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay); InitBehaviorTree(); FindPlayer(); } private void Update() { // Nếu mất reference (Player chết hoặc chưa spawn), liên tục tìm lại if (player == null) { FindPlayer(); } // Chỉ chạy AI nếu đã tìm thấy player (hoặc bạn có thể cho tuần tra ngay cả khi chưa có player tùy logic game) behaviorTreeRoot?.Evaluate(); } private void FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) { player = playerObj.transform; } } private void InitBehaviorTree() { // Ưu tiên 1: Player có artifact -> focus + shoot (Cao nhất) var laserSequence = new Sequence(new List { new TaskNode(CheckHasArtifact), new TaskNode(ActionFocusAndShoot) }); // Ưu tiên 2: Thấy player -> rượt đuổi var chaseSequence = new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionMoveToPlayer) }); // Ưu tiên 3: Gần NPC khác -> nói chuyện (Mới) var talkSequence = new Sequence(new List { new TaskNode(CheckCanTalkToNPC), new TaskNode(ActionTalk) }); // Ưu tiên cuối: Tuần tra var patrolNode = new TaskNode(ActionPatrol); behaviorTreeRoot = new Selector(new List { laserSequence, chaseSequence, talkSequence, patrolNode }); } #region CONDITIONS private NodeState CheckCanTalkToNPC() { if (playerHasArtifact || hasSpottedPlayer) return NodeState.Failure; if (Time.time < lastTalkTime + talkCooldown) return NodeState.Failure; if (isTalking) return NodeState.Success; // Tìm NPC gần nhất Collider[] hitColliders = Physics.OverlapSphere(transform.position, talkRange); foreach (var hit in hitColliders) { if (hit.gameObject != gameObject && hit.CompareTag("Enemy")) { EnemyAI other = hit.GetComponent(); if (other != null && !other.isTalking && Time.time >= other.lastTalkTime + talkCooldown) { talkingPartner = other; return NodeState.Success; } } } return NodeState.Failure; } private NodeState CheckHasArtifact() { // Khi bị phát hiện hoặc player có artifact, ngắt lời ngay if (playerHasArtifact || hasSpottedPlayer) StopConversation(); return playerHasArtifact ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanSeePlayer() { if (player == null) return NodeState.Failure; float distance = Vector3.Distance(transform.position, player.position); if (distance <= detectRange) { if (!hasSpottedPlayer) { hasSpottedPlayer = true; AudioManager.Instance?.Play(alertSound, position: transform.position); StopConversation(); // Ngắt hội thoại khi thấy player } return NodeState.Success; } hasSpottedPlayer = false; return NodeState.Failure; } #endregion #region ACTIONS private NodeState ActionTalk() { if (talkingPartner == null) return NodeState.Failure; if (!isTalking) { isTalking = true; agent.isStopped = true; // Xoay về phía bạn FaceTarget(talkingPartner.transform.position); // Bắt đầu hội thoại qua Gemini StartNPCConversation(); } return NodeState.Running; } private void StartNPCConversation() { string prompt = $"You are {npcName}. You are talking to your fellow guard {talkingPartner.npcName}. " + "Keep it short (1 sentence). Topic: gold security or complaining about work."; Hallucinate.AI.GeminiService.Instance.GetResponse(persona, prompt, (response) => { if (chatBubble != null) chatBubble.Show(response); // Hẹn giờ kết thúc hội thoại Invoke(nameof(EndConversation), 5f); }); // Thông báo cho bạn diễn cũng dừng lại để "nghe" talkingPartner.OnPartnerTalked(this); } public void OnPartnerTalked(EnemyAI partner) { isTalking = true; talkingPartner = partner; agent.isStopped = true; FaceTarget(partner.transform.position); // Chờ bạn nói xong mới phản hồi (Tùy chọn: có thể thêm logic phản hồi ở đây) Invoke(nameof(EndConversation), 6f); } private void EndConversation() { isTalking = false; lastTalkTime = Time.time; if (agent != null) agent.isStopped = false; talkingPartner = null; } private void StopConversation() { if (!isTalking) return; CancelInvoke(nameof(EndConversation)); EndConversation(); if (chatBubble != null) chatBubble.Show("Suỵt! Có gì đó không ổn...", 2f); } private void FaceTarget(Vector3 targetPos) { Vector3 dir = targetPos - transform.position; dir.y = 0; if (dir != Vector3.zero) { transform.rotation = Quaternion.LookRotation(dir); } } private NodeState ActionPatrol() { // Debug.Log("Patrolling..."); if (!agent.isActiveAndEnabled || !agent.isOnNavMesh) return NodeState.Failure; agent.isStopped = false; // Đảm bảo NPC được phép di chuyển agent.speed = moveSpeed * 0.5f; // Đi dạo nên đi chậm lại một chút // Kiểm tra xem NPC đã đến điểm đích chưa if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { currentWaitTime += Time.deltaTime; // Chờ một lúc rồi mới chọn điểm mới if (currentWaitTime >= patrolWaitTime) { // Tìm một điểm ngẫu nhiên trong bán kính cho trước Vector3 randomDirection = Random.insideUnitSphere * patrolRadius; randomDirection += startPosition; NavMeshHit hit; // Đảm bảo điểm ngẫu nhiên nằm trên bề mặt NavMesh hợp lệ if (NavMesh.SamplePosition(randomDirection, out hit, patrolRadius, 1)) { agent.SetDestination(hit.position); } currentWaitTime = 0f; } } return NodeState.Running; } private NodeState ActionMoveToPlayer() { if (player == null) return NodeState.Failure; // Debug.Log("Chasing Player"); if (!agent.isActiveAndEnabled || !agent.isOnNavMesh) return NodeState.Failure; agent.isStopped = false; agent.speed = moveSpeed; // Phục hồi tốc độ rượt đuổi agent.SetDestination(player.position); return NodeState.Running; } private NodeState ActionFocusAndShoot() { if (player == null) return NodeState.Failure; // Debug.Log("Focus and Shoot!"); if (!agent.isActiveAndEnabled || !agent.isOnNavMesh) return NodeState.Failure; // Dừng NavMeshAgent lại để đứng bắn, tránh bị trượt agent.isStopped = true; // Focus player Vector3 dir = player.position - transform.position; dir.y = 0f; if (dir != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(dir); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); } // Shoot with random delay if (Time.time >= nextShootTime) { ShootLaser(); nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay); } return NodeState.Running; } private void ShootLaser() { if (laserPrefab == null || firePoint == null) return; Instantiate(laserPrefab, firePoint.position, firePoint.rotation); AudioManager.Instance?.Play(shootSound, position: transform.position); // Debug.Log("Laser Shot!"); } #endregion }