メインコンテンツまでスキップ

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

Last updated on July 28, 2025

What's on the menu

In this section, you’ll prepare the widgets used to display store displays and sections. The relevant files can be found in the Resources section.

Section widget entry

  • SectionWidgetEntry: 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/InGameStoreDisplays/UI/Components/SectionWidgetEntry.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/Components/SectionWidgetEntry.cpp
    • Blueprint widget: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/Components/W_SectionEntry.uasset

The SectionWidgetEntry is a UI component designed to display all items within a specific store section. It can also show a countdown timer if the section has a time-limit. While it handles the visual presentation of section content, it does not include any integration of Store Displays. Instead, you’ll use this widget as part of another widget.

Below is a preview of the SectionWidgetEntry blueprint widget:

Section widget entry preview

The component used in this widget are defined in the SectionWidgetEntry class header file:

private:
// ...
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteWarsActivatableWidget> DetailWidgetClass;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_TimeLeft;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_SectionName;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UBorder* B_SectionBorder;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UListView* Lv_Items;

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

  • Function to set up the item entry when a store section data has been assigned to this widget.
	virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
  • Function to open store detail when an item is clicked.
protected:
void OnItemClicked(UObject* Item) const;
  • Variable to keep track of how much time left until the section rotates.
private:
FTimespan TimeLeft;

Sectioned shop widget

  • SectionedShopWidget_Starter: 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/InGameStoreDisplays/UI/SectionedShopWidget_Starter.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/SectionedShopWidget_Starter.cpp
    • Daily shop blueprint widget: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_DailyShop_Starter.uasset
    • Featured shop blueprint widget: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_FeaturedShop_Starter.uasset

The SectionedShopWidget_Starter widget has two blueprint widgets: W_DailyShop_Starter for displaying the Daily store display and W_FeaturedShop_Starter for the Featured store display. These widgets are integrated into the main Item Store widget you set up in the In-game store essentials module. Once this module is activated, those two widgets will appear as two new tabs, Daily and Featured, allowing your players to browse curated item sections within your in-game store.

Below is a preview of the W_DailyShop_Starter blueprint widget:

Daily shop preview

Below is a preview of the W_FeaturedShop_Starter blueprint widget:

Featured shop preview

The component used in these widgets are defined in the SectionedShopWidget_Starter header file:

private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UListView* Lv_Root;

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

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

  • Reference to the subsystem that handles the Store Display logic.
private:
UPROPERTY()
UInGameStoreDisplaysSubsystem_Starter* InGameStoreDisplaysSubsystem;
void USectionedShopWidget_Starter::NativeOnActivated()
{
// ...
InGameStoreDisplaysSubsystem = GetGameInstance()->GetSubsystem<UInGameStoreDisplaysSubsystem_Starter>();
ensure(InGameStoreDisplaysSubsystem);
// ...
}
  • Variable to store the data of the Display this widget should displays.
private:
// ...
UPROPERTY()
TArray<USectionDataObject*> SectionDatas;
  • Variable to specify which Display this widget should displays. The value is already set to Daily for W_DailyShop_Starter and Featured for W_FeaturedShop_Starter. You can view and change this value in the blueprint through the Editor.
private:
// ...
UPROPERTY(EditAnywhere)
FString TargetDisplayName;
  • Function to set the section color based on its index and the mapping. This is Byte Wars feature. Not related to the service integration.
private:
FLinearColor GetSectionPresetColor(const int Index) const;

const TArray<FLinearColor> SectionBackgroundColorPreset = {
FLinearColor(0.27f, 0.004f, 0.3f),
FLinearColor(0.2f, 0.25f, 0.3f),
FLinearColor(0.32f, 0.24f, 0.23f)
};

Ready the menu

To display offers from a specific Store Display, the widget must follow a series of steps. First, it queries all available displays, then filters the results to find the one it’s responsible for showing. Next, it retrieves all sections within that display, followed by querying the offers contained in each section. The diagram below illustrates the complete flow of this logic.

  1. Open the SectionedShopWidget_Starter header file and add the following variables declarations.

    • bRefreshing: Prevents multiple processes from running at the same time.
    • QueryOrGetOffersInSectionCount: Tracks how many sections' offers have been received.
    private:
    // ...
    bool bRefreshing = false;
    int QueryOrGetOffersInSectionCount = 0;
  2. Add the following functions declarations.

    private:
    // ...
    UFUNCTION()
    void OnParentRefreshButtonClicked();

    void OnQueryOrGetDisplaysCompleted(
    TArray<TSharedRef<FAccelByteModelsViewInfo>>& Displays,
    const FOnlineError& Error);
    void OnQueryOrGetSectionsCompleted(
    TArray<TSharedRef<FAccelByteModelsSectionInfo>>& Sections,
    const FOnlineError& Error);
    void OnQueryOrGetOffersInSectionCompleted(
    TArray<UStoreItemDataObject*>& Offers,
    const FOnlineError& Error,
    FString SectionId);
  3. Open the SectionedShopWidget_Starter CPP file and implement the OnParentRefreshButtonClicked() function. For now, this only sets the widget to its loading state. You’ll later add the display query call here.

    void USectionedShopWidget_Starter::OnParentRefreshButtonClicked()
    {
    if (bRefreshing)
    {
    return;
    }
    bRefreshing = true;

    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
    // ...
    }
  4. Implement the OnQueryOrGetDisplaysCompleted() function. This filters the received displays and uses only those that match the TargetDisplayName variable. You’ll later call the section query here.

    void USectionedShopWidget_Starter::OnQueryOrGetDisplaysCompleted(
    TArray<TSharedRef<FAccelByteModelsViewInfo>>& Displays,
    const FOnlineError& Error)
    {
    if (Error.bSucceeded)
    {
    for (const TSharedRef<FAccelByteModelsViewInfo>& Display : Displays)
    {
    /**
    * Trigger query sections for target display only
    * Use Name to find the Display Name set on Admin Portal
    * Use Title to retrieve the localized name set on Admin Portal
    */
    if (Display->Name.Equals(TargetDisplayName))
    {
    // ...
    return;
    }
    }
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Empty);
    }
    else
    {
    Ws_Root->ErrorMessage = Error.ErrorMessage;
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Error);
    bRefreshing = false;
    }
    }
  5. Implement the OnQueryOrGetSectionsCompleted() function. This stores the section data in SectionDatas. At this point, the offer data is not included yet. It also increments the QueryOrGetOffersInSectionCount variable, which helps the game track how many responses to wait for before displaying the data. You’ll call the offer query in this function later.

    void USectionedShopWidget_Starter::OnQueryOrGetSectionsCompleted(
    TArray<TSharedRef<FAccelByteModelsSectionInfo>>& Sections,
    const FOnlineError& Error)
    {
    if (Error.bSucceeded)
    {
    if (Sections.IsEmpty())
    {
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Empty);
    return;
    }

    SectionDatas.Empty();
    QueryOrGetOffersInSectionCount = Sections.Num();
    for (int i = 0; i < Sections.Num(); ++i)
    {
    USectionDataObject* Data = NewObject<USectionDataObject>();
    Data->SectionInfo = Sections[i].Get();
    Data->SectionColor = GetSectionPresetColor(i);
    SectionDatas.Add(Data);
    // ...
    }
    }
    else
    {
    Ws_Root->ErrorMessage = Error.ErrorMessage;
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Error);
    bRefreshing = false;
    }
    }
  6. Implement the OnQueryOrGetOffersInSectionCompleted function. This updates the offer data in SectionDatas and displays the offers once QueryOrGetOffersInSectionCount reaches zero.

    void USectionedShopWidget_Starter::OnQueryOrGetOffersInSectionCompleted(
    TArray<UStoreItemDataObject*>& Offers,
    const FOnlineError& Error,
    const FString SectionId)
    {
    if (Error.bSucceeded)
    {
    USectionDataObject* Filter = NewObject<USectionDataObject>();
    Filter->SectionInfo.SectionId = SectionId;
    if (USectionDataObject** Data = SectionDatas.FindByPredicate([SectionId](const USectionDataObject* Obj)
    {
    return Obj->SectionInfo.SectionId.Equals(SectionId);
    }))
    {
    (*Data)->Offers = Offers;
    }
    }
    else
    {
    Ws_Root->ErrorMessage = Error.ErrorMessage;
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Error);
    bRefreshing = false;
    return;
    }

    QueryOrGetOffersInSectionCount--;
    if (QueryOrGetOffersInSectionCount <= 0)
    {
    // Check if all data is empty or not.
    bool bIsNoOffers = true;
    for (const USectionDataObject* SectionData : SectionDatas)
    {
    if (!SectionData->Offers.IsEmpty())
    {
    bIsNoOffers = false;
    break;
    }
    }

    Ws_Root->SetWidgetState(bIsNoOffers ? EAccelByteWarsWidgetSwitcherState::Empty : EAccelByteWarsWidgetSwitcherState::Not_Empty);
    Lv_Root->SetListItems(SectionDatas);
    bRefreshing = false;
    }
    }
  7. Find the NativeOnActivated() function and replace the current implementation with the code below. This binds the OnParentRefreshButtonClicked() function to the Item Store's Refresh button.

    void USectionedShopWidget_Starter::NativeOnActivated()
    {
    Super::NativeOnActivated();

    InGameStoreDisplaysSubsystem = GetGameInstance()->GetSubsystem<UInGameStoreDisplaysSubsystem_Starter>();
    ensure(InGameStoreDisplaysSubsystem);

    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
    // ...
    // Bind parent's (store essentials) refresh function with this refresh function
    if (UShopWidget* ShopWidget = GetFirstOccurenceOuter<UShopWidget>())
    {
    ShopWidget->OnRefreshButtonClickedDelegates.AddUObject(this, &ThisClass::OnParentRefreshButtonClicked);
    }
    else if (UShopWidget_Starter* ShopWidget_Starter = GetFirstOccurenceOuter<UShopWidget_Starter>())
    {
    ShopWidget_Starter->OnRefreshButtonClickedDelegates.AddUObject(this, &ThisClass::OnParentRefreshButtonClicked);
    }
    }
  8. Replace the implementation of the NativeOnDeactivated() function with the code below to ensure all bindings are cleared when the widget is closed.

    void USectionedShopWidget_Starter::NativeOnDeactivated()
    {
    Super::NativeOnDeactivated();

    if (UShopWidget* ShopWidget = GetFirstOccurenceOuter<UShopWidget>())
    {
    ShopWidget->OnRefreshButtonClickedDelegates.RemoveAll(this);
    }
    else if (UShopWidget_Starter* ShopWidget_Starter = GetFirstOccurenceOuter<UShopWidget_Starter>())
    {
    ShopWidget_Starter->OnRefreshButtonClickedDelegates.RemoveAll(this);
    }
    }
  9. Build the project and open it with the Unreal Editor once it's done.

  10. Open Content/TutorialModules/Monetization/InGameStoreDisplays/DA_InGameStoreDisplays.uasset and enable the Is Starter Mode Active. Save the Data Asset.

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

  11. Click Play in the editor. At this point, you will be able to navigate to Store > Item Shop > Featured and Store > Item Shop > Daily. At this stage, these menus will display a loading screen only.

Resources