작성일 :

GameObject에서 Transform으로

Unity 엔진 핵심 (1) - GameObject와 Component에서 Unity의 컴포지션 아키텍처를 다루었습니다. GameObject는 빈 컨테이너이고, Component를 붙여서 기능을 부여합니다. 모든 GameObject에 반드시 존재하는 유일한 필수 컴포넌트가 Transform입니다.


Transform은 위치/회전/크기 정보를 저장하는 역할에 그치지 않습니다. 부모-자식 관계를 정의하고, 좌표 변환을 전파하며, 씬 전체의 계층 구조를 형성합니다. Transform의 동작 방식을 이해하면 렌더링, 물리, 애니메이션에서 발생하는 비용의 원인을 파악하는 데 도움이 됩니다.


이전 글에서 GetComponent의 비용이 컴포넌트 목록의 선형 검색에서 비롯됨을 확인했습니다. Transform에도 비슷한 비용 구조가 있습니다. Transform의 position을 한 번 변경하면, 내부적으로 모든 자식의 좌표 재계산과 물리/렌더링 시스템 통지가 연쇄적으로 일어납니다. 이 비용은 계층 구조의 깊이와 자식 수에 따라 커집니다.


이 글에서는 Transform 컴포넌트의 세 가지 속성(위치, 회전, 스케일)부터 시작하여, 로컬/월드 좌표 체계, 부모-자식 관계, 씬 그래프 구조, Transform 변경 시 발생하는 전파 비용, 그리고 이 비용을 줄이는 최적화 방법까지 살펴봅니다.


Transform 컴포넌트

세 가지 속성

Transform 컴포넌트는 세 가지 속성을 가집니다: 위치(position), 회전(rotation), 스케일(localScale).


Transform position Vector3 (x, y, z) 3D 공간에서의 위치 rotation Quaternion (x, y, z, w) 3D 공간에서의 회전 상태 localScale Vector3 (x, y, z) 각 축 방향의 크기 비율


position은 오브젝트의 3D 위치입니다. Vector3(0, 0, 0)이면 원점에 위치하고, Vector3(3, 1, -2)이면 x축으로 3, y축으로 1, z축으로 -2만큼 떨어진 곳에 배치됩니다.


rotation은 오브젝트의 3D 회전 상태입니다. 회전을 표현하는 가장 직관적인 방법은 오일러 각도(Euler Angles)로, 도 단위의 x, y, z 회전 값 세 개를 순서대로 적용하는 방식입니다. 그러나 오일러 각도는 특정 조건에서 회전 자유도 하나가 사라지는 짐벌 락(Gimbal Lock) 문제가 있습니다.

Unity는 이 문제를 피하기 위해 rotation을 쿼터니언(Quaternion) 형태로 저장합니다. 쿼터니언은 4개의 float 값(x, y, z, w)으로 3D 회전을 표현하며, 회전을 4차원 단위 구 위의 한 점으로 나타냅니다. 오일러 각도와 달리 세 축을 순서대로 적용하지 않고 회전 전체를 하나의 값으로 표현하므로, 축 간 의존성이 없어 짐벌 락이 발생하지 않습니다. 보간(Slerp, Spherical Linear Interpolation)도 구면 위를 따라 일정한 각속도로 수행됩니다.

짐벌 락의 발생 메커니즘과 쿼터니언의 수학적 배경은 그래픽스 수학 (2) - 행렬과 변환에서 확인할 수 있습니다.


localScale은 각 축 방향의 크기 비율입니다. Vector3(1, 1, 1)이면 원본 크기이고, Vector3(2, 1, 1)이면 x축 방향으로 2배 늘어난 상태입니다. 이름에 “local”이 붙어 있는 것처럼, 스케일은 항상 부모 기준(로컬)으로 적용됩니다.


월드 기준 스케일인 lossyScale은 읽기 전용으로 제공됩니다. 부모가 비균등 스케일(예: x=2, y=1)을 가진 상태에서 자식이 회전하면, 한 축의 스케일이 다른 축으로 섞여 들어가 기울어짐(skew)이 발생합니다.

