Unreal 게임 개발 과정 TIL

Today's I Learning

카테고리 없음

08.28 Unreal 본캠프) CDO 시점 UMG 로드 크래시 원인 분석 및 MVVM 트러블슈팅

Sunlight__ 2026. 8. 28. 19:55

📌 오늘 배운 핵심 요약 (Summary)

언리얼 엔진 5의 UMG와 Pure MVVM 패턴을 활용해 인게임 설정 창(WBP_Settings)을 구축했다. 시야각(FOV), 마우스 감도, Y축 반전, 사운드 볼륨, VSync, 해상도 등 다양한 옵션을 실시간으로 동기화했다. 이 과정에서 발생한 엔진 기동 크래시, 데이터 바인딩 유실, 양방향 통신 시 발생하는 재귀 참조(순환 루프) 오류를 분석하고 C++로 해결했다.

🏗️ 핵심 구현 내용

1) Pure MVVM 구조 정립

  • UI 위젯에 불필요한 임시 변수를 두는 안티 패턴을 철저히 배제했다.
  • C++ UCh4SettingsViewModel을 단일 진실 공급원(Single Source of Truth)으로 삼아 UMG View Bindings를 통한 동기화를 달성했다.
  • 슬라이더, 텍스트 상자, 체크박스 등 상호작용 요소들의 값 변경과 저장 로직을 뷰모델 내부에 캡슐화하여 설계했다.

2) 팀원 코드를 보존하는 PlayerController 입력 가로채기

  • 기존에 구현된 캐릭터 C++ 코드를 직접 수정하지 않고, ACh4_multiGamePlayerController의 AddPitchInput과 AddYawInput 함수를 오버라이드(Override)했다.
  • GConfig에 저장된 마우스 감도(Sensitivity)와 Y축 반전(Invert) 설정을 컨트롤러 레벨에서 곱연산 처리하여 플레이어의 시선 조작에 실시간으로 반영되도록 구현했다.

🚨 트러블슈팅 (Troubleshooting Log)

Issue 1: 에디터 재시작 시 바인딩 유실 및 기동 크래시 (EXCEPTION_ACCESS_VIOLATION)

  • 증상: 에디터를 껐다 켤 때마다 WBP_Settings의 뷰모델 등록과 바인딩이 전부 삭제되거나, 기동 시 엔진이 널 포인터 크래시로 종료됨.
  • 원인: 콜스택 분석 결과 ACh4_multiGamePlayerController의 C++ 생성자에서 ConstructorHelpers::FClassFinder로 위젯을 하드 로드하고 있었다. C++ 생성자는 엔진 기동 극초기(CDO 생성 시점)에 실행되는데, MVVM 에디터 모듈이 아직 준비되지 않은 상태에서 위젯 바이트코드를 조기 파싱하려다 충돌이 발생한 것이다.
  • 해결: C++ 생성자에서의 FClassFinder 강제 로드 코드를 완전히 제거했다. 실제 메뉴를 여는 시점(ShowPauseMenu)에 StaticLoadClass를 활용하여 지연 로드(Lazy Load) 하도록 전환하여 기동 충돌과 바인딩 증발 문제를 해결했다.

Issue 2: 양방향 바인딩 컴파일 에러 (Property is not writable at runtime)

  • 증상: UMG View Bindings 창에서 양방향(↔) 바인딩을 걸었을 때 런타임 쓰기 불가 컴파일 에러 발생.
  • 원인: C++ 뷰모델의 변수들이 BlueprintReadOnly로 선언되어 있어 엔진 바인딩 시스템이 위젯에서의 역방향 쓰기를 거부함.
  • 해결: 조작이 필요한 속성들을 BlueprintReadWrite 및 Setter = "Set..." 속성으로 변경하여 UMG 바인딩 시스템이 C++ Setter를 자동 호출할 수 있도록 통로를 개방했다.

Issue 3: 무한 핑퐁 렉 & 재귀 바인딩 에러 (Recursive binding detected)

  • 증상: 슬라이더를 드래그하는 순간 게임 프레임이 급격히 저하되며 엔진 로그에 재귀 바인딩 경고가 다수 출력됨.
  • 원인: 슬라이더 조작 ➔ 뷰모델 값 갱신 ➔ 뷰모델 브로드캐스트 ➔ 바인딩된 슬라이더가 다시 신호를 받아 뷰모델을 찌르는 순환 참조(메아리 현상)가 발생했다. 로드 시점에도 체크박스가 초기값을 받으면서 역방향 Setter를 호출하는 문제가 동반되었다.
    • 재귀 쉴드(bIsInternalUpdating): 뷰모델의 모든 Setter 및 Load 함수 입구에 가드 플래그를 두어, 내부 연산이 진행되는 프레임 동안 재유입되는 역방향 신호를 즉시 return으로 차단했다.
    • 텍스트 처리 분리: 입력 상자(Editable Text Box)는 단방향(←) 바인딩으로 설정하고, 엔터 키 입력 시(OnTextCommitted)에만 C++ Setter를 1회 호출하도록 분리하여 에코(Echo)를 차단했다.해결 (안티 에코 패턴 적용):

Issue 4: 입력창 초과값 및 비정상 문자 미교정 현상

  • 증상: FOV 입력창에 한계를 초과하는 수치나 문자를 섞어 입력했을 때, 내부 데이터는 정상적으로 보정되지만 화면 텍스트 입력창에는 사용자가 입력한 오타가 그대로 남음.
  • 원인: 기존 C++ 최적화 코드에서 현재 FOV 값이 보정값(Clamped)과 동일하면 화면 텍스트 갱신 함수(Broadcast)를 건너뛰도록 설계되어 있었다.
  • 해결: Str.IsNumeric() 검증을 추가했다. 내부 데이터 값이 이전과 동일하더라도, 사용자의 오타를 무조건 올바른 텍스트 포맷으로 덮어쓰도록 강제 브로드캐스트(Force Overwrite)를 적용하여 텍스트 불일치를 바로잡았다.

🧠 배운 점 (Insights)

  1. C++ 생성자에서 UMG 에셋 강제 로드 지양: CDO 생성 시점에 블루프린트 위젯을 하드 로드하면 엔진의 플러그인 로딩 순서와 충돌하여 예측 불가능한 크래시나 직렬화 유실을 유발한다. UI 에셋은 지연 로드(Lazy Load)나 에디터 노출 프로퍼티를 활용해 런타임에 로드해야 한다.
  2. Two-Way 바인딩과 안티 에코(Anti-Echo) 방어: 양방향 데이터 바인딩을 설계할 때는 조작 주체에게 역방송을 하지 않도록 구조를 분리하거나, 가드 플래그(bIsUpdating)를 두어 순환 참조를 차단해야 무한 루프로 인한 프레임 저하를 막을 수 있다.
  3. Pure MVVM의 가치: 위젯 블루프린트에 복잡한 노드 그래프를 짜맞추는 대신 C++ ViewModel에 비즈니스 로직을 집약시켰다. 그 결과, 요소가 많은 복잡한 설정 시스템임에도 유지보수가 매우 용이한 견고한 아키텍처를 완성할 수 있었다.