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 = 50f; [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 = 8f; public float dodgeDuration = 0.25f; public float dodgeCooldown = 1.5f; private bool isDodging = false; private float nextDodgeTime; // Gốc của Cây hành vi 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 trong Map giống RobotBrain patrolPoints = GameObject.FindGameObjectsWithTag("PatrolPoint") .Select(go => go.transform).ToArray(); // Cấu hình Rigidbody để không bị đổ ngã khi va chạm vật lý thông thường rb.isKinematic = true; rb.freezeRotation = true; FindPlayer(); InitBehaviorTree(); } private void Update() { if (player == null) FindPlayer(); // Thực thi cây hành vi liên tục mỗi khung hình behaviorTreeRoot?.Evaluate(); } private void FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) player = playerObj.transform; } private void InitBehaviorTree() { // Ưu tiên số 1: Kiểm tra và thực hiện né đòn var dodgeNode = new TaskNode(CheckAndActionDodge); // Ưu tiên số 2: Có cổ vật -> Đứng lại tập trung bắn hạ var laserSequence = new Sequence(new List { new TaskNode(CheckHasArtifact), new TaskNode(ActionFocusAndShoot) }); // Ưu tiên số 3: Tương tác tầm nhìn (Đuổi theo hoặc Đi kiểm tra vết tích) var trackingSelector = new Selector(new List { // Nhìn thấy trực tiếp -> dí theo new Sequence(new List { new TaskNode(CheckCanSeePlayer), new TaskNode(ActionChasePlayer) }), // Mất dấu -> đi đến vị trí cuối cùng để điều tra new Sequence(new List { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) }) }); // Ưu tiên số 4: Mặc định đi tuần tra vòng quanh Map var patrolNode = new TaskNode(ActionPatrol); // Tạo cây tổng hợp theo thứ tự ưu tiên từ trên xuống dưới behaviorTreeRoot = new Selector(new List { dodgeNode, laserSequence, trackingSelector, patrolNode }); } #region CONDITIONS & COMPOSITE NODES private NodeState CheckAndActionDodge() { if (isDodging) return NodeState.Running; // ĐIỀU KIỆN NÉ: Phải nhìn thấy Player VÀ Player nhấn chuột trái VÀ hết cooldown 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 // Coroutine xử lý né bằng lực đẩy Rigidbody một cách thực tế private IEnumerator DodgeRollRoutine() { isDodging = true; agent.enabled = false; // Tắt định vị NavMesh để nhường quyền cho Vật lý rb.isKinematic = false; // Bật chế độ vật lý động để nhận lực lực đẩy // Tính toán hướng né: Vuông góc với hướng nhìn của Player (Tránh sang trái hoặc phải) Vector3 directionToPlayer = (player.position - transform.position).normalized; Vector3 perpendicularDir = new Vector3(-directionToPlayer.z, 0, directionToPlayer.x); // Chọn ngẫu nhiên trái hoặc phải Vector3 dodgeDirection = (Random.Range(0, 2) == 0 ? perpendicularDir : -perpendicularDir).normalized; // Tác dụng lực đẩy Impulse tức thì rb.AddForce(dodgeDirection * dodgeForce, ForceMode.Impulse); yield return new WaitForSeconds(dodgeDuration); // Kết thúc né: Trả lại quyền điều khiển cho NavMeshAgent rb.linearVelocity = Vector3.zero; // Cú pháp chuẩn của Unity 6 (thay cho rb.velocity) rb.isKinematic = true; agent.enabled = true; isDodging = false; } private NodeState ActionPatrol() { if (patrolPoints.Length == 0) return NodeState.Failure; Debug.Log("Patrolling..."); agent.isStopped = false; agent.speed = moveSpeed * 0.6f; // Đi tuần tra chậm rãi quay theo hướng đi tự động của NavMesh 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 ActionChasePlayer() { Debug.Log("Chasing Player!"); agent.isStopped = false; agent.speed = moveSpeed; // Chạy nhanh hết tốc lực agent.SetDestination(player.position); return NodeState.Running; } private NodeState ActionInvestigate() { Debug.Log("Investigating Last Position..."); agent.isStopped = false; agent.speed = moveSpeed * 0.8f; agent.SetDestination(fov.lastKnownPlayerPosition); if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance) { // Đến nơi rồi mà không thấy ai, xóa vị trí cuối cùng để quay lại tuần tra fov.lastKnownPlayerPosition = Vector3.zero; return NodeState.Success; } return NodeState.Running; } private NodeState ActionFocusAndShoot() { Debug.Log("Focus and Shoot!"); agent.isStopped = true; // Dừng di chuyển để đứng ngắm bắn cố định // Tự xoay người hướng thẳng về phía Player 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); } // Đếm ngược thời gian bắn ngẫu nhiên 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 }