Skip to main content

Adding UI - In-Game Store Essentials - (Unreal Engine module)

Last updated on June 23, 2025

What's on the menu

In this section, you will learn how to prepare widgets that you will use to display store items and its detail menu. The related files are available in the Resources section.

Item Detail widget

  • StoreItemDetailWidget: A C++ class used to display item details. You won't need to modify this menu, as it’s provided as a ready-to-use component.
    • Header file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/StoreItemDetailWidget.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/StoreItemDetailWidget.cpp
    • Blueprint widget: Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_StoreItemDetail.uasset

Below is a preview of the StoreItemDetailWidget Blueprint widget. It may seem redundant at this stage since the detail shown is similar to what's in the shop widget. However, this widget serves as a placeholder where additional modules can display their UI. This is where the actual item purchase will take place, which you will implement in a later module.

StoreItemDetailWidget preview image

The components used in this widget are defined in the StoreItemDetailWidget class Header file.

private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UStoreItemListEntry* W_ItemDetail;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget* W_PurchaseOuter;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget* W_WalletOuter;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Back;

To initialize this widget, use the Setup() function. This function updates the UI components based on the provided store item data.

public:
// ...
void Setup(const UStoreItemDataObject* Object);

Shop widget

  • ShopWidget_Starter: A C++ class that drives the core store functionality.
    • Header file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.cpp
    • Blueprint widget for currency shop: Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_CurrencyShop_Starter.uasset
    • Blueprint widget for item shop: Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_ItemShop_Starter.uasset

ShopWidget_Starter is used by two Blueprint widgets. The difference is the item category each displays: W_CurrencyShop_Starter shows currency items, while W_ItemShop_Starter shows regular store items. Their behavior is otherwise identical.

The Shop widget has four state:

  • Success - displays available store items and category filter tabs.
  • Empty - indicates no items are available.
  • Loading - indicates the store is retrieving data.
  • Error - indicates an issue occurred while retrieving store items.

These state changes are managed via the AccelByteWars Widget Switcher, a custom Widget Switcher with predefined states.

Shop widget also has a Refresh and Back buttons. Below is a preview of both W_CurrencyShop_Starter and W_ItemShop_Starter widgets:

W_CurrencyShop_Starter preview

W_ItemShop_Starter preview

The components used in these widgets are declared in the Header file:

private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidget* W_ListOuter;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher* Ws_Loader;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsTabListWidget* Tl_ItemCategory;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTileView* Tv_ContentOuter;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Back;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Refresh;

This widget includes pre-configured variables and helper functions tailored for Byte Wars integration.

  • A variable to define the category path to fetch store items from.
private:
UPROPERTY(EditAnywhere)
FString RootPath;
  • A reference to the subsystem that handles the store logic.
private:
// ...
UPROPERTY()
UInGameStoreEssentialsSubsystem_Starter* StoreSubsystem;
void UShopWidget_Starter::NativeOnActivated()
{
// ...
StoreSubsystem = GetGameInstance()->GetSubsystem<UInGameStoreEssentialsSubsystem_Starter>();
ensure(StoreSubsystem);
// ...
}
  • Delegates for responding to widget actions.
public:
inline static TMulticastDelegate<void(const APlayerController*)> OnActivatedMulticastDelegate;
FSimpleMulticastDelegate OnRefreshButtonClickedDelegates;
  • A function to switch widget states.
protected:
// ...
void SwitchContent(EAccelByteWarsWidgetSwitcherState State) const;
void UShopWidget_Starter::SwitchContent(EAccelByteWarsWidgetSwitcherState State) const
{
UWidget* FocusTarget;

switch (State)
{
case EAccelByteWarsWidgetSwitcherState::Loading:
FocusTarget = Btn_Back;
break;
case EAccelByteWarsWidgetSwitcherState::Not_Empty:
FocusTarget = Tv_ContentOuter;
// Initialize FTUE after item shops available.
InitializeFTUEDialogues(bOnActivatedInitializeFTUE);
break;
case EAccelByteWarsWidgetSwitcherState::Empty:
FocusTarget = Btn_Back;
break;
default:
FocusTarget = Btn_Back;
}

FocusTarget->SetUserFocus(GetOwningPlayer());
Ws_Loader->SetWidgetState(State);

// Disable refresh button on loading state.
Btn_Refresh->SetIsEnabled(State != EAccelByteWarsWidgetSwitcherState::Loading);
}
  • A reference to the detail widget used when a store item is selected.
private:
// ...
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteWarsActivatableWidget> DetailWidgetClass;
  • A reference to a widget component called W_WalletOuter and a function to retrieve its child. This is used to house a widget from Wallet Essentials module to display the player's currency. You will learn about this widget in later module.
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget* W_WalletOuter;

UAccelByteWarsActivatableWidget* GetBalanceWidget() const;

Ready the menu

  1. Open the ShopWidget_Starter Header file and add the following function declarations. These will handle store responses.

    protected:
    void OnGetOrQueryCategoriesComplete(TArray<FOnlineStoreCategory> Categories);
    void OnRefreshCategoriesComplete(TArray<FOnlineStoreCategory> Categories);
    void OnGetOrQueryOffersComplete(const TArray<UStoreItemDataObject*> Offers) const;
  2. In the CPP file, implement the OnGetOrQueryCategoriesComplete() function to populate the tab list with item categories and add a default "All" tab.

    void UShopWidget_Starter::OnGetOrQueryCategoriesComplete(TArray<FOnlineStoreCategory> Categories)
    {
    if (Categories.IsEmpty())
    {
    SwitchContent(EAccelByteWarsWidgetSwitcherState::Empty);
    return;
    }

    Tl_ItemCategory->RemoveAllTabs();
    SwitchContent(EAccelByteWarsWidgetSwitcherState::Not_Empty);

    // Register "All" categories entry.
    Tl_ItemCategory->RegisterTabWithPresets(FName(RootPath), FText::FromString("All")); // TODO: Localization

    // Register tab list.
    for (const FOnlineStoreCategory& Category : Categories)
    {
    // Only register end of branch category.
    if (Category.SubCategories.IsEmpty())
    {
    Tl_ItemCategory->RegisterTabWithPresets(FName(Category.Id), Category.Description);
    }
    }
    }
  3. Still in the CPP file, implement the OnRefreshCategoriesComplete() function to restore the previously selected category after refresh.

    void UShopWidget_Starter::OnRefreshCategoriesComplete(TArray<FOnlineStoreCategory> Categories)
    {
    FName LastSelectedTab = Tl_ItemCategory->GetSelectedTabId();
    Tl_ItemCategory->SetVisibility(ESlateVisibility::Visible);

    // Temporary unbind, as it will automatically select first tab when rebuild tab list, while we want to keep the previous tab selection.
    Tl_ItemCategory->OnTabSelected.RemoveDynamic(this, &ThisClass::SwitchCategory);

    OnGetOrQueryCategoriesComplete(Categories);

    // Reselect tab if exist, otherwise select all item tab.
    const bool bLastSelectedTabExist = Categories.ContainsByPredicate([&LastSelectedTab](FOnlineStoreCategory& Category)
    {
    return Category.SubCategories.IsEmpty() && Category.Id.Equals(LastSelectedTab.ToString());
    });
    const FName TabSelectionTarget = bLastSelectedTabExist ? LastSelectedTab : FName(RootPath);
    // ...
    // Re-add after manually select and switch category, because re-add before it will be unstable.
    if(IsActivated())
    {
    Tl_ItemCategory->OnTabSelected.AddDynamic(this, &ThisClass::SwitchCategory);
    }
    }
  4. Implement the OnGetOrQueryOffersComplete() function to populate the item tile view.

    void UShopWidget_Starter::OnGetOrQueryOffersComplete(const TArray<UStoreItemDataObject*> Offers) const
    {
    TArray<UStoreItemDataObject*> ExistingItems;
    for (UObject* Object : Tv_ContentOuter->GetListItems())
    {
    if (UStoreItemDataObject* ExistingItem = Cast<UStoreItemDataObject>(Object))
    {
    ExistingItems.Add(ExistingItem);
    }
    }
    ExistingItems.Append(Offers);
    Tv_ContentOuter->SetListItems(ExistingItems);

    SwitchContent(ExistingItems.IsEmpty() ?
    EAccelByteWarsWidgetSwitcherState::Empty : EAccelByteWarsWidgetSwitcherState::Not_Empty);
    }
  5. Go back to the ShopWidget_Starter Header file and add the following function declarations.

    protected:
    // ...
    void OnStoreItemClicked(UObject* Item) const;
    void OnRefreshButtonClicked();

    UFUNCTION()
    void SwitchCategory(FName Id);
  6. Go to the ShopWidget_Starter CPP file and implement the OnStoreItemClicked() function to open the item detail view.

    void UShopWidget_Starter::OnStoreItemClicked(UObject* Item) const
    {
    if (!IsValid(DetailWidgetClass))
    {
    return;
    }

    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    if (!GameInstance)
    {
    return;
    }

    UAccelByteWarsBaseUI* BaseUI = GameInstance->GetBaseUIWidget();
    if (!BaseUI)
    {
    return;
    }

    UAccelByteWarsActivatableWidget* Widget = BaseUI->PushWidgetToStack(EBaseUIStackType::Menu, DetailWidgetClass);

    UStoreItemDetailWidget* ItemDetailWidget = Cast<UStoreItemDetailWidget>(Widget);
    const UStoreItemDataObject* StoreItem = Cast<UStoreItemDataObject>(Item);
    if (!ItemDetailWidget || !StoreItem)
    {
    return;
    }

    ItemDetailWidget->Setup(StoreItem);
    }
  7. Implement the OnRefreshButtonClicked() function to update the widget state, broadcast a refresh event, and re-activate the Wallet module's widget to trigger its set up logic. You will call the actual refresh function in later page.

    void UShopWidget_Starter::OnRefreshButtonClicked()
    {
    OnRefreshButtonClickedDelegates.Broadcast();

    SwitchContent(EAccelByteWarsWidgetSwitcherState::Loading);

    Tv_ContentOuter->ClearListItems();
    Tl_ItemCategory->SetVisibility(ESlateVisibility::Hidden);
    // ...
    // Refresh balance.
    UAccelByteWarsActivatableWidget* BalanceWidget = GetBalanceWidget();
    if(BalanceWidget != nullptr)
    {
    BalanceWidget->DeactivateWidget();
    BalanceWidget->ActivateWidget();
    }
    }
  8. Implement the SwitchCategory() function to clear the item list for the selected category. You will use this function to call the store item retrieve request based on a given category later.

    void UShopWidget_Starter::SwitchCategory(FName Id)
    {
    Tv_ContentOuter->ClearListItems();
    // ...
    }
  9. In the ShopWidget_Starter CPP file, find the NativeOnActivated() function and add the following highlighted lines. These codes does 3 things:

    • Binds the functions you just implemented to the tile view entry, refresh button, and tab list entry.

    • Set the menu state to loading as this menu assumes you will call query in this function later.

    • Calls the OnActivatedMulticastDelegate delegate to notify other object that this menu was just activated.

      void UShopWidget_Starter::NativeOnActivated()
      {
      // ...
      // Event binding.
      Btn_Back->OnClicked().AddUObject(this, &ThisClass::DeactivateWidget);
      Btn_Refresh->OnClicked().AddUObject(this, &ThisClass::OnRefreshButtonClicked);
      Tl_ItemCategory->OnTabSelected.AddDynamic(this, &ThisClass::SwitchCategory);
      Tv_ContentOuter->OnItemClicked().AddUObject(this, &ThisClass::OnStoreItemClicked);
      // ...
      // Reset UI.
      SwitchContent(EAccelByteWarsWidgetSwitcherState::Loading);
      // ...
      OnActivatedMulticastDelegate.Broadcast(GetOwningPlayer());
      }
  10. Find the NativeOnDeactivated() function and add the following highlighted lines. This codes does 2 things:

    • Clear up function bound to buttons.
    • Clear tile view to make sure for a clean exit that when the widget closed.
    void UShopWidget_Starter::NativeOnDeactivated()
    {
    Super::NativeOnDeactivated();

    Btn_Back->OnClicked().RemoveAll(this);
    Btn_Refresh->OnClicked().RemoveAll(this);
    Tv_ContentOuter->OnItemClicked().RemoveAll(this);
    Tl_ItemCategory->OnTabSelected.RemoveAll(this);
    Tv_ContentOuter->ClearListItems();
    }
  11. Build the project and open it with the Unreal Editor once it's done.

  12. Open Content/TutorialModules/Monetization/InGameStoreEssentials/DA_InGameStoreEssentials.uasset and enable the Is Starter Mode Active. Save the Data Asset.

    Data Asset changes preview Unreal Byte Wars In-game Store Essentials

  13. Click Play in the editor. At this point, you will be able to navigate to Store > Item Shop and Store > Currency Shop. Although, you will be presented with only the loading screen in that menu, for now.

Resources