안녕하세요! 유니티로 2D 게임을 개발하다 보면 문득 의아한 점이 생깁니다.
우리는 분명 transform.position은 3D 좌표인 Vector3 타입이라고 배웠습니다.
그런데 실제 코드를 짤 때는 Vector2 변수에 그냥 대입해도 아무런 에러가 나지 않습니다.
void Update() {
// Vector3 타입을 Vector2 변수에 넣는데 에러가 안 난다?
Vector2 myPos = transform.position;
}
혹시 유니티가 2D 모드라서 알아서 타입을 바꿔준 걸까요? 아닙니다. 여기에는 C# 언어의 아주 편리하면서도 재미있는 비밀이 숨겨져 있습니다.
오늘은 겉으로 보이는 현상뿐만 아니라, 그 내부에서 일어나는 실제 코드의 동작 원리(암시적 형변환과 연산자 오버로딩)까지 깊이 있게, 하지만 아주 쉽게 파헤쳐 보겠습니다.
1. 팩트 체크: Unity는 뼈속까지 3D 엔진이다
가장 먼저 확실히 짚고 넘어가야 할 사실이 있습니다. 여러분이 2D 프로젝트를 생성했더라도, transform.position은 언제나 Vector3 (x, y, z) 타입입니다.
유니티의 인스펙터 창을 자세히 보세요. 2D 스프라이트라도 Transform 컴포넌트에는 항상 X, Y, Z 세 가지 축이 모두 존재합니다.
단지 2D 게임에서는 우리가 Z축을 잘 안 쓰거나, 카메라가 평면으로 비춰주고 있을 뿐이죠.
그렇다면, 왜 3개짜리 데이터(Vector3)가 2개짜리 그릇(Vector2)에 쏙 들어가는 걸까요?
2. 마법의 정체: 암시적 형변환 연산자 (Implicit Operator)
이 현상은 유니티의 버그가 아니라, C# 문법을 이용해 유니티 개발자들이 미리 만들어둔 변환 규칙 덕분입니다.
이를 전문 용어로 연산자 오버로딩(Operator Overloading) 중에서도 암시적 형변환(Implicit Conversion)이라고 부릅니다.
우리가 보는 Vector3 구조체의 내부 코드를 뜯어보면(실제로는 컴파일되어 있지만), 대략 아래와 같은 형태의 특수 함수가 숨겨져 있습니다.
Vector3 내부의 비밀 코드
public struct Vector3
{
public float x;
public float y;
public float z;
// 핵심: 'implicit operator' 키워드
// 의미: "누군가 나(Vector3)를 Vector2로 쓰려고 하면, 이 함수를 몰래 실행해."
public static implicit operator Vector2(Vector3 v)
{
// Vector3의 x, y만 따서 새로운 Vector2를 반환 (z는 버림)
return new Vector2(v.x, v.y);
}
}
Vector2 내부의 비밀 코드
반대로 Vector2에도 비슷한 코드가 들어있습니다.
public struct Vector2
{
public float x;
public float y;
// 의미: "누군가 나(Vector2)를 Vector3로 쓰려고 하면, 이 함수를 실행해."
public static implicit operator Vector3(Vector2 v)
{
// x, y는 그대로 쓰고, z 자리에 0을 채워서 반환
return new Vector3(v.x, v.y, 0);
}
}
즉, 개발자가 일일이 변환 함수를 호출하지 않아도 컴파일러가 알아서 이 타입은 저 타입으로 바꿀 수 있다고 약속되어 있어라고 판단하고 변환 코드를 끼워 넣는 것입니다.
3. 컴파일러가 하는 일 (자동 통역)
여러분이 코드를 작성하고 플레이 버튼을 누르면, 컴파일러는 여러분의 코드를 기계가 이해할 수 있게 번역합니다. 이 과정에서 어떤 일이 일어날까요?
작성한 코드
Vector3 pos3D = new Vector3(10, 20, 5);
Vector2 pos2D = pos3D; // 그냥 대입
컴파일러가 실제로 변환한 코드
Vector3 pos3D = new Vector3(10, 20, 5);
// 컴파일러: "타입이 다르네? 아! implicit operator가 정의되어 있구나. 함수를 호출하자."
Vector2 pos2D = Vector3.op_Implicit(pos3D); // 변환 함수 자동 호출
우리가 (Vector2)라고 명시적으로 형변환을 적지 않아도 되는 이유는, 컴파일러가 이 약속된 규칙을 보고 알아서 처리해주기 때문입니다.
4. 왜 명시적(Explicit)이 아니라 암시적(Implicit)일까?
C#에서는 보통 데이터 손실이 일어나는 변환(Vector3에서 z값이 사라지는 상황)은 명시적(Explicit) 키워드를 사용해서, 개발자가 (Vector2)pos3D 처럼 직접 캐스팅을 하도록 강제하는 것이 원칙입니다. 실수를 방지하기 위해서죠.
하지만 유니티는 게임 개발의 편의성을 선택했습니다.
"2D 게임을 만들 때 transform.position은 무조건 Vector3인데, 이걸 매번 (Vector2)transform.position이라고 캐스팅해서 쓰게 하면 개발자들이 너무 귀찮지 않을까? 그냥 자동으로 되게 해주자!"
이러한 유니티 엔지니어들의 배려 덕분에 우리는 코드를 훨씬 간결하게 작성할 수 있게 된 것입니다.
5. 주의! 초보자가 자주 겪는 Z축 실종 사건
하지만 이 편리함에는 함정이 있습니다. 바로 Z축 데이터의 손실입니다. 암시적 변환이 일어날 때, Vector2에서 Vector3로 갈 때는 Z가 무조건 0으로 채워진다는 점을 잊으면 안 됩니다.
버그가 발생하는 코드 예시
내 캐릭터가 현재 (5, 5, -10) 위치에 있어서 카메라에 잘 보인다고 가정해 봅시다. (Z가 -10이라서 카메라 앞에 있음)
void Update() {
// 1. 현재 위치를 Vector2로 가져옵니다.
// 여기서 Z값(-10)은 버려집니다! -> (5, 5)
Vector2 tempPos = transform.position;
// 2. X 좌표만 살짝 움직입니다.
tempPos.x += 1f;
// 3. 다시 적용합니다.
// 이때 Vector2 -> Vector3 변환이 일어나면서 Z는 자동으로 '0'이 됩니다.
transform.position = tempPos;
// 결과: 내 캐릭터 위치는 (6, 5, 0)이 되었습니다.
// 원래 있던 -10이 0으로 바뀌면서, 배경 뒤로 숨거나 카메라에서 사라질 수 있습니다!
}
올바른 해결 방법
Z축 값을 유지하면서 X, Y만 바꾸고 싶다면, Vector2 변수에 담았다가 다시 넣는 것보다는 아래처럼 새로운 Vector3를 만드는 것이 가장 안전합니다.
// Z값은 원래 내 Z값을 그대로 유지!
transform.position = new Vector3(newX, newY, transform.position.z);
요약
- transform.position은 2D 프로젝트여도 무조건 Vector3 타입입니다.
- Vector2로 그냥 써도 되는 이유는 유니티 내부 코드에 정의된 암시적 형변환(Implicit Operator) 기능 덕분입니다.
- 컴파일러가 이 규칙을 보고 자동으로 변환 함수를 연결해 줍니다.
- 편리하지만 변환 과정에서 Z값이 0으로 초기화되거나 사라질 수 있으니, 깊이(Depth)가 중요한 게임에서는 주의해야 합니다.
'개발 > Unity' 카테고리의 다른 글
| (Unity) GetComponent 완벽 정리: 기본부터 최적화까지 (feat. InChildren, InParent) (0) | 2025.12.26 |
|---|---|
| (Unity) 인스펙터 창을 깔끔하게! [Header] 속성 완벽 가이드 (0) | 2025.12.20 |
| (Unity) 근처 적을 스캔하고 최단 거리 대상 찾기 (1) | 2025.12.13 |
| (Unity) 회전하는 오브젝트 구현 가이드 (0) | 2025.12.12 |
| Unity 내장 오브젝트 풀(Object Pool) 사용하기 (0) | 2025.10.13 |