참고자료
https://github.com/sturdyspoon/unity-movement-ai
https://youtu.be/mjKINQigAE4?si=lBTplBeF36Vmzoyr
https://youtu.be/bqtqltqcQhw?si=vsX2F9HNGAYcfHjO
NavMesh AI 설치
NavMesh Agent, NavMesh Obstacle 활용..
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public enum AIState { Idle, Wandering, Attacking, Fleeing } public class MonsterController : MonoBehaviour { [Header("Stats")] public float walkSpeed; public float runSpeed; [Header("AI")] public float detectDistance; public float safeDistance; private AIState aiState; [Header("Wandering")] public float minWanderDistance; public float maxWanderDistance; public float minWanderWaitTime; public float maxWanderWaitTime; [Header("Combat")] public float attackDistance; private float playerDistance; public float fieldOfView = 120f; public Transform playerTransform; private NavMeshAgent agent; //private Animator animator; //private SkinnedMeshRenderer[] meshRenderers; void Start() { agent = GetComponent<NavMeshAgent>(); //animator = GetComponent<Animator>(); //meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>(); SetState(AIState.Wandering); } void Update() { playerDistance = Vector3.Distance(transform.position, playerTransform.position); //animator.SetBool("Moving", aiState != AIState.Idle); switch (aiState) { case AIState.Idle: case AIState.Wandering: PassiveUpdate(); break; case AIState.Attacking: AttackingUpdate(); break; case AIState.Fleeing: FleeingUpdate(); break; } } public void SetState(AIState state) { aiState = state; switch (aiState) { case AIState.Idle: agent.speed = walkSpeed; agent.isStopped = true; break; case AIState.Wandering: agent.speed = walkSpeed; agent.isStopped = false; break; case AIState.Attacking: agent.speed = runSpeed; agent.isStopped = false; break; case AIState.Fleeing: agent.speed = runSpeed; agent.isStopped = false; break; } //animator.speed = agent.speed / walkSpeed; } void PassiveUpdate() { if (aiState == AIState.Wandering && agent.remainingDistance < 0.1f) { SetState(AIState.Idle); Invoke("WanderToNewLocation", Random.Range(minWanderWaitTime, maxWanderWaitTime)); } if (playerDistance < detectDistance) { SetState(AIState.Attacking); } } void AttackingUpdate() { if ((playerDistance <= attackDistance) && IsPlayerInFieldOfView()) { agent.isStopped = true; } else { if (playerDistance < detectDistance) { agent.isStopped = false; NavMeshPath path = new NavMeshPath(); if (agent.CalculatePath(playerTransform.position, path)) { Debug.Log($"{playerTransform.position}, {aiState}"); agent.SetDestination(playerTransform.position); } } else { agent.SetDestination(transform.position); agent.isStopped = true; SetState(AIState.Wandering); } //animator.speed = 1; //animator.SetTrigger("Attack"); } } void FleeingUpdate() { if (agent.remainingDistance < 0.1f) { agent.SetDestination(GetFleeLocation()); } else { SetState(AIState.Wandering); } } void WanderToNewLocation() { if (aiState != AIState.Idle) { return; } SetState(AIState.Wandering); agent.SetDestination(GetWanderLocation()); } bool IsPlayerInFieldOfView() { Vector3 directionToPlayer = playerTransform.position - transform.position; float angle = Vector3.Angle(transform.forward, directionToPlayer); return angle < fieldOfView * 0.5f; } Vector3 GetFleeLocation() { NavMeshHit hit; NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas); int i = 0; while (GetDestinationAngle(hit.position) > 90 || playerDistance < safeDistance) { NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas); i++; if (i == 30) break; } return hit.position; } Vector3 GetWanderLocation() { NavMeshHit hit; NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas); int i = 0; while (Vector3.Distance(transform.position, hit.position) < detectDistance) { NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas); i++; if (i == 30) break; } return hit.position; } float GetDestinationAngle(Vector3 targetPos) { return Vector3.Angle(transform.position - playerTransform.position, transform.position + targetPos); } }