유니티 고수만 아는 싱글턴 관리 비법: '이 코드' 한 줄로 하이어라키(Hierarchy) 깔끔하게 정리하기

안녕하세요, 유니티 개발자 여러분!
혹시 여러분의 유니티 프로젝트 하이어라키(Hierarchy) 창은 안녕하신가요? 프로젝트 규모가 조금만 커져도 GameManager, SoundManager, UIManager, DataManager 등... 온갖 매니저들이 최상단(루트)에 흩어져 지저분해 보이는 경험, 다들 한 번쯤은 있으실 겁니다.
"어쩔 수 없지. 싱글턴 매니저는 DontDestroyOnLoad를 써야 하고, 그건 루트 오브젝트에만 되니까..."
라고 생각하며 스스로를 위로하셨나요? 오늘, 그 고정관념을 깨뜨릴 아주 우아하고 참신한 방법을 소개해 드리고자 합니다. 단 한 줄의 코드로 지저분했던 하이어라키를 전문가처럼 정리하고, 동료에게 "오, 이거 어떻게 하셨어요?" 라는 질문을 받게 될 준비를 하세요!

우리가 겪는 딜레마: 왜 매니저들은 흩어져야만 했을까?
문제의 시작은 바로 DontDestroyOnLoad 입니다. 이 함수는 씬(Scene)이 전환될 때 게임 오브젝트가 파괴되지 않도록 보호해주는, 싱글턴 매니저에게는 필수적인 기능입니다. 하지만 Unity 엔진의 규칙상 이 기능은 부모가 없는 최상위, 즉 루트(Root) 게임 오브젝트에만 적용할 수 있다는 치명적인 제약이 있습니다.
결국 우리는 울며 겨자 먹기로 모든 전역 매니저들을 하이어라키 최상단에 꺼내놓을 수밖에 없었습니다. 이는 당장 기능 구현에는 문제가 없지만, 다음과 같은 문제점들을 야기합니다.
- 가독성 저하: 중요한 오브젝트를 찾기 위해 계속 스크롤해야 합니다.
- 관리의 어려움: 매니저가 10개, 20개로 늘어나면 어떤 것이 있는지 한눈에 파악하기 힘듭니다.
- 협업의 비효율: 여러 사람이 작업할 때, 누가 어떤 매니저를 추가했는지 파악하기 어렵고 충돌의 원인이 되기도 합니다.
발상의 전환: 에디터에서만 부모를 갖게 하자!
여기서 우리는 생각을 조금 비틀어 볼 필요가 있습니다. "꼭 처음부터 루트 오브젝트일 필요가 있을까? 게임이 시작되는 그 순간에만 루트 오브젝트가 되면 되지 않을까?"
바로 이 아이디어가 모든 문제의 해결 열쇠입니다.
핵심 원리는 이렇습니다.
- 에디터에서는 깔끔한 정리를 위해 비어있는 게임 오브젝트(@[Managers])의 자식으로 모든 매니저를 넣어 둡니다.
- 게임이 실행되면(런타임), 각 매니저들이 Awake() 함수에서 스스로 부모-자식 관계를 끊고 루트 오브젝트로 독립하게 만듭니다.
결과적으로, 개발할 땐 깔끔한 상태를 유지하고, 게임이 실행될 땐 Unity의 규칙을 완벽하게 준수하게 되는 것입니다.

실전 적용 가이드 (코드 포함)
이제 마법을 부려볼 시간입니다. 단계별로 차근차근 따라 해 보세요.
1단계: 정리용 부모 오브젝트 생성
하이어라키 창에서 마우스 오른쪽 클릭 -> Create Empty를 선택해 빈 게임 오브젝트를 만듭니다. 그리고 이름을 @[Managers] 또는 --- Singleton Managers --- 처럼 눈에 잘 띄게 지어주세요. (@나 -를 앞에 붙이면 하이어라키 상단에 정렬되어 편리합니다.)
2단계: 매니저들을 자식으로 이동
기존에 루트에 흩어져 있던 GameManager, SoundManager 등의 모든 싱글턴 매니저들을 방금 만든 @[Managers] 오브젝트 안으로 드래그해서 넣어주세요.
(이미지 Alt 정보: Unity-Singleton-Hierarchy)
3단계: 매니저 스크립트에 코드 한 줄 추가
이제 가장 중요한 단계입니다. 모든 싱글턴 매니저 스크립트의 Awake() 함수로 이동하여 DontDestroyOnLoad 호출 바로 아래에 다음 코드를 단 한 줄 추가합니다.
// 대부분의 싱글턴 매니저가 사용하는 일반적인 Awake() 함수 예시입니다.
// 여러분의 코드에 맞게 적용하세요.
private static GameManager _instance;
public static GameManager Instance
{
get
{
if (_instance == null)
Debug.LogError("GameManager is NULL");
return _instance;
}
}
void Awake()
{
if (_instance != null)
{
Destroy(gameObject);
return;
}
_instance = this;
// 이 두 줄이 핵심입니다!
DontDestroyOnLoad(gameObject);
transform.SetParent(null); // <<-- 바로 이 코드 한 줄을 추가하세요!
}
transform.SetParent(null); 이 코드는 "나의 부모를 null(없음)으로 설정하겠다" 라는 의미입니다. 즉, 게임이 시작될 때 스스로 @[Managers] 와의 부모-자식 관계를 끊고 당당하게 루트 오브젝트로 독립하는 것입니다.

이 방법이 가져다주는 엄청난 이점
- 압도적인 가독성: 하이어라키가 놀랍도록 깔끔해져 원하는 오브젝트를 바로 찾을 수 있습니다.
- 향상된 프로젝트 구조: 누가 봐도 매니저들이 어디에 모여 있는지 명확하게 알 수 있습니다.
- 협업 효율 극대화: 팀원 모두가 동일한 규칙으로 매니저를 관리하므로 충돌과 혼란이 줄어듭니다.
- 실수 방지: 새로운 매니저를 만들 때 자연스럽게 @[Managers] 폴더에 넣게 되어 실수를 예방합니다.
마치며
개발은 단순히 기능을 구현하는 것에서 끝나지 않습니다. 내가 만든 코드를 동료가, 그리고 미래의 내가 얼마나 쉽게 이해하고 유지보수할 수 있는지가 진정한 실력의 척도가 되기도 합니다.
오늘 소개해 드린 transform.SetParent(null); 트릭은 단순한 코드 한 줄을 넘어, 여러분의 프로젝트를 한 단계 더 성숙시키는 현명한 습관이 될 것입니다. 지금 바로 여러분의 프로젝트에 적용해보고 전문가 수준으로 관리되는 하이어라키의 쾌적함을 느껴보시길 바랍니다.
