이 튜토리얼은 Unity에서 한 오브젝트가 다른 오브젝트를 향하도록 만드는 방법에 대한 두 개의 강의 중 첫 번째입니다. 구체적으로는 감시 카메라의 3D 모델이 씬 안에서 Player의 움직임을 따라 자동으로 해당 방향을 향하도록 만드는 방법을 살펴보겠습니다.
이 튜토리얼은 한국어 음성을 제공하지 않습니다. 아래에서 영어 음성으로 된 영상을 한국어, 영어 및 기타 언어의 자막과 함께 시청하실 수 있습니다. 영상 아래에는 한국어로 번역된 스크립트가 있습니다.
영상 자막 텍스트
안녕하세요 여러분!
이 튜토리얼은 Unity에서 한 오브젝트가 다른 오브젝트를 향하도록 만드는 방법에 대한 두 개의 강의 중 첫 번째입니다. 구체적으로는 감시 카메라의 3D 모델이 씬 안에서 Player의 움직임을 따라 자동으로 해당 방향을 향하도록 만드는 방법을 살펴보겠습니다.

이 튜토리얼은 Unity 2022를 사용하여 제작되었으며 두 부분으로 구성되어 있습니다.
첫 번째 파트에서는 문제를 분석하고 Rotate 메서드를 사용하여 카메라를 즉시 회전시키는 방법을 다룹니다.
두 번째 파트에서는 쿼터니언과 Slerp 메서드를 사용하여 더 부드럽고 점진적인 회전을 구현할 것입니다.
구현으로 넘어가기 전에 먼저 해결해야 할 문제를 분석해 보겠습니다.
카메라는 자신의 로컬 수직 축을 기준으로만 회전할 수 있으므로, 문제를 2D 평면으로 단순화할 수 있습니다. 이 부분을 더 이해하기 쉽게 하기 위해, 저는 씬을 TOP 뷰에서 Orthogonal 모드로 보고 있습니다.

감시 카메라의 3D 모델과 Player는 모든 게임 오브젝트와 마찬가지로, 씬 내 위치를 나타내는 XYZ Position 좌표를 가지고 있습니다.
카메라는 로컬 수직 축을 기준으로만 회전하므로 position.y 좌표는 고려하지 않습니다. 따라서 두 오브젝트가 동일한 y 값을 가지는 같은 평면 위에 있다고 가정하고 x와 z 좌표만 고려하겠습니다.
또한 이 경우 감시 카메라를 기준 좌표계의 원점, 즉 데카르트 좌표계의 0,0 위치에 해당하는 지점으로 간주할 수 있습니다.
뿐만 아니라, 카메라가 바라보고 있는 방향, 여기서는 로컬 음수 Y축에 해당하는 방향을 데카르트 좌표계의 한 축으로 간주할 수 있습니다.
이제 가상의 카메라와 Player의 위치를 연결하는 방향을 정의할 수 있습니다. 이것은 단순한 벡터이며, 이 경우 GLOBAL X와 Z의 두 차원으로만 고려합니다.
따라서 감시 카메라에 적용해야 할 회전은, 해당 에셋의 현재 방향, 즉 로컬 음수 Y축과 감시 카메라에서 Player로 향하는 이상적인 벡터 사이의 각도입니다. 이 각도를 구하면 로컬 수직 축을 기준으로 3D 모델에 적용해야 할 회전값을 얻을 수 있습니다.

다행히도 이를 계산해 주는 수학 함수가 있습니다. 두 개의 인자를 받는 아크탄젠트 함수로, Mathf의 Atan2 메서드로 구현되어 있습니다. 이 함수는 두 점의 좌표 차이로 구성된 방향 벡터를 매개변수로 받아 바로 이 각도를 계산합니다.
구체적으로 Atan2는 데카르트 평면의 X축과 원점에서 목표 지점으로 향하는 방향 벡터 사이의 각도를 계산합니다.
우리의 경우, 원점은 감시 카메라 3D 모델의 X와 Z 좌표입니다. 따라서 목표 좌표 x, y는 감시 카메라와 Player 사이의 좌표 차이가 되며, X축은 감시 카메라의 로컬 음수 Y축과 일치합니다.
이 함수는 두 매개변수를 역순으로 받습니다. 데카르트 평면에서는 atan2(y, x) 형태입니다. Unity의 좌표계에서는 x축이 좌우 방향, z축이 전후 방향, y축이 수직 방향이며 여기서는 y축을 고려하지 않으므로 atan2(x, z) 형태로 작성합니다.
Unity 공식 문서에 따르면 Mathf.Atan2는 y와 x 좌표를 받아 y/x의 탄젠트를 가지는 각도를 라디안 값 float으로 반환합니다.

