From 024cc4fc697dd77d3a379218a14dd9d37b3dc86f Mon Sep 17 00:00:00 2001 From: ngtuanz1 Date: Fri, 5 Jun 2026 16:06:59 +0700 Subject: [PATCH] ahaha --- Assets/Scripts/AI NPC/EnemyAI.cs | 218 ++++++++----------------------- 1 file changed, 52 insertions(+), 166 deletions(-) diff --git a/Assets/Scripts/AI NPC/EnemyAI.cs b/Assets/Scripts/AI NPC/EnemyAI.cs index 2fbff0a8..ae55a78d 100644 --- a/Assets/Scripts/AI NPC/EnemyAI.cs +++ b/Assets/Scripts/AI NPC/EnemyAI.cs @@ -17,7 +17,7 @@ public class EnemyAI : MonoBehaviour [Header("Movement & Rotation")] public float moveSpeed = 3f; - public float rotateSpeed = 50f; + public float rotateSpeed = 10f; // Tốc độ xoay mượt mờ khi rượt đuổi [Header("Patrol Waypoints")] public Transform[] patrolPoints; @@ -35,24 +35,14 @@ public class EnemyAI : MonoBehaviour public float maxShootDelay = 3f; private float nextShootTime; - [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; - [Header("Dodge Settings (Rigidbody)")] - public float dodgeForce = 8f; - public float dodgeDuration = 0.25f; - public float dodgeCooldown = 1.5f; + public float dodgeForce = 10f; + public float dodgeDuration = 0.2f; + public float dodgeCooldown = 1.2f; private bool isDodging = false; private float nextDodgeTime; - // Gốc của Cây hành vi + // Root của Behavior Tree public Node behaviorTreeRoot; private void Start() @@ -60,15 +50,14 @@ public class EnemyAI : MonoBehaviour agent = GetComponent(); rb = GetComponent(); fov = GetComponent(); - chatBubble = GetComponentInChildren(true); agent.speed = moveSpeed; - // Tự động tìm tất cả điểm PatrolPoint trong Map + // Tự động tìm tất cả điểm PatrolPoint có trên Map patrolPoints = GameObject.FindGameObjectsWithTag("PatrolPoint") .Select(go => go.transform).ToArray(); - // Cấu hình Rigidbody để không bị đổ ngã khi va chạm vật lý thông thường + // Setup Rigidbody ban đầu ở chế độ Kinematic để tránh lỗi xung đột NavMesh thường ngày rb.isKinematic = true; rb.freezeRotation = true; @@ -80,7 +69,7 @@ public class EnemyAI : MonoBehaviour { if (player == null) FindPlayer(); - // Thực thi cây hành vi liên tục mỗi khung hình + // Luôn cập nhật cây hành vi behaviorTreeRoot?.Evaluate(); } @@ -92,53 +81,43 @@ public class EnemyAI : MonoBehaviour private void InitBehaviorTree() { - // Ưu tiên số 1: Kiểm tra và thực hiện né đòn + // 1. Ưu tiên cao nhất: Kiểm tra né đòn var dodgeNode = new TaskNode(CheckAndActionDodge); - // Ưu tiên số 2: Có cổ vật -> Đứng lại tập trung bắn hạ + // 2. Ưu tiên hai: Player lấy được cổ vật -> Đứng lại nhắm bắn var laserSequence = new Sequence(new List { new TaskNode(CheckHasArtifact), new TaskNode(ActionFocusAndShoot) }); - // Ưu tiên số 3: Tương tác tầm nhìn (Đuổi theo hoặc Đi kiểm tra vết tích) + // 3. Ưu tiên ba: Tương tác đuổi theo bằng Vector hoặc Đi điều tra vết tích cuối cùng var trackingSelector = new Selector(new List { - // Nhìn thấy trực tiếp -> dí theo - new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) }), - // Mất dấu -> đi đến vị trí cuối cùng để điều tra +new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) }), new Sequence(new List { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) }) }); - // Ưu tiên số 4: 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 số 5: Mặc định đi tuần tra vòng quanh Map + // 4. Mặc định: Đi tuần tra theo các Waypoint var patrolNode = new TaskNode(ActionPatrol); - // Tạo cây tổng hợp theo thứ tự ưu tiên từ trên xuống dưới + // Gom tất cả vào bộ Selector gốc behaviorTreeRoot = new Selector(new List { dodgeNode, laserSequence, trackingSelector, - talkSequence, patrolNode }); } - #region CONDITIONS & COMPOSITE NODES + #region CONDITIONS private NodeState CheckAndActionDodge() { if (isDodging) return NodeState.Running; - // ĐIỀU KIỆN NÉ: Phải nhìn thấy Player VÀ Player nhấn chuột trái VÀ hết cooldown né + // Chỉ né khi: Thấy Player VÀ Player click chuột trái VÀ đã hồi chiêu né if (fov.canSeePlayer && Input.GetMouseButtonDown(0) && Time.time >= nextDodgeTime) { StartCoroutine(DodgeRollRoutine()); @@ -149,39 +128,13 @@ public class EnemyAI : MonoBehaviour return NodeState.Failure; } - private NodeState CheckCanTalkToNPC() - { - if (playerHasArtifact || fov.canSeePlayer) 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() { - if (playerHasArtifact) StopConversation(); return playerHasArtifact ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanSeePlayer() { - if (fov.canSeePlayer) StopConversation(); return fov.canSeePlayer ? NodeState.Success : NodeState.Failure; } @@ -194,40 +147,60 @@ public class EnemyAI : MonoBehaviour #region ACTIONS - // Coroutine xử lý né bằng lực đẩy Rigidbody một cách thực tế private IEnumerator DodgeRollRoutine() { isDodging = true; - agent.enabled = false; // Tắt định vị NavMesh để nhường quyền cho Vật lý - rb.isKinematic = false; // Bật chế độ vật lý động để nhận lực lực đẩy + agent.enabled = false; // Tắt tạm thời Agent để giải phóng vật lý + rb.isKinematic = false; // Bật chế độ tương tác vật lý thực tế - // Tính toán hướng né: Vuông góc với hướng nhìn của Player (Tránh sang trái hoặc phải) + // Tính toán hướng né: Vuông góc góc nhìn đối diện với Player (Né sang trái hoặc phải) Vector3 directionToPlayer = (player.position - transform.position).normalized; Vector3 perpendicularDir = new Vector3(-directionToPlayer.z, 0, directionToPlayer.x); - - // Chọn ngẫu nhiên trái hoặc phải Vector3 dodgeDirection = (Random.Range(0, 2) == 0 ? perpendicularDir : -perpendicularDir).normalized; - // Tác dụng lực đẩy Impulse tức thì + // Búng vật lý bằng lực Impulse rb.AddForce(dodgeDirection * dodgeForce, ForceMode.Impulse); yield return new WaitForSeconds(dodgeDuration); - // Kết thúc né: Trả lại quyền điều khiển cho NavMeshAgent - rb.linearVelocity = Vector3.zero; // Cú pháp chuẩn của Unity 6 (thay cho rb.velocity) + // Trả lại trạng thái cũ cho NavMesh + rb.linearVelocity = Vector3.zero; // Cú pháp Unity 6 rb.isKinematic = true; agent.enabled = true; isDodging = false; } + private NodeState ActionChasePlayer() + { + if (player == null) return NodeState.Failure; + + Debug.Log("Chasing Player via Direct Vector!"); + agent.isStopped = false; + + // 1. Tính toán hướng đi thẳng tắp (Bỏ trục Y) + Vector3 dir = player.position - transform.position; + dir.y = 0f; + dir.Normalize(); +// 2. Di chuyển tịnh tiến bằng Vector tích hợp qua Agent.Move để cản xuyên tường + Vector3 movement = dir * moveSpeed * Time.deltaTime; + agent.Move(movement); + + // 3. Tự xoay mượt mà theo hướng di chuyển + if (dir != Vector3.zero) + { + Quaternion targetRotation = Quaternion.LookRotation(dir); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); + } + + return NodeState.Running; + } + private NodeState ActionPatrol() { if (patrolPoints.Length == 0) return NodeState.Failure; - Debug.Log("Patrolling..."); agent.isStopped = false; - agent.speed = moveSpeed * 0.6f; // Đi tuần tra chậm rãi quay theo hướng đi tự động của NavMesh - + agent.speed = moveSpeed * 0.5f; agent.SetDestination(patrolPoints[currentPatrolIndex].position); if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) @@ -242,27 +215,15 @@ public class EnemyAI : MonoBehaviour return NodeState.Running; } - private NodeState ActionChasePlayer() - { - Debug.Log("Chasing Player!"); - agent.isStopped = false; - agent.speed = moveSpeed; // Chạy nhanh hết tốc lực - agent.SetDestination(player.position); - - return NodeState.Running; - } - private NodeState ActionInvestigate() { - Debug.Log("Investigating Last Position..."); agent.isStopped = false; - agent.speed = moveSpeed * 0.8f; + agent.speed = moveSpeed * 0.7f; agent.SetDestination(fov.lastKnownPlayerPosition); if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { - // Đến nơi rồi mà không thấy ai, xóa vị trí cuối cùng để quay lại tuần tra - fov.lastKnownPlayerPosition = Vector3.zero; + fov.lastKnownPlayerPosition = Vector3.zero; // Không thấy ai nữa thì hủy điểm nghi vấn return NodeState.Success; } return NodeState.Running; @@ -270,10 +231,8 @@ public class EnemyAI : MonoBehaviour private NodeState ActionFocusAndShoot() { - Debug.Log("Focus and Shoot!"); - agent.isStopped = true; // Dừng di chuyển để đứng ngắm bắn cố định + agent.isStopped = true; - // Tự xoay người hướng thẳng về phía Player Vector3 dir = player.position - transform.position; dir.y = 0f; if (dir != Vector3.zero) @@ -282,7 +241,6 @@ public class EnemyAI : MonoBehaviour transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); } - // Đếm ngược thời gian bắn ngẫu nhiên if (Time.time >= nextShootTime) { ShootLaser(); @@ -298,77 +256,5 @@ public class EnemyAI : MonoBehaviour Instantiate(laserPrefab, firePoint.position, firePoint.rotation); } - 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 - 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); - } - } - #endregion -} +} \ No newline at end of file