직사각형은 가로와 세로만으로 형태가 정해지지만, 평행사변형은 기울어진 각도까지 알아야 하는 것처럼, 기울어진 3D 형태도 x, y, z 스케일 값 세 개만으로는 온전히 담을 수 없습니다. 그래서 lossyScale은 이름 그대로 손실(lossy)이 있는 근삿값만 돌려줍니다.


로컬 좌표 vs 월드 좌표

두 가지 좌표 체계

position과 rotation은 각각 두 가지 좌표 체계로 표현됩니다. 로컬 좌표(Local Coordinates)월드 좌표(World Coordinates)입니다.


로컬 좌표와 월드 좌표 y x 월드 원점 (0, 0, 0) 부모 월드 위치: (10, 0, 0) 자식 월드 위치: (10, 3, 0) 로컬 위치 (0, 3, 0) x = 10

localPosition은 부모 오브젝트를 기준으로 한 상대적 위치입니다. 부모의 위치가 (10, 0, 0)이고 자식의 localPosition이 (0, 3, 0)이면, 자식은 부모로부터 y축 방향으로 3만큼 떨어진 위치에 있습니다.

position(월드 좌표)은 씬의 절대 원점을 기준으로 한 위치입니다. 위 예시에서 자식의 월드 위치는 (10, 3, 0)이 됩니다. 부모에 회전이나 스케일이 없으면 부모 축과 월드 축의 방향이 같으므로, 월드 위치는 부모 위치 (10, 0, 0)에 로컬 위치 (0, 3, 0)을 더한 값과 같습니다.

부모에 회전이 적용되면 부모 축과 월드 축의 방향이 달라집니다. 부모가 y축으로 90도 회전하면 부모의 앞쪽(z축)이 월드의 x축을 가리키게 되므로, 자식의 로컬 오프셋 (0, 0, 5)는 월드 기준으로 x축 방향 5만큼의 위치가 됩니다. 부모에 스케일이 적용되어도 오프셋의 크기가 변합니다. 이처럼 부모의 회전과 스케일을 로컬 오프셋에 반영해야 정확한 월드 위치를 얻을 수 있으며, 이를 한 번에 처리하기 위해 행렬 곱셈이 사용됩니다.


회전도 같은 구조입니다. localRotation은 부모 기준의 상대 회전이고, rotation은 월드 기준의 절대 회전입니다. 부모에 회전이 없으면 localRotation과 rotation이 동일합니다.

부모가 y축으로 30도 회전한 상태에서 자식의 localRotation이 y축 45도이면, 자식의 월드 회전은 y축 75도입니다. 같은 축의 회전이므로 각도의 덧셈과 같은 결과이지만, 서로 다른 축이 섞이면 단순 덧셈이 성립하지 않습니다. 위치에서 부모의 회전과 스케일을 반영하기 위해 행렬 곱셈이 필요했던 것처럼, 회전의 합성에는 쿼터니언 곱셈이 사용됩니다.


부모 변환의 전파

로컬 좌표와 월드 좌표가 부모를 기준으로 연결되어 있으므로, 부모의 Transform이 변하면 자식의 월드 좌표도 함께 변합니다. 자식의 localPosition은 그대로이지만, 부모가 이동했으므로 씬 원점 기준의 절대 위치인 월드 좌표가 달라집니다.


부모 이동에 따른 자식의 월드 좌표 변화

항목 변경 전 변경 후 비고
부모 월드 위치 (10, 0, 0) (20, 0, 0) 변경
자식 로컬 위치 (0, 3, 0) (0, 3, 0) 변하지 않음
자식 월드 위치 (10, 3, 0) (20, 3, 0) 자동으로 재계산


이동이 전파되면 자식의 위치만 바뀌지만, 회전이 전파되면 위치와 방향이 동시에 바뀝니다. 부모를 중심으로 자식의 월드 위치가 호를 그리며 이동하고, 자식이 바라보는 방향도 부모의 회전만큼 함께 돌아갑니다.


부모 회전에 따른 자식의 위치 변화 부모 위치: (0, 0, 0) · 자식 로컬 위치: (0, 0, 5) · 부모가 y축으로 90도 회전 변경 전 z x 부모 (0, 0, 0) 자식 (0, 0, 5) 변경 후 z x 부모 (0, 0, 0) 자식 (5, 0, 0) → 자식의 로컬 위치 (0, 0, 5)은 변하지 않음 → 부모의 y축 90도 회전에 의해 z축 오프셋이 x축으로 전환됨 → 월드 위치가 (0, 0, 5) → (5, 0, 0)로 변환됨


