Adding UI - In Game Store Displays - (Unreal Engine module)
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
- Header file:
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:
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
- Header file:
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:
Below is a preview of the W_FeaturedShop_Starter
blueprint widget:
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
forW_DailyShop_Starter
andFeatured
forW_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.
-
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; -
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); -
Open the
SectionedShopWidget_Starter
CPP file and implement theOnParentRefreshButtonClicked()
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);
// ...
} -
Implement the
OnQueryOrGetDisplaysCompleted()
function. This filters the received displays and uses only those that match theTargetDisplayName
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;
}
} -
Implement the
OnQueryOrGetSectionsCompleted()
function. This stores the section data inSectionDatas
. At this point, the offer data is not included yet. It also increments theQueryOrGetOffersInSectionCount
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;
}
} -
Implement the
OnQueryOrGetOffersInSectionCompleted
function. This updates the offer data inSectionDatas
and displays the offers onceQueryOrGetOffersInSectionCount
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;
}
} -
Find the
NativeOnActivated()
function and replace the current implementation with the code below. This binds theOnParentRefreshButtonClicked()
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);
}
} -
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);
}
} -
Build the project and open it with the Unreal Editor once it's done.
-
Open
Content/TutorialModules/Monetization/InGameStoreDisplays/DA_InGameStoreDisplays.uasset
and enable theIs Starter Mode Active
. Save the Data Asset. -
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
- The files used in this tutorial section are available in the Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/SectionedShopWidget_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/SectionedShopWidget_Starter.cpp
- AccelByteWars/Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_DailyShop_Starter.uasset
- AccelByteWars/Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_FeaturedShop_Starter.uasset
- Content/TutorialModules/Monetization/InGameStoreDisplays/DA_InGameStoreDisplays.uasset