using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using System.Linq; // Quy trình ưu tiên: Né đòn --> Bắn hạ (Artifact) --> Đuổi theo (Vector) --> Điều tra --> Nói chuyện --> Đi tuần [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Rigidbody))] public class EnemyAI : MonoBehaviour { [Header("References")] public Transform player; private NavMeshAgent agent; private Rigidbody rb; private FieldOfView fov; [Header("Movement Settings")] public float moveSpeed = 3f; public float rotateSpeed = 10f; [Header("Patrol Settings")] public Transform[] patrolWaypoints; public int currentWaypointIndex = 0; public float patrolWaitTime = 2f; private float currentWaitTime = 0f; [Header("Combat State")] public bool playerHasArtifact; public GameObject laserPrefab; public Transform firePoint; public float minShootDelay = 1f; public float maxShootDelay = 3f; private float nextShootTime; [Header("Dodge Settings")] public float dodgeForce = 10f; public float dodgeDuration = 0.2f; public float dodgeCooldown = 1.2f; private bool isDodging = false; private float nextDodgeTime = 0f; [Header("Conversation Settings")] public string npcName = "Guard"; [TextArea] public string persona = "You are a grumpy guard protecting gold."; public float talkRange = 10f; public float talkCooldown = 15f; private float lastTalkTime; public bool isTalking; // Public để debug private EnemyAI talkingPartner; private Hallucinate.UI.ChatBubble chatBubble; public Node rootNode; void Start() { agent = GetComponent(); rb = GetComponent(); fov = GetComponent(); chatBubble = GetComponentInChildren(true); // Rigidbody setup cho Unity 6 rb.isKinematic = true; rb.freezeRotation = true; if (player == null) { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) player = playerObj.transform; } /* // Tạm thời comment đoạn này để tránh lỗi Tag chưa định nghĩa if (patrolWaypoints == null || patrolWaypoints.Length == 0) { patrolWaypoints = GameObject.FindGameObjectsWithTag("PatrolPoint") .Select(go => go.transform).ToArray(); } */ InitTree(); Debug.Log($"[AI {npcName}] Init complete. Waypoints: {patrolWaypoints.Length}"); } void InitTree() { var dodgeSequence = new Sequence(new List { new TaskNode(CheckDodgeConditions), new TaskNode(ActionDodge) }); var laserSequence = new Sequence(new List { new TaskNode(CheckHasArtifact), new TaskNode(ActionFocusAndShoot) }); var chaseSequence = new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) }); var investigateSequence = new Sequence(new List { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) }); var talkSequence = new Sequence(new List { new TaskNode(CheckCanTalkToNPC), new TaskNode(ActionTalk) }); var patrolAction = new TaskNode(ActionPatrol); rootNode = new Selector(new List { dodgeSequence, laserSequence, chaseSequence, investigateSequence, talkSequence, patrolAction }); } void Update() { if (player == null) return; // An toàn cho NavMeshAgent if (!agent.isOnNavMesh) { Debug.LogWarning($"[AI {npcName}] NPC is NOT on NavMesh!"); return; } if (!isTalking && !isDodging && agent.isStopped) agent.isStopped = false; rootNode?.Evaluate(); } #region CONDITIONS private NodeState CheckDodgeConditions() { if (isDodging) return NodeState.Success; if (fov != null && fov.canSeePlayer && Input.GetMouseButtonDown(0) && Time.time >= nextDodgeTime) return NodeState.Success; return NodeState.Failure; } private NodeState CheckHasArtifact() { if (playerHasArtifact) StopConversation(); return playerHasArtifact ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanSeePlayer() { bool canSee = fov != null && fov.canSeePlayer; if (canSee) StopConversation(); return canSee ? NodeState.Success : NodeState.Failure; } private NodeState CheckHasInvestigateTarget() { return (fov != null && fov.lastKnownPlayerPosition != Vector3.zero) ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanTalkToNPC() { if (playerHasArtifact || (fov != null && fov.canSeePlayer)) return NodeState.Failure; if (Time.time < lastTalkTime + talkCooldown) return NodeState.Failure; if (isTalking) return NodeState.Success; // Quét tìm NPC Collider[] hitColliders = Physics.OverlapSphere(transform.position, talkRange); foreach (var hit in hitColliders) { if (hit.gameObject == gameObject) continue; EnemyAI other = hit.GetComponentInParent(); if (other != null && !other.isTalking && Time.time >= other.lastTalkTime + talkCooldown) { // Chỉ ID nhỏ hơn gọi để tránh trùng if (gameObject.GetInstanceID() < other.gameObject.GetInstanceID()) { talkingPartner = other; return NodeState.Success; } } } return NodeState.Failure; } #endregion #region ACTIONS private NodeState ActionTalk() { if (talkingPartner == null) return NodeState.Failure; if (!isTalking) { isTalking = true; agent.isStopped = true; FaceTarget(talkingPartner.transform.position); Debug.Log($"[AI {npcName}] Talking to {talkingPartner.npcName}"); string prompt = $"You are {npcName}. Speak 1 short sentence in English to your colleague {talkingPartner.npcName} about the shift."; Hallucinate.AI.GeminiService.Instance.GetResponse(persona, prompt, (response) => { if (chatBubble != null) chatBubble.Show(response); Invoke(nameof(EndConversation), 5f); }); talkingPartner.OnPartnerTalked(this); } return NodeState.Running; } public void OnPartnerTalked(EnemyAI partner) { isTalking = true; talkingPartner = partner; agent.isStopped = true; FaceTarget(partner.transform.position); Invoke(nameof(EndConversation), 6f); } private void EndConversation() { isTalking = false; lastTalkTime = Time.time; if (agent != null && agent.isOnNavMesh) agent.isStopped = false; talkingPartner = null; } private void StopConversation() { if (!isTalking) return; CancelInvoke(nameof(EndConversation)); EndConversation(); if (chatBubble != null) chatBubble.Show("Wait, what's that?!", 2f); } private NodeState ActionPatrol() { if (patrolWaypoints == null || patrolWaypoints.Length == 0) return NodeState.Failure; agent.isStopped = false; agent.speed = moveSpeed * 0.5f; var target = patrolWaypoints[currentWaypointIndex]; agent.SetDestination(target.position); if (Vector3.Distance(transform.position, target.position) < 1.5f) { currentWaitTime += Time.deltaTime; if (currentWaitTime >= patrolWaitTime) { currentWaypointIndex = (currentWaypointIndex + 1) % patrolWaypoints.Length; currentWaitTime = 0f; } } return NodeState.Running; } private NodeState ActionChasePlayer() { agent.isStopped = false; agent.speed = moveSpeed; agent.SetDestination(player.position); return NodeState.Running; } private NodeState ActionInvestigate() { agent.isStopped = false; agent.speed = moveSpeed * 0.7f; agent.SetDestination(fov.lastKnownPlayerPosition); if (Vector3.Distance(transform.position, fov.lastKnownPlayerPosition) < 1.5f) { fov.lastKnownPlayerPosition = Vector3.zero; return NodeState.Success; } return NodeState.Running; } private NodeState ActionFocusAndShoot() { agent.isStopped = true; FaceTarget(player.position); if (Time.time >= nextShootTime) { if (laserPrefab && firePoint) Instantiate(laserPrefab, firePoint.position, firePoint.rotation); nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay); } return NodeState.Running; } private NodeState ActionDodge() { if (!isDodging) StartCoroutine(DodgeRollRoutine()); return NodeState.Running; } private IEnumerator DodgeRollRoutine() { isDodging = true; agent.enabled = false; rb.isKinematic = false; Vector3 dir = (player.position - transform.position).normalized; Vector3 perp = new Vector3(-dir.z, 0, dir.x); rb.AddForce((Random.value > 0.5f ? perp : -perp) * dodgeForce, ForceMode.Impulse); yield return new WaitForSeconds(dodgeDuration); rb.linearVelocity = Vector3.zero; rb.isKinematic = true; agent.enabled = true; isDodging = false; } private void FaceTarget(Vector3 pos) { Vector3 dir = (pos - transform.position); dir.y = 0; if (dir != Vector3.zero) transform.rotation = Quaternion.LookRotation(dir); } #endregion private void OnDrawGizmos() { // Vẽ vùng nói chuyện (Xanh lá) Gizmos.color = Color.green; Gizmos.DrawWireSphere(transform.position, talkRange); // Vẽ đường nối tới bạn diễn nếu đang nói if (isTalking && talkingPartner != null) { Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position + Vector3.up, talkingPartner.transform.position + Vector3.up); } } }