Adding UI - Store Item Purchase - (Unreal Engine module)
Game setup
To simplify the implementation, Byte Wars includes a custom UObject
class named UStoreItemPriceDataObject
, which stores only the essential price data for items. The data stored is shown below:
UCLASS(BlueprintType)
class UStoreItemPriceDataObject : public UObject
{
// ...
private:
UPROPERTY(EditAnywhere)
ECurrencyType CurrencyType;
UPROPERTY(EditAnywhere)
int64 RegularPrice;
UPROPERTY(EditAnywhere)
int64 FinalPrice;
};
What's on the menu
In this section, you'll learn how to prepare the widgets used to purchase items from the in-game store. The related files are available in the Resources section.
Item purchase button
ItemPurchaseButton
: 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/StoreItemPurchase/UI/Component/ItemPurchaseButton.h
- CPP file:
Source/AccelByteWars/TutorialModules/Monetization/StoreItemPurchase/UI/Component/ItemPurchaseButton.cpp
- Blueprint widget:
Content/TutorialModules/Monetization/StoreItemPurchase/UI/Components/W_ItemPurchaseButton.uasset
- Header file:
The ItemPurchaseButton
is a button that includes an item price widget component. You’ll later bind a function to trigger the purchase request to this widget’s pressed event, but the widget itself does not contain purchase-specific logic.
Here is a preview of the ItemPurchaseButton
Blueprint widget:
The components used in this widget are defined in the ItemPurchaseButton
class header file.
private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UStoreItemPriceListEntry* W_Price;
To initialize this widget, use the SetPrice()
function. It updates the UI components based on the provided price data and amount multiplier.
public:
void SetPrice(const UStoreItemPriceDataObject* PriceData, const int32 PriceMultiplier = 1) const;
Item purchase widget
ItemPurchaseWidget_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/StoreItemPurchase/UI/ItemPurchaseWidget_Starter.h
- CPP file:
Source/AccelByteWars/TutorialModules/Monetization/StoreItemPurchase/UI/ItemPurchaseWidget_Starter.cpp
- Blueprint widget:
Content/TutorialModules/Monetization/StoreItemPurchase/UI/W_StoreItemPurchase_Starter.uasset
- Header file:
Here is a preview of the ItemPurchaseWidget_Starter
Blueprint widget:
The components used in this widget are defined in the ItemPurchaseWidget_Starter
class header file.
private:
// ...
UPROPERTY()
UStoreItemDetailWidget* W_Parent;
UPROPERTY(EditAnywhere)
TSubclassOf<UItemPurchaseButton> PurchaseButtonClass;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher* Ws_Root;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget* W_PurchaseButtonsOuter;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_Success;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_Error;
This widget includes pre-configured variables and helper functions tailored for Byte Wars integration.
- Variable that stores the item data represented by this purchase widget.
private:
// ...
UPROPERTY()
UStoreItemDataObject* StoreItemDataObject;
void UItemPurchaseWidget_Starter::NativeOnActivated()
{
// ...
W_Parent = GetFirstOccurenceOuter<UStoreItemDetailWidget>();
ensure(W_Parent);
StoreItemDataObject = W_Parent->StoreItemDataObject;
ensure(StoreItemDataObject);
// ...
}
- Reference to the subsystem that handles the item purchase logic.
private:
UPROPERTY()
UStoreItemPurchaseSubsystem_Starter* PurchaseSubsystem;
void UItemPurchaseWidget_Starter::NativeOnActivated()
{
// ...
PurchaseSubsystem = GetGameInstance()->GetSubsystem<UStoreItemPurchaseSubsystem_Starter>();
ensure(PurchaseSubsystem);
// ...
}
- Static delegate that lets other objects respond after a purchase is completed. This delegate will be used in the Entitlement Essentials module.
public:
inline static TMulticastDelegate<void(const APlayerController*)> OnPurchaseCompleteMulticastDelegate;
- Function to set up the item purchase buttons.
private:
void SetupPurchaseButtons(TArray<UStoreItemPriceDataObject*> Prices);
- Function to update the price shown on the purchase buttons based on the selected amount.
private:
// ...
void UpdatePrice(const int32 SelectedIndex);
- Function to get the currently selected amount.
private:
// ...
int32 GetSelectedAmount() const;
Ready the menu
-
Open the
ItemPurchaseWidget_Starter
header file and add the following function declarations.private:
// ...
void OnClickPurchase(const int32 PriceIndex) const;
void OnPurchaseComplete(const FOnlineError& Error) const; -
Open the
ItemPurchaseWidget_Starter
CPP file and implement theOnClickPurchase()
function. At this stage, it only shows the loading state. You’ll call the purchase request in this function later.void UItemPurchaseWidget_Starter::OnClickPurchase(const int32 PriceIndex) const
{
Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
// ...
} -
Find the
SetupPurchaseButtons()
function and replace the existing implementation with the code below. This code binds theOnClickPurchase()
function to the item purchase button and set up the price to be shown.void UItemPurchaseWidget_Starter::SetupPurchaseButtons(TArray<UStoreItemPriceDataObject*> Prices)
{
W_PurchaseButtonsOuter->ClearChildren();
for (int i = 0; i < Prices.Num(); ++i)
{
UItemPurchaseButton* Entry = CreateWidget<UItemPurchaseButton>(this, PurchaseButtonClass);
ensure(Entry);
Entry->SetPrice(Prices[i], GetSelectedAmount());
Entry->OnClicked().AddUObject(this, &ThisClass::OnClickPurchase, i);
W_PurchaseButtonsOuter->AddChild(Entry);
}
} -
Implement the
OnPurchaseComplete()
function. This function triggers theOnPurchaseCompleteMulticastDelegate
delegate so that other objects can respond as soon as the request completes. It also displays the result, whether it succeeded or failed.void UItemPurchaseWidget_Starter::OnPurchaseComplete(const FOnlineError& Error) const
{
OnPurchaseCompleteMulticastDelegate.Broadcast(GetOwningPlayer());
Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Not_Empty);
if (Error.bSucceeded)
{
Tb_Success->SetVisibility(ESlateVisibility::Visible);
Tb_Error->SetVisibility(ESlateVisibility::Collapsed);
// update wallet
if (TWeakObjectPtr<UAccelByteWarsActivatableWidget> Widget = W_Parent->GetBalanceWidget(); Widget.IsValid())
{
Widget->DeactivateWidget();
Widget->ActivateWidget();
}
}
else
{
Tb_Error->SetText(FText::FromString(Error.ErrorRaw));
Tb_Success->SetVisibility(ESlateVisibility::Collapsed);
Tb_Error->SetVisibility(ESlateVisibility::Visible);
}
} -
Find the
NativeOnActivated()
function and replace the existing implementation with the code below. This code resets the UI.void UItemPurchaseWidget_Starter::NativeOnActivated()
{
Super::NativeOnActivated();
W_Parent = GetFirstOccurenceOuter<UStoreItemDetailWidget>();
ensure(W_Parent);
StoreItemDataObject = W_Parent->StoreItemDataObject;
ensure(StoreItemDataObject);
PurchaseSubsystem = GetGameInstance()->GetSubsystem<UStoreItemPurchaseSubsystem_Starter>();
ensure(PurchaseSubsystem);
// ...
// Setup UI
SetupPurchaseButtons(StoreItemDataObject->GetPrices());
Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Not_Empty);
Tb_Success->SetVisibility(ESlateVisibility::Collapsed);
Tb_Error->SetVisibility(ESlateVisibility::Collapsed);
Ss_Amount->SetSelectedIndex(0);
Ss_Amount->OnSelectionChangedDelegate.AddUObject(this, &ThisClass::UpdatePrice);
// Show amount if consumable
Ss_Amount->SetVisibility(StoreItemDataObject->GetIsConsumable() ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
// ...
// Set focus
if (W_PurchaseButtonsOuter->HasAnyChildren())
{
W_PurchaseButtonsOuter->GetChildAt(0)->SetUserFocus(GetOwningPlayer());
}
FTUESetup();
} -
Navigate to the
NativeOnDeactivated()
function and replace the existing implementation with the code below. This code unbinds theOnSelectionChangedDelegate
to ensure the widget is cleanly closed.void UItemPurchaseWidget_Starter::NativeOnDeactivated()
{
Super::NativeOnDeactivated();
// ...
Ss_Amount->OnSelectionChangedDelegate.RemoveAll(this);
// ...
} -
Build the project and open it with the Unreal Editor once it's done.
-
Open
Content/TutorialModules/Monetization/StoreItemPurchase/DA_StoreItemPurchase.uasset
and enable theIs Starter Mode Active
. Save the Data Asset.
Resources
- The files used in this tutorial section are available in the Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/StoreItemPurchase/UI/ItemPurchaseWidget_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/StoreItemPurchase/UI/ItemPurchaseWidget_Starter.cpp
- AccelByteWars/Content/TutorialModules/Monetization/StoreItemPurchase/UI/W_StoreItemPurchase_Starter.uasset
- AccelByteWars/Content/TutorialModules/Monetization/StoreItemPurchase/DA_StoreItemPurchase.uasset