Post List

[실용적 예제로 본 게임 인공지능 프로그램하기] 4. 단순 축구

[실용적 예제로 본 게임 인공지능 프로그램하기] 4. 단순 축구

이 책은 "모든 전술과 기술을 모델링하는 방법을 설명하려는 것이 아니라, 자기 자신의 아이디어를 지원할 수 있는 팀 스포츠 AI 프레임워크를 디자인하는 법을 보여주려는 것" 이라고 말합니다.

학창시절 저는 PS2와 PS3을 가지고 있었는데 이때 위닝일레븐과 피파 시리즈에 정말 깊게 빠진적이 있습니다. 초등학교때는 PS2 위닝일레븐의 감독모드를 하며 같은 학년 친구들을 전부 게임에 반영한 적도 있습니다. (물론 저랑 친한 친구들은 외모는 비슷하게, 능력치는 더욱 좋게 했었지요.) 피파 시리즈에서는 선수 한명이 되는 모드에 정말 깊게 빠져들었었는데, 이때 탑뷰로 보는 축구에서는 볼 수 없었던, 1인칭으로 모드를 즐기다보니 같은 팀 멤버의 위치와 패스 방향, 적절성등에 더 신경을 쓸 수 있었습니다. 

이 장에서는 우리팀이 공을 가지고 있을 때 어떻게 패스를 받을 최적의 위치를 정할수 있는지에 대해 다룹니다. 
패스를 받을 최적의 위치라 함은 정확한 패스, 스루 패스, 그리고 슛까지 모든것을 포괄합니다.

이 모든것을 구현하기 위해 앞장의 조종행동과 FSM, 그리고 전보를 사용합니다. 그리고 최적의 패스 위치를 정하기 위해 다음과 같은 함수가 필요합니다.
  1. 미래의 공 위치
  2. 새 위치에 도달하는 시간

1. 미래의 공 위치 (Future Position)

시간이 매개변수로 주어졌을 때, 이 함수는 공의 궤적이 중단되지 않고 계속된다고 가정하여 미래의 시간에 공이 어디에 위치할 지를 계산합니다.
시간 t에서의 공의위치 Pt를 계산하기 위해, 다음과 같은 식을 사용합니다.

Δx = uΔt + 1/2 * aΔt²
여기서 x는 움직인 거리,  u는 공의 속도, a는 마찰로 인한 감속 입니다.

소스코드 :

1
2
3
4
5
6
7
8
9
10
11
12
public Vector2 FuturePosition(float time)
    {
        // Use Expression : x = ut  + 1/2at^2;
        Vector2 ut = v_velocity * time;
        float half_a_t_squared = 0.5f * 마찰계수 * time * time;
        Vector2 scalarToVector = half_a_t_squared * v_velocity.normalized;
        // 미래의 위치는 현 위치에 ut와 1/2at^2 를 더한것임.
        return (Vector2)transform.position + ut + scalarToVector;
    }
cs


2. 새 위치에 도달하는 시간

새로운 위치를 1번의 메소드로 계산을 했다면, 그 새로운 위치로 갔을 때 정말 내 공이 안전한지 알아야 합니다. 이때 TimeToCoverDistance 메소드를 사용하여 현재 위치에서 새로운 위치로 도달하는데 걸리는 시간을 계산합니다. 
이 함수에서 얻은 '걸리는 시간'을 토대로 상대 플레이어와 내 플레이어들의 미래위치도 계산해 보는 것 입니다.

이때 우리는 다음과 같은 수식을 사용합니다.

v = u + aΔt
Δt = (u - v) / a

a는 마찰계수 이므로, v는 새로운 위치에서의 공의 속도, u는 공을 찬 직후의 공의 속도일 때 v와 u를 알아야 합니다. 여기서 우리는 가정을 해야 합니다. 정지된 공을 차는게 아니라면, 모든 패스는 공을 차기 직전의 속도벡터는 (0,0)이 아닐 것입니다. 하지만 우리는 (0,0)이라고 가정합니다. 이렇게 기술적으로 비현실적일지라도 '관찰자에게는 여전히 현실적으로 보이고' 이 메소드를 더욱 쉽게 계산할 수 있게 됩니다. 
이렇게 가정하여 u는 공을 찬 직후의 가속도, 즉 

u = a = F/m

이 되고, Δt 를 구하기 위해 이제 a와 v를 계산하면 됩니다.  v를 구하기 위해 다음과 같은 식을 사용합니다.

Δt = (u - v) / a 의 Δt를, Δx = uΔt + 1/2 * aΔt²에 대입한다면,
v² = u² + 2aΔx
 v = sqrt(u² + 2aΔx)

