From e92d06ed54e6e8023186108a7ddd7ee7f265e50d Mon Sep 17 00:00:00 2001 From: manhduyhoang90 Date: Fri, 5 Jun 2026 23:18:29 +0700 Subject: [PATCH] Update:EnemyAI, KamikazeAI --- Assets/Scripts/AI NPC/AutoDestroy.cs | 37 +++++++- Assets/Scripts/AI NPC/EnemyAI.cs | 104 +++++++++++++---------- Assets/Scripts/AI NPC/LaserProjectile.cs | 8 +- 3 files changed, 101 insertions(+), 48 deletions(-) diff --git a/Assets/Scripts/AI NPC/AutoDestroy.cs b/Assets/Scripts/AI NPC/AutoDestroy.cs index fa9d1645..643b35d5 100644 --- a/Assets/Scripts/AI NPC/AutoDestroy.cs +++ b/Assets/Scripts/AI NPC/AutoDestroy.cs @@ -1,16 +1,47 @@ +using Invector; using UnityEngine; public class AutoDestroy : MonoBehaviour { - // Start is called once before the first execution of Update after the MonoBehaviour is created + public int damageAmount = 30; void Start() { - + Destroy(gameObject,2f); } // Update is called once per frame - void Update() + private void OnTriggerEnter(Collider other) { + // Debug: Log tên và tag của bất cứ thứ gì đạn chạm vào + Debug.Log( + $"Laser collided with: {other.name} | Tag: {other.tag} | Layer: {LayerMask.LayerToName(other.gameObject.layer)}"); + + // Kiểm tra nếu trúng Player + if (other.CompareTag("Player") || other.GetComponentInParent() != null) + { + var healthController = other.GetComponentInParent(); + + if (healthController != null) + { + Debug.Log( + $"HIT PLAYER! Found health controller on {healthController.gameObject.name}. Applying {damageAmount} damage."); + var damage = new vDamage(damageAmount); + damage.sender = transform; + damage.hitPosition = transform.position; + healthController.TakeDamage(damage); + } + + // Luôn phá hủy đạn khi trúng Player + Impact(); + + } + } + + private void Impact() + { + + // Phá hủy đạn ngay lập tức + Destroy(gameObject); } } diff --git a/Assets/Scripts/AI NPC/EnemyAI.cs b/Assets/Scripts/AI NPC/EnemyAI.cs index ffe02250..8af96be7 100644 --- a/Assets/Scripts/AI NPC/EnemyAI.cs +++ b/Assets/Scripts/AI NPC/EnemyAI.cs @@ -10,7 +10,7 @@ using Random = UnityEngine.Random; [Serializable] public class DialogueResult { public string text; public float speedMod; public float suspicionMod; } -// 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 +// Quy trình ưu tiên: Né đòn --> Bắn hạ (Artifact / Sound Aggro) --> Đuổi theo (Vector) --> Điều tra --> Nói chuyện --> Đi tuần [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Rigidbody))] public class EnemyAI : MonoBehaviour @@ -26,20 +26,18 @@ public class EnemyAI : MonoBehaviour public float rotateSpeed = 10f; [Header("Patrol Settings")] - 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 - - + public float patrolRadius = 12f; private Vector3 startPosition; + [Header("Combat State")] public bool playerHasArtifact; + public bool isAggroedBySound; // <-- MỚI: Trạng thái bị đánh động bởi âm thanh public GameObject laserPrefab; public Transform firePoint; - public float minShootDelay = 1.5f; // Delay giữa các LOẠT BẮN + public float minShootDelay = 1.5f; public float maxShootDelay = 3.5f; private float nextShootTime; @@ -59,11 +57,9 @@ public class EnemyAI : MonoBehaviour public float approachWeight = 0.35f; public float minCombatDistance = 5.0f; - - 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 + private int strafeDirectionSign = 1; + private bool isShootingBurst = false; [Header("Conversation Settings")] public string npcName = "Guard"; @@ -71,7 +67,7 @@ public class EnemyAI : MonoBehaviour public float talkRange = 12f; public float talkCooldown = 60f; private float lastTalkTime; - public bool isTalking; // Public để debug + public bool isTalking; private EnemyAI talkingPartner; private Hallucinate.UI.ChatBubble chatBubble; @@ -92,6 +88,7 @@ public class EnemyAI : MonoBehaviour rb.isKinematic = true; rb.freezeRotation = true; + startPosition = transform.position; if (player == null) { @@ -105,7 +102,10 @@ public class EnemyAI : MonoBehaviour 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) }); + + // Đổi hàm CheckHasArtifact thành CheckCombatConditions để dùng chung cho cả âm thanh + var laserSequence = new Sequence(new List { new TaskNode(CheckCombatConditions), 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) }); @@ -128,9 +128,14 @@ public class EnemyAI : MonoBehaviour if (!agent.isOnNavMesh) return; - // Decay suspicion suspicionLevel = Mathf.Max(0, suspicionLevel - Time.deltaTime * 0.5f); + // Bình tĩnh lại và tắt chế độ bắn dồn dập khi mức độ nghi ngờ về 0 + if (suspicionLevel <= 0f) + { + isAggroedBySound = false; + } + if (!isTalking && !isDodging && agent.isStopped) agent.isStopped = false; @@ -141,7 +146,7 @@ public class EnemyAI : MonoBehaviour private NodeState CheckDodgeConditions() { - if (playerHasArtifact) return NodeState.Failure; // Có cổ vật -> Không Dash né nữa + if (playerHasArtifact || isAggroedBySound) return NodeState.Failure; if (isDodging) return NodeState.Success; if (fov != null && fov.canSeePlayer && Mouse.current.leftButton.isPressed) @@ -149,10 +154,12 @@ public class EnemyAI : MonoBehaviour return NodeState.Failure; } - private NodeState CheckHasArtifact() + // Node này thay thế cho CheckHasArtifact cũ + private NodeState CheckCombatConditions() { - if (playerHasArtifact) StopConversation(); - return playerHasArtifact ? NodeState.Success : NodeState.Failure; + bool shouldCombat = playerHasArtifact || isAggroedBySound; + if (shouldCombat) StopConversation(); + return shouldCombat ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanSeePlayer() @@ -176,7 +183,7 @@ public class EnemyAI : MonoBehaviour private NodeState CheckCanTalkToNPC() { - if (playerHasArtifact || (fov != null && fov.canSeePlayer)) return NodeState.Failure; + if (playerHasArtifact || isAggroedBySound || (fov != null && fov.canSeePlayer)) return NodeState.Failure; if (Time.time < lastTalkTime + talkCooldown) return NodeState.Failure; if (isTalking) return NodeState.Success; @@ -216,6 +223,13 @@ public class EnemyAI : MonoBehaviour { suspicionLevel += volume * 15f; if (fov != null) fov.lastKnownPlayerPosition = location; + + // CẬP NHẬT: Nếu AI nghe thấy tiếng động làm mức nghi ngờ vượt mốc điều tra, chuyển sang trạng thái chiến đấu (bắn bồi) + if (suspicionLevel >= investigationThreshold) + { + isAggroedBySound = true; + } + if (suspicionLevel >= alertNeighborsThreshold) AlertNeighbors(); StopConversation(); Debug.Log($"[AI {npcName}] Heard noise! Suspicion: {suspicionLevel}"); @@ -279,31 +293,25 @@ public class EnemyAI : MonoBehaviour private NodeState ActionPatrol() { - 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 + randomDirection += startPosition; 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ờ + currentWaitTime = 0f; } } @@ -339,13 +347,30 @@ public class EnemyAI : MonoBehaviour private NodeState ActionFocusAndShoot() { - if (player == null) return NodeState.Failure; + if (player == null) return NodeState.Failure; if (agent.hasPath) agent.ResetPath(); agent.isStopped = false; - // 1. XOAY THÂN THEO TRỤC NGANG HƯỚNG VỀ PLAYER - Vector3 bodyDir = player.position - transform.position; + // CẢI TIẾN: Xác định mục tiêu linh hoạt để tránh Wallhack. + Vector3 targetPos = player.position; // Mặc định khóa chặt người chơi + + // Nếu không cầm Artifact và cũng chưa bị nhìn thấy, AI chỉ nhắm vào MỐC ÂM THANH + if (!playerHasArtifact && fov != null && !fov.canSeePlayer && fov.lastKnownPlayerPosition != Vector3.zero) + { + targetPos = fov.lastKnownPlayerPosition; + + // Nếu AI tiến hành áp sát và xả đạn vào nơi phát ra tiếng mà không thấy ai, ngưng bắn + if (Vector3.Distance(transform.position, targetPos) < 2f) + { + isAggroedBySound = false; + suspicionLevel *= 0.5f; + return NodeState.Success; + } + } + + // 1. XOAY THÂN THEO TRỤC NGANG HƯỚNG VỀ MỤC TIÊU (Người chơi hoặc Tiếng động) + Vector3 bodyDir = targetPos - transform.position; bodyDir.y = 0f; Vector3 bodyDirNormal = bodyDir.normalized; if (bodyDir != Vector3.zero) @@ -365,32 +390,28 @@ public class EnemyAI : MonoBehaviour // 3. TÍNH TOÁN TRỘN VECTOR: CHỈ TIẾN LÊN KHI ĐANG ĐI NGANG TRÁI/PHẢI Vector3 finalMovementVector = Vector3.zero; - // Kiểm tra xem AI có đang di chuyển ngang không (strafeDirectionSign khác 0) if (strafeDirectionSign != 0 && bodyDir != Vector3.zero) { - // Vector đi ngang trái/phải finalMovementVector = new Vector3(-bodyDirNormal.z, 0, bodyDirNormal.x) * strafeDirectionSign; - // Tiến tới tịnh tiến dần dần nếu chưa đạt khoảng cách tối thiểu - float currentDistance = Vector3.Distance(transform.position, player.position); + float currentDistance = Vector3.Distance(transform.position, targetPos); if (currentDistance > minCombatDistance) { finalMovementVector += bodyDirNormal * approachWeight; } } - // Áp dụng di chuyển thực tế bằng Agent.Move if (finalMovementVector != Vector3.zero) { - finalMovementVector.Normalize(); // Chuẩn hóa vector + finalMovementVector.Normalize(); agent.speed = moveSpeed * 0.75f; agent.Move(finalMovementVector * agent.speed * Time.deltaTime); } - // 4. XOAY HỌNG SÚNG TRỤC DỌC NHẮM VÀO NGƯỜI PLAYER + // 4. XOAY HỌNG SÚNG TRỤC DỌC NHẮM VÀO MỤC TIÊU if (firePoint != null) { - Vector3 targetCenter = player.position + Vector3.up * 1f; + Vector3 targetCenter = targetPos + Vector3.up * 1f; Vector3 aimDir = targetCenter - firePoint.position; if (aimDir != Vector3.zero) { @@ -409,7 +430,6 @@ public class EnemyAI : MonoBehaviour return NodeState.Running; } - // Coroutine xử lý bắn loạt đạn kết hợp RANDOM ĐỘ LỆCH (Spread) private IEnumerator ShootBurstRoutine(int bulletCount) { isShootingBurst = true; @@ -418,19 +438,15 @@ public class EnemyAI : MonoBehaviour { 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); @@ -440,7 +456,7 @@ public class EnemyAI : MonoBehaviour isShootingBurst = false; } - private void ShootLaser() { } // Hàm cũ không dùng nữa, đã có Burst lo + private void ShootLaser() { } private NodeState ActionDodge() { diff --git a/Assets/Scripts/AI NPC/LaserProjectile.cs b/Assets/Scripts/AI NPC/LaserProjectile.cs index a0155230..56c58ae1 100644 --- a/Assets/Scripts/AI NPC/LaserProjectile.cs +++ b/Assets/Scripts/AI NPC/LaserProjectile.cs @@ -46,7 +46,13 @@ public class LaserProjectile : MonoBehaviour Impact(); return; } - +// KIỂM TRA LAYER "GROUND" + if (other.gameObject.layer == LayerMask.NameToLayer("Ground")) + { + Debug.Log("Laser hit GROUND layer."); + Impact(); + return; + } // Phá hủy đạn nếu trúng tường, sàn nhà (mọi thứ không phải trigger khác) if (!other.isTrigger) {