Unreal 게임 개발 과정 TIL

Today's I Learning

카테고리 없음

08.14 Unreal 본캠프) 언리얼 MVVM 기반 멀티플레이 UI 및 OnlineSubsystem 설계

Sunlight__ 2026. 8. 14. 21:47

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

언리얼 엔진의 MVVM 패턴을 기반으로 멀티플레이어 UI 개발을 시작했다. 디자인 패턴 중 행동 패턴(옵저버, Pub/Sub 등)의 차이를 이해하고, Listen Server와 OnlineSubsystem의 역할 분리, 그리고 NULL에서 Steam으로의 유연한 전환 원리를 학습했다. 이를 바탕으로 현재 팀 프로젝트의 로비 시스템을 분석하고 메인 메뉴 UI 구조를 설계했다.

🛠️ 주요 학습 내용

1. 디자인 패턴 3대 분류와 행동 패턴의 이해

상황 변화를 알린다는 동일한 목적을 다양한 방식으로 풀어내는 행동(Behavioral) 패턴의 차이를 분석했다.

분류 목적 예시
생성 (Creational) 객체를 어떻게 만들 것인가 Singleton, Factory, Builder
구조 (Structural) 객체를 어떻게 조립할 것인가 Adapter, Composite, Proxy
행동 (Behavioral) 객체가 어떻게 소통할 것인가 Observer, Pub/Sub, Event Bus
  • 옵저버 (Observer): 발행자(Subject)가 구독자(Observer) 목록을 직접 보유한다. 서로를 알고 있어 결합도가 높다. (예: OnPlayerCountChanged.AddDynamic(...))
  • 구독/발행 (Pub/Sub): 브로커(채널)가 중간에서 메시지를 전달한다. 발행자와 구독자가 서로를 모르기 때문에 결합도가 낮다. (예: UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED)
  • 이벤트 버스 (Event Bus): Pub/Sub을 전역(Global)으로 확장한 형태다. 결합도는 가장 낮지만, 흐름 추적이 어려워지는 단점이 있다.
  • 노티파이 (Notify): 상태 변화를 단방향 알림으로만 전달한다. (예: OnRep_bIsReady(), AnimNotify)

2. "구독(Subscription)"의 정확한 의미

구독이란 "나중에 이벤트가 발생하면 나에게도 알려달라"고 예약해 두는 행위다. 구독하지 않으면 매 프레임 직접 상태를 확인(Polling)해야 하지만, 구독하면 이벤트 발생 시 지정된 함수가 자동 호출되어 매우 효율적이다.

C++
 
// 알림을 받을 대상(this)과 실행할 함수(OnCountChanged)를 등록(구독)
GameState->OnPlayerCountChanged.AddDynamic(this, &UMyWidget::OnCountChanged);

3. Listen Server vs OnlineSubsystem

멀티플레이 네트워크 토폴로지와 세션 관리 방식을 구분했다. 둘은 독립적인 개념이므로 Listen Server 환경에서도 OnlineSubsystem을 사용할 수 있다.

구분 Listen Server OnlineSubsystem
핵심 역할 호스트가 서버와 플레이어 역할을 동시 수행 (누가 서버인가) 세션 생성 및 방 탐색 관리 (방을 어떻게 찾는가)
방 찾기 접속할 IP 주소를 직접 입력해야 함 조건에 맞는 세션을 자동 탐색 가능
주요 코드 ServerTravel("맵이름?listen") CreateSession, FindSessions

4. NULL Subsystem에서 Steam 전환이 쉬운 이유

OnlineSubsystem은 추상화 레이어(인터페이스) 역할을 수행한다. 플랫폼(Steam, Epic 등)이 바뀌어도 C++ 코드는 수정할 필요가 없다.

C++
 
// NULL이든 Steam이든 아래의 C++ 코드는 완전히 동일하게 작동한다.
IOnlineSessionPtr Sessions = Subsystem->GetSessionInterface();
Sessions->CreateSession(...);
Sessions->FindSessions(...);
  • 변경이 필요한 3곳:
    1. Build.cs (모듈 추가)
    2. DefaultEngine.ini (Subsystem 속성을 NULL에서 Steam으로 변경)
    3. .uproject (Steam 플러그인 활성화)

5. MVVM 레이어 분리 원칙

MVVM(Model-View-ViewModel) 패턴을 활용하면 UI 디자인이 변경되어도 C++ 백엔드 로직은 수정할 필요가 없다.

  • View (Blueprint): 화면에 "보여주기"만 담당한다. ViewModel만 알며 Model은 모른다.
  • ViewModel (C++): 데이터를 "처리하기"만 담당한다. Model만 알며 View는 모른다.
  • Model (Engine/Net): 게임의 "실제 동작"만 담당한다.

🚀 진행 상황 및 구현 내용

1. 현재 프로젝트 분석 (팀원 코드)

인게임 진입점(MainMenu)을 직접 구현하기 위해, 팀원이 완성해 둔 로비 시스템 코드를 분석했다.

  • LobbyGameMode: Ready 체크 및 ServerTravel 로직 완성.
  • LobbyGameState: 플레이어 수 네트워크 복제 완성.
  • LobbyPlayerState: bIsReady 상태 네트워크 복제 완성.
  • LobbyPlayerController: R키 입력 시 ServerSetReady RPC 호출 로직 완성.

2. 메인 메뉴(MainMenu) UI 설계

  • 화면 흐름: Main ➔ RoomSelection ➔ (방 만들기 / 방 찾기)
  • 상태 제어: EMenuPanel 열거형(enum)을 정의하여 ViewModel이 패널 전환을 제어하도록 설계했다.
  • 세션 관리: 초기에는 NULL Subsystem으로 LAN 방 목록 자동 탐색을 구현하고, 추후 bIsLANMatch = false로 변경하여 Steam으로 전환할 예정이다.

3. 오늘 실제 작업 내역

  • Build.cs에 ModelViewViewModel, OnlineSubsystem 모듈 의존성 추가 완료.
  • Source/UI/MainMenu/Ch4MainMenuTypes.h 헤더 생성 및 EMenuPanel 열거형 정의 완료.

⏭️ 다음 할 일 (Next Steps)

  • Ch4MainMenuViewModel.h 헤더 설계 및 작성
  • Ch4MainMenuViewModel.cpp 세부 로직 구현
  • 코드 컴파일 후 언리얼 UMG 에디터에서 실제 위젯 레이아웃 구성 및 데이터 바인딩

🧠 오늘의 회고 (Reflection)

  • 서버 로직은 팀원이 훌륭하게 구축해 두었으므로, UI 담당자로서 클라이언트의 진입점(Entry Point)을 매끄럽게 설계하고 연동하는 것이 내 핵심 역할임을 명확히 인지했다.
  • MVVM 아키텍처를 도입하는 이유가 단순히 코드가 깔끔해지기 때문이 아님을 깨달았다. UI(View)와 로직(ViewModel/Model)이 독립적으로 분리되어, 훗날 디자인을 완전히 갈아끼우더라도 C++ 코드는 단 한 줄도 고칠 필요가 없는 확장성이야말로 MVVM의 진짜 가치다.