좋습니다. 이제 v의 값도 구할 수 있게 되었습니다. 이제 제곱근 값이 음수라면 속도는 실수가 아니게 되고(복소수 입니다. 하지만 구할 수 없다고 가정합니다.) 이것은 공이 기존위치에서 새 위치로 이동할 수 없음을 의미합니다. Δx는 (미래의 공 위치 - 현재 공 위치)로 간단하게 구할 수 있습니다.

소스코드 : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public float TimeToCoverDistance(Vector2 from, Vector2 to, float force)
    {
        float speed = force / ballRb.mass;
        // Use Expression : v^2 = u^2 + ax;
        float distanceToCover = Vector2.Distance(from, to);
        float term = speed * speed + 2f * distanceToCover * 마찰계수 ;
        if (term <= 0return 0f;
        float v = Mathf.Sqrt(term);
        return (v - speed) / 마찰계수 ;
    }
cs





Best Supporting Spot 계산하기



PLAYER는 매 순간 A 부터 I 중 어느곳으로 패스를 해야할 지 선택해야 합니다.

사전에 상대 필드에 촘촘히 spot들을 박아 놓습니다. 이 spot들은 DetermineBSS(Best Supporting Spot) 함수에서 순회되며 가장 최적의 위치를 도출해 내는데에 도움을 줍니다.

BSS가 결정되는 순서는 다음과 같습니다.
  1. DetermineBSS 함수가 호출된다.
  2. 만약 Player에서 한 Spot으로 공이 가게 된다면 어떤 결과를 초래하는지 시뮬레이션 합니다.
  3. 모든 Spot에 대해 2를 반복합니다.
이때, 2번에서 시뮬레이션 하는 검사는 다음과 같습니다.
  1. 공의 위치에서 이 위치까지 안전하게 패스할 수 있는지를 결정한다.
  2. 이 위치에서 골이 기록될 수 있는지를 결정한다.
  3. 이 지점이 제어선수로부터 얼마나 떨어져있는지를 계산한다.(멀리 떨어져 있을수록 점수가 더 높다)
  4. 이 지점이 지금까지 최적의 장소인지 검사한다.(가장 높은 점수를 가지고 있는지)

시뮬레이션 후 가장 높은 점수를 기록한 spot이 BSS로 선정됩니다.

소스코드 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public Vector2 DetermineBestSupportingPosition(TeamColor team)
    {
        bestSupportSpot = new Vector2(00);
        float bestScoreSoFar = 0f;
        List<SupportSpot> list = team == TeamColor.Red ? redZone : blueZone;
        GameObject teamName = team == TeamColor.Red ? GameObject.Find("RedTeam").gameObject : GameObject.Find("BlueTeam").gameObject;
      
        
        foreach (var item in list)
        {
            item.SetScore(1f);
            //check 1
            if (teamName.GetComponent<SoccerTeam>().ControllingPlayer() != null)
            {
                if (teamName.GetComponent<SoccerTeam>().IsPassSafeFromAllOpponents(teamName.GetComponent<SoccerTeam>().ControllingPlayer().transform.position, item.transform.position, null, Prm.instance.MaxPassingForce))
                {
                    item.SetScore(item.GetScore() + Prm.instance.Spot_PassSafeStrength);
                }
                Vector2 tmp = new Vector2();
                //check 2
                if (teamName.GetComponent<SoccerTeam>().CanShoot(item.transform.position, Prm.instance.MaxShootingForce, ref tmp))
                {
                    item.SetScore(item.GetScore() + Prm.instance.Spot_CanScoreStrength);
                }
                //check 3
                if (teamName.GetComponent<SoccerTeam>().SupportingPlayer())
                {
                    const float optimalDistance = 4f;
                 
                    float dist = Vector2.Distance(teamName.GetComponent<SoccerTeam>().ControllingPlayer().transform.position, item.transform.position);
                    float temp = Mathf.Abs(optimalDistance - dist);
                    if (temp < optimalDistance)
                    {
                        item.SetScore(item.GetScore() + Prm.instance.Spot_DistFromControllingPlayerStrength * (optimalDistance - temp) / optimalDistance);
                    }
                }
            }
            if(item.GetScore() > bestScoreSoFar)
            {
                bestScoreSoFar = item.GetScore();
                bestSupportSpot = item.transform.position;
            }
        }
        return bestSupportSpot;
    }
cs



BSS는 어디서 활용이 되는거죠?

BSS는 모든 곳에서 사용됩니다. 우선 패스받는 선수는 패스를 받을 위치로 가기 위해 BSS로 갈 것이고, 슛팅을 할 선수는 지금 있는곳이 모든 spot들로부터 안전하게 슛팅을 할 수 있는 위치인지 확인하기 위해 BSS를 계산할 것입니다. 물론 활용방법들은 다양하고 DetermineBSS 메소드가 호출되는 위치는 다르나, 기본적으로 같은 개념을 가지고 있습니다.
















댓글