PART 4 · 강의 3/3

ViewModel 패턴

Global ViewModel Collection, 실전 MVVM 아키텍처 구현, 그리고 ViewModel 기반 UI 테스트 전략을 학습합니다.

01

Global ViewModel Collection

전역적으로 접근 가능한 ViewModel 관리

Global Viewmodel Collection은 MVVM Game Subsystem에서 관리하는 전역 ViewModel 목록입니다. 설정 메뉴, 플레이어 상태 등 여러 UI에서 공유해야 하는 데이터에 적합합니다.

C++ - Global ViewModel Collection
// Global ViewModel Collection에 등록 void UMyGameInstance::Init() { Super::Init(); // 게임 설정 ViewModel (전역으로 사용) UGameSettingsViewModel* SettingsVM = NewObject<UGameSettingsViewModel>(this); // MVVM 서브시스템을 통해 전역 등록 UMVVMGameSubsystem* MVVMSubsystem = GetSubsystem<UMVVMGameSubsystem>(); if (MVVMSubsystem) { FMVVMViewModelContext Context; Context.ContextClass = UGameSettingsViewModel::StaticClass(); Context.ContextName = FName("GameSettings"); MVVMSubsystem->GetViewModelCollection()->AddViewModelInstance( Context, SettingsVM); } } // 어떤 UI에서든 접근 가능 UGameSettingsViewModel* UMyWidget::GetSettingsVM() const { UMVVMGameSubsystem* MVVMSubsystem = /* 서브시스템 획득 */; FMVVMViewModelContext Context; Context.ContextClass = UGameSettingsViewModel::StaticClass(); Context.ContextName = FName("GameSettings"); return Cast<UGameSettingsViewModel>( MVVMSubsystem->GetViewModelCollection()->FindViewModelInstance(Context)); }
02

실전 MVVM 아키텍처

인벤토리 시스템을 예로 한 완전한 MVVM 구현

C++ - 인벤토리 MVVM 예제
// Model: 실제 인벤토리 데이터 USTRUCT(BlueprintType) struct FInventoryItem { GENERATED_BODY() UPROPERTY() FName ItemID; UPROPERTY() int32 Quantity; UPROPERTY() UTexture2D* Icon; }; // ViewModel: UI에 필요한 형태로 데이터 가공 UCLASS() class UInventoryViewModel : public UMVVMViewModelBase { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, FieldNotify, Getter) TArray<FInventoryItem> Items; UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, Setter) int32 SelectedIndex = -1; UPROPERTY(BlueprintReadOnly, FieldNotify, Getter) int32 TotalWeight = 0; UPROPERTY(BlueprintReadOnly, FieldNotify, Getter) int32 MaxWeight = 100; // Model에서 데이터 동기화 void SyncFromInventoryComponent(UInventoryComponent* InvComp) { Items = InvComp->GetItems(); UE_MVVM_SET_PROPERTY_VALUE(TotalWeight, InvComp->GetTotalWeight()); // FieldNotify가 바인딩된 UI를 자동 갱신 BroadcastFieldValueChanged( UInventoryViewModel::FFieldNotificationClassDescriptor::Items); } // UI에서 호출하는 커맨드 UFUNCTION(BlueprintCallable) void UseSelectedItem() { if (Items.IsValidIndex(SelectedIndex)) { OnItemUsed.Broadcast(Items[SelectedIndex].ItemID); } } UPROPERTY(BlueprintAssignable) FOnItemUsedDelegate OnItemUsed; // Getters & Setters... const TArray<FInventoryItem>& GetItems() const { return Items; } int32 GetSelectedIndex() const { return SelectedIndex; } void SetSelectedIndex(int32 V) { UE_MVVM_SET_PROPERTY_VALUE(SelectedIndex, V); } int32 GetTotalWeight() const { return TotalWeight; } int32 GetMaxWeight() const { return MaxWeight; } };
03

ViewModel 테스트 전략

MVVM의 핵심 장점 - UI 로직의 독립적 테스트

MVVM의 가장 큰 장점 중 하나는 ViewModel을 UI 없이 독립적으로 테스트할 수 있다는 것입니다. 게임 로직 테스트에서 위젯 인스턴스를 생성할 필요가 없습니다.

C++ - ViewModel 단위 테스트
// Automation Test IMPLEMENT_SIMPLE_AUTOMATION_TEST( FInventoryViewModelTest, "Game.UI.InventoryViewModel", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) bool FInventoryViewModelTest::RunTest(const FString& Parameters) { // ViewModel만 생성 (UI 위젯 불필요) UInventoryViewModel* VM = NewObject<UInventoryViewModel>(); // 초기 상태 검증 TestEqual("초기 아이템 수", VM->GetItems().Num(), 0); TestEqual("초기 선택 인덱스", VM->GetSelectedIndex(), -1); // 선택 인덱스 변경 테스트 VM->SetSelectedIndex(2); TestEqual("선택 인덱스 변경", VM->GetSelectedIndex(), 2); // FieldNotify 검증 bool bNotified = false; VM->GetFieldNotificationDelegate( UInventoryViewModel::FFieldNotificationClassDescriptor::SelectedIndex ).AddLambda([&](UObject*, UE::FieldNotification::FFieldId) { bNotified = true; }); VM->SetSelectedIndex(3); TestTrue("FieldNotify 발생", bNotified); return true; }
테스트 가능한 아키텍처의 가치

ViewModel에 UI 로직을 집중시키면 자동화 테스트가 가능해집니다. 위젯 렌더링이 필요 없으므로 테스트가 빠르고 안정적입니다. 상태 전환, 데이터 변환, 커맨드 실행 등을 모두 ViewModel 수준에서 검증할 수 있습니다.

04

MVVM vs 기존 패턴 비교

프로젝트 규모와 상황에 따른 패턴 선택 가이드

패턴 복잡도 테스트 추천 규모
직접 참조 (Set 호출) 낮음 어려움 소규모/프로토타입
델리게이트/이벤트 중간 보통 중간 규모
MVVM (UMG ViewModel) 높음 쉬움 대규모/팀 프로젝트
패턴 선택 기준

MVVM은 모든 프로젝트에 필수는 아닙니다. 소규모 프로젝트에서는 오히려 보일러플레이트 코드가 늘어날 수 있습니다. UI가 10개 이상의 화면을 가지거나, 팀 단위 개발에서 UI 프로그래머와 게임플레이 프로그래머가 분리된 경우, 또는 자동화 테스트가 중요한 프로젝트에서 MVVM의 가치가 극대화됩니다.

SUMMARY

핵심 요약

  • Global ViewModel Collection은 여러 UI에서 공유해야 하는 ViewModel을 전역적으로 관리합니다
  • 실전 MVVM에서 ViewModel은 Model 데이터를 UI에 적합한 형태로 가공하고, 커맨드로 사용자 액션을 처리합니다
  • MVVM의 최대 장점은 ViewModel을 UI 없이 독립적으로 테스트할 수 있다는 것입니다
  • 프로젝트 규모와 팀 구성에 따라 직접 참조, 델리게이트, MVVM 중 적절한 패턴을 선택해야 합니다
  • 대규모 프로젝트나 팀 개발 환경에서 MVVM의 관심사 분리와 테스트 용이성이 큰 가치를 제공합니다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: Global ViewModel Collection 구성

UMVVMGameSubsystem을 통해 전역 ViewModel Collection을 구성하고, PlayerViewModel과 InventoryViewModel을 등록하세요. 여러 UI 위젯에서 동일한 ViewModel 인스턴스에 접근하여 데이터를 공유하는 것을 확인합니다.

실습 2: ViewModel 단위 테스트 작성

Automation Framework를 사용하여 ViewModel의 프로퍼티 변경 시 FieldNotify가 정상 발생하는지, 바인딩된 위젯이 올바르게 업데이트되는지 검증하는 단위 테스트를 작성하세요.

심화 과제

GameStateViewModel -> PlayerViewModel -> InventoryViewModel -> ItemViewModel 계층 구조를 설계하고, 상위 ViewModel 변경이 하위에 전파되는 반응형 시스템을 구축하세요. 순환 참조 방지와 메모리 관리를 고려합니다.