덴바의 노트

내일배움캠프 20일차 TIL : 수학을 이용한 시야각 구하기 본문

프로그래밍 노트/TIL

내일배움캠프 20일차 TIL : 수학을 이용한 시야각 구하기

덴바 2024. 5. 16. 21:58

 

오늘의 키워드
  • 부채꼴 시야각 내에 적 찾

이번 캠프에서는 플레이어를 중심으로

 

부채꼴 모양의 시야각에 적이 있는지를 판단하는 기능을 구현했습니다.

 

이번 기능은 수학적인 부분이 매우 많이 들어갔기 때문에 상당히 애를 먹었습니다.

 

내적, Acos, Degree, Radian 등을 사용했는데,

 

막상 사용하려니 머리 속에서 그려지지 않아서 하루 정도를 이를 이해하는데 쓴 것 같습니다.

 

일단 제가 이해한게 100% 맞을지 모르겠지만 이를 토대로 설명을 해보고자 합니다.

 


 

삼각함수, 역삼각함수

 

삼각함수, 역삼각함수

 

Sin, Cos, Tan

 

Asin, Acos, Atan

 

을 사용합니다.

 

가장 중요한 것은 이를 왜 사용하냐는 것입니다.

 

삼각함수는 각도를 중심으로 빗변, 밑변, 높이의 비율을 알기 위해서 사용합니다.

 

또한 이를 이용하면 각 변의 길이를 알 수도 있습니다.

 

 

만약 반지름이 1일 경우,

 

Sin θ = 높이 / 빗변

Cos θ  = 밑변 / 빗변

 

여기서 빗변은 1로 정해졌기에

 

Sin θ = 높이

 

Cos θ  = 밑변

 

이 됩니다.

 

이는 즉 Sin θ은 X축 값이 되고 

 

Cos θ은 Y축 값이 됩니다.


역삼각함수는 각 변의 비율을 통해서 각을 구하기 위해서 사용합니다.

 

이를 위해 활용하여 상대방이 시야에 존재하는지를 확인합니다.

 

   private void CheckAlert()
   {
       colliders = Physics2D.OverlapCircleAll(transform.position, radius, targetMask);

       if (colliders.Length > 0)
       {
           foreach (var enemy in colliders)
           {
               Vector3 enemyDir = (enemy.transform.position - transform.position).normalized;
               float dot = Vector2.Dot(transform.up, enemyDir);
               float theta = Mathf.Acos(dot) * Mathf.Rad2Deg;
               if (theta <= fov * 0.5f)
               {
                   animator.SetBool(blinking, true);
               }
               else
               {
                   animator.SetBool(blinking, false);
               }
           }
       }
       else
       {
           animator.SetBool(blinking, false);
       }
   }

 

 

먼저 상대방의 방향을 알 필요가 있습니다.

 

Physics2D.OverlapCircleAll(transform.position, radius, targetMask);

 

Physics2D.OverlapCircleAll을 이용하여 radius 만큼의 충돌 탐색 범위를 만듭니다.

 

그 후 

 

if (colliders.Length > 0)

 

즉 충돌 대상이 존재할 경우

 

Vector3 enemyDir = (enemy.transform.position - transform.position).normalized;

 

상대방의 위치에서 플레이어의 위치를 뺀 후, 해당 값을 normalized 처리하여 방향을 구합니다.

 

 

float dot = Vector2.Dot(transform.up, enemyDir);

 

그 후 적(빨강)과 플레이어의 방향을 Vector2.Dot을 이용하여 내적

 

즉, 스칼라 값을 구합니다.

 

 

그 후

 

float theta = Mathf.Acos(dot) * Mathf.Rad2Deg;

 

Acos를 이용하면 θ의 radian 값이 나오게 됩니다.

 

 마지막으로 해당 값의 각도를 알아야 하기 때문에 Mathf.Rad2Deg를 곱해주면

 

플레이어와 적 사이의 각도를 알 수 있게 됩니다.

 

그 후 시야각을 나태내는 변수 Fov를 기준으로 Fov값의 절반보다 적을 경우

 

Enemy는 플레이어의 시야각 내에 있는 것이며

 

더 클 경우는 시야각의 밖에 있는 것으로 처리할 수 있습니다.

 


 

만약 유니티 내에서 시각적으로 해당 각도를 보면서 하고 싶다면

 

OnDrawGizmos() Life Cycle을 이용하여

 

기즈모로 표현할 수도 있습니다.

 

 

 

    private void OnDrawGizmos()
    {
        DrawRadius();
        DrawSight();
    }

    private void DrawSight()
    {
        Gizmos.color = Color.blue;
        Vector3 lBound = BoundaryAngle(-fov * 0.5f);
        Vector3 rBound = BoundaryAngle(fov * 0.5f);

        Gizmos.DrawRay(transform.position + transform.up, lBound );
        Gizmos.DrawRay(transform.position + transform.up, rBound );
    }

    private void DrawRadius()
    {
        Gizmos.color = Color.red;
        Vector3 start = transform.position;
        int segmentCount = 64;
        for (int i = 0; i <= segmentCount; i++)
        {
            // 원의 각도를 계산 (라디안 단위)
            float angle = i * Mathf.PI * 2 / segmentCount;
            // X와 Y 좌표를 계산하여 원의 한 점을 정의
            Vector3 point = new Vector3(Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius, 0) + start;
            // 다음 점을 계산
            Vector3 nextPoint = new Vector3(Mathf.Cos((i + 1) * Mathf.PI * 2 / segmentCount) * radius, Mathf.Sin((i + 1) * Mathf.PI * 2 / segmentCount) * radius, 0) + start;
            // 현재 점에서 다음 점으로 선을 그림
            Gizmos.DrawLine(point, nextPoint);
        }
    }

 

오늘의 회고

 

이렇게 하여 오늘은 플레이어의 시야각을 구현해봤습니다.

 

벡터의 내적, 외적(아직 안다뤘습니다) 등은 유니티에서 다양한 기능을 구현하는데 많이 사용된다고 합니다.

 

게임 기능적인 부분 외에도 쉐이더의 빛의 Normal Vector의 방향에 따른 빛과 그림자의 처리 등을 구할 때

 

백터의 내적을 사용하기 한답니다.

 

아직 100% 마스터를 하지 못했지만, 백터의 내적과 외적을 좀 더 공부하여

 

다양한 기능들을 구현해보고자 합니다...