7. Mathf.Atan2에 대해 알아보자

Mathf.Atan2란?

Unity에서 제공하는 static class, Mathf 에서 제공하는 static method Atan2를 설명한다.
인수로 들어온 x와 y의 값으로 아크 탄젠트 함수를 이용해 연산해서 결과값을 라디안 값으로 반환하는 메소드이다.

2가 붙은 이유는, 본래 Atan이라고 y/x 값을 실수로, 인수를 하나만 받는 메소드가 있는데 여기서는 0으로 나누게 되면 에러가 나는걸 캐치할 수 없어서 x와 y값을 따로 받아서 메소드 내부에서 0으로 나누지 못하게 처리해주는 메소드이다. 즉, 인수가 2개. 그래서 Atan'2'.

왜 이게 필요할까?

우리는 종종 특정 오브젝트가 타 오브젝트를 바라보도록 해야할 때가 있다. 보통 이럴때 삼각함수가 필요하다고 많은 꼬꼬마 프로그래머분들이 알고 있을 것이다.
여기서, 탄젠트 함수는 tan(r) = y/x 라는 값을 도출해낼 수 있는 것을 알고 계시는 분들이 많을 것이다. 몰랐다면 이제 알면 된다. x분의 y는 탄젠트 r이다.

그럼, 여기서 r은 어떻게 구해야하는걸까?
고등학생 시절에 배운 역함수를 여기서 이용할 수 있다.
tan(r) = y/x 를 이용해보자. 그럼 탄젠트의 역함수를 이용하면,
tan^-1(y/x) = r 이라는 값을 도출해낼 수 있을 것이다.
바로, 이 탄젠트의 역함수가 아크 탄젠트 이다.

그것을 유니티에서는 Atan 혹은 Atan2 라는 이름의 메소드로 제공한다. 이제 무슨 이야긴지 알겠죠?

바로, 오브젝트간의 각도 를 알아낼 때 이용할 수 있습니다.

제대로 된 형태를 알아보자!

실제로 유니티에서 제공하는 Atan2의 형태는 다음과 같습니다.

Mathf.Atan2(float y, float x);

우리가 이용할 때 또한 대충 비슷합니다.

float rad = Mathf.Atan2(뭐시기좌표.y, 뭐시기좌표.x);

이런식으로 받아오면 됩니다.
어? 그런데 문제가 하나 있네요?

유니티에서 제공하고 있는 아크탄젠트 함수는 반환값이 라디안값이에요!

라디안이 뭐길래?

위키백과 - 라디안
우리는 지금까지 각도를 부를때 n° 라고 부르며, '도' 단위를 이용했다.
이를 육십분법이라 하는데, 우리는 이제부터 '라디안' 이라는 새로운 단위를 배워보고자 한다.

라디안을 단위로 쓰는 방식은 '호도법' 이라고 부르는데, 바로 호를 이용한 각도를 표기하는 방법을 뜻한다.
먼저, 반지름이 r인 원에서 호의 길이가 반지름과 같은 호 AB를 그려놓고 그 각을 a°라고 해보겠다.
그림은 다음과 같다.

그림판으로 메다닥 그려서 그림이 지나가던 유치원생이 그려준 것 같은 퀄리티는 독자분들이 양해 해주세요 ㅎㅎ;

아무튼 이때, 호의 길이는 중심각에 비례하므로 다음과 같은 비례식을 만들 수 있습니다.
360° : 2πr = a° : r 중학 수학을 배운 사람들이라면 이정도 비례식은 이해하실 수 있을겁니다. 왜냐면 수학 9등급인 저도 이해할 수 있으니, 여러분도 가능할겁니다.

이때, 는 반지름의 길이와 관계없이 언제나 일정한 '상수값'을 갖게 된다.
위의 비례식을 이용해 다음과 같은 식을 만들 수 있다.
a° = 360*r/2πr ... a° = 180/π
즉, 'a°' 라는 이 각도는 180/π 라는 상수값을 가지게 되는데, 이 값을 1 라디안 이라고 한다.
이를 통해 다음과 같은 식을 만들어낼 수 있다.
1 radian = 180°/π, 1° = π/180 radian 이 식을 통해 우리는 호도법으로 계산한 값과 육십분법으로 계산한 값을 자유자재로 바꿀 수 있다.

이제 우리는 라디안으로 계산한 값을 반환한 Atan2의 반환값을 이용할 수 있는 지식을 얻게됐습니다.