Unity의 Rotate 함수는 각도를 도 단위로 요구하므로, 코드를 작성할 때 단위 차이와 필요한 변환을 고려해야 합니다.
이제 문제와 해결 방법을 분석했으므로 첫 번째 구현으로 넘어가겠습니다. Rotate를 사용한 즉시 회전입니다.
먼저 감시 카메라에 연결할 새 C# 스크립트를 생성하고 이름을 LookAtObject로 지정합니다. 여기서 실제 감시 카메라라고 말하는 이유는 이 에셋이 벽에 고정되는 베이스, 피벗 요소, 그리고 카메라의 세 부분으로 구성되어 있기 때문입니다.
우리가 원하는 것은 카메라가 자신의 로컬 수직 축을 기준으로 회전하는 것입니다.
Unity의 가상 공간에서 GLOBAL 수직 축은 y축이지만, 각 오브젝트는 자체적인 로컬 좌표계를 가질 수 있습니다. 감시 카메라 3D 모델의 경우, Unity 에디터에서 LOCAL 표시 모드로 전환하면 확인할 수 있듯이 로컬 수직 축은 Z축입니다.

이 차이점은 프로젝트에 약간의 복잡성을 추가합니다. 씬의 글로벌 평면에서 X와 Z 좌표를 사용하여 회전 각도를 계산한 다음, 에셋의 로컬 수직 축을 기준으로 회전을 적용해야 합니다.
먼저 Inspector에서 Player와 연결할 Transform 변수 objectA를 생성하여 추적할 오브젝트에 대한 참조를 만듭니다.
[SerializeField] private Transform objectA;

이제 Update로 이동하여 앞서 설명한 해결 방법을 구현합니다. 카메라의 로컬 수직 축은 Z축이라는 점을 기억하세요.
필요한 코드는 세 줄입니다.
첫 번째 줄에서는 카메라의 현재 방향과 목표 방향 사이의 각도를 계산합니다.
두 번째 줄에서는 원하는 방향으로 맞추기 위해 카메라의 로컬 Z축을 기준으로 회전해야 할 각도를 계산합니다.
세 번째 줄에서는 Rotate 메서드를 사용하여 실제 회전을 수행합니다. 이 메서드에는 로컬 수직 벡터와 해당 벡터를 기준으로 회전할 각도를 전달해야 합니다.
먼저 Mathf의 Atan2 함수를 사용하여 두 방향 사이의 각도를 계산합니다. 이 함수는 라디안 값을 반환하므로 도 단위로 변환합니다.
2D 평면에서 Atan2는 두 점의 Y 좌표 차이와 X 좌표 차이를 매개변수로 받습니다. 목표와 관찰자 순서입니다.
Unity의 글로벌 좌표계에서 X와 Z가 2D 평면을 이루고 GLOBAL Y가 수직 축이므로 다음과 같이 작성합니다.
float theta = Mathf.Atan2((objectA.transform.position.x - transform.position.x), (objectA.transform.position.z - transform.position.z)) * Mathf.Rad2Deg;
이제 두 번째 줄로 넘어갑니다. 도 단위로 회전해야 할 각도를 계산합니다.
float deltaAngle = Mathf.DeltaAngle(transform.eulerAngles.z, theta);
여기서 transform.eulerAngles.z는 감시 카메라의 현재 방향이고, theta는 이전 단계에서 계산한 목표 방향입니다.
마지막 세 번째 줄에서는 실제로 감시 카메라를 회전시킵니다.
transform.Rotate(Vector3.forward, deltaAngle);
여기서 회전 기준 벡터로 Vector3.forward를 사용했습니다. 이 3D 모델에서는 로컬 Z축이 전후 방향이 아니라 로컬 수직 축이기 때문입니다.
스크립트를 저장한 후 Unity 에디터로 돌아가 Play를 눌러 결과를 확인합니다.
참고로 감시 카메라가 목표를 향하지 않는다면 Inspector에서 감시 카메라의 초기 XYZ Rotation 값이 모두 0으로 설정되어 있는지 확인한 후 다시 시도해 보세요.


정리하자면, 이번 첫 번째 파트에서는 문제를 분석하고 두 오브젝트를 동일한 평면에서 고려하는 방법, 방향 벡터를 계산하는 방법, 그리고 감시 카메라의 로컬 좌표계를 고려하여 이를 적용하는 방법을 살펴보았습니다.
그 다음 Rotate 함수를 사용하여 매 프레임마다 Player를 향하도록 즉시 회전을 적용했습니다.
다음 파트에서는 회전 쿼터니언을 계산하고 Slerp 함수를 사용하여 오브젝트를 더 천천히 회전시키는 방법을 살펴보겠습니다.