Update AI
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user