다시 한번, 어떻게 이용하는데?

기본적으로 유니티에서는 회전을 표현할 때 Quaternion 을 이용하는데, 이 내용까지 다루기엔 필자의 지식 또한 깊지않으며 이 내용을 쉽게 이해하기도 어려우니 이 내용은 스킵하고 설명하겠다.

어쨌거나 유니티에서는 쿼터니언, 사원수를 이용하는데, 각 오브젝트의 Transform에서는 rotation이라는 프로퍼티를 갖고있고, 이 프로퍼티 또한 Quaternion 객체이다. 그런데, 이 사원수를 우리가 그대로 이용하기엔 아직 지식이 깊지 않고, 또한 사원수는 일반적인 개발자가 알아보기에 직관적이지 못한 모습을 보여주기도 한다.

그래서 우리는 Vector3 의 형태로 이루어진 값을 사원수로 바꿀 예정이다. 어떻게? 바로 오일러 각(Euler Angle) 을 이용할 것이다.

오일러 각은 회전한 강체의 회전을 나타내는 방법 중 하나인데, 쉽게 설명하면 x/y/z 각각의 각마다 회전 축이 정해져있고, 이를 순서대로 x축은 a°만큼 돌리고, y축은 b°만큼 돌리고, z축은 c°만큼 돌리는 방법을 이야기한다.

방식이 이렇다는거고, 결론적으로 유니티에서 이걸 또 제공한다.
바로 다음과 같은 형태를 갖고있다.

Quaternion.Euler(float x, float y, float z)
Quaternion.Euler(Vector3 euler)

둘 중 어느 형태를 써도 같은 결과를 보장하니 크게 신경은 쓰지 않아도 된다.
해당 메소드를 이용하면, 인수에 입력된 값을 통해 회전 후 회전한 강체의 형태를 쿼터니언의 형태로 반환해준다. 결론적으로는 오일러 각 방식으로 회전한 값을 쿼터니언 값으로 반환한다 이거지.

예를 들어 x축으로 90도를 돌리려고 할 때, 대충 다음과 같은 방식으로 쓸 수 있습니다.

transform.rotation = Quaternion.Euler(90, 0, 0);

뭐 그런거죠. 오일러 각이니 쿼터니언이니 이해가 잘 안된다면, 그냥 저 메소드에 입력된 도 만큼 회전해서 반환해주니까 그걸 써라 정도만 이해해도 됩니다. 공식처럼요. 잘 몰라도 어떻게 쓰는지만 알면 당장은 이용할 수 있겠죠.

직접 써보자!

자, 먼저 문제 상황을 제시해보겠습니다.

이 사진은 제가 저번 2020 인디게임위크엔드에서 개발한 EmoGarden 이라는 게임의 스크린샷인데요, 저기 얼굴처럼 보이는 녀석은 터치하는 방향으로 입의 방향을 돌리며 이동합니다. 바로 이럴 때 이용하는거죠.
바로 터치한 곳으로 얼굴을 돌려야합니다. 이걸 아까 설명한 Atan2, Quaternion.Euler 등등을 이용해서 구현해보겠습니다!

먼저 터치한 곳의 좌표를 받아와야 합니다.
물론 지금 얘가 중요한건 아니니까, 대충 간단하게만 코드로 보여드리겠습니다.

var target = Camera.main.ScreenToWorldPoint(Input.mousePosition);

이런식으로, 터치한 곳의 좌표 자체는 Input.mousePosition 으로, 이게 화면상의 좌표이므로 얘를 유니티의 좌표계로 옮기는 메소드인 Camera.main.ScreenToWorldPoint 를 이용해서 터치한 곳의 좌표를 받아왔습니다.

다음 코드는 얘가 뭐지? 싶으실텐데, 바로 돌아간 얼굴이 너무 많이 돌아가면... 방향이 이상해집니다.

바로 이딴식으로요. 그래서 스프라이트를 뒤집어줄거에요.
물론 코드는 실제로 다음과 똑같은 코드를 이용하진 않았습니다. 우리 게임에 맞춰 조금 다르게 구현해뒀는데, 궁금하신 분은 EmoGarden의 깃허브 저장소에서 확인하시기 바랍니다. ㅎ.ㅎ

Vector2 dir = target - transform.position;
var usingFlip = transform.position - target;
            
var scale = transform.localScale;
if (usingFlip.x > 0)
{
    scale.x *= -1f;
}
transform.localScale = scale;

