[Unity] DontDestroyOnLoad는 어떻게 동작할까?

ChangBeom·2025년 11월 24일

Unity

목록 보기
12/17
post-thumbnail

Unity에서 자주 사용하는 기능중에 DontDestroyOnLoad라는 기능이 있다.

새로운 씬으로 넘어가도 오브젝트가 사라지지 않도록 해주는 기능이라 게임 전체를 관리하는 매니저 객체에서 흔히 사용된다.

나는 "DontDestroyOnLoad로 지정하면 Unity가 해당 오브젝트를 따로 리스트 같은 곳에 저장해두고 파괴하지 않는 방식이겠지?" 라고 생각했었다.

하지만 최근에 알게 됐는데, 내가 예상했던 방식과는 완전히 다르게 동작하고 있었다.

그래서 이번 글을 통해 DontDestroyOnLoad가 실제로 어떤 구조로 동작하는지, 그리고 이 구조 때문에 생기는 특징들이 무엇인지 정리해보려고 한다.


[1. DontDestroyOnLoad의 본질: "삭제되지 않는 씬으로 이동"]

많은 개발자들이 나처럼 DontDestroyOnLoad를

"씬이 넘어갈 때 해당 오브젝트를 예외로 두고 삭제하지 않는 건가?"

라고 착각할 수 있다.

하지만 Unity의 실제 동작은 다음과 같다.

DontDestroyOnLoad를 호출하는 순간 Unity는 다음 두 작업을 수행한다.

  • 해당 오브젝트를 현재 씬에서 제거
  • "DontDestroyOnLoad Scene" 이라는 특수한 씬으로 이동

"DontDestroyOnLoad Scene"은 Unity에서 표시되지 않아 직접적으로 확인할 수는 없지만, 엔진 내부에는 실제로 존재하는 독립적인 씬이다.

즉,

오브젝트를 남기는 게 아니라, 애초에 Destroy 대상에서 제외되는 별도의 공간으로 옮겨버리는 것이다.

이것이 "DontDestroyOnLoad"의 핵심이다.


[2. 씬이 바뀔 때 Unity 내부에서는 무슨 일이 일어날까?]

Unity의 기본 씬 전환 과정은 다음과 같다.

1. 현재 씬의 모든 GameObject를 Destroy
2. DontDestroyOnLoad Scene은 건드리지 않음
3. 새 씬을 로드하고 오브젝트 생성
4. Awake -> OnEnable -> Start 실행

따라서,

A Scene 로드 -> A Scene 오브젝트 파괴 -> DontDestroyOnLoad Scene 유지 -> B Scene 로드

해당 흐름에서 DontDestroyOnLoad Scene은 "오브젝트 파괴" 루틴의 영향을 받지 않는다.

그래서 오브젝트가 살아남는 것이다.


[3. 내부 구조 때문에 나타나는 대표적 특징들]

<1. Hierarchy 창에서 안 보임>
DontDestroyOnLoad 씬은 에디터에 표시되지 않기 때문에 오브젝트가 어느 씬에 속했는지 바로 확인할 수 없다.

<2. 같은 객체를 여러 번 만들면 중복이 존재하게 된다.>
아래와 같은 코드가 여러 씬에서 실행된다고 가정해보자.

var go = Instantiate(goPrefab);
DontDestroyOnLoad(go);
  • A Scene에서 만들어진 go -> DontDestroyOnLoad Scene으로 이동
  • B Scene에 들어가서 또 Instantiate -> 또 DontDestroyOnLoad Scene으로 이동
  • C Scene에 들어가서 또 Instantiate -> 또 DontDestroyOnLoad Scene으로 이동

-> go라는 오브젝트가 3개가 존재하게 됨

이렇게 생성된 모든 인스턴스는 DontDestroyOnLoad Scene에 쌓이기 때문에 싱글톤 패턴과 함께 사용해야한다.

<3. 부모-자식 관계가 끊어질 수 있다.>

DontDestroyOnLoad(child);

라고 하면:

  • child는 DontDestroyOnLoad 씬으로 이동
  • parent는 기존 씬에 남음

씬 전환 시 parent는 Destroy되지만 child는 살아남기 때문에 부모-자식 관계가 깨진다.

그래서 보통 루트 전체를 DontDestroyOnLoad하는 방식을 사용한다.


[4. DontDestroyOnLoad를 사용할 때 고려해야 할 점]

<1. 정말 전역적으로 유지해야 하는 객체인가?>
일부 시스템은 씬마다 존재하는 것이 더 자연스러울 때도 있다.

<2. 싱글톤 패턴이 사실상 필수>
중복 생성을 막기 위해 싱글톤 패턴이 일반적으로 쓰인다.

void Awake()
{
	if (instance != null)
    {
    	Destroy(gameObject);
        return;
    }
	
    instance = this;
    DontDestroyOnLoad(gameObject);
}

<3. 디버깅이 어려울 수 있다.>
DontDestroyOnLoad Scene으로 이동한 오브젝트는 Hierarchy창에서 어느 Scene에 속해있는지 안보인다.
이때 gameObject.scene.name을 출력하면 큰 도움이 된다.

Debug.Log(gameObject.scene.name);

출력 ex)

SampleScene
DontDestroyOnLoad

이렇게 나오기 때문에,

  • 언제 DontDestroyOnLoad가 호출됐는지
  • 지금 오브젝트가 DontDestroyOnLoad 씬으로 이동했는지
  • 중복 인스턴스가 몇 개나 생겼는 지

를 바로 확인할 수 있다.

Hierarchy에 안 보여도, scene.name 로그만으로 현재 상태를 정확히 알 수 있다는 것이 핵심이다.


[5. 정리]

DontDestroyOnLoad는 단순히 "씬이 넘어가도 유지되는 오브젝트"가 아니다. 그 본질은 다음 한 문장으로 요약할 수 있다.

오브젝트를 Destroy 루틴의 대상에서 제외하기 위해 별도의 `"DontDestroyOnLoad Scene"으로 이동시키는 기능이다.

이 구조 때문에 다음과 같은 현상들이 발생한다.

  • 중복 생성 시 DontDestroyOnLoad 씬에 계속 쌓임
  • 부모-자식 관계가 분리됨
  • OnDestroy가 호출되지 않음
  • 싱글톤 패턴이 사실상 필수

겉으로는 단순한 기능처럼 보이지만,
Unity의 씬 관리 방식이 그대로 드러나는 매우 중요한 개념이다.


[6. 마치며]

나는 정말 오랫동안 DontDestroyOnLoad를 "그냥 오브젝트를 파괴하지 않는 옵션" 정도로만 생각했었다.
하지만 실제로는 특수한 전용 씬으로 옮기는 구조였고, 그걸 이해하고 나니 그동안 애매(?)했던 여러 동작들이 한 번에 연결되었다.

  • 왜 중복 인스턴스가 생기는지
  • 왜 Awake 타이밍이 중요한지
  • 왜 부모-자식 관계가 끊어지는지

전부 이 내부 구조 때문이었다.

개발을 하다 보면 이렇게 겉으로는 단순해 보이지만 알고 나면 훨씬 명확해지는 기능들이 많다.
이번 DontDestroyOnLoad도 그중 하나였고, 덕분에 이제 더 확실한 기준을 가지고 사용할 수 있게 된 것 같다.

앞으로도 이런 식으로 내가 헷갈렸던 부분이나
한 번쯤은 원리를 파고들어 보고 싶었던 것들을 틈틈이 정리해보려고 한다.

이런 글들이 쌓여 나중에 다시 돌아봐도 도움이 될 만한 기록이 되었으면 좋겠다.

0개의 댓글