Update AI

This commit is contained in:
manhduyhoang90
2026-06-05 22:24:16 +07:00
parent c03f78b557
commit 9c66433e4e

View File

@@ -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<EnemyAI>();
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<DialogueResult>(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($"<color=green>[AI {npcName}]</color> 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($"<color=red>[AI {npcName}]</color> 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($"<color=cyan>[AI Burst]</color> 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);
}
}
}
}