메인 콘텐츠로 건너뛰기

보이지 않는 이벤트 체인: 볼 수 없는 것은 디버깅할 수 없다

TinyGiants
GES Creator & Unity Games & Tools Developer

플레이어가 죽습니다. 사망 사운드가 재생됩니다. 래그돌이 활성화됩니다. "You Died" UI 팝업이 나타납니다. 게임이 자동 저장됩니다. 분석 이벤트가 발생합니다. 리스폰 타이머가 카운트다운을 시작합니다. 하나의 이벤트 OnPlayerDeath에 여섯 개의 다른 시스템이 응답합니다. 하지만 질문 하나 — 이게 어디에 문서화되어 있나요?

코드에 없습니다. 프로젝트 관리 도구에도 없습니다. 어떤 다이어그램에도 없습니다. 딱 한 곳에 존재합니다: 원래 설정한 사람의 머릿속. 그 사람이 6개월 전에 팀을 떠났다면, 아무 데도 존재하지 않습니다.

이것이 이벤트 기반 아키텍처의 더러운 비밀입니다. 시스템을 디커플링하기 위해 채택합니다. AudioManagerUIManager에 대한 참조가 필요 없다는 걸 축하합니다. 하지만 비용에 대해서는 절대 이야기하지 않습니다: 실행 흐름이 보이지 않게 됩니다. 그리고 보이지 않는 것은, 정의상, 시각적으로 디버깅할 수 없습니다.

가시성 문제는 생각보다 심각하다

"보이지 않는 이벤트 체인"이 실제로 무엇을 의미하는지 솔직하게 이야기합시다. 추상적인 아키텍처 우려가 아닙니다. 매우 구체적이고, 매우 고통스러운 방식으로 나타납니다.

Grep 의식

새 개발자가 팀에 합류합니다. 첫 주. 사망 화면이 가끔 나타나지 않는 버그를 보고 있습니다. "플레이어가 죽으면 뭐가 일어나요?"라고 물어봅니다.

OnPlayerDeath가 관련된다는 걸 알고 있습니다. 그래서 코드베이스를 grep합니다. 해당 이벤트 이름을 참조하는 파일 20개를 찾습니다. 일부는 구독. 일부는 구독 해제. 일부는 주석. 일부는 8개월 전에 "임시로" 비활성화된 죽은 코드. 한 시간을 들여 결과를 정리하고, 체인의 멘탈 맵을 구축합니다.

그러다 당신이 말합니다: "아, PlayerHealth 컴포넌트에 OnDeath를 발생시키는 UnityEvent도 있어요." 이건 다른 구독 메커니즘입니다. Grep으로 찾지 못했습니다. 문자열 "OnPlayerDeath"가 근처 어디에도 없기 때문입니다 — Inspector에서 연결되어 씬 파일에 시리얼라이즈되어 있으니까요.

또 한 시간. 그리고 아직도 전부 찾았는지 확신할 수 없습니다.

연쇄 문제

진짜 복잡해지는 부분입니다. OnPlayerDeathOnDisableInput을 트리거하고, 이는 OnPauseEnemyAI를 트리거합니다. OnPlayerDeath는 또한 OnStartRespawnSequence를 트리거하고, 이는 OnFadeToBlack을 트리거하고, 이는 OnLoadCheckpoint를 트리거하고, 이는 OnResetEnemyPositions를 트리거합니다.

이벤트 A가 B를 트리거하고, B가 C와 D를 트리거합니다. D가 E와 F를 트리거합니다. F가 G를 트리거합니다.

코드에서 이 체인을 추적해보세요. InputManager.cs에서 OnPlayerDeath 구독을 찾습니다. 그 핸들러가 OnDisableInput을 발생시킵니다. 그래서 OnDisableInput의 구독을 검색합니다. EnemyAIController.cs에서 하나를 찾습니다. 그 핸들러가 발생시키는 건... 없나요? 아니면 있나요? 확인합니다. OnAIPaused를 발생시키지만 특정 플래그가 설정된 경우에만. 그래서 체인이 조건적으로 분기합니다.

이걸 리스폰 시퀀스가 병렬로 실행되는 것과 곱하세요. 오디오 체인도. 분석 체인도.

이것은 이벤트 관계의 방향 비순환 그래프인데, 개별 파일을 읽어서 재구성하려 하는 겁니다. 개별 주소를 읽어서 도시의 도로망을 이해하려는 것과 같습니다.

온보딩 세금

모든 신규 팀원이 같은 세금을 냅니다. "X가 일어나면 뭐가 되나요?"라는 질문에는 코드베이스 가이드 투어가 필요합니다. "이벤트 흐름: 플레이어 사망" 같은 섹션에 무엇이 무엇에 응답해서 발생하는지 15개 항목을 나열하는 온보딩 문서를 봤습니다. 그 문서는 두 번째 스프린트가 되면 이미 구식입니다.

문제는 팀이 문서화를 못하는 게 아닙니다. 이벤트 흐름은 텍스트 형태로 문서화할 수 없다는 겁니다. 그래프입니다 — 노드와 엣지, 분기와 병합, 병렬과 순차 경로. 그래프를 글머리 기호 목록으로 설명하는 것은 회로도를 산문으로 설명하는 것과 같습니다. 할 수는 있지만, 아무도 실제로 사용할 수 없습니다.

시퀀스 조율 악몽

사망 응답 여섯 개 중 일부는 동시에 일어나야 합니다. 사망 사운드와 래그돌은 같은 시점에 시작해야 합니다 — 하나를 기다릴 이유가 없습니다. 하지만 화면 페이드는 리스폰 로드 전에 반드시 일어나야 합니다. 리스폰 로드는 반드시 텔레포트 전에 완료되어야 합니다. 텔레포트는 반드시 페이드 인 전에 완료되어야 합니다.

같은 흐름 안에 병렬 AND 순차.

Unity에서 이걸 표현하려면 코루틴입니다. 코루틴이 코루틴을 호출합니다. 페이드가 끝났는지 추적하는 콜백. 입력을 게이트하는 _isRespawning bool. 다음 단계를 트리거하는 _fadeComplete 플래그. 어쩌면 DeathState, FadingState, LoadingState, TeleportingState, FadingInState가 있는 상태 머신.

전부 보이지 않습니다. 전부 취약합니다. 두 단계의 순서를 바꾸면 코루틴 체인을 리팩토링해야 합니다. 새 단계를 추가하면 올바른 위치에 삽입했기를 기도합니다. 단계를 제거하면 다운스트림에서 타이밍에 의존하는 것이 없기를 바랍니다.

다른 분야가 알아낸 것

짜증나는 점이 있습니다. 다른 소프트웨어 분야에서는 이 문제를 이미 오래전에 해결했습니다.

CI/CD 파이프라인? 비주얼 파이프라인 에디터에서 모든 단계, 모든 의존성, 모든 병렬 분기를 볼 수 있습니다. GitHub Actions, Jenkins Blue Ocean, GitLab CI — 모두 DAG를 보여줍니다.

데이터 엔지니어링? Apache Airflow가 데이터 파이프라인을 방향 그래프로 보여줍니다. 모든 태스크, 모든 의존성, 모든 조건 분기가 한눈에 보입니다.

웹 개발? Chrome DevTools가 요청 워터폴을 보여줍니다. 모든 네트워크 호출, 타이밍, 의존성이 전부 시각적.

마이크로서비스 아키텍처? Jaeger나 Zipkin 같은 분산 추적 도구가 서비스 간 요청 흐름을 비주얼 타임라인으로 보여줍니다.

게임 이벤트 시스템? 아무것도. Debug.Log와 grep이 전부입니다. 지금까지는요.

GES의 Flow Graph Editor: 이벤트 체인을 보이게 만들기

GES Flow Graph Editor는 코드(또는 머릿속)에만 존재하던 이벤트 관계를 비주얼 노드 그래프로 렌더링합니다. 이벤트가 노드입니다. 관계가 연결선입니다. 전체 흐름이 한 곳에 보입니다.

이것이 무엇이 아닌지 분명히 해두겠습니다. 비주얼 스크립팅이 아닙니다. C# 게임 로직을 대체하지 않습니다. AudioManager는 여전히 C#으로 사운드를 재생합니다. UIManager는 여전히 C#으로 화면을 관리합니다. Flow Graph Editor는 그 시스템들 사이의 관계를 시각화합니다 — 이벤트 A가 발생하면, 어떤 다른 이벤트가 응답하고, 어떤 순서로, 어떤 조건으로.

게임의 이벤트 오케스트라를 위한 지휘자의 총보라고 생각하세요. 개별 악기(C# 스크립트)가 자기 파트를 연주합니다. 총보(Flow Graph)는 각각이 언제 연주하고, 어떻게 관련되고, 전체가 어떻게 맞물리는지를 보여줍니다.

Flow Graph Editor Overview

두 가지 기본 패턴: Trigger와 Chain

게임의 모든 이벤트 흐름은 두 가지 실행 패턴으로 귀결되며, Node Editor에서 시각적으로 구분됩니다.

Trigger (병렬, 주황색): 소스 이벤트가 발생하면, 연결된 모든 타겟이 동시에 발생합니다. Fire-and-forget. 하나의 타겟이 실패해도 나머지는 실행됩니다. "사운드 재생 AND 파티클 생성 AND UI 업데이트" 패턴입니다.

Flow Graph Trigger

Chain (순차, 초록색): 소스 이벤트가 발생하면, 연결된 타겟이 엄격한 순서로 하나씩 실행됩니다. 각 단계는 이전 단계가 완료될 때까지 기다립니다. "화면 페이드 THEN 씬 로드 THEN 플레이어 텔레포트" 패턴입니다.

Flow Graph Chain

시각적 구분이 즉각적입니다. 주황색 선이 펼쳐지면 — 병렬. 초록색 선이 순서대로 흐르면 — 순차. 누구든 그래프를 보고 실행 모델을 즉시 이해할 수 있습니다. 코드 읽기 없음. 머릿속 추적 없음. 그냥 선을 따라가면 됩니다.

그리고 네, 혼합할 수 있습니다. 처음의 플레이어 사망 흐름? 세 개의 주황색 trigger 선(사운드 + 래그돌 + 분석, 병렬)과 초록색 chain 시퀀스(페이드 → 로드 → 텔레포트 → 페이드 인, 순차)가 나란히. 비주얼 레이아웃이 병렬/순차 분할을 한눈에 명확하게 만듭니다.

캔버스 내비게이션

에디터는 무한 캔버스입니다. 마우스 중간 버튼이나 Alt+좌클릭 드래그로 패닝. 스크롤 휠로 줌. F를 눌러 모든 노드를 뷰에 맞추거나, 선택한 것만 맞출 수 있습니다. 상단 툴바에서 저장, 검색, 미니맵 토글, 그리드 스냅, 디버그 모드를 제공합니다.

Flow Graph Toolbar

빈 공간에서 우클릭하면 노드 추가. 노드에서 우클릭하면 노드별 옵션. 연결선에서 우클릭하면 설정. 컨텍스트 메뉴는 맥락에 따라 다릅니다 — 클릭한 대상에 따라 다른 옵션이 나옵니다.

Flow Graph 만들기

흐름을 만드는 것은 다른 노드 에디터에서 기대하는 대로 동작합니다:

  1. 캔버스에서 우클릭하고 프로젝트의 각 이벤트에 대한 노드를 추가
  2. 출력 포트에서 입력 포트로 클릭해서 드래그하여 연결 생성
  3. 연결이 Trigger(병렬)인지 Chain(순차)인지 선택
  4. 연결 속성 설정 — 조건, argument transformer, 타이밍

각 노드는 이벤트 이름, 인자 타입, 입출력 포트를 보여줍니다. 연결선은 색상 코드로 실행 타입(trigger vs chain)을 나타냅니다. 설정 중에 에디터가 연결된 이벤트 간의 타입 호환성을 검증하고 불일치에 대해 경고합니다.

Flow Graph Editor Example

복잡한 흐름을 위한 그룹

조직 없는 50개 노드 그래프는 대체된 코드보다 더 나쁩니다. 그룹 시스템이 이걸 해결합니다. 노드를 선택하고, 우클릭하고, 그룹을 만듭니다. 이름을 지정합니다 — "플레이어 사망 흐름", "오디오 이벤트", "보스 페이즈 2." 색상을 할당합니다. 이제 그래프에 도메인 경계를 한눈에 전달하는 비주얼 영역이 있습니다.

Flow Graph Groups

그룹은 순수하게 조직적입니다 — 실행에는 영향을 미치지 않습니다. 하지만 가독성에 필수적입니다. 초기에 팀 색상 규칙을 정하는 걸 권장합니다: 시스템 이벤트는 파랑, 게임플레이는 초록, UI는 주황, 오디오는 보라. 그래프가 일관된 색상을 사용하면, 줌아웃해서 노드 레이블을 읽지 않고도 구조를 즉시 이해할 수 있습니다.

그룹은 중첩도 지원합니다. "보스 전투" 그룹 안에 "페이즈 1", "페이즈 2", "페이즈 3" 하위 그룹이 있을 수 있습니다. 각 페이즈 안에 "이펙트"와 "게임플레이" 하위 하위 그룹이 있을 수 있습니다. 이 계층적 조직은 수백 개의 노드가 있는 흐름까지 확장됩니다.

런타임 시각화: 흐름 실행을 지켜보기

모든 것을 바꾸는 기능입니다. Node Editor를 열고 디버그 모드를 활성화한 상태에서 Play Mode에 진입하면, 그래프가 살아 움직입니다.

활성 노드는 이벤트가 발생할 때 펄스합니다. 연결선은 소스에서 타겟으로 데이터가 흐르는 것을 애니메이션합니다. Chain 단계는 각각 실행될 때 순서대로 하이라이트됩니다. 실패한 조건은 차단한 연결에서 빨간색으로 깜박입니다. 말 그대로 이벤트 흐름이 실시간으로 실행되는 것을 볼 수 있습니다.

처음의 플레이어 사망 디버깅 시나리오를 기억하나요? 그래프를 엽니다. 죽습니다. 지켜봅니다. OnPlayerDeath 노드가 빛나고, 주황색 선이 사운드와 래그돌 노드로 동시에 애니메이션됩니다. 초록색 체인이 페이드, 로드, 텔레포트, 페이드 인을 순서대로 진행합니다. 사망 화면이 나타나지 않으면, 어떤 노드가 발생하지 않았는지 그리고 왜인지 정확히 볼 수 있습니다 — 조건이 차단했거나, 연결이 빠져있거나, 체인이 이전 단계에서 끊어졌거나.

이것이 콘솔 출력에서 "OnPlayerDeath fired"와 "OnFadeToBlack fired"를 읽고 머릿속에서 타이밍을 재구성하는 것과, 전체 흐름이 실시간으로 시각적으로 실행되는 것을 보는 것의 차이입니다.

디버그 시각화는 프레임당 대략 0.5-1ms의 오버헤드를 추가하며, 개발 중에는 문제없습니다. 빌드에서는 자동으로 비활성화됩니다 — 출시 게임에서 런타임 비용 제로.

진짜 요점

Node Editor는 코드를 대체하는 것이 아닙니다. 모든 이벤트 기반 아키텍처가 겪는 가시성 문제를 해결하는 겁니다. C# 스크립트가 구현입니다. Flow Graph가 지도입니다.

다른 모든 성숙한 소프트웨어 분야에는 실행 흐름을 이해하기 위한 비주얼 도구가 있습니다. 게임 개발이 드디어 따라잡고 있습니다.

신규 개발자가 "플레이어가 죽으면 뭐가 일어나요?"라고 물으면, 더 이상 2시간 동안 코드를 안내하지 않아도 됩니다. 그래프를 엽니다. 화면을 가리킵니다. "이겁니다."

그것만으로도 가치가 있습니다.


🚀 글로벌 개발자 서비스

🇨🇳 중국 개발자 커뮤니티

🌐 글로벌 개발자 커뮤니티

📧 지원