만약 플레이어의 위치가 마우스 위치보다 오른쪽, 즉 바로 위의 그림과 같은 상황일 때라면 스프라이트의 x축을 한번 뒤집어줍니다. 그러면 원래 의도한 모양으로 바라보게 될거에요.

중요한 내용은 여기입니다. 여기서 바로 얘를 '진짜로' 돌려줄거거든요.

float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);

필요없는 코드를 상당 수 쳐냈습니다.
여러분에게는 여러분이 알 필요가 있는 정보만 전달해야하니까요.

결론만 보자면 위 두줄이 핵심입니다.
dir은 위~쪽에서 잘 보셨다면 아시겠지만, target - transform.position 의 결과값입니다. 마우스 좌표의 위치에서 현재 저녀석의 머리 위치를 뺀거라고 보시면 됩니다. 그럼 마우스를 보고있는 방향의 Vector3 값을 구하게 됩니다.

그리고 구한 Vector의 y값과 x값을 Atan2 메소드에 넣어준 뒤, 반환값에 Mathf.Rad2Deg 라는 값을 곱해줍니다. 어? 이녀석은 아까 설명 안했는데 뭐냐구요?

바로 라디안 값으로 반환된 Atan2의 결과값에, 저 Rad2Deg라는 상수값을 곱해서 육십분법, 즉 '도' 로 나타내주는겁니다.

그리고 Quaternion.AngleAxis 라는 녀석도 나타났습니다. 오늘 여러분이 상당히 머리가 아플거에요. 얘는 쉽게 이야기하면, 뒤에 들어간 Vector3 값을 angle만큼 돌린 뒤 그 형태를 Quaternion 값으로 반환해주는 메소드입니다.

Vector3.forward 는 Vector3 클래스에서 제공하는 readonly 프로퍼티 중 하나인데, 내부적으로는 (0,0,1) 의 형태를 가지고 있습니다. 즉 얘를 돌리면 z축을 기준으로 돌려서 반환하는겁니다.

이렇게 z축이 돌아간 값을 transform.rotation에 그대로 대입해줍니다.
그럼 끝!
사실 EulerAngle은 안썼네요. 실제로는 필요없거든요. 그냥 제 코드에서 있길래 열심히 다시 공부해와서 써넣었는데, 필요없는걸 쳐내다보니까 실제로는 안써도 되는 문제였어요.

결론적으로, 이렇게 Atan2를 이용해 각도를 알아내고, 그 각도만큼 AngleAxis로 z축을 돌려서 스프라이트의 방향을 돌려준다~ 가 되겠습니다.

정리해보겠습니다!

정리

지금껏 알아본 Atan2 는 다음과 같습니다.

  1. 탄젠트의 역함수이다.
  2. 이를 이용해 각 오브젝트간의 각도를 알아낼 수 있다.
  3. 특정 오브젝트를 바라보게 하는 등에 이용할 수 있다.

또한, 이를 이용해 마우스의 위치를 바라보게 하는 과정은 다음과 같았습니다.

  1. 마우스의 위치를 받아온다.
  2. 마우스 위치 - 내 위치를 계산해서 저장한다.
  3. 계산한 값은 Vector3 로 저장되는데, 저장된 y값과 x값을 Atan2 에 넣어 각도를 알아낸다.
  4. Quaternion.AngleAxis 를 이용해 구한 각도만큼 z축을 돌려서 transform.rotation 에 저장한다.
  5. ???
  6. profit!

마치며

온갖 개념과 함께, Atan2에 대해 알아보았습니다.
게임 공부하다보면 삼각함수 삼각함수 하는데 이걸 게임 개발을 하며 처음 써보는 초보 개발자분들이 많을겁니다. 저도 그랬거든요.
미약하고 조금 낮다고 할지도 모르는 수준의 이 글이 다른 개발자분들께 도움이 됐으면 좋겠습니다.

설명하다보니 너무 많은 내용이 들어가 조금 난잡한 글이 되었는데, 사실 이 글을 쓰기 전에 Euler Angle이라던가 Quaternion에 대해 먼저 정리한 후 해당 글을 참고하라며 링크만 남겨뒀더라면 훨씬 좋았을 것 같다는 생각이 들어 급 후회중입니다. 다음에는 더 나은 퀄리티와 읽기 좋은 코드, 알기 쉬운 내용으로 함께 찾아뵙겠습니다.

고등학생 게임 개발자 김선민이었습니다.

좋은 웹페이지 즐겨찾기