Chapter 7-7. AI : 발소리를 듣고 추격하는 동물

Date:     Updated:

카테고리:

태그:

인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click

Chapter 7. 동물들의 공격형 AI, 도망형 AI 구현

발소리를 듣고 추격하는 동물

🚖 Nav Mesh Path

Nav Mesh Agent 는 최단 경로로 목적지에 도달하려고 한다.

        NavMeshPath _path = new NavMeshPath();
        nav.CalculatePath(_targetPos, _path);
  • Nav Mesh Agent 의 CalculatePath 함수
    • Nav Mesh Agent 위치로부터 목적지 _targetPos까지의 최단 경로를 계산한 후
    • Nav Mesh Path 타입의 데이터 _path최단 경로의 코너(정점)들을 배열로 저장한다.
      • 정확히는 _path.corners 배열에 최단 경로의 코너들의 위치 벡터가 저장됨
        • 단, 출발지와 목적지는 저장되지 않는다.

image

Nav Mesh Agent 는 갈 수 있는 지형을 판단하고 연산한 Bake 된 지형에만 갈 수 있다. 그림 속 가운데 장애물을 갈 수 없다면 Nav Mesh Agent 는 돌아가야 하며 이를 감안한 최단 경로를 찾는다.

  • CalculatePath 함수
    • 최단 경로를 구한다.
    • 연산된 최단 경로에서 꺾이는 부분의 위치 벡터들을 Nav Mesh Path 타입의 객체의 corners 멤버 배열에 저장한다.
      • 즉, 길을 저장함
  • 그림과 같이 꺾이는 두 부분의 좌표가 corners 배열에 저장 된다.

플레이어와 사자가 매우 가까이 있어도 둘 사이에 아주 기다란 벽이 있다면, 사자가 플레이어로 가는 최단 경로는 길어질 것이다. 이렇게 Nav Mesh Agent를 통하여 장애물도 고려하여 구한 최단 경로를 사자의 시야 반경 viewDistance 와 비교하여 사자가 플레이어를 추격할지 말지 정하게 한다. 둘 사이의 장애물이 그럭 저럭 짧은 편이라면 사자의 시야각 내에 플레이어가 없더라도 장애물을 피하여 플레이어에게 도착하는 최단 경로가 viewDistance보다 짧다면 플레이어를 추격한다. 이처럼 플레이어가 장애물 등으로 인해 사자 시야에 보이지 않아도, 플레이어가 사자 뒤에 있거나 사자와 짧은 장애물을 사이에 두고 있다면 사자가 플레이어를 인지하고 추격할 수 있게 한다. 이를 사자가 플레이어의 발 소리를 듣고 움직였다고 치자!


🚖 구현

📜PlayerContorller.cs

    public bool GetRun()
    {
        return isRun;
    }

플레이어가 뛰고 있는 중인지를 나타내는 isRun 접근 함수. 플레이어가 사자의 시야에서 보이지 않아도 플레이어가 뛰어 다니면 사자가 플레이어를 추격하도록 할 것이라서 필요하다.


📜FieldOfViewAngle.cs

using UnityEngine.AI;

    private NavMeshAgent nav;

    void Start()
    {
        thePlayer = FindObjectOfType<PlayerController>();
        nav = GetComponent<NavMeshAgent>();
    }

    public bool View()
    {
        Collider[] _target = Physics.OverlapSphere(transform.position, viewDistance, targetMask);

        for (int i = 0; i < _target.Length; i++)
        {
            Transform _targetTf = _target[i].transform;
            if (_targetTf.name == "Player")
            {
                Vector3 _direction = (_targetTf.position - transform.position).normalized;
                float _angle = Vector3.Angle(_direction, transform.forward);

                if (_angle < viewAngle * 0.5f)
                {
                    RaycastHit _hit;
                    if(Physics.Raycast(transform.position + transform.up, _direction, out _hit, viewDistance))
                    {
                        if (_hit.transform.name == "Player")
                        {
                            Debug.Log("플레이어가 시야 내에 있습니다.");
                            Debug.DrawRay(transform.position + transform.up, _direction, Color.blue);

                            return true;
                        }
                    }
                }
            }

            if (thePlayer.GetRun())
            {
                if (CalcPathLength(thePlayer.transform.position) <= viewDistance)
                {
                    Debug.Log("주변에 뛰고 있는 플레이어의 움직임을 파악했습니다.");
                    return true;
                }
            }
        }
        return false;
    }

    private float CalcPathLength(Vector3 _targetPos)
    {
        NavMeshPath _path = new NavMeshPath();
        nav.CalculatePath(_targetPos, _path);

        Vector3[] _wayPoint = new Vector3[_path.corners.Length + 2];

        _wayPoint[0] = transform.position;
        _wayPoint[_path.corners.Length + 1] = _targetPos;

        float _pathLength = 0;  // 경로 길이를 더함
        for (int i = 0; i < _path.corners.Length; i++)
        {
            _wayPoint[i + 1] = _path.corners[i];
            _pathLength += Vector3.Distance(_wayPoint[i], _wayPoint[i + 1]); 
        }

        return _pathLength;
    }
  • ⭐ 사자가 플레이어를 발견(추격)하는 기준
    • 👉 View() 에서 return true 되는 경우 2 가지
      • 1️⃣ 사자의 시야 각도 내에 플레이어가 있을 때. 플레이어를 사자 눈으로 확인 했을 때.
      • 2️⃣ 사자 시야에 플레이어가 보이지 않아도 (장애물 혹은 플레이어가 사자 뒤에 있는 경우)
        • 플레이어가 뛰고 있는 중 일때 and
        • 사자에서 플레이어로 향하는 최단 거리가 viewDistance 내에 있을 경우 플레이어를 추격
          • 플레이어가 뛰는 발 소리 듣고 움직이는거라고 치자.
              if (thePlayer.GetRun())
              {
                  if (CalcPathLength(thePlayer.transform.position) <= viewDistance)
                  {
                      Debug.Log("주변에 뛰고 있는 플레이어의 움직임을 파악했습니다.");
            
                      return true;
                  }
              }
            

image

  • CalcPathLength(Vector3 _targetPos)
    • 최단 경로 길이를 계산하여 리턴한다.
      • Nav Mesh Agent 의 CalculatePath 함수로, 최단 경로의 코너들을 _path.corners에 저장한다.
      • 이는 출발지와 목적지는 포함하지 않으므로 wayPoint 라는 벡터 배열을 새로 만들어서 출발지, 목적지, _path.corners 배열 원소들을 다 저장해주었다.
      • wayPoint 배열 원소들 끼리의 거리를 구하여 이를 _pathLength에 다 더한다.
      • _pathLength가 바로 최단 경로의 길이가 되며 이를 viewDistance와 비교하여 플레이어를 추격할지 말지 결정할 것이다.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 3 카테고리 내 다른 글 보러가기

댓글 남기기