팀 프로젝트의 MVP 개발 중, UI 테스트를 위해 IntroScene
과 GameScene
을 분리했습니다. IntroScene
의 메인 메뉴에서 '새 게임' 버튼을 누르면 GameScene
으로 잘 넘어갔지만, 이상한 문제가 발생했습니다.
GameScene
이 시작됐는데, 이전에 테스트하느라 켜뒀던 PausePanel
(일시정지 메뉴)이 화면에 그대로 나타나 있었습니다.Esc
키를 눌러도 PausePanel
이 사라지지 않았습니다.마치 IntroScene
의 UI 유령이 GameScene
까지 쫓아온 듯한 현상이었습니다.
처음에는 이 문제의 원인을 DontDestroyOnLoad
로 살아남은 UIManager
의 상태가 초기화되지 않았기 때문이라고 생각했습니다. UIManager
가 씬을 넘어오면서 _isPaused
같은 변수나 PausePanel
의 활성화 상태를 그대로 가져왔다고 추측했죠.
그래서 SceneManager.sceneLoaded
이벤트를 구독하여, 씬이 로드될 때마다 UI 상태를 초기화하는 코드를 추가했습니다.
// UIManager.cs
private void OnEnable()
{
// 씬이 로드될 때마다 OnSceneLoaded 함수를 실행하도록 등록
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 씬이 새로 로드되었으니, UI 상태를 깨끗하게 초기화
InitializeUI();
}
private void InitializeUI()
{
_isPaused = false;
_pausePanel.SetActive(false);
// ...
}
💥 근본적인 원인 분석: 중복된 싱글톤과 깨진 참조
디버깅을 위해 씬이 전환된 직후 에디터를 일시정지하고 Hierarchy를 살펴보니, 결정적인 단서를 찾았습니다.
SampleScene에 UIManager 오브젝트가 두 개 존재했습니다!
상황은 이렇습니다.
IntroScene에 있던 UIManager_A는 DontDestroyOnLoad 덕분에 파괴되지 않고 GameScene으로 넘어옵니다.
GameScene이 로드되면서, 씬 안에 원래 존재하던 UIManager_B가 생성됩니다.
UIManager_B는 Awake()에서 싱글톤 인스턴스(UIManager.Instance)가 이미 UIManager_A로 할당된 것을 보고 스스로를 파괴(Destroy)합니다.
결국 GameScene에 남는 것은 IntroScene에서 넘어온 UIManager_A 입니다.
진짜 문제는 여기서 발생합니다.
UIManager_A의 인스펙터에 연결된 _pausePanel 변수는 IntroScene에 있던 PausePanel을 가리키고 있었습니다. GameScene으로 넘어오면서 IntroScene의 PausePanel은 파괴되었고, UIManager_A가 들고 있던 참조는 깨져버린(null) 것입니다.
씬을 넘어 살아남은 싱글톤은, 씬과 함께 파괴된 과거의 유령(UI 요소)을 참조하고 있었던 것입니다. 그러니 Esc 키를 눌러봤자 허공에 대고 명령하는 셈이라 아무 일도 일어나지 않았던 거죠.
✅ 해결책: "각 씬은 각자의 주인이 있다"
이 복잡한 문제를 해결하기 위해, 저희 팀은 MVP 단계에서는 더 단순하고 명확한 구조를 채택하기로 했습니다.
UIManager의 DontDestroyOnLoad를 포기하고, 각 씬이 자신의 UI 매니저를 책임지도록 구조를 단순화하자!
UIManager.cs의 Awake() 함수를 아래와 같이 수정했습니다.
// UIManager.cs - 수정 후
private void Awake()
{
// 한 씬에 UIManager가 두 개 이상 있는 경우를 방지
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
// 이 씬이 파괴될 때 UIManager도 함께 파괴되도록 DontDestroyOnLoad를 제거!
// DontDestroyOnLoad(gameObject);
}
이제 IntroScene에는 MainMenu 스크립트가 UI를 담당하고, GameScene으로 전환되면 그곳에 있는 새로운 UIManager가 깨끗한 상태로 생성되어 자기 씬의 UI를 완벽하게 제어하게 됩니다.
🚀 결론
싱글톤과 DontDestroyOnLoad는 매우 편리하고 강력한 패턴이지만, 그 생명주기와 씬 간의 참조 관계를 명확히 이해하지 않으면 예상치 못한 버그를 낳을 수 있습니다.
이번 트러블슈팅을 통해, 무조건적인 디자인 패턴 사용보다는 현재 프로젝트의 규모와 팀의 상황에 맞는 단순하고 명확한 구조가 더 좋을 수 있다는 것을 다시 한번 깨달았습니다.
혹시 저와 비슷한 문제를 겪는 분이 있다면 이 글이 도움이 되기를 바랍니다.