using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using System.Linq; [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(FieldOfView))] public class EnemyAI : MonoBehaviour { [Header("References")] public Transform player; private NavMeshAgent agent; private Rigidbody rb; private FieldOfView fov; [Header("Movement & Rotation")] public float moveSpeed = 3f; public float rotateSpeed = 10f; // Tốc độ xoay mượt mờ khi rượt đuổi [Header("Patrol Waypoints")] public Transform[] patrolPoints; public float patrolWaitTime = 2f; private int currentPatrolIndex = 0; private float currentWaitTime; [Header("Artifact State")] public bool playerHasArtifact; [Header("Laser Weapon")] public GameObject laserPrefab; public Transform firePoint; public float minShootDelay = 1f; public float maxShootDelay = 3f; private float nextShootTime; [Header("Dodge Settings (Rigidbody)")] public float dodgeForce = 10f; public float dodgeDuration = 0.2f; public float dodgeCooldown = 1.2f; private bool isDodging = false; private float nextDodgeTime; // Root của Behavior Tree public Node behaviorTreeRoot; private void Start() { agent = GetComponent(); rb = GetComponent(); fov = GetComponent(); agent.speed = moveSpeed; // Tự động tìm tất cả điểm PatrolPoint có trên Map patrolPoints = GameObject.FindGameObjectsWithTag("PatrolPoint") .Select(go => go.transform).ToArray(); // Setup Rigidbody ban đầu ở chế độ Kinematic để tránh lỗi xung đột NavMesh thường ngày rb.isKinematic = true; rb.freezeRotation = true; FindPlayer(); InitBehaviorTree(); } private void Update() { if (player == null) FindPlayer(); // Luôn cập nhật cây hành vi behaviorTreeRoot?.Evaluate(); } private void FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) player = playerObj.transform; } private void InitBehaviorTree() { // 1. Ưu tiên cao nhất: Kiểm tra né đòn var dodgeNode = new TaskNode(CheckAndActionDodge); // 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) }); // 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 { new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) }), new Sequence(new List { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) }) }); // 4. Mặc định: Đi tuần tra theo các Waypoint var patrolNode = new TaskNode(ActionPatrol); // Gom tất cả vào bộ Selector gốc behaviorTreeRoot = new Selector(new List { dodgeNode, laserSequence, trackingSelector, patrolNode }); } #region CONDITIONS private NodeState CheckAndActionDodge() { if (isDodging) return NodeState.Running; // 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()); nextDodgeTime = Time.time + dodgeCooldown; return NodeState.Running; } return NodeState.Failure; } private NodeState CheckHasArtifact() { return playerHasArtifact ? NodeState.Success : NodeState.Failure; } private NodeState CheckCanSeePlayer() { return fov.canSeePlayer ? NodeState.Success : NodeState.Failure; } private NodeState CheckHasInvestigateTarget() { return fov.lastKnownPlayerPosition != Vector3.zero ? NodeState.Success : NodeState.Failure; } #endregion #region ACTIONS private IEnumerator DodgeRollRoutine() { isDodging = true; 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 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); Vector3 dodgeDirection = (Random.Range(0, 2) == 0 ? perpendicularDir : -perpendicularDir).normalized; // Búng vật lý bằng lực Impulse rb.AddForce(dodgeDirection * dodgeForce, ForceMode.Impulse); yield return new WaitForSeconds(dodgeDuration); // 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; agent.isStopped = false; agent.speed = moveSpeed * 0.5f; agent.SetDestination(patrolPoints[currentPatrolIndex].position); if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { currentWaitTime += Time.deltaTime; if (currentWaitTime >= patrolWaitTime) { currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length; currentWaitTime = 0f; } } return NodeState.Running; } private NodeState ActionInvestigate() { agent.isStopped = false; agent.speed = moveSpeed * 0.7f; agent.SetDestination(fov.lastKnownPlayerPosition); if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { 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; } private NodeState ActionFocusAndShoot() { agent.isStopped = true; Vector3 dir = player.position - transform.position; dir.y = 0f; if (dir != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(dir); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); } if (Time.time >= nextShootTime) { ShootLaser(); nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay); } return NodeState.Running; } private void ShootLaser() { if (laserPrefab == null || firePoint == null) return; Instantiate(laserPrefab, firePoint.position, firePoint.rotation); } #endregion }