update small
This commit is contained in:
@@ -124,6 +124,7 @@ GameObject:
|
||||
- component: {fileID: 8272839718325411334}
|
||||
- component: {fileID: 5770331367975928816}
|
||||
- component: {fileID: 4112854993683970537}
|
||||
- component: {fileID: 7573101351210360154}
|
||||
m_Layer: 0
|
||||
m_Name: xNPC
|
||||
m_TagString: Untagged
|
||||
@@ -161,23 +162,15 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::EnemyAI
|
||||
player: {fileID: 0}
|
||||
viewAngle: 90
|
||||
viewRadius: 20
|
||||
targetLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
obstacleLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
patrolPoints: []
|
||||
moveSpeed: 3
|
||||
chaseSpeed: 5
|
||||
rotateSpeed: 50
|
||||
patrolPoints: []
|
||||
patrolWaitTime: 2
|
||||
playerHasArtifact: 0
|
||||
laserPrefab: {fileID: 3965388737199864462, guid: fbec2b501d70daa4c9cb481ba53fc0b8, type: 3}
|
||||
firePoint: {fileID: 5863061020199015852}
|
||||
minShootDelay: 1
|
||||
maxShootDelay: 3
|
||||
rotateSpeed: 50
|
||||
dodgeForce: 8
|
||||
dodgeDuration: 0.5
|
||||
dodgeCooldown: 3
|
||||
@@ -230,6 +223,28 @@ Rigidbody:
|
||||
m_Interpolate: 0
|
||||
m_Constraints: 112
|
||||
m_CollisionDetection: 0
|
||||
--- !u!114 &7573101351210360154
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7522161431095319480}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 210b37cfe4a84a34a91d0a9e58856a60, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::FieldOfView
|
||||
viewAngle: 90
|
||||
viewRadius: 20
|
||||
obstacleLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 128
|
||||
targetLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 256
|
||||
canSeePlayer: 0
|
||||
lastKnownPlayerPosition: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &7561534673732472622
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
16
Assets/Scripts/AI NPC/AnimatorAI.cs
Normal file
16
Assets/Scripts/AI NPC/AnimatorAI.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class AnimatorAI : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/AI NPC/AnimatorAI.cs.meta
Normal file
2
Assets/Scripts/AI NPC/AnimatorAI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35bba55c2a743d042ab1fff35e29db50
|
||||
@@ -1,80 +1,75 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
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("Field of View")]
|
||||
[Range(0, 360)] public float viewAngle = 90f;
|
||||
public float viewRadius = 20f;
|
||||
public LayerMask targetLayerMask; // Gán layer của Player
|
||||
public LayerMask obstacleLayerMask; // Gán layer của Tường, chướng ngại vật
|
||||
|
||||
private bool canSeePlayer = false;
|
||||
private Vector3 lastKnownPlayerPosition;
|
||||
private bool isInvestigating = false;
|
||||
|
||||
[Header("Patrol Area")]
|
||||
public Transform[] patrolPoints;
|
||||
private int currentPatrolIndex = 0;
|
||||
[Header("Movement & Rotation")]
|
||||
public float moveSpeed = 3f;
|
||||
public float chaseSpeed = 5f;
|
||||
public float rotateSpeed = 50f;
|
||||
|
||||
[Header("Artifact")]
|
||||
[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")]
|
||||
[Header("Laser Weapon")]
|
||||
public GameObject laserPrefab;
|
||||
public Transform firePoint;
|
||||
public float minShootDelay = 1f;
|
||||
public float maxShootDelay = 3f;
|
||||
public float rotateSpeed = 50f;
|
||||
|
||||
[Header("Dodge Mechanics")]
|
||||
public float dodgeForce = 8f; // Lực đẩy văng đi
|
||||
public float dodgeDuration = 0.5f; // Thời gian nhào lộn/né
|
||||
public float dodgeCooldown = 3f; // Thời gian chờ giữa 2 lần né
|
||||
|
||||
private float nextDodgeTime;
|
||||
private bool isDodging = false;
|
||||
private Rigidbody rb;
|
||||
|
||||
private float nextShootTime;
|
||||
private NavMeshAgent agent;
|
||||
|
||||
[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<NavMeshAgent>();
|
||||
rb = GetComponent<Rigidbody>();
|
||||
// Tự động tìm các điểm tuần tra nếu chưa gán
|
||||
if (patrolPoints == null || patrolPoints.Length == 0)
|
||||
{
|
||||
patrolPoints = GameObject.FindGameObjectsWithTag("PatrolPoint")
|
||||
.Select(go => go.transform).ToArray();
|
||||
}
|
||||
fov = GetComponent<FieldOfView>();
|
||||
|
||||
nextShootTime = Time.time + Random.Range(minShootDelay, maxShootDelay);
|
||||
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();
|
||||
StartCoroutine(FindTargetWithDelay(0.1f)); // Chạy FOV quét mục tiêu
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (player == null) FindPlayer();
|
||||
if (Input.GetMouseButtonDown(0) && canSeePlayer && !isDodging && Time.time >= nextDodgeTime)
|
||||
{
|
||||
StartCoroutine(DodgeRoutine());
|
||||
}
|
||||
if (isDodging) return;
|
||||
|
||||
// Thực thi cây hành vi liên tục mỗi khung hình
|
||||
behaviorTreeRoot?.Evaluate();
|
||||
}
|
||||
|
||||
@@ -84,83 +79,56 @@ public class EnemyAI : MonoBehaviour
|
||||
if (playerObj != null) player = playerObj.transform;
|
||||
}
|
||||
|
||||
// Coroutine tối ưu việc quét mục tiêu
|
||||
private IEnumerator FindTargetWithDelay(float delay)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
FindVisibleTargets();
|
||||
}
|
||||
}
|
||||
|
||||
private void FindVisibleTargets()
|
||||
{
|
||||
canSeePlayer = false;
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position, viewRadius, targetLayerMask);
|
||||
|
||||
foreach (var col in colliders)
|
||||
{
|
||||
Transform target = col.transform;
|
||||
Vector3 direction = (target.position - transform.position).normalized;
|
||||
|
||||
float angle = Vector3.Angle(transform.forward, direction);
|
||||
|
||||
// Nếu nằm trong góc nhìn
|
||||
if (angle < viewAngle / 2)
|
||||
{
|
||||
float distanceToTarget = Vector3.Distance(transform.position, target.position);
|
||||
|
||||
// Nếu không có vật cản che khuất
|
||||
if (!Physics.Raycast(transform.position, direction, distanceToTarget, obstacleLayerMask))
|
||||
{
|
||||
canSeePlayer = true;
|
||||
isInvestigating = true;
|
||||
lastKnownPlayerPosition = target.position;
|
||||
|
||||
Debug.DrawLine(transform.position, target.position, Color.blue, 0.1f);
|
||||
break; // Thấy player rồi thì dừng vòng lặp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitBehaviorTree()
|
||||
{
|
||||
// 1. Cầm Artifact -> Đứng bắn
|
||||
// Ư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<Node>
|
||||
{
|
||||
new TaskNode(CheckHasArtifact),
|
||||
new TaskNode(ActionFocusAndShoot)
|
||||
});
|
||||
|
||||
// 2. Thấy Player -> Đuổi theo
|
||||
var chaseSequence = new Sequence(new List<Node>
|
||||
// Ư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<Node>
|
||||
{
|
||||
new TaskNode(CheckCanSeePlayer),
|
||||
new TaskNode(ActionMoveToPlayer)
|
||||
// Nhìn thấy trực tiếp -> dí theo
|
||||
new Sequence(new List<Node> { 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<Node> { new TaskNode(CheckHasInvestigateTarget), new TaskNode(ActionInvestigate) })
|
||||
});
|
||||
|
||||
// 3. Mất dấu Player -> Đi tới vị trí cuối cùng để điều tra
|
||||
var investigateSequence = new Sequence(new List<Node>
|
||||
{
|
||||
new TaskNode(CheckShouldInvestigate),
|
||||
new TaskNode(ActionInvestigate)
|
||||
});
|
||||
|
||||
// 4. Không có gì -> Tuần tra theo điểm
|
||||
// Ư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<Node>
|
||||
{
|
||||
dodgeNode,
|
||||
laserSequence,
|
||||
chaseSequence,
|
||||
investigateSequence,
|
||||
trackingSelector,
|
||||
patrolNode
|
||||
});
|
||||
}
|
||||
|
||||
#region CONDITIONS
|
||||
#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()
|
||||
{
|
||||
@@ -169,45 +137,70 @@ public class EnemyAI : MonoBehaviour
|
||||
|
||||
private NodeState CheckCanSeePlayer()
|
||||
{
|
||||
return canSeePlayer ? NodeState.Success : NodeState.Failure;
|
||||
return fov.canSeePlayer ? NodeState.Success : NodeState.Failure;
|
||||
}
|
||||
|
||||
private NodeState CheckShouldInvestigate()
|
||||
private NodeState CheckHasInvestigateTarget()
|
||||
{
|
||||
return isInvestigating ? NodeState.Success : NodeState.Failure;
|
||||
return fov.lastKnownPlayerPosition != Vector3.zero ? NodeState.Success : NodeState.Failure;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ACTIONS
|
||||
|
||||
private NodeState ActionPatrol()
|
||||
// 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;
|
||||
agent.speed = moveSpeed * 0.6f; // Đi tuần tra chậm rãi quay theo hướng đi tự động của NavMesh
|
||||
|
||||
// Đi tới điểm tuần tra hiện tại
|
||||
agent.SetDestination(patrolPoints[currentPatrolIndex].position);
|
||||
|
||||
// Nếu đã tới nơi, chuyển sang điểm tiếp theo
|
||||
if (agent.remainingDistance <= agent.stoppingDistance && !agent.pathPending)
|
||||
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
|
||||
{
|
||||
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
|
||||
currentWaitTime += Time.deltaTime;
|
||||
if (currentWaitTime >= patrolWaitTime)
|
||||
{
|
||||
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
|
||||
currentWaitTime = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
return NodeState.Running;
|
||||
}
|
||||
|
||||
private NodeState ActionMoveToPlayer()
|
||||
private NodeState ActionChasePlayer()
|
||||
{
|
||||
if (player == null) return NodeState.Failure;
|
||||
|
||||
Debug.Log("Chasing Player...");
|
||||
Debug.Log("Chasing Player!");
|
||||
agent.isStopped = false;
|
||||
agent.speed = chaseSpeed;
|
||||
agent.speed = moveSpeed; // Chạy nhanh hết tốc lực
|
||||
agent.SetDestination(player.position);
|
||||
|
||||
return NodeState.Running;
|
||||
@@ -215,29 +208,26 @@ public class EnemyAI : MonoBehaviour
|
||||
|
||||
private NodeState ActionInvestigate()
|
||||
{
|
||||
Debug.Log("Investigating last known position...");
|
||||
Debug.Log("Investigating Last Position...");
|
||||
agent.isStopped = false;
|
||||
agent.speed = moveSpeed;
|
||||
|
||||
agent.SetDestination(lastKnownPlayerPosition);
|
||||
agent.speed = moveSpeed * 0.8f;
|
||||
agent.SetDestination(fov.lastKnownPlayerPosition);
|
||||
|
||||
// Nếu đi tới nơi mà vẫn không thấy player -> Hủy điều tra, quay về tuần tra
|
||||
if (agent.remainingDistance <= agent.stoppingDistance && !agent.pathPending)
|
||||
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
|
||||
{
|
||||
isInvestigating = false;
|
||||
return NodeState.Success;
|
||||
// Đế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()
|
||||
{
|
||||
if (player == null) return NodeState.Failure;
|
||||
Debug.Log("Focus and Shoot!");
|
||||
agent.isStopped = true; // Dừng di chuyển để đứng ngắm bắn cố định
|
||||
|
||||
agent.isStopped = true; // Đứng lại để bắn
|
||||
|
||||
// Xoay người về phía player
|
||||
// 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)
|
||||
@@ -246,7 +236,7 @@ public class EnemyAI : MonoBehaviour
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
// Bắn
|
||||
// Đếm ngược thời gian bắn ngẫu nhiên
|
||||
if (Time.time >= nextShootTime)
|
||||
{
|
||||
ShootLaser();
|
||||
@@ -260,67 +250,7 @@ public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
if (laserPrefab == null || firePoint == null) return;
|
||||
Instantiate(laserPrefab, firePoint.position, firePoint.rotation);
|
||||
Debug.Log("Laser Shot!");
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region DODGE MECHANIC
|
||||
|
||||
private IEnumerator DodgeRoutine()
|
||||
{
|
||||
Debug.Log("Dodging!");
|
||||
isDodging = true;
|
||||
nextDodgeTime = Time.time + dodgeCooldown;
|
||||
|
||||
// 1. Tắt AI tìm đường để Vật lý tiếp quản
|
||||
agent.enabled = false;
|
||||
rb.isKinematic = false; // Đảm bảo Rigidbody có thể nhận lực
|
||||
|
||||
// 2. Tính toán hướng né: Random nhảy sang Trái hoặc Phải
|
||||
int randomDirection = Random.Range(0, 2) == 0 ? -1 : 1;
|
||||
|
||||
// Lấy vector hướng ngang của NPC nhân với trái (-1) hoặc phải (1)
|
||||
Vector3 dodgeDir = transform.right * randomDirection;
|
||||
|
||||
// Có thể cộng thêm một chút lực nhảy lên (trục Y) nếu muốn NPC hơi nảy lên
|
||||
// dodgeDir.y = 0.5f;
|
||||
|
||||
// 3. Tác dụng lực đẩy tức thời (Impulse)
|
||||
rb.AddForce(dodgeDir * dodgeForce, ForceMode.Impulse);
|
||||
|
||||
// 4. Chờ NPC văng đi trong thời gian chỉ định
|
||||
yield return new WaitForSeconds(dodgeDuration);
|
||||
|
||||
// 5. Thắng gấp (Dừng toàn bộ gia tốc vật lý lại)
|
||||
// Lưu ý: Unity 6 dùng linearVelocity thay vì velocity như các bản cũ
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
rb.angularVelocity = Vector3.zero;
|
||||
|
||||
// 6. Bật lại AI tìm đường
|
||||
rb.isKinematic = true; // Trả lại Rigidbody về trạng thái không ảnh hưởng vật lý
|
||||
agent.enabled = true;
|
||||
|
||||
isDodging = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Vẽ FOV trên Scene để dễ debug
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.white;
|
||||
Gizmos.DrawWireSphere(transform.position, viewRadius);
|
||||
|
||||
Vector3 viewAngleA = DirFromAngle(-viewAngle / 2);
|
||||
Vector3 viewAngleB = DirFromAngle(viewAngle / 2);
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(transform.position, transform.position + viewAngleA * viewRadius);
|
||||
Gizmos.DrawLine(transform.position, transform.position + viewAngleB * viewRadius);
|
||||
}
|
||||
|
||||
private Vector3 DirFromAngle(float angleInDegrees)
|
||||
{
|
||||
angleInDegrees += transform.eulerAngles.y;
|
||||
return new Vector3(Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), 0, Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
Assets/Scripts/AI NPC/FieldOfView.cs
Normal file
52
Assets/Scripts/AI NPC/FieldOfView.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class FieldOfView : MonoBehaviour
|
||||
{
|
||||
[Range(0, 360)]
|
||||
public float viewAngle = 90f;
|
||||
public float viewRadius = 20f;
|
||||
public LayerMask obstacleLayerMask;
|
||||
public LayerMask targetLayerMask;
|
||||
|
||||
[HideInInspector] public bool canSeePlayer = false;
|
||||
[HideInInspector] public Vector3 lastKnownPlayerPosition;
|
||||
|
||||
void Start()
|
||||
{
|
||||
StartCoroutine(FindTargetWithDelay(0.1f));
|
||||
}
|
||||
|
||||
IEnumerator FindTargetWithDelay(float delay)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
FindVisibleTargets();
|
||||
}
|
||||
}
|
||||
|
||||
private void FindVisibleTargets()
|
||||
{
|
||||
canSeePlayer = false;
|
||||
var colliders = Physics.OverlapSphere(transform.position, viewRadius, targetLayerMask);
|
||||
|
||||
for (int i = 0; i < colliders.Length; i++)
|
||||
{
|
||||
var target = colliders[i].transform;
|
||||
var direction = (target.position - transform.position).normalized;
|
||||
var angle = Vector3.Angle(transform.forward, direction);
|
||||
|
||||
if (angle < viewAngle / 2)
|
||||
{
|
||||
float distanceToTarget = Vector3.Distance(transform.position, target.position);
|
||||
if (!Physics.Raycast(transform.position, direction, distanceToTarget, obstacleLayerMask))
|
||||
{
|
||||
canSeePlayer = true;
|
||||
lastKnownPlayerPosition = target.position;
|
||||
Debug.DrawLine(transform.position, target.position, Color.blue, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/AI NPC/FieldOfView.cs.meta
Normal file
2
Assets/Scripts/AI NPC/FieldOfView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 210b37cfe4a84a34a91d0a9e58856a60
|
||||
Reference in New Issue
Block a user