부모의 스케일도 자식에 전파됩니다. 부모의 localScale이 (2, 2, 2)이면 자식의 크기가 2배가 되고, 부모와의 거리도 2배로 늘어납니다. 모든 축의 스케일이 동일한 균등 스케일이면 자식의 비율이 유지됩니다.

부모의 스케일이 비균등(Non-uniform)하면 자식의 비율이 깨집니다. x만 2배이고 y, z가 1배인 경우, 정육면체는 직육면체로, 구는 타원체로 변형됩니다.

비균등 스케일은 물리 충돌에도 영향을 줍니다. SphereCollider나 CapsuleCollider는 균등 스케일을 전제로 설계되어, 비균등 스케일에서는 충돌 판정이 실제 메시 형태와 어긋납니다.

조명 계산에서도 문제가 생깁니다. GPU는 메시 표면의 법선(노멀) 벡터를 월드 공간으로 변환해 조명을 계산하는데, 균등 스케일에서는 모델 행렬의 상위 3×3 부분을 그대로 곱해도 노멀 방향이 유지되지만, 비균등 스케일에서는 노멀이 표면에 대해 비스듬해져 조명이 부정확해집니다. 이를 보정하려면 모델 행렬의 역전치 행렬(Inverse Transpose Matrix)로 노멀을 변환해야 합니다. Unity는 이 행렬을 셰이더 내장 변수(unity_WorldToObject의 전치)로 제공하므로 렌더링은 자동 보정되지만, 물리 충돌체의 부정확성은 해결되지 않으므로 비균등 스케일은 가능하면 피하는 편이 좋습니다.


부모-자식 관계

부모-자식 관계는 하이어라키 창에서 드래그 앤 드롭으로 설정할 수도 있고, 코드에서 직접 설정할 수도 있습니다.

SetParent

코드에서는 Transform.SetParent(Transform parent)로 부모를 지정합니다.


SetParent의 동작 weapon.transform.SetParent(hand.transform); 설정 전 캐릭터 └ 몸통 └ 팔 └ 손 무기 (독립) 설정 후 캐릭터 └ 몸통 └ 팔 └ 손 └ 무기 ← 손의 자식으로 부착 → 손이 움직이면 무기도 함께 움직임 → 팔이 회전하면 손과 무기도 함께 회전


캐릭터의 손에 무기를 부착하는 것이 대표적인 예시입니다. 무기를 손의 자식으로 설정하면, 캐릭터가 팔을 휘두를 때 무기도 자연스럽게 따라 움직입니다. 무기의 위치를 매 프레임 수동으로 계산할 필요가 없습니다.


다만 SetParent는 가벼운 연산이 아닙니다. 호출하면 기존 부모의 자식 목록에서 제거하고 새 부모의 자식 목록에 추가하는 계층 재구성이 일어나고, worldPositionStays가 true이면 월드 좌표를 유지하기 위해 새 부모의 역행렬로 로컬 좌표를 역산합니다. 이 계층 변경은 TransformChangeDispatch를 통해 물리·렌더링 등 관련 시스템에도 전달되므로, 매 프레임 호출하면 재구성·역산·통지가 반복됩니다. 따라서 한 번 설정하고 유지하는 방식이 일반적입니다.


SetParent의 두 번째 매개변수

SetParent(parent, worldPositionStays)의 두 번째 매개변수는 월드 좌표를 유지할지 여부를 결정합니다.


true(기본값)이면 월드 좌표를 유지합니다. 부모가 바뀌더라도 오브젝트가 씬에서 같은 위치, 같은 회전, 같은 크기로 보입니다. 자식의 월드 좌표는 부모의 월드 행렬에 자식의 로컬 좌표를 곱한 결과이므로, 부모가 바뀌었는데 월드 좌표를 유지하려면 새 부모 기준의 로컬 좌표를 거꾸로 구해야 합니다. 이 역산에 새 부모 월드 행렬의 역행렬이 사용되어 localPosition, localRotation, localScale을 새로 계산합니다. 새 부모에 회전이나 스케일이 없으면 단순 뺄셈과 같지만, 회전이나 스케일이 있으면 역행렬 연산이 필요합니다.


false이면 로컬 좌표(localPosition, localRotation, localScale)를 그대로 유지합니다. 같은 오프셋이 새 부모 기준으로 적용되므로 월드 좌표가 달라집니다.


초기 조건 – 오브젝트: 부모 없음(루트), 월드 위치 (5, 3, 0) · 새 부모 월드 위치: (10, 0, 0)

매개변수 월드 위치 localPosition 동작 설명
SetParent(parent, true) (5, 3, 0) 유지 (-5, 3, 0) 재계산 월드 좌표를 유지하고 localPosition을 역산
SetParent(parent, false) (15, 3, 0) 변경 (5, 3, 0) 유지 localPosition을 유지하고 새 부모 기준으로 적용

씬 그래프 (Scene Graph)

트리 구조

Unity의 씬은 모든 GameObject의 Transform이 부모-자식 관계로 연결된 트리(Tree) 구조입니다. 이 트리 전체가 씬 그래프(Scene Graph)입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
씬 그래프의 구조

씬 (Scene)
│
├── Main Camera          (루트 오브젝트)
│
├── Directional Light    (루트 오브젝트)
│
├── Player               (루트 오브젝트)
│   ├── Body
│   │   ├── LeftArm
│   │   │   └── LeftHand
│   │   │       └── Shield
│   │   └── RightArm
│   │       └── RightHand
│   │           └── Sword
│   ├── Head
│   │   └── HealthBar (UI)
│   └── Legs
│
├── Environment          (루트 오브젝트)
│   ├── Ground
│   ├── Tree_01
│   ├── Tree_02
│   └── Building
│       ├── Wall_North
│       ├── Wall_South
│       ├── Roof
│       └── Door
│
└── UI Canvas            (루트 오브젝트)
    ├── HUD
    │   ├── HPBar
    │   └── MiniMap
    └── Menu
        ├── StartButton
        └── OptionButton


루트 오브젝트(Root Object)는 부모가 없는 최상위 GameObject입니다. Hierarchy 창의 최상위에 표시됩니다. 루트 오브젝트의 Transform은 부모가 없으므로, localPosition과 position이 동일합니다.


트리 구조에서 어떤 오브젝트의 월드 좌표를 구하려면, 루트부터 해당 오브젝트까지 경로에 있는 모든 부모의 변환을 순서대로 적용해야 합니다.


각 노드의 이동, 회전, 스케일은 하나의 4×4 TRS(Translation-Rotation-Scale) 행렬로 합칩니다. 3×3 행렬은 회전과 스케일만 표현할 수 있고 이동은 표현할 수 없으므로, 이동까지 포함하기 위해 좌표에 네 번째 성분을 추가한 4×4 행렬을 사용합니다. 이 확장된 좌표 체계를 동차 좌표계(Homogeneous Coordinates)라 합니다.


루트부터 대상 오브젝트까지 각 노드의 TRS 행렬을 순서대로 곱하면 최종 월드 좌표를 얻습니다. 이 과정을 변환 행렬의 연쇄 곱(Matrix Concatenation)이라 합니다. 변환 행렬의 수학적 구조는 그래픽스 수학 (2) - 행렬과 변환에서 다룹니다.


Sword의 월드 좌표 계산 Player 변환 × Body 변환 × RightArm 변환 × RightHand 변환 × Sword 로컬 좌표 = 월드 좌표 → 루트에서 해당 오브젝트까지의 모든 변환을 순서대로 적용 → 계층이 깊을수록 행렬 곱셈 횟수가 많아짐

Transform 변경의 비용

변경 전파의 메커니즘

Transform의 position이나 rotation을 변경하면, Unity 엔진 내부에서 여러 작업이 연쇄적으로 발생합니다.


첫째, 변경된 position, rotation, scale을 반영하여 해당 오브젝트의 TRS 행렬을 새로 합성합니다.

둘째, 모든 자식의 월드 좌표가 재계산됩니다. 자식이 많을수록 이 단계의 비용이 커집니다.

셋째, TransformChangeDispatch가 물리 엔진(Collider 위치 갱신), 렌더링 시스템(바운딩 박스 갱신), 애니메이션 시스템 등에 변경을 통지하고, 각 시스템이 자체 갱신을 수행합니다.


Transform 변경 시 내부 처리 흐름 transform.position = newPos; 1. 자신의 변환 행렬 재계산 2. 모든 자식의 월드 좌표 재계산 (자식의 자식까지 재귀적으로) 3. TransformChangeDispatch → 물리 엔진에 통지 (Collider 갱신) → 렌더링 시스템에 통지 (AABB 갱신) → 애니메이션 시스템에 통지 → 파티클 시스템에 통지


계층 깊이와 비용의 관계

자식이 없는 오브젝트의 Transform을 변경하면, 자신의 행렬 재계산과 TransformChangeDispatch 통지만 발생합니다. 하지만 자식이 많거나 계층이 깊은 오브젝트의 Transform을 변경하면, 하위 트리 전체에 재계산이 전파되므로 비용이 커집니다.


계층 깊이에 따른 전파 비용 얕은 계층 Parent ← 변경 Child A ← 재계산 Child B ← 재계산 → 전파 대상: 2개 깊은 계층 Parent ← 변경 Child A ← 재계산 Child B ← 재계산 Child C ← 재계산 A-1 A-2 B-1 C-1 C-2 A-1-1 A-1-2 A-2-1 모든 하위 노드에 재계산 전파 → 전파 대상: 11개


하나의 Transform을 변경했는데 11개 오브젝트의 월드 좌표가 재계산되고, 각각에 대해 물리/렌더링 시스템에 통지가 발생합니다. 매 프레임 이러한 변경이 반복되면 비용이 누적됩니다.


TransformChangeDispatch의 세부 비용

TransformChangeDispatch가 보내는 통지를 받은 각 시스템은 다음 작업을 수행합니다.


TransformChangeDispatch 통지 후 각 시스템의 작업 TransformChangeDispatch 물리 엔진 → Collider AABB 재계산 → 브로드페이즈 구조 갱신 → Rigidbody 상태 갱신 렌더링 시스템 → 바운딩 박스 재계산 → Frustum Culling 데이터 갱신 애니메이션 → 본(Bone) 위치 갱신 → 스키닝(Skinning) 갱신 파티클 → 시뮬레이션 공간 갱신


모든 오브젝트가 위 통지를 전부 받는 것은 아닙니다.

TransformChangeDispatch는 오브젝트에 부착된 컴포넌트를 기준으로 필요한 시스템에만 통지를 보냅니다. Collider가 없는 오브젝트는 물리 엔진 통지가 생략되고, Renderer가 없는 오브젝트는 렌더링 통지가 생략됩니다.

다만 Collider와 Renderer가 모두 있는 오브젝트라면 물리와 렌더링 양쪽에 통지가 발생합니다. 동일한 Transform 변경이라도, 부착된 컴포넌트의 종류와 수에 따라 실제 통지 비용은 달라집니다.


TransformChangeDispatch는 엔진 내부 시스템에 자동으로 통지하지만, 사용자 스크립트에는 통지하지 않습니다. 예를 들어 플레이어가 이동할 때만 미니맵 아이콘을 갱신하는 스크립트가 있다면, 매 프레임 이전 위치를 저장해 두고 현재 위치와 비교해야 합니다.

Transform.hasChanged는 이 비교를 대신합니다. position, rotation, scale 중 하나라도 변경되면 Unity가 이 플래그를 true로 설정합니다. 스크립트에서 플래그를 확인하고 필요한 처리를 마친 뒤, 직접 false로 초기화합니다. Unity는 변경이 일어난 시점만 알고 스크립트가 처리를 완료한 시점은 모르므로, 초기화를 자동으로 하지 않습니다.


Transform 계층 최적화

앞서 다룬 재계산·전파·통지 비용은 계층 구조와 변경 빈도에 따라 줄일 수 있습니다.

불필요한 중간 노드 제거

씬을 정리할 목적으로 빈 GameObject를 폴더처럼 사용하는 경우가 흔합니다. 에디터에서는 가독성이 좋아지지만, 런타임에서는 변환 전파 경로가 길어져 재계산 비용이 늘어납니다.


불필요한 중간 노드 에디터 정리용 구조 Enemies (빈 GO) Ranged (빈 GO) Melee (빈 GO) Archer_01 Archer_02 Archer_03 Knight_01 Knight_02 → Enemies나 Ranged가 이동하면 모든 자식에 전파 → 빈 GO를 폴더로 쓰지만, Transform 전파 비용은 발생 최적화된 구조 Archer_01 (루트) Archer_02 (루트) Archer_03 (루트) Knight_01 (루트) Knight_02 (루트) → 각 오브젝트가 독립적으로 이동 → 부모 변경에 의한 전파 없음


폴더용 빈 GameObject가 절대 이동하지 않는다면 전파 비용은 발생하지 않습니다. 하지만 실수로 이동시키거나, 씬 로딩 시 초기화 과정에서 Transform이 설정되는 경우가 있으므로, 런타임에 불필요한 중간 노드는 제거하는 것이 안전합니다. 다만 에디터에서의 작업 효율(오브젝트 일괄 선택, 논리적 그룹화)과의 트레이드오프가 있으므로, 빈번하게 이동하는 오브젝트의 상위 계층을 우선적으로 정리하는 것이 실전적인 접근입니다.


계층 깊이 최소화

같은 수의 자식이라도 계층이 깊으면 전파 비용이 커집니다. 깊은 체인에서는 부모의 월드 좌표가 확정되어야 자식을 계산할 수 있으므로, 단계마다 이전 결과를 기다려야 합니다. 얕은 구조에서는 한 부모의 결과만으로 모든 자식을 바로 계산할 수 있습니다.


계층 깊이 비교 깊은 계층 (전파 비용 높음) A B C D E F 전파 깊이: 5단계 A를 변경하면 B, C, D, E, F 모두 재계산 재계산 깊이: 5단계 얕은 계층 (전파 비용 낮음) A B C D E F A를 변경하면 B, C, D, E, F 모두 재계산 재계산 깊이: 1단계 → 자식 수는 같지만 전파 깊이가 다름 → 전파 깊이가 깊을수록 비용 증가


행렬 곱셈의 총 횟수는 양쪽 모두 5회로 동일하지만, 깊은 계층에서는 A → B → C → D → E → F 순서로 각 단계의 결과가 확정되어야 다음 단계를 계산할 수 있으므로 5단계의 순차 의존성이 생깁니다. 얕은 계층에서는 B~F 모두 A의 결과 하나만 참조하므로 의존 깊이가 1단계로 줄어들어, 전파 비용이 더 낮습니다.


빈번하게 변하는 오브젝트는 얕은 계층에

자식 오브젝트 자신이 매 프레임 이동하는 경우에도 계층 깊이가 비용에 영향을 줍니다. Unity는 그 오브젝트의 localPosition에 부모 체인의 행렬을 곱하여 월드 좌표를 계산합니다. 부모 체인이 길수록 이 계산 단계가 늘어납니다. 매 프레임 위치가 변하는 오브젝트(이동하는 캐릭터, 날아가는 총알 등)는 얕은 계층에 배치하면 이 비용을 줄일 수 있습니다.


이동 오브젝트의 계층 배치 비효율적 World Region Area Bullet 매 프레임 이동 계층 3단계 → 이동 시 3단계를 거쳐 월드 좌표 계산 효율적 World Bullet 매 프레임 이동 계층 1단계 → 이동 시 1단계만 거침 → 빈번히 이동하는 오브젝트는 얕은 계층에 배치하여 행렬 계산 단계를 줄임


Bullet이 논리적으로 어떤 Area에 속하더라도, Transform 계층에서 Area의 자식일 필요는 없습니다. 게임 로직에서의 소속 관계와 Transform의 부모-자식 관계는 분리할 수 있습니다. 소속 관계는 스크립트의 변수로 관리하고, Transform 계층은 실제로 변환 전파가 필요한 경우에만 사용하는 것이 성능에 유리합니다.


localPosition 사용

계층 구조뿐 아니라, 좌표를 설정하는 방식에 따라서도 비용이 달라집니다. transform.position(월드 좌표)을 설정하면, Unity 내부에서 부모의 역행렬(월드 좌표를 부모 기준 로컬 좌표로 되돌리는 행렬)을 곱하여 localPosition을 역산합니다. transform.localPosition을 직접 설정하면 이 역행렬 계산 단계를 건너뛸 수 있습니다.


position vs localPosition 설정 비용 transform.position = worldPos 내부 처리 (5단계) 1 부모의 월드 변환 행렬의 역행렬 계산 2 역행렬 x worldPos → localPosition 계산 3 localPosition 저장 4 변환 행렬 재계산 5 자식에 전파 생략 가능 transform.localPosition = localPos 내부 처리 (3단계) 역행렬 계산 불필요 (2단계 생략) 1 localPosition 저장 2 변환 행렬 재계산 3 자식에 전파 → localPosition 설정 시 역행렬 계산 단계(1~2)가 생략됨


개별 호출의 비용은 작지만, 매 프레임 수백 개 오브젝트의 위치를 갱신하면 역행렬 계산이 그만큼 반복되어 무시할 수 없는 비용이 됩니다. position 캐싱 등 추가적인 Transform 최적화 기법은 스크립트 최적화 (2) - Unity API와 실행 비용에서 다룹니다.


SetPositionAndRotation

위치와 회전을 함께 바꿔야 할 때 positionrotation을 따로 설정하면, 행렬 재계산과 TransformChangeDispatch 통지가 두 번 발생합니다. Transform.SetPositionAndRotation()은 이 과정을 한 번으로 묶어 비용을 절반으로 줄입니다.


1
2
3
4
5
6
// 개별 설정: 행렬 재계산 + 통지가 2회 발생
transform.position = newPos;    // ← 재계산 + 통지
transform.rotation = newRot;    // ← 재계산 + 통지

// 배치 설정: 행렬 재계산 + 통지가 1회만 발생
transform.SetPositionAndRotation(newPos, newRot);


Unity 2021.3.11f1부터 제공되는 Transform.SetLocalPositionAndRotation()은 로컬 좌표 기준으로 동작하므로 역행렬 계산이 필요 없고, 위치와 회전을 한 번에 설정하므로 통지도 1회로 줄어듭니다.


마무리

Transform의 부모-자식 계층은 씬 전체의 공간 관계를 정의하며, 그 구조가 런타임 성능에도 영향을 줍니다.

  • Transform은 위치(position), 회전(rotation), 스케일(localScale)을 저장하며, 모든 GameObject에 필수로 포함됩니다
  • localPosition은 부모 기준 상대 좌표, position은 씬 원점 기준 절대 좌표입니다. 부모의 변환이 바뀌면 자식의 월드 좌표가 자동으로 재계산됩니다
  • SetParent()로 부모-자식 관계를 설정하며, 자식은 부모의 이동·회전·스케일에 따라 함께 변환됩니다
  • 월드 좌표는 루트부터 해당 오브젝트까지의 변환 행렬을 순서대로 곱하여 계산됩니다
  • Transform 변경은 자식 전파와 TransformChangeDispatch 통지를 수반하며, 자식이 많고 계층이 깊을수록 전파 비용이 커집니다
  • 불필요한 중간 노드 제거, 계층 깊이 최소화, 빈번히 이동하는 오브젝트의 얕은 배치, localPosition·SetPositionAndRotation 사용으로 이 비용을 줄일 수 있습니다

이 비용은 계층 구조 자체에서 발생하므로, 런타임이 아닌 씬 설계 단계에서 계층 깊이와 변경 빈도를 함께 고려하면 효과적으로 줄일 수 있습니다.


다음 글에서는 Unity가 매 프레임 Awake, FixedUpdate, Update, LateUpdate, 렌더링 콜백, 코루틴 재개 시점을 어떤 순서로 호출하는지 다룹니다. Unity 엔진 핵심 (3) - Unity 실행 순서에서 이어집니다.



관련 글

시리즈

전체 시리즈

Tags: Transform, Unity, 모바일, 씬그래프, 엔진

Categories: ,