[Unity, C#] 몹을 랜덤한 경로로 순찰(patrol)시키기

순찰하는 경찰 그림

토이 프로젝트로 좀비 TPS 게임을 만들고 있다.

순찰(patrol)이란 몹이 일정한 지점들을 돌아다니는 것을 이야기한다. 좀비 게임을 생각해보면, 좀비가 가만히 있기만(idle) 하면 난이도가 너무 쉬울 것이다. 좀비가 idle/patrol 상태에 있다가 플레이어를 발견하면 추적(chase) 모드로 전환되어 플레이어를 쫓아간다. 이때 빠르게 도망가지 못하고 좀비와의 거리가 일정 값 이하가 되면 좀비가 플레이어를 공격(attack)한다. 플레이어가 도망가서 인식 범위를 나갔다면 좀비는 처음에 있던 장소로 돌아간다(return). 처음에 있던 장소로 돌아간 이후에는 다시 idle/patrol 상태가 된다.

위 과정을 도식으로 간단히 나타내면 다음과 같다.

💡 return이 필요한 이유는 무엇일까?
return이 없는 경우에 플레이어는 맵을 돌아다니다 한번이라도 플레이어를 인식한 몹은 계속 chase 상태가 되므로, 많은 몹을 끌고 오게 된다. 너무 많은 몹이 한 장소에 쏠리게 되어 시스템에 과부하가 올 수 있고 난이도 조절도 어렵게 된다.

순찰(patrol)하는 지점들을 waypoint라고 한다. waypoint를 직접 지정하는 것 외에, 몹의 초기 위치에서 적당히 떨어진 곳으로 랜덤하게 설정하는 방법을 알아보자.

  1. patrol할 지점을 적당히 List로 만들어준다.
    local 좌표계로 생각해서 넣으면 된다.
List<Monster> Monster_List; // 맵에 있는 모든 몹을 가지고 있는 List
List<Vector3> Monster_Patrol_Positions;

Monster_Patrol_Positions = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(2, 0, 2),
    new Vector3(-2, 0, -2),
    new Vector3(2, 0, -2),
    new Vector3(-2, 0, 2),
    new Vector3(3, 0, 0),
    new Vector3(-3, 0, 0)
};
  1. patrol waypoint를 지정한다.
public void InitMonsterPatrol()
{
    Monster_Patrol_Positions = new List<Vector3>
    {
        new Vector3(0, 0, 0),
        new Vector3(2, 0, 2),
        new Vector3(-2, 0, -2),
        new Vector3(2, 0, -2),
        new Vector3(-2, 0, 2),
        new Vector3(3, 0, 0),
        new Vector3(-3, 0, 0)
    };

    int numberOfPosToSelect = 3; // 순찰할 지점의 개수를 정한다.
    foreach (Monster monster in Monster_List)
    {
        // 몹이 처음에 idle 상태일 경우 patrol waypoint를 지정하지 않았다.
        if (monster.m_State == Character.State.Idle)
            continue;

        List<Vector3> positionsCopy = new List<Vector3>(Monster_Patrol_Positions);
        List<Vector3> selectedPositions = new List<Vector3>();

        // 위에서 지정한 개수만큼 patrol position을 랜덤하게 뽑는다.
        while (selectedPositions.Count < numberOfPosToSelect && positionsCopy.Count > 0)
        {
            int randomIndex = Random.Range(0, positionsCopy.Count);
            selectedPositions.Add(positionsCopy[randomIndex]);
            positionsCopy.RemoveAt(randomIndex);
        }

        // 모두 뽑으면 몹의 property인 patrol waypoint를 지정해준다.
        monster.SetPatrolWaypoints(selectedPositions);
        monster.StartPatrol();
    }
}

Monster 클래스의 메소드인 SetPatrolWaypoints, StartPatrol의 코드는 다음과 같다.

NavMeshAgent agent;
int waypointIndex;
Vector3 waypointTarget;
Vector3 initPosition;
List<Vector3> patrolWaypoints;

public void SetPatrolWaypoints(List<Vector3> posDiffs)
{
    foreach (Vector3 posDiff in posDiffs)
    {
        patrolWaypoints.Add(transform.position + posDiff);
    }
}

public void StartPatrol()
{
    waypointIndex = 0;
    UpdateDestination();
}

void UpdateDestination()
{
    if (patrolWaypoints.Count <= waypointIndex)
        return;
    waypointTarget = patrolWaypoints[waypointIndex];
    agent.SetDestination(waypointTarget);
}

경로를 정확히 지정해주고 싶은 경우 (앞뒤, 좌우, 삼각형, 사각형 등) 이 방법을 응용해 2차원 List를 사용하면 될 것 같다.