메인 콘텐츠로 건너뛰기

"Best Practices" 태그의 게시물 4개 게시물건

Development best practices and tips

모든 태그 보기

크로스씬 이벤트: 아무도 말하지 않지만 모두가 겪는 영속화 문제

TinyGiants
GES Creator & Unity Games & Tools Developer

AudioManager가 배경 음악을 재생한다. OnLevelStart에 구독하여 플레이어가 새 지역에 진입하면 트랙을 변경한다. DontDestroyOnLoad 오브젝트에 AudioManager를 배치해서 씬 로드 간에 유지되도록 한다. 같은 씬에서만 테스트하고 있으니까 개발 중에는 모든 것이 잘 동작한다.

그러다 누군가 처음으로 Level 1에서 Level 2를 로드한다. 음악이 바뀌지 않는다. AudioManager는 살아있다 — DontDestroyOnLoad이 제 역할을 했다 — 하지만 이벤트 구독이 전환을 넘기지 못했다. 더 나쁜 경우: 이전 구독이 여전히 남아있고, 파괴된 Level 1의 이벤트 발생자를 가리키고 있어서, 다음에 뭔가가 호출하려 하면 게임플레이 도중에 MissingReferenceException이 터진다.

이것이 영속화 문제이며, 씬이 두 개 이상인 모든 Unity 프로젝트가 결국 맞닥뜨린다.

출시 후 발견되는 이벤트 시스템의 함정: 메모리 누수, 데이터 오염, 재귀 트랩

TinyGiants
GES Creator & Unity Games & Tools Developer

게임을 5분씩 테스트하고 있다. 잘 돌아간다. 그런데 QA가 리포트를 올린다: "30분 플레이 세션 동안 메모리 사용량이 꾸준히 증가합니다. 6개의 씬을 로드하면 프레임 레이트가 60에서 40으로 떨어집니다." 프로파일링해 본다. 12개여야 할 이벤트에 847개의 리스너가 등록되어 있다. 매 씬 로드마다 새로운 구독이 추가됐지만 이전 것은 제거되지 않았다. 오브젝트는 파괴됐지만, 델리게이트 참조가 살아남아 죽은 MonoBehaviour들을 메모리에 고정시키고 있어서 가비지 컬렉터가 건드릴 수 없다.

아니면 이런 것: "두 번째 Play Mode 세션에서 체력 값이 틀립니다. 첫 번째 실행은 정상입니다." Play를 누르고, 전투를 테스트하고, 멈춘다. 다시 Play. 플레이어가 100이 아닌 73 HP로 시작한다. 아무도 리셋하지 않아서 마지막 세션의 ScriptableObject 상태가 그대로 남아 있는 것이다.

혹은 고전적인 것: 게임이 3초 동안 멈추더니 Unity가 크래시한다. Event A의 리스너가 Event B를 Raise했다. Event B의 리스너가 Event A를 Raise했다. 스택 오버플로우. 하지만 때로는 크래시하지 않는다 — 그냥 멈춰서 아무런 에러도 표시하지 않은 채 CPU를 갈아먹는 무한 루프로 빠진다.

이것들은 가설이 아니다. 프로덕션 게임에서 출시된 걸 직접 본 버그들이다. 그리고 모두 같은 근본 원인을 갖고 있다: 개별적으로는 올바르게 보이지만 규모가 커지면 실패하는 이벤트 시스템 패턴.

실행 순서 버그: "누가 먼저 응답하느냐"에 숨겨진 위험

TinyGiants
GES Creator & Unity Games & Tools Developer

플레이어가 25 데미지를 받는다. 체력 시스템이 현재 HP에서 이를 차감한다. UI가 체력바를 갱신한다. 그런데 체력바에 75가 아니라 100이 표시된다. 20분 동안 코드를 들여다본 끝에 원인을 알게 된다: UI 리스너가 체력 시스템 리스너보다 먼저 실행된 것이다. UI는 이전 HP 값을 읽어서 렌더링했고, 그 후에야 체력 시스템이 값을 업데이트했다. 데이터가 올바르게 갱신됐을 때는 이미 프레임이 그려진 후였다.

이것이 바로 실행 순서 버그다. 이벤트 기반 아키텍처를 사용하는 게임을 출시해 본 적이 있다면, 자기도 모르게 이런 버그를 몇 개씩 함께 출시했을 가능성이 높다. 테스트할 때는 스크립트가 우연히 올바른 순서로 초기화되어 잘 동작하다가, 프로덕션에서 Unity가 다른 순서로 로딩하면서 깨지는 그런 종류의 버그다.

이건 드문 엣지 케이스가 아니다. 대부분의 이벤트 시스템이 가진 구조적 결함이다 — Unity의 UnityEvent와 표준 C# event 델리게이트를 포함해서. 왜 이런 일이 생기는지 이해하고 나면, 다시는 모른 척할 수 없게 된다.

이벤트 200개 돌파: 이벤트 관리가 무너지는 이유

TinyGiants
GES Creator & Unity Games & Tools Developer

새 Unity 프로젝트를 시작한다. 이벤트 10개를 만든다. OnPlayerDeath, OnScoreChanged, OnLevelComplete. 적절한 이름을 붙이고, 폴더에 넣고, 넘어간다. 인생이 좋다. 전체 이벤트 구조를 머릿속에 담을 수 있다.

6개월이 지났다. 이벤트가 200개다. Project 창은 ScriptableObject 파일의 벽이다. OnPlayerHealthDepleted가 필요한데 — 아니면 OnPlayerHPLow였나? 아니면 OnPlayerHealthZero? 전부 OnPlayer로 시작하는 이름들을 눈을 가늘게 뜨고 스크롤한다. 3분 후 포기하고 새로 하나 만든다, 원하는 이벤트가 이미 있는지조차 확신이 안 서니까.

모든 이벤트 기반 Unity 프로젝트가 결국 도달하는 곳이다. 이벤트 패턴이 잘못돼서가 아니라, 아무도 규모에서 이벤트를 관리하기 위한 도구를 만들지 않았기 때문이다. Unity는 Animation 창, Shader Graph, Timeline, Input System 디버거를 제공한다. 이벤트에는... Project 창뿐이다.