
유니티에서 씬이 바뀌어도 점수 · 설정 · 사운드 같은 관리자를 남기려면 그 오브젝트의 Awake 에서 DontDestroyOnLoad(gameObject) 를 부릅니다. 다만 그 씬에 다시 돌아올 때마다 같은 관리자가 하나씩 더 생기므로, static 속성 instance 에 첫 번째 것만 남기고 나중에 생긴 것은 지우는 싱글톤으로 같이 씁니다.
if (instance != null && instance != this) { Destroy(gameObject); return; }
instance = this;
DontDestroyOnLoad(gameObject);
타이틀 씬에 GameManager 를 두고 게임 씬으로 넘어가면 GameManager 가 사라져 점수가 0 으로 돌아갑니다. 타이틀로 돌아올 때마다 GameManager 가 늘어나지 않게 막는 것까지가 이번 글입니다. 씬을 오가도 하나만 남는 GameManager 와, 씬마다 그 점수를 다시 읽어 띄우는 ScoreButton 을 만들고, 플레이 모드에서 씬을 바꿔 가며 GameManager 의 개수와 점수를 확인했습니다.
C# (UNITY 3D)
GameManager 를 첫 씬(타이틀)의 빈 오브젝트에 붙이면 씬을 옮겨도 점수가 그대로 남고, 어느 스크립트에서든 GameManager.instance.score 로 읽을 수 있습니다.
1. 준비 — DontDestroyOnLoad 가 하는 일
using UnityEngine;
DontDestroyOnLoad 는 UnityEngine.Object 의 함수라 using UnityEngine; 하나면 됩니다. 씬을 바꾸는 SceneManager.LoadScene 은 using UnityEngine.SceneManagement; 가 더 필요하고, 타이틀 · 게임 두 씬을 Build Profiles(Build Settings)의 씬 목록에 넣어 두어야 이름으로 불러집니다.
SceneManager.LoadScene 으로 씬을 바꾸면 지금 씬의 오브젝트는 모두 지워집니다. DontDestroyOnLoad 를 부른 오브젝트는 플레이 중 Hierarchy 에 따로 생기는 「DontDestroyOnLoad」 씬으로 옮겨져, 그 뒤 씬을 몇 번 바꿔도 남습니다.
2. 하나만 남는 GameManager
using UnityEngine;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class GameManager : MonoBehaviour
{
public static GameManager instance { get; private set; } // 어디서든 GameManager.instance 로 부름
public int score;
void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject); // 씬에 돌아와 또 생긴 것은 지움
return;
}
instance = this;
transform.SetParent(null); // DontDestroyOnLoad 는 맨 위 오브젝트에만 됨
DontDestroyOnLoad(gameObject);
}
void OnDestroy()
{
if (instance == this)
instance = null;
}
public void Add_Score(int amount)
{
score += amount;
}
}
instance 는 static 이라 씬이 바뀌어도 값이 남습니다. 처음 Awake 가 불린 GameManager 가 instance 가 되고 DontDestroyOnLoad 로 남습니다. 타이틀 씬을 다시 불러 새 GameManager 가 생기면, 그 Awake 에서는 instance 가 이미 다른 것이므로 자기 오브젝트를 지우고 끝냅니다. 남아 있던 쪽의 점수는 그대로입니다. get 은 public, set 은 private 이라 바깥에서 instance 를 바꿔 넣을 수 없습니다.
transform.SetParent(null) 은 GameManager 를 다른 오브젝트 아래에 넣어 두었을 때를 위한 줄입니다. DontDestroyOnLoad 는 맨 위(루트) 오브젝트에만 듣기 때문에, 부모를 떼어 루트로 꺼낸 뒤 부릅니다. OnDestroy 는 남아 있던 관리자가 지워질 때 instance 를 비워, 지워진 오브젝트를 계속 가리키지 않게 합니다.
변수(variable)
instance(GameManager, static) : 지금 남아 있는 하나뿐인 GameManager
score(int) : 씬을 옮겨도 남길 점수
함수(function)
Awake : 이미 다른 GameManager 가 있으면 자신을 지우고, 없으면 instance 가 되어 DontDestroyOnLoad 로 남습니다.
OnDestroy : 자신이 instance 였으면 비웁니다.
Add_Score : 점수를 더합니다.
3. 다른 씬에서 점수 읽기
using TMPro;
using UnityEngine;
using UnityEngine.UI;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class ScoreButton : MonoBehaviour
{
public Button button_score;
public TMP_Text text_score;
void Start()
{
if (button_score != null)
{
button_score.onClick.RemoveAllListeners();
button_score.onClick.AddListener(On_ClickScore);
}
Show_Score(); // 씬이 바뀌어도 관리자의 점수를 다시 읽어 띄움
}
void On_ClickScore()
{
if (GameManager.instance == null)
return;
GameManager.instance.Add_Score(10);
Show_Score();
}
void Show_Score()
{
if (text_score == null)
return;
int score = GameManager.instance != null ? GameManager.instance.score : 0;
text_score.text = "점수 " + score;
}
}
ScoreButton 은 씬마다 있는 버튼 쪽 스크립트입니다. 버튼 · 글자는 씬과 함께 지워지고 다시 생기므로, 점수는 자기가 들고 있지 않고 GameManager.instance 에서 읽습니다. 인스펙터에서 GameManager 를 끌어 넣는 방식은 씬이 바뀌면 끊기지만, instance 로 부르면 어느 씬에서든 같은 관리자를 찾습니다. 버튼 연결은 유니티 Button onClick 코드로 연결 · interactable 로 잠금 · 연타 막기 (AddListener, 반복문 람다) 와 같은 꼴입니다.
함수(function)
Start : 버튼에 On_ClickScore 를 걸고 지금 점수를 띄웁니다.
On_ClickScore : 관리자 점수에 10 을 더하고 다시 띄웁니다.
Show_Score : 관리자의 점수를 글자로 띄웁니다(관리자가 없으면 0).
4. 씬을 오가며 개수 세어 보기

Unity 6000.6.3f1 플레이 모드에서 타이틀 씬에 세 가지를 두고 「타이틀 → 게임 → 타이틀 → 타이틀」 로 바꿔 가며 씬마다 개수를 셌습니다. 위 GameManager, 중복 검사 없이 DontDestroyOnLoad 만 부르는 관리자, 그리고 다른 오브젝트의 자식에 붙여 DontDestroyOnLoad 를 부른 관리자입니다.
GameManager 는 끝까지 1개였고, 타이틀에서 더한 점수 10 이 게임 씬과 다시 불러온 타이틀에서도 그대로 10 이었습니다. 다시 생긴 GameManager 는 Awake 에서 바로 지워져 Start 도 불리지 않았습니다(Start 가 불린 횟수 1). 중복 검사가 없는 관리자는 타이틀에 돌아올 때마다 1 → 2 → 3 으로 늘었습니다. 자식에 붙인 관리자는 「DontDestroyOnLoad only works for root GameObjects or components on root GameObjects.」 경고가 뜨고, 게임 씬으로 넘어가자 사라졌습니다.
SceneManager.LoadScene 을 부른 바로 그 줄에서는 아직 타이틀 씬이었고, 프레임을 넘긴 뒤에야 게임 씬이 되어 있었습니다. 씬을 부른 직후 새 씬의 오브젝트를 찾으면 못 찾는 까닭입니다.
5. 첫 씬이 아닌 곳에서 시작할 때
using UnityEngine;
//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public static class ManagerLoader
{
public static GameManager Get_Manager()
{
if (GameManager.instance == null)
new GameObject("GameManager").AddComponent<GameManager>(); // AddComponent 때 Awake 가 바로 불림
return GameManager.instance;
}
}
에디터에서 게임 씬을 바로 열고 플레이하면 타이틀을 거치지 않아 GameManager 가 없습니다. 그럴 때 ManagerLoader.Get_Manager() 로 부르면 없을 때만 새로 만들어 돌려줍니다. 확인해 보니 관리자를 지운 뒤 부르면 점수 0 인 GameManager 가 하나 생겼고, 한 번 더 부르면 같은 것을 돌려줬습니다. 어느 씬에서 시작해도 관리자가 꼭 있어야 하면 ScoreButton 의 GameManager.instance 를 ManagerLoader.Get_Manager() 로 바꿔 씁니다.
6. 안 될 때 — 자주 하는 실수
타이틀에 돌아오면 관리자가 늘어난다
DontDestroyOnLoad 만 부르고 중복 검사를 안 했을 때입니다. Awake 맨 앞에서 instance 를 보고, 이미 있으면 Destroy(gameObject) 하고 return 합니다.
경고가 뜨고 씬을 바꾸면 사라진다
「Managers」 같은 빈 오브젝트 아래에 넣어 정리해 두면 DontDestroyOnLoad 가 듣지 않습니다. 관리자를 루트에 두거나, 부모 오브젝트 쪽에서 DontDestroyOnLoad 를 부르거나, 위처럼 transform.SetParent(null) 로 꺼냅니다.
씬을 바꾸면 인스펙터에 넣은 칸이 비어 있다(Missing)
남는 관리자가 씬 안의 버튼 · 글자를 인스펙터로 들고 있으면, 씬이 바뀔 때 그 오브젝트가 지워져 칸이 비어 버립니다. 씬 쪽 오브젝트가 관리자를 찾아가게(ScoreButton 처럼) 방향을 바꿉니다.
지운 바로 뒤에도 instance 가 남아 있다
Destroy 는 그 프레임이 끝날 때 지웁니다. 확인해 보니 Destroy 를 부른 줄 바로 뒤에는 instance 가 아직 남아 있었고, 한 프레임 뒤에는 비어 있었습니다(OnDestroy 에서 비움).
DontDestroyOnLoad 동작은 Unity 공식 문서 Object.DontDestroyOnLoad 와 Unity 6000.6.3f1 에디터(Windows) 플레이 모드에서 씬을 바꿔 본 결과를 기준으로 적었습니다.
'Unity3D > C#' 카테고리의 다른 글
| 유니티 Canvas Scaler 해상도 대응 · Match 값 고르기 (Scale With Screen Size) (0) | 2026.11.05 |
|---|---|
| 유니티 Scroll View 맨 아래로 자동 스크롤 (ScrollRect verticalNormalizedPosition) (0) | 2026.11.04 |
| 유니티 코루틴으로 몇 초 뒤 실행 (WaitForSeconds, StopCoroutine) (0) | 2026.11.03 |
| 유니티 씬 전환 · 로딩 바 (SceneManager.LoadScene, LoadSceneAsync) (0) | 2026.11.02 |
| 유니티 노치 화면 대응 · 안전 영역 (Screen.safeArea) (0) | 2026.10.31 |