유니티를 사용하다보면 모든 오브젝트가 기본적으로
Transform 컴포넌트를 사용한다는 것을 알 수 있을 겁니다.
Transform 컴포넌트는 오브젝트의 위치, 회전, 크기에 대한 데이터를 가지고 있습니다.
오브젝트에 빠질 수 없는 기능이기 때문에 임의로 삭제도 불가능합니다.
위치는 x, y, z로 구성됩니다.
Vector3 클래스를 통해 게임 오브젝트의 위치 이동을 구현할 수 있습니다.
[SerializeField] Transform target;
[Range(0f, 1f)]
[SerializeField] float rate;
[SerializeField] float moveSpeed;
// x축으로 10만큼 이동 시키기
transform.position = new Vector3(10, 0, 0);
// 방향으로 이동 시키기 (forward)
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
// 해당 목적지로 이동 시키기 (MoveTowards)
transform.position = Vector3.MoveTowards(transform.position, target.position, moveSpeed * Time.deltaTime);
// 보간을 통한 이동 (Lerp)
transform.position = Vector3.Lerp (transform.position, target.position, rate);
회전은 객체가 어느 방향으로 돌아있는지를 나타냅니다.
회전의 개념은 자세히 들어가면 상당히 어려운 개념입니다... 😵
아마도 수학과나 물리학과를 나오신 분들 아니라면 완전히 이해하는 사람은 드물거에요.
유니티는 내부적으로 보면 주로 쿼터니언(Quaternion)을 사용하여 회전을 표현하는데
포지션과 동일하게 x, y, z축으로 보여질 수 있습니다.
하지만 내부에서는 4차원 복소수로, 3차원 공간에서의 회전을 부드럽고 효율적으로 표현이 가능합니다!
즉, 쿼터니언은 4개의 성분(x, y, z, w)으로 구성됩니다.
이러한 특징으로 연산속도가 빠르고 3축을 사용했을 때의 짐벌락 현상을 방지하는 데 효과적입니다.
쿼터니언은 연산적으로 좋은 성능을 보여주지만 개념이 너무 어렵습니다... 😵
정말 다행히도 유니티에서는 3차원으로 쓰더라도 쿼터니언으로 변경하는
즉, 오일러(Euler)로 직접 변경하여 더 수월하게 사용이 가능합니다.
[SerializeField] Transform target;
[SerializeField] float rotateSpeed;
private void Update()
{
// 1. 회전 직접 지정 : Euler를 이용하여 Quaternion으로 변환하여 사용 권장
transform.rotation = Quaternion.Euler(0, 60, 0);
// 2. 축을 기준으로 회전
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
// 3. 지점을 기준으로 회전
transform.RotateAround(target.position, Vector3.up, rotateSpeed * Time.deltaTime);
// 4. 지점을 바라보도록 회전
transform.LookAt(target.position);
// 1-1 오일러를 쿼터니언으로 변환
Quaternion a = Quaternion.Euler(0, 60, 0);
// 1-2 쿼터니언을 오일러로 변환
Vector3 b = transform.rotation.eulerAngles;
// 1-3 방향벡터를 쿼터니언으로 변환
Quaternion c = Quaternion.LookRotation(Vector3.right);
// 1-4 현재 회전을 방향벡터로 변환
Vector3 d = transform.right;
}
짐벌은 한 축으로 중심으로 회전하는 구조물을 말하는데
3개의 원이 있다면 각 축을 가지고 있고 이를 통해 회전하는 방식인거죠.
짐벌락은 3축 가진 시스템이 특정 회전 상황에서 두 축만 남게 되어 사실상 2축으로 제한되는 현상을 말합니다. 즉, 회전하는 동안 짐벌 고리가 겹쳐지면서 세 축 중 두 축이 동일한 방향을 가리키게 되는 현상인거죠.
예를 들어, Y축을 기준으로 90도 회전한 후 X축을 기준으로 다시 90도 회전하면 Z축이 사라지는 현상이 발생할 수 있습니다.
이 현상은 게임에서 상당히 치명적인데 게임 내에서 카메라나 객체가 의도하지 않은 방향으로 회전하거나 움직이지 않게 되는 문제를 초래할 수도 있습니다.
크기는 말 그대로 객체의 크기를 나타내는 속성입니다.
각 축(x, y, z)에 대해 개별적으로 크기를 조절할 수 있습니다.
스케일 조정은 객체의 중심점을 기준으로 이루어집니다.
Vector3(2f, 2f, 2f)로 설정하면 모든 축이 중심점에서 바깥쪽으로 2배씩 증가합니다.
여기서 중요한 것은 부모 객체의 스케일이 자식 객체에 영향을 미칩니다.
즉, 부모 객체가 확대되면 자식 객체도 함께 확대됩니다.
// 크기 조절
Transform.localScale = new Vector3(x, y, z);
Transform.localScale = new Vector3(2f, 2f, 2f); // 모든 축을 2배로 확대
그리고 유니티에서는 lossyScale이라는 속성이 있는데 부모 객체들의 스케일이 누적되면서 발생하는 비틀림(skew) 현상 때문에 정확한 스케일을 표현하지 못하는 경우를 대비한 것으로
부모 오브젝트의 크기에 상관 없이 자신 고유의 Scale 수치를 반환합니다.
비틀림(skew) 현상은 부모 객체들의 스케일이 누적되면서 발생하는 스케일의 왜곡 현상으로
값을 산정하기 힘들기 때문에 lossyScale을 사용해 가장 근접한 값을 얻습니다.
물론 근삿값이라 정확한 값이 요구되는 프로그램에 사용하기에는 적절하지 않을 수도 있습니다.
// lossyScale
Transform.lossyScale // 부모 오브젝트의 크기에 상관 없이 자신 고유의 Scale 수치를 반환
월드 스페이스는 전체 장면을 기준으로 하는 좌표계를 말합니다.
게임에서 월드 스페이스를 보면 게임의 전체 장면을 나타내는 좌표계로
모든 게임 오브젝트는 이 월드 스페이스에 배치됩니다.
이 좌표계는 게임의 시작부터 끝까지 변하지 않으며,
모든 오브젝트들이 이 월드 스페이스 내에서 위치를 가지게 됩니다.
그럼 월드는 게임에서는 어떻게 활용될까요?
게임 맵에 배치된 나무, 건물 등은 월드 스페이스에 배치되어 게임의 전체 환경을 구성합니다.
카메라의 경우도 월드 스페이스를 기준으로 이동하며 다양한 시점을 제공합니다.
캐릭터가 벽에 부딪히거나 바닥에 떨어질 때의 상호작용 같은 물리 기반 상호작용도
월드 스페이스를 사용하여 오브젝트 간의 충돌을 감지하고 반응합니다.
로컬 스페이스는 개별 객체를 기준으로 하는 좌표계를 말합니다.
로컬 좌표계는 개별 객체의 상대적 위치를 나타내며,
객체 자체의 위치, 회전, 스케일을 기준으로 합니다.
이 개념은 부모-자식 관계에서 중요합니다!
자식 오브젝트의 로컬 스페이스는 부모 오브젝트의 위치를 기준으로 움직입니다.
자식 오브젝트의 로컬 포지션이 (0, 0, 0)이라면 부모 오브젝트의 위치와 동일하다는 것을 의미합니다.
그럼 로컬은 게임에서는 어떻게 활용될까요?
자동차를 만든다고 했을 때도 여러 객체를 상속을 이용하므로 로컬 스페이스를 가집니다.
즉, 자동차의 바퀴, 본체, 창문 등이 각각의 로컬 좌표계에서 정의됩니다.
그러면 바퀴, 본체, 창문이 각각 따로 움직이는 것이 아닌 융합된 한 객체로 움직입니다.