unity를 써보면서 기본적이면서도 당황스러운 녀석이 Vector3였다. transform.position.set()을 해도 먹히지가 않네? update()안에서 이렇게 new를 남발해도 되나? 같은 것들.
void Start()
{
transform.position.Set(1, 1, 1); // 작동 안함.
}
void Update()
{
// Update 안에서 반복된 new를 해도 되는거야?
transform.position = new Vector3(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), 0);
}
다른 언어들을 다루다보면, Vector는 당연히 객체로 다룬다. 거기다가 new까지 쓰고 있는걸? 당연하게 머리속에 있던 내용들이 혼란스러워진다.
답은 마음에 안들겠지만 의외로 간단한데, C#에서 Vector는 구조체 타입이다. 그리고 이 구조체 타입은 기본 타입인 int, float, char, enum등과 같이 value type이라서 스택에 저장되며 new를 한다고 힙에 메모리할당이 일어나지 않는다. object와 string은 다른 언어들과 동일하게 reference type이다. string이야 스택에 저장되긴 하겠지만 변수는 레퍼런스란 얘기다. https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/builtin-types/built-in-types 참조.
너무 이상하지? 나도 그렇다. 언어가 좀 잘못 발전되어 온게 아닌가 생각될 정도다.
우선, 첫번째 미스테리를 풀어보자. transform.position으로 가져오는 Vector3는 Vector3가 레퍼런스가 아니므로, 복사된 값이 저장된 Vector3 이다. 그래서 Set()을 해주면 복사된 값을 변경하는 것이라서 적용이 되질 않는다. 그래서 다음과 같이 써야한다.
void Start()
{
transform.position = new Vector3(1, 1, 1);
}
transform.position이 복사된 Vector3라며? property의 getter와 setter의 차이라고 생각하면 될거 같다. 가져오는건 복사된 값이지만, 새로운 벡터를 할당하는건 가능하다.
아니 그럼, 혼란스럽게 new는 왜 써주는걸까? 이걸 질문했던 사람이 있는데, 흥미롭게도 ‘통일성과 생성자 호출을 위해서’라는 답변이 있었다. 참고 링크는 글의 마지막에 둘테니 참고하시길.
그러면, 두번째 미스테리로 new를 쓰는데, Update에서 반복호출해도 괜찮을걸까? 앞 내용의 연장선인데, Vector3가 struct type이고 new는 통일성을 위해 사용하는 것이라서 실제로는 스택에 생성되기 때문에 반복된 메모리릭이나 할당/해제문제는 발생하지 않는다. 마음놓고 써도 된다는 얘기다.
혼란스럽지? 이 글을 작성하면서도 나역시 여전히 혼란스러웠다. 그게 이걸 기록으로 남겨야 겠다고 생각한 부분이기도 하다. 참고로 Quaternion도 struct 이다. value type이라는 얘기.
- 이미 명쾌한 답을 준 블로그 글이 있다. : https://3dmpengines.tistory.com/1566
- 유니티 커뮤니티에서 이미 비슷한 일이 2012년에… : https://answers.unity.com/questions/225729/gameobject-positionset-not-working.html
- 위에 있는 링크지만, MS 공식 C# 문서 : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/builtin-types/built-in-types