Unity에서는 AI 시스템이 존재합니다. 이것으로 간편하게 AI몹들이 움직이게 할 수 있습니다. 장애물을 알아서 피하게 할 수 있고, 속도나 회전을 모두 설정할 수 있습니다.
자세한 설명은 이 게시글을 확인해주세요!
Package Manager에서 Unity Registry -> AI Navigation을 설치합니다.
자식이 아닌 최상위 부모. 즉 지형지물을 담은 Map에서 NavMeshSurface 컴포넌트를 추가합니다. 그리고 Bake 해주세요. 그러면 Nav Mesh Data에 데이터가 생길겁니다.
Bake를 하고 나면 이러한 파란색이 영역이 뜹니다. 뜨지 않을 경우 오른쪽 상단 gizmo를 클릭해주세요.
적 만들기 #1 에서 플레이어를 따라가는 코드를 작성했죠? 그것은 이제 모두 필요없습니다!
적 프리팹에 Nav Mesh Agent 컴포넌트를 추가합시다. 또 스크립트를 수정합시다.
// Enemy.cs
private NavMeshAgent agent;
protected override void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// ChaseState.cs
private void Moving()
{
// 기존 이동, 회전 코드 모두 지움
// 현재 이 AI의 목표는 enemy.target임
enemy.agent.destination = enemy.target.transform.position;
}
그리고 AttackState와 IdleState에서 각각 Enter()에는 enemy.agent.isStoppend = true를 넣고, Exit()에는 enemy.agent.isStoppend = false를 넣어주시면 됩니다.
true면 이동을 멈추고 false면 이동을 다시 시작합니다.
몹이 알아서 지형지물을 피하고 플레이어를 따라가는 모습입니다.
이제 플레이어를 감지하지 않고 있을 때 가만히만 있으면 어색하잖아요? 그러니 Idle상태가 아니라 홀로 걸어다니는 PatrolSate를 만들 생각입니다.
각 적들은 순찰을 하는 Waypoint가 존재하며, 그 중 랜덤으로 이동하게 할 겁니다. 그렇다면 NavMesh를 이용해 목표지점으로 이동하고 다음 지점을 변경한뒤 기다리는 로직이 필요합니다.
//Enemy.cs
public Transform[] waypoints;
// PatrolState에서 코루틴을 종료시키기 위함
[HideInInspector] public Coroutine patrolCoroutine;
public void PerformPatrol()
{
patrolCoroutine = StartCoroutine(WaitForPatrolCoroutine());
}
IEnumerator WaitForPatrolCoroutine()
{
// 무한 반복
while (true)
{
// 배열 중 랜덤으로 하나 선택
int randomIndex = Range(0, waypoints.Length);
// 해당 포지션으로 이동
agent.SetDestination(waypoints[randomIndex].position);
// 도착 전까지는 리턴
while (agent.pathRending || agent.remainingDistance > 0.5f)
yield return null;
// 도착 후 2초 기다림
yield return new WaitForSeconds(2f);
}
}
// PatrolState.cs
public override void Enter()
{
Debug.Log("적이 순찰 상태로 진입");
enemy.PerformPatrol();
}
public override void Update()
{
if (enemy.StartChase())
{
enemy.ChangeState(new ChaseState(enemy));
}
}
public override void Exit()
{
Debug.Log("적이 순찰 행동을 벗어남");
enemy.StopCoroutine(enemy.patrolCoroutine);
}
임시로 Empty 오브젝트를 맵에 설치하여 적 오브젝트에 적용시킵니다.
이때 적이 생성되면 PatrolState로 시작됩니다. 돌아다니다가 플레이어를 만나면 ChaseState로 바뀌고 플레이어가 일정 거리를 벗어나면 다시 PatrolState로 바뀝니다.
제가 설계한 게임은 오픈월드이기에 AI몹의 포인트를 맵에 지정해주는 것이 힘들뿐더러, 이미 정해져 있으면 부자연스러워 보입니다. 그렇기 때문에 Random함수를 이용하여 AI가 이동하게 할 겁니다.
// Enemy.cs
// 하지 않으면 오류가 떠서 명시적 선언을 함
using UnityRandom = UnityEngine.Random;
private Vector3 centerPoint;
protected override void start()
{
// 처음 스폰된 위치를 이동 반경의 중심으로 설정
centerPoint = transform.position;
}
IEnumerator WaitForPatrolCoroutine()
{
while (true)
{
Debug.Log("코루틴 실행");
SetNewRandomDestination();
while (agent.pathPending || agent.remainingDistance > 0.5f)
yield return null;
yield return new WaitForSeconds(2f);
}
}
void SetNewRandomDestination()
{
// 구형식의 랜덤 좌표
Vector3 randomDirection = UnityRandom.insideUnitSphere * patrolRadius;
// 처음 스폰된 위치에 랜덤 좌표를 더함
randomDirection += centerPoint;
NavMeshHit hit;
// 갈 수 있는 근처 NavMesh 좌표로 이동
if (NavMesh.SamplePosition(randomDirection, out hit, patrolRadius, NavMesh.AllAreas))
{
agent.SetDestination(hit.position);
}
}
만약 중심점에서 플레이어가 일정 반경 이상 멀어지면 더이상 쫓아가지 않게 해야 합니다.
// Enemy.cs
public bool IsInCenter(Transform target)
{
return Vector3.Distance(centerPoint, target.position) <= patrolRadius;
}
// ChaseState.cs
public override void Update()
{
if (enemy.IsInAttackRange(enemy.target.transform))
{
enemy.ChangeState(new AttackState(enemy));
}
else if (!enemy.IsInDistance(enemy.target.transform) || !enemy.IsInCenter(enemy.target.transform))
{
enemy.ChangeState(new PatrolState(enemy));
}
Moving();
}
순찰을 하다가 플레이어를 감지하면 추적을 하나, 플레이어가 일정 영역을 벗어나면 더이상 쫓지 않습니다.
다음 글에서는: