📌 오늘 배운 핵심 요약 (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곳:
- Build.cs (모듈 추가)
- DefaultEngine.ini (Subsystem 속성을 NULL에서 Steam으로 변경)
- .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의 진짜 가치다.