Post List

[실용적 예제로 본 게임 인공지능 프로그램하기] 3. 조종 행동

[실용적 예제로 본 게임 인공지능 프로그램하기] 

3. 조종 행동

모든 소스코드는 여기 에서 확인할 수 있습니다.


조종행동 챕터에서는 어떻게 에이전트를 목표지점까지 움직이게 하는지를 다룹니다.
2장에서 특정 상태에 진입했을 때 그 상태에 맞게 에이전트를 조종한다고 생각하면 됩니다.
예를들면, '도망치기' 상태에 진입했을 때, 맨 처음 만나게되는 Enter 부분에서 '죽을힘을 다해 상대로부터 멀어지기' 코드를 실행한다고 했을 때, '멀어지기'는 조종행동 입니다.

우리는 1. 수학 및 물리학 입문 에서 '힘'이 주어졌을 때 그로부터 가속도와 속도, 더 나아가 새로운 위치까지 어떻게 정해지는지 살펴보았습니다. 이 조종행동 챕터에서는, 특정 상태에서 특정 움직임이 켜졌을 때, 자신에게 주어지는 '힘'으로 자신을 자동으로 조종하는 기술을 다룹니다.

이번 포스트에서는 아래 조종행동을 다루며 이중 1~6, 8~9가 어떤방식으로 코딩되었는지 살펴보겠습니다.
  1. 찾기
  2. 달아나기
  3. 도착하기
  4. 추격하기
  5. 도피하기
  6. 배회하기
  7. 장애물 피하기
  8. 끼워넣기
  9. 숨기기
  10. 오프셋 추격하기
  11. 결합, 분리, 정렬

1. 찾기

찾기(Seek) 조종은 에이전트를 목표 위치로 이끄는 힘을 되돌려 줍니다.
우선 'DesiredVelocity'를 계산해야 합니다. 이는 이상적인 세계에서 에이전트가 목표위치로 도달하는데에 필요한 속도 입니다. 이는 

(DestinationPosition - AgentPosition).Normalize() * MaxSpeed

로 구할 수 있습니다. 그러면 남은것은 이제 플레이어를 DesiredVelocity 로 향하는 벡터를 구하는 것 입니다. 이는

DesiredVelocity - AgentVelocity

로 구할 수 있습니다. 
요약하자면, 찾기 행동은 플레이어가 자신의 최고실력으로 목표지점의 어디까지 갈 수 있는지 속도를 구한 후, 현 속도에서 그 속도로 가는 속도를 구하는 것 입니다.

2. 달아나기

달아나기(Flee) 조종은 찾기의 반대 조종행동 입니다. 찾기에서는 에이전트에서 목표지점으로의 원하는속도를 계산한 반면, 이 달아나기 조종행동에서는 목표지점에서 에이전트로의 원하는 속도를 계산 합니다.

소스코드 :
1
2
3
4
5
6
7
8
9
10
public Vector2 Flee(Vector2 targetPos)
    {
        // If the target located in inside of PanicDistance area, vehicle only flee away from that.
        // Operated within powered space.
        Vector2 DesiredVelocity = (vehicle.Pos() - targetPos).normalized * vehicle.GetMaxSpeed();
        Debug.Log("Fleeing");
        return DesiredVelocity - vehicle.GetVelocity();
    }
cs


3. 도착하기

찾기 조종은 에이전트를 올바른 방향으로 이동시키지만, 목표 부근에서는 천천히 목표물에 접근하는 것을 원할수 있습니다. 그것을 구현하기위해 도착하기 행동을 사용합니다.
매개변수로 Deceleration을 사용하여 일정 범위마다 열거체의 원소를 slow - normal - fast 로 바꾸어 줍니다. 

소스코드 : 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Vector2 Arrive(Vector2 targetPos, Deceleration deceleration)
    {
        Vector2 toTarget = targetPos - vehicle.Pos();
        // calculate a distance to destination
        // Length()(a.k.a .magnitude) is using sqrt computation. It might cause overhead.
        float dist = toTarget.magnitude;
        if (dist > 0)
        {
            const float decelerationTweaker = .3f;
            float speed = dist / (float)deceleration * decelerationTweaker;
            speed = Mathf.Min(speed, vehicle.GetMaxSpeed());
            Vector2 desiredVelocity = toTarget * speed / dist;
            Debug.Log("Arriving");
            return desiredVelocity - vehicle.GetVelocity();
        }
        return new Vector2(0, 0f);
    }
cs


4. 추격하기

추격하기 행동은 에이전트가 움직이는 목표를 가로채는 것이 요구될 때 유용합니다. 찾기만 해서는 그다지 지능적이라는 느낌을 들게 하는것에는 도움이 되지 않습니다.

추격하기는, 상대의 미래 위치로 에이전트를 움직이게 하는 것 입니다. 이 기능의 성공 여부는 추격자가 얼마나 도피자의 궤적을 잘 예측하는지에 달려있습니다.

이 소스코드에서는 우선 도피자의 역방향이 추격자(에이전트) 방향에서 대략 20도 이내에 있어야 대면하다고 판단하였습니다. 대면 하고 있다면 그저 Seek 함수를 사용하면 될 것입니다.

상대가 내 '앞'에 있고 '20도 이내'를 위해 벡터의 내적 연산을 하였습니다.

소스코드 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Vector2 Pursuit(Vehicle evader)
    {
        // 만약 도피자가 앞에있고 에이전트를 대면하면
        // 도피자의 현재 위치만 찾는다.
        Vector2 toEvader = evader.Pos() - vehicle.Pos();
        // 시야각
        float relativeHeading = Vector2.Dot(vehicle.Heading(), evader.Heading());
        // 대면하며 20도 이내 위치한다.
        if (Vector2.Dot(toEvader, vehicle.Heading()) > 0 && 
            (relativeHeading < -0.95f))
        {
            return Seek(evader.Pos());
        }
        // 예측시간 연산
        float lookAheadTime = toEvader.magnitude / (vehicle.GetMaxSpeed() + evader.GetVelocity().magnitude);
        // 도피자의 미래위치로 
        return Seek(evader.Pos() + evader.GetVelocity() * lookAheadTime);
    }
cs


5. 도피하기

도피하기는 추격하기의 반대 입니다. Evade 함수에서 매개변수로 Pursuer를 받는데, 이때  Pursuer의 미래위치로부터 Flee 하는 방식 입니다.

6. 배회하기

배회하기를 구현하는 데에는 여러 알고리즘이 사용될 수 있습니다. 배회하기에서 중요한 것은 인위적이지 않으면서 무작위로 이동하듯이 보여져야 하는 것 입니다. 

이때 매 단계마다 무작위로 조종힘을 계산하는 것이 단순한 해결법 이지만, 이것은 장기간의 지속적인 변화를 얻기에는 힘들다고 합니다. 또한 Perlin Noise 처럼 세련된 알고리즘을 사용한다고 하면 CPU시간을 잡아먹기에 적합하지 않을 수 있습니다. 

따라서 이 책에서는 에이전트 앞에 원을 투사하고, 원의 둘레에 위치한 특정 지점을 향해 나아가는 것을 추천하였습니다. 원 둘레의 특정지점에 소량의 무작위 변위를 추가하면 깨끗한 이동을 만들어 낼 수 있다고 합니다.


8. 끼워넣기

끼워넣기는 두개의 다른 에이전트 사이를 연결하는 선분의 중간지점으로 운반기를 이동시키는 조종힘을 되돌려줍니다. 자신을 고용한 사람을 위해 총탄을 받아내는 경호원이나 패스된 공을 가로채는 축구선수가 이런형식의 조종을 사용합니다.

추격하기와 마찬가지로, 에이전트는 미래의 시간 T에 두 에이전트가 어디에 위치할 것인지 추정해야 합니다. 하지만 우리는 좋은 T를 모르기 때문에 계산을 해야 합니다.

우선 두 에이전트 사이의 현 중간지점을 계산한 후, 플레이어가 그 중간지점으로 최대속도로 가는데 걸리는 T를 구합니다. 이것이 '좋은T' 이며 이 T를 사용하여 두 에이전트의 T 후 위치를 계산하고 이때의 중간지점으로 플레이어를 이동시키는 원리 입니다.



9. 숨기기

숨기기는 에이전트를 어느 위치에 둠으로써 사냥꾼과 같은 자신이 피하려는 에이전트와 자신 사이에 항상 장애물이 있도록 하는 것입니다. 예를들면 숲속을 따라 이나무 저나무 옮겨다니며 게임실행자를 슬며시 따라다니는 힘을가진 NPC에 응용할 수 있습니다.
이 숨기기 조종행동을 위한 단계는 2가지가 있습니다.
  1. 각 장애물들에 대해 은신지점을 결정한다.
  2. 은신지점까지의 거리가 결정되면 에이전트는 도착하기 행동을 사용하여 이동한다.







이상의 언급한 조종행동을은 이후 포스팅에서 이어질 단순축구의 선수 움직임 제어에 쓰입니다. 여기까지 공부하며 벡터가 정말 게임에 많이 쓰임을 몸소 느끼게 되었습니다.




댓글