ViewModel 패턴
Global ViewModel Collection, 실전 MVVM 아키텍처 구현, 그리고 ViewModel 기반 UI 테스트 전략을 학습합니다.
Global ViewModel Collection
전역적으로 접근 가능한 ViewModel 관리
Global Viewmodel Collection은 MVVM Game Subsystem에서 관리하는 전역 ViewModel 목록입니다. 설정 메뉴, 플레이어 상태 등 여러 UI에서 공유해야 하는 데이터에 적합합니다.
// 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));
}
실전 MVVM 아키텍처
인벤토리 시스템을 예로 한 완전한 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; }
};
ViewModel 테스트 전략
MVVM의 핵심 장점 - UI 로직의 독립적 테스트
MVVM의 가장 큰 장점 중 하나는 ViewModel을 UI 없이 독립적으로 테스트할 수 있다는 것입니다. 게임 로직 테스트에서 위젯 인스턴스를 생성할 필요가 없습니다.
// 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 수준에서 검증할 수 있습니다.
MVVM vs 기존 패턴 비교
프로젝트 규모와 상황에 따른 패턴 선택 가이드
| 패턴 | 복잡도 | 테스트 | 추천 규모 |
|---|---|---|---|
| 직접 참조 (Set 호출) | 낮음 | 어려움 | 소규모/프로토타입 |
| 델리게이트/이벤트 | 중간 | 보통 | 중간 규모 |
| MVVM (UMG ViewModel) | 높음 | 쉬움 | 대규모/팀 프로젝트 |
MVVM은 모든 프로젝트에 필수는 아닙니다. 소규모 프로젝트에서는 오히려 보일러플레이트 코드가 늘어날 수 있습니다. UI가 10개 이상의 화면을 가지거나, 팀 단위 개발에서 UI 프로그래머와 게임플레이 프로그래머가 분리된 경우, 또는 자동화 테스트가 중요한 프로젝트에서 MVVM의 가치가 극대화됩니다.
핵심 요약
- Global ViewModel Collection은 여러 UI에서 공유해야 하는 ViewModel을 전역적으로 관리합니다
- 실전 MVVM에서 ViewModel은 Model 데이터를 UI에 적합한 형태로 가공하고, 커맨드로 사용자 액션을 처리합니다
- MVVM의 최대 장점은 ViewModel을 UI 없이 독립적으로 테스트할 수 있다는 것입니다
- 프로젝트 규모와 팀 구성에 따라 직접 참조, 델리게이트, MVVM 중 적절한 패턴을 선택해야 합니다
- 대규모 프로젝트나 팀 개발 환경에서 MVVM의 관심사 분리와 테스트 용이성이 큰 가치를 제공합니다
도전 과제
배운 내용을 직접 실습해보세요
UMVVMGameSubsystem을 통해 전역 ViewModel Collection을 구성하고, PlayerViewModel과 InventoryViewModel을 등록하세요. 여러 UI 위젯에서 동일한 ViewModel 인스턴스에 접근하여 데이터를 공유하는 것을 확인합니다.
Automation Framework를 사용하여 ViewModel의 프로퍼티 변경 시 FieldNotify가 정상 발생하는지, 바인딩된 위젯이 올바르게 업데이트되는지 검증하는 단위 테스트를 작성하세요.
GameStateViewModel -> PlayerViewModel -> InventoryViewModel -> ItemViewModel 계층 구조를 설계하고, 상위 ViewModel 변경이 하위에 전파되는 반응형 시스템을 구축하세요. 순환 참조 방지와 메모리 관리를 고려합니다.