198 lines
5.9 KiB
C#
198 lines
5.9 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
[RequireComponent(typeof(NavMeshAgent))]
|
|
public class KamikazeAI : MonoBehaviour
|
|
{
|
|
[Header("References")]
|
|
public Transform player;
|
|
|
|
[Header("Detection")]
|
|
public float detectRange = 15f;
|
|
private bool canSeePlayer = false;
|
|
|
|
[Header("Movement & Random Patrol")]
|
|
public float patrolSpeed = 2.5f;
|
|
public float chaseSpeed = 7f;
|
|
public float patrolRadius = 12f; // Bán kính của khu vực tuần tra ngẫu nhiên
|
|
public float patrolWaitTime = 2f; // Thời gian đứng nghỉ trước khi đổi sang điểm ngẫu nhiên mới
|
|
|
|
private Vector3 startPosition; // Tâm của khu vực tuần tra (Vị trí ban đầu)
|
|
private float currentWaitTime;
|
|
private NavMeshAgent agent;
|
|
private bool isExploding = false;
|
|
public Node behaviorTreeRoot;
|
|
public GameObject explosionEffectPrefab;
|
|
|
|
private void Start()
|
|
{
|
|
agent = GetComponent<NavMeshAgent>();
|
|
|
|
// Lưu lại vị trí xuất phát để làm tâm, NPC sẽ chỉ đi loay hoay quanh khu vực này
|
|
startPosition = transform.position;
|
|
|
|
InitBehaviorTree();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (isExploding) return;
|
|
|
|
if (player == null) FindPlayer();
|
|
else CheckVision();
|
|
|
|
behaviorTreeRoot?.Evaluate();
|
|
}
|
|
|
|
private void FindPlayer()
|
|
{
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
if (playerObj != null) player = playerObj.transform;
|
|
}
|
|
|
|
private void CheckVision()
|
|
{
|
|
if (Vector3.Distance(transform.position, player.position) <= detectRange)
|
|
canSeePlayer = true;
|
|
else
|
|
canSeePlayer = false;
|
|
}
|
|
|
|
private void InitBehaviorTree()
|
|
{
|
|
var explodeSequence = new Sequence(new List<Node>
|
|
{
|
|
new TaskNode(CheckIsCloseEnoughToExplode),
|
|
new TaskNode(ActionTriggerExplosion)
|
|
});
|
|
|
|
var chaseSequence = new Sequence(new List<Node>
|
|
{
|
|
new TaskNode(CheckCanSeePlayer),
|
|
new TaskNode(ActionChase)
|
|
});
|
|
|
|
// Hành động tuần tra ngẫu nhiên
|
|
var patrolNode = new TaskNode(ActionRandomPatrol);
|
|
|
|
behaviorTreeRoot = new Selector(new List<Node>
|
|
{
|
|
explodeSequence,
|
|
chaseSequence,
|
|
patrolNode
|
|
});
|
|
}
|
|
|
|
#region CONDITIONS
|
|
|
|
private NodeState CheckCanSeePlayer()
|
|
{
|
|
return canSeePlayer ? NodeState.Success : NodeState.Failure;
|
|
}
|
|
|
|
private NodeState CheckIsCloseEnoughToExplode()
|
|
{
|
|
if (player == null) return NodeState.Failure;
|
|
float dist = Vector3.Distance(transform.position, player.position);
|
|
return dist <= 3f ? NodeState.Success : NodeState.Failure;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ACTIONS
|
|
|
|
// HÀM TUẦN TRA NGẪU NHIÊN MỚI
|
|
private NodeState ActionRandomPatrol()
|
|
{
|
|
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
|
|
|
|
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;
|
|
}
|
|
|
|
private NodeState ActionChase()
|
|
{
|
|
if (player == null) return NodeState.Failure;
|
|
|
|
Debug.Log("Kamikaze is rushing you!");
|
|
agent.isStopped = false;
|
|
agent.speed = chaseSpeed;
|
|
agent.SetDestination(player.position);
|
|
|
|
return NodeState.Running;
|
|
}
|
|
|
|
private NodeState ActionTriggerExplosion()
|
|
{
|
|
StartCoroutine(ExplosionRoutine());
|
|
return NodeState.Success;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region EXPLOSION LOGIC
|
|
|
|
private IEnumerator ExplosionRoutine()
|
|
{
|
|
isExploding = true;
|
|
agent.isStopped = true;
|
|
agent.velocity = Vector3.zero;
|
|
|
|
Debug.Log("BOMB ARMED!");
|
|
yield return new WaitForSeconds(1.5f);
|
|
|
|
if (player != null)
|
|
{
|
|
float distToPlayer = Vector3.Distance(transform.position, player.position);
|
|
if (distToPlayer <= 4f)
|
|
{
|
|
Debug.Log("BOOM! Player took damage!");
|
|
}
|
|
}
|
|
|
|
if (explosionEffectPrefab != null)
|
|
{
|
|
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity);
|
|
}
|
|
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Vẽ vùng giới hạn tuần tra màu xanh lá cây trên Scene để bạn dễ căn chỉnh độ rộng
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = Color.green;
|
|
// Nếu game đang chạy thì vẽ quanh tâm startPosition, nếu chưa chạy thì vẽ quanh vị trí hiện tại
|
|
Vector3 center = Application.isPlaying ? startPosition : transform.position;
|
|
Gizmos.DrawWireSphere(center, patrolRadius);
|
|
}
|
|
} |