From 9c66433e4e6775ca3b6d6276196404af421c2955 Mon Sep 17 00:00:00 2001 From: manhduyhoang90 Date: Fri, 5 Jun 2026 22:24:16 +0700 Subject: [PATCH 1/2] Update AI --- Assets/Scripts/AI NPC/EnemyAI.cs | 158 ++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 47 deletions(-) diff --git a/Assets/Scripts/AI NPC/EnemyAI.cs b/Assets/Scripts/AI NPC/EnemyAI.cs index 2b53ca7c..8906a533 100644 --- a/Assets/Scripts/AI NPC/EnemyAI.cs +++ b/Assets/Scripts/AI NPC/EnemyAI.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using System.Linq; +using UnityEngine.InputSystem; using Random = UnityEngine.Random; [Serializable] @@ -25,17 +26,21 @@ public class EnemyAI : MonoBehaviour public float rotateSpeed = 10f; [Header("Patrol Settings")] - public Transform[] patrolWaypoints; - public int currentWaypointIndex = 0; + public float patrolWaitTime = 2f; private float currentWaitTime = 0f; + public float patrolSpeed = 2.5f; + public float patrolRadius = 12f; // Bán kính của khu vực tuần tra ngẫu nhiên + + + private Vector3 startPosition; [Header("Combat State")] public bool playerHasArtifact; public GameObject laserPrefab; public Transform firePoint; - public float minShootDelay = 1f; - public float maxShootDelay = 3f; + public float minShootDelay = 1.5f; // Delay giữa các LOẠT BẮN + public float maxShootDelay = 3.5f; private float nextShootTime; [Header("Dodge Settings")] @@ -45,6 +50,19 @@ public class EnemyAI : MonoBehaviour private bool isDodging = false; private float nextDodgeTime = 0f; + [Header("Artifact Combat Upgrades (New)")] + [Tooltip("Khoảng cách di chuyển trái/phải ngẫu nhiên qua thời gian duy trì")] + public float minStrafeDuration = 0.5f; + public float maxStrafeDuration = 2.2f; + [Tooltip("Độ lệch tâm bắn (Độ). Số càng nhỏ bắn càng chuẩn, số lớn bắn càng lệch")] + public float maxSpreadAngle = 6f; + [Tooltip("Tốc độ bắn giữa các viên trong cùng 1 loạt đạn")] + public float burstInterval = 0.12f; + + private float nextStrafeChangeTime; + private int strafeDirectionSign = 1; // -1: Trái, 1: Phải, 0: Đứng im bắn + private bool isShootingBurst = false; // Khóa chống trùng lặp loạt bắn + [Header("Conversation Settings")] public string npcName = "Guard"; [TextArea] public string persona = "You are a bored security guard. You love coffee and hate night shifts."; @@ -121,8 +139,10 @@ public class EnemyAI : MonoBehaviour private NodeState CheckDodgeConditions() { + if (playerHasArtifact) return NodeState.Failure; // Có cổ vật -> Không Dash né nữa + if (isDodging) return NodeState.Success; - if (fov != null && fov.canSeePlayer && Input.GetMouseButtonDown(0) && Time.time >= nextDodgeTime) + if (fov != null && fov.canSeePlayer && Mouse.current.leftButton.isPressed) return NodeState.Success; return NodeState.Failure; } @@ -146,7 +166,6 @@ public class EnemyAI : MonoBehaviour { if (suspicionLevel > investigationThreshold) { - // Randomly decide to check or stay on patrol if (Random.value < (suspicionLevel / 100f)) return NodeState.Success; } } @@ -175,7 +194,6 @@ public class EnemyAI : MonoBehaviour EnemyAI other = hit.GetComponentInParent(); if (other != null && !other.isTalking && Time.time >= other.lastTalkTime + talkCooldown) { - // Kiểm tra khoảng cách thực tế giữa 2 NPC float dist = Vector3.Distance(transform.position, other.transform.position); if (dist <= talkRange && gameObject.GetInstanceID() < other.gameObject.GetInstanceID()) { @@ -220,8 +238,6 @@ public class EnemyAI : MonoBehaviour if (isTalking) { agent.isStopped = true; - - // Nếu bạn diễn đi quá xa trong khi đang nói, ngắt hội thoại if (talkingPartner != null) { if (Vector3.Distance(transform.position, talkingPartner.transform.position) > talkRange + 2f) @@ -231,7 +247,6 @@ public class EnemyAI : MonoBehaviour return NodeState.Failure; } } - return NodeState.Running; } return NodeState.Failure; @@ -244,12 +259,9 @@ public class EnemyAI : MonoBehaviour DialogueResult result = JsonUtility.FromJson(json); if (chatBubble != null) chatBubble.Show(result.text); - // Apply minor stat mods moveSpeed += result.speedMod; suspicionLevel = Mathf.Clamp(suspicionLevel + result.suspicionMod, 0, 100); lastTalkTime = Time.time; - - Debug.Log($"[AI {npcName}] Conv result: {result.text} | SpeedMod: {result.speedMod}"); } catch { if (chatBubble != null) chatBubble.Show(json); } } @@ -265,23 +277,34 @@ public class EnemyAI : MonoBehaviour private NodeState ActionPatrol() { - if (patrolWaypoints == null || patrolWaypoints.Length == 0) return NodeState.Failure; - + Debug.Log("Wandering randomly..."); agent.isStopped = false; - agent.speed = moveSpeed * (suspicionLevel > 20 ? 0.7f : 0.5f); // Walk faster if suspicious - - var target = patrolWaypoints[currentWaypointIndex]; - agent.SetDestination(target.position); + agent.speed = patrolSpeed; - if (Vector3.Distance(transform.position, target.position) < 1.5f) + // 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) { - currentWaypointIndex = (currentWaypointIndex + 1) % patrolWaypoints.Length; - currentWaitTime = 0f; + // 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; } @@ -302,10 +325,10 @@ public class EnemyAI : MonoBehaviour if (Vector3.Distance(transform.position, fov.lastKnownPlayerPosition) < 1.5f) { currentWaitTime += Time.deltaTime; - if (currentWaitTime > 3f) // Look around for 3 seconds + if (currentWaitTime > 3f) { fov.lastKnownPlayerPosition = Vector3.zero; - suspicionLevel *= 0.5f; // Decrease suspicion after check + suspicionLevel *= 0.5f; return NodeState.Success; } } @@ -316,46 +339,93 @@ public class EnemyAI : MonoBehaviour { if (player == null) return NodeState.Failure; - agent.isStopped = true; // Đứng im bắn cố định khi có cổ vật + if (agent.hasPath) agent.ResetPath(); + agent.isStopped = false; - // 1. XOAY THÂN THEO TRỤC NGANG (Giúp NPC luôn đứng thẳng lưng, không bị đổ người) + // 1. XOAY THÂN THEO TRỤC NGANG HƯỚNG VỀ PLAYER Vector3 bodyDir = player.position - transform.position; - bodyDir.y = 0f; // Khóa trục dọc của thân + bodyDir.y = 0f; + Vector3 bodyDirNormal = bodyDir.normalized; if (bodyDir != Vector3.zero) { - Quaternion targetRotation = Quaternion.LookRotation(bodyDir); + Quaternion targetRotation = Quaternion.LookRotation(bodyDirNormal); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); } - // 2. XOAY HỌNG SÚNG THEO TRỤC DỌC (Chúi xuống hoặc ngước lên thẳng vào Player) + // 2. RANDOM KHOẢNG CÁCH DI CHUYỂN TRÁI/PHẢI (Tính theo thời gian duy trì) + if (Time.time >= nextStrafeChangeTime) + { + int[] choices = { -1, 1, 0 }; // Trái, Phải, Đứng yên bắn + strafeDirectionSign = choices[Random.Range(0, choices.Length)]; + + // Ép khoảng cách di chuyển dài ngắn ngẫu nhiên bằng cách random thời gian đổi hướng + nextStrafeChangeTime = Time.time + Random.Range(minStrafeDuration, maxStrafeDuration); + } + + if (strafeDirectionSign != 0 && bodyDir != Vector3.zero) + { + Vector3 strafeDir = new Vector3(-bodyDirNormal.z, 0, bodyDirNormal.x) * strafeDirectionSign; + agent.speed = moveSpeed * 0.75f; + agent.Move(strafeDir * agent.speed * Time.deltaTime); + } + + // 3. XOAY HỌNG SÚNG TRỤC DỌC NHẮM VÀO NGƯỜI PLAYER if (firePoint != null) { - // Cộng thêm Vector3.up * 1f để họng súng nhắm vào NGỰC/BỤNG player, không bị bắn chúi xuống đất (bắn vào chân) Vector3 targetCenter = player.position + Vector3.up * 1f; Vector3 aimDir = targetCenter - firePoint.position; - if (aimDir != Vector3.zero) { - // Họng súng nhìn thẳng hoàn toàn vào Player (bao gồm cả trục Y chéo xuống) firePoint.rotation = Quaternion.LookRotation(aimDir); } } - // 3. BẮN LASER (Laser sinh ra sẽ tự động lấy rotation chéo của firePoint) - if (Time.time >= nextShootTime) + // 4. RANDOM SỐ ĐẠN (1-3) & RANDOM DELAY GIỮA CÁC ĐỢT BẮN + if (Time.time >= nextShootTime && !isShootingBurst) { - ShootLaser(); + int randomBulletCount = Random.Range(1, 4); // Trả về ngẫu nhiên 1, 2, hoặc 3 viên + StartCoroutine(ShootBurstRoutine(randomBulletCount)); + + // Cập nhật thời gian chờ cho loạt đạn tiếp theo (Random delay) nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay); } return NodeState.Running; } - private void ShootLaser() + + // Coroutine xử lý bắn loạt đạn kết hợp RANDOM ĐỘ LỆCH (Spread) + private IEnumerator ShootBurstRoutine(int bulletCount) { - if (laserPrefab == null || firePoint == null) return; - Instantiate(laserPrefab, firePoint.position, firePoint.rotation); - Debug.Log($"[AI {npcName}] Fired chéo Laser downwards/upwards at Player!"); + isShootingBurst = true; + + for (int i = 0; i < bulletCount; i++) + { + if (laserPrefab == null || firePoint == null) break; + + // Tính toán độ lệch ngẫu nhiên (Xoay quanh trục X và Y của họng súng để tạo độ "lệch tâm thông minh") + float randomX = Random.Range(-maxSpreadAngle, maxSpreadAngle); + float randomY = Random.Range(-maxSpreadAngle, maxSpreadAngle); + Quaternion spreadRotation = Quaternion.Euler(randomX, randomY, 0f); + + // Nhân góc xoay gốc của họng súng với góc lệch ngẫu nhiên + Quaternion finalBulletRotation = firePoint.rotation * spreadRotation; + + // Sinh đạn + Instantiate(laserPrefab, firePoint.position, finalBulletRotation); + Debug.Log($"[AI Burst] Viên thứ {i + 1}/{bulletCount} | Độ lệch X:{randomX:F1}, Y:{randomY:F1}"); + + // Nếu còn đạn trong loạt, đợi một khoảng ngắn (burstInterval) rồi mới bắn tiếp viên sau + if (i < bulletCount - 1) + { + yield return new WaitForSeconds(burstInterval); + } + } + + isShootingBurst = false; } + + private void ShootLaser() { } // Hàm cũ không dùng nữa, đã có Burst lo + private NodeState ActionDodge() { if (!isDodging) StartCoroutine(DodgeRollRoutine()); @@ -377,11 +447,7 @@ public class EnemyAI : MonoBehaviour isDodging = false; } - public void SetTalkingPartner(EnemyAI partner) - { - talkingPartner = partner; - } - + public void SetTalkingPartner(EnemyAI partner) { talkingPartner = partner; } public void FaceTarget(Vector3 pos) { Vector3 dir = (pos - transform.position); @@ -393,15 +459,13 @@ public class EnemyAI : MonoBehaviour 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); } } -} +} \ No newline at end of file From 67e78d1413541cdf5939bb4c480a38d3b22d104e Mon Sep 17 00:00:00 2001 From: ngtuanz1 Date: Fri, 5 Jun 2026 22:34:41 +0700 Subject: [PATCH 2/2] updaye --- .idea/.idea.HALLUCINATE/.idea/workspace.xml | 14 ++-- .../Animator/Invector@ShooterMelee.controller | 31 ++++++++- Assets/Scenes/Cho môn AI/Only AI.unity | 16 +++++ Assets/Scripts/AI NPC/AnimatorAI.cs | 66 ++++++++++++------- 4 files changed, 91 insertions(+), 36 deletions(-) diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index 310eddb4..717a37a4 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -5,15 +5,8 @@ - - - - - - - - - + +