· Filip Iliescu · Game Development  · 6 min read

Implementing MVVM Pattern in Unreal Engine

Learn how to adapt the Model-View-ViewModel pattern for Unreal Engine game development to create maintainable and testable UI systems.

Learn how to adapt the Model-View-ViewModel pattern for Unreal Engine game development to create maintainable and testable UI systems.

Implementing MVVM in Unreal Engine with C++

The Model-View-ViewModel (MVVM) pattern has long been a staple in web and app development for creating maintainable UI systems. While Unreal Engine doesn’t include built-in support for MVVM, several community plugins have emerged to bring this powerful pattern to UE development. This tutorial will guide you through implementing MVVM in your Unreal Engine projects using C++.

Understanding MVVM in the Context of Unreal Engine

Before diving into implementation, let’s clarify how MVVM maps to Unreal Engine concepts:

  • Model: The data layer, typically C++ classes that represent your game state (e.g., character stats, inventory)
  • View: UMG widgets that display information to the player
  • ViewModel: The intermediary that transforms Model data for the View and handles UI logic

The key benefit of MVVM in Unreal is the separation of concerns - your UI logic becomes independent from both your game logic and your UI layout.

Setting Up the MVVM Plugin

The first step is to add an MVVM plugin to your project. We’ll be using the popular “Unreal MVVM” plugin by Bozoskill for this tutorial.

1. Install the Plugin

You can either:

  • Download from the GitHub repository and place in your Plugins folder
  • Add as a submodule if your project uses Git
  • Install from the Unreal Marketplace if available

Add this to your .uproject file:

"Plugins": [
    {
        "Name": "MVVM",
        "Enabled": true
    }
]

2. Add Dependencies to Your Module

In your project’s Build.cs file, add the MVVM module as a dependency:

PublicDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "CoreUObject",
        "Engine",
        "InputCore",
        "UMG",
        "MVVM" // Add this line
    }
);

Creating Your First MVVM Components

Let’s implement a simple health display system using MVVM:

1. Create the Model

First, define your data model:

// CharacterStatsModel.h
#pragma once

#include "CoreMinimal.h"
#include "CharacterStatsModel.generated.h"

UCLASS()
class YOURGAME_API UCharacterStatsModel : public UObject
{
    GENERATED_BODY()

public:
    UCharacterStatsModel();

    UPROPERTY()
    float Health;

    UPROPERTY()
    float MaxHealth;
};

// CharacterStatsModel.cpp
#include "CharacterStatsModel.h"

UCharacterStatsModel::UCharacterStatsModel()
{
    Health = 100.0f;
    MaxHealth = 100.0f;
}

2. Create the ViewModel

Now, create a ViewModel that will prepare the Model data for display:

// CharacterStatsViewModel.h
#pragma once

#include "CoreMinimal.h"
#include "MVVMViewModelBase.h"
#include "CharacterStatsModel.h"
#include "CharacterStatsViewModel.generated.h"

UCLASS()
class YOURGAME_API UCharacterStatsViewModel : public UMVVMViewModelBase
{
    GENERATED_BODY()

public:
    UCharacterStatsViewModel();

    // Bindable properties
    UPROPERTY(BlueprintReadOnly, FieldNotify)
    float HealthPercentage;

    UPROPERTY(BlueprintReadOnly, FieldNotify)
    FText HealthText;

    // Reference to the model
    UPROPERTY()
    UCharacterStatsModel* StatsModel;

    // Call this when model updates
    UFUNCTION()
    void UpdateFromModel();

    // Bindable actions
    UFUNCTION(BlueprintCallable)
    void UseHealthKit();
};

// CharacterStatsViewModel.cpp
#include "CharacterStatsViewModel.h"

UCharacterStatsViewModel::UCharacterStatsViewModel()
{
    StatsModel = CreateDefaultSubobject<UCharacterStatsModel>(TEXT("StatsModel"));
    UpdateFromModel();
}

void UCharacterStatsViewModel::UpdateFromModel()
{
    if (StatsModel)
    {
        HealthPercentage = StatsModel->Health / StatsModel->MaxHealth;
        HealthText = FText::Format(NSLOCTEXT("CharacterStats", "HealthFormat", "{0}/{1}"), 
                                   FText::AsNumber(FMath::RoundToInt(StatsModel->Health)), 
                                   FText::AsNumber(FMath::RoundToInt(StatsModel->MaxHealth)));
    }
}

void UCharacterStatsViewModel::UseHealthKit()
{
    if (StatsModel)
    {
        // Apply healing logic
        StatsModel->Health = FMath::Min(StatsModel->Health + 25.0f, StatsModel->MaxHealth);
        UpdateFromModel();
    }
}

3. Create the View

Now, create a UMG widget for your View. This is typically done in the UMG editor, but here’s how to set up the view bindings in C++:

// HealthBarWidget.h
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MVVMViewBase.h"
#include "CharacterStatsViewModel.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "Components/Button.h"
#include "HealthBarWidget.generated.h"

UCLASS()
class YOURGAME_API UHealthBarWidget : public UUserWidget, public IMVVMViewBase
{
    GENERATED_BODY()

protected:
    virtual void NativeConstruct() override;
    virtual void BindViewModel(UMVVMViewModelBase* InViewModel) override;

    UPROPERTY(meta = (BindWidget))
    UProgressBar* HealthBar;

    UPROPERTY(meta = (BindWidget))
    UTextBlock* HealthText;

    UPROPERTY(meta = (BindWidget))
    UButton* HealthKitButton;

    UPROPERTY()
    UCharacterStatsViewModel* ViewModel;

    UFUNCTION()
    void OnHealthKitButtonClicked();
};

// HealthBarWidget.cpp
#include "HealthBarWidget.h"

void UHealthBarWidget::NativeConstruct()
{
    Super::NativeConstruct();

    if (HealthKitButton)
    {
        HealthKitButton->OnClicked.AddDynamic(this, &UHealthBarWidget::OnHealthKitButtonClicked);
    }
}

void UHealthBarWidget::BindViewModel(UMVVMViewModelBase* InViewModel)
{
    ViewModel = Cast<UCharacterStatsViewModel>(InViewModel);
    if (ViewModel)
    {
        // Set up bindings
        if (HealthBar)
        {
            UMVVMBindingHelper::BindProperty(this, ViewModel, "HealthPercentage", HealthBar, "Percent");
        }

        if (HealthText)
        {
            UMVVMBindingHelper::BindProperty(this, ViewModel, "HealthText", HealthText, "Text");
        }
    }
}

void UHealthBarWidget::OnHealthKitButtonClicked()
{
    if (ViewModel)
    {
        ViewModel->UseHealthKit();
    }
}

Connecting Everything in Your Game

Now let’s integrate our MVVM components into a game character:

// YourCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CharacterStatsViewModel.h"
#include "HealthBarWidget.h"
#include "YourCharacter.generated.h"

UCLASS()
class YOURGAME_API AYourCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    AYourCharacter();

    virtual void BeginPlay() override;

    // Called when character takes damage
    UFUNCTION(BlueprintCallable)
    void ApplyDamage(float DamageAmount);

protected:
    // The ViewModel instance
    UPROPERTY()
    UCharacterStatsViewModel* StatsViewModel;

    // Widget class to instantiate
    UPROPERTY(EditDefaultsOnly, Category = "UI")
    TSubclassOf<UHealthBarWidget> HealthWidgetClass;

    // Widget instance
    UPROPERTY()
    UHealthBarWidget* HealthWidget;
};

// YourCharacter.cpp
#include "YourCharacter.h"

AYourCharacter::AYourCharacter()
{
    // Create and setup the ViewModel
    StatsViewModel = CreateDefaultSubobject<UCharacterStatsViewModel>(TEXT("StatsViewModel"));
}

void AYourCharacter::BeginPlay()
{
    Super::BeginPlay();

    // Create the widget and add it to viewport
    if (HealthWidgetClass)
    {
        HealthWidget = CreateWidget<UHealthBarWidget>(GetWorld(), HealthWidgetClass);
        if (HealthWidget)
        {
            HealthWidget->BindViewModel(StatsViewModel);
            HealthWidget->AddToViewport();
        }
    }
}

void AYourCharacter::ApplyDamage(float DamageAmount)
{
    if (StatsViewModel && StatsViewModel->StatsModel)
    {
        StatsViewModel->StatsModel->Health = FMath::Max(StatsViewModel->StatsModel->Health - DamageAmount, 0.0f);
        StatsViewModel->UpdateFromModel();
    }
}

Advanced MVVM Techniques

Once you’ve got the basics working, you can explore these more advanced MVVM features:

1. Data Transformation

ViewModels can transform data into more view-friendly formats:

FText UCharacterStatsViewModel::GetFormattedDamageBonus()
{
    float bonus = StatsModel->DamageBonus;
    return FText::Format(NSLOCTEXT("CharacterStats", "DamageBonus", "+{0}%"), 
                         FText::AsNumber(FMath::RoundToInt(bonus * 100)));
}

2. Command Binding

For handling UI actions more elegantly:

// In ViewModel
UFUNCTION()
FMVVMCommand GetUseHealthKitCommand()
{
    return FMVVMCommand::CreateUObject(this, &UCharacterStatsViewModel::UseHealthKit);
}

// In View
UMVVMBindingHelper::BindCommand(this, ViewModel, "GetUseHealthKitCommand", HealthKitButton, "OnClicked");

3. Collection Binding

For lists of items:

// In ViewModel
UPROPERTY(BlueprintReadOnly, FieldNotify)
TArray<UInventoryItemViewModel*> InventoryItems;

// In View (using ListView)
UMVVMBindingHelper::BindProperty(this, ViewModel, "InventoryItems", InventoryListView, "ItemsSource");

Common Challenges and Solutions

1. Performance Considerations

MVVM can introduce overhead, especially with large numbers of bindings. Consider these optimizations:

  • Use sparse updates to your model when appropriate
  • Batch view updates when multiple properties change simultaneously
  • Consider lower update frequencies for non-critical UI elements

2. Blueprint Integration

While this tutorial focuses on C++, you may want to expose functionality to Blueprint:

UCLASS(BlueprintType, Blueprintable)
class YOURGAME_API UCharacterStatsViewModel : public UMVVMViewModelBase
{
    // ...

    UFUNCTION(BlueprintCallable)
    void UpdateHealthValue(float NewValue);
}

3. Managing ViewModel Lifetime

Be careful about ViewModel destruction to avoid crashes:

void AYourCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
    
    if (HealthWidget)
    {
        HealthWidget->RemoveFromParent();
    }
    
    // Ensure any references to the ViewModel are cleared
}

Conclusion

Implementing MVVM in Unreal Engine provides a powerful way to organize your UI code, making it more maintainable and testable. While there’s an initial learning curve and setup cost, the long-term benefits for complex UIs are substantial.

For our projects at AyaDog Games, we’ve found that MVVM particularly shines for:

  • Inventory systems
  • Character stat displays
  • Complex menus with state-dependent options
  • Data-driven UIs that need to reflect changing game state

The decoupling of UI presentation from game logic makes it much easier to iterate on both independently, and to adjust UI layouts without breaking functionality.

Remember that MVVM is a tool, not a requirement - use it where it makes sense for your project’s needs and complexity level. For simple UIs, a more direct approach might be more appropriate.

Back to Blog

Related Posts

View All Posts »
Unity to Unreal Engine Transition Guide

Unity to Unreal Engine Transition Guide

A comprehensive guide for game developers looking to transition from Unity to Unreal Engine, covering key differences, best practices, and optimization tips.