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

Adding UI - Entitlements Essentials - (Unreal Engine module)

Last updated on December 17, 2025

What's on the menu

In this section, you'll learn how to prepare the widgets used to display items owned by the player. The related files are available in the Resources section.

Owned count widget

  • OwnedCountWidgetEntry_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/EntitlementsEssentials/UI/OwnedCountWidgetEntry_Starter.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/EntitlementsEssentials/UI/OwnedCountWidgetEntry_Starter.cpp
    • Blueprint widget: Content/TutorialModules/Monetization/EntitlementEssentials/UI/W_OwnedIndicator_Starter.uasset

The OwnedCountWidgetEntry_Starter shows how many units of a certain item the player owns. For durable items, it will display owned instead of a count. This widget is attached to the W_StoreItemEntry widget using the Byte Wars modular system. That means W_StoreItemEntry will always include OwnedCountWidgetEntry_Starter when the entitlements module is active.

Here is a preview of the OwnedCountWidgetEntry_Starter Blueprint widget:

OwnedCountWidgetEntry_Starter preview

The components used in this widget are defined in the OwnedCountWidgetEntry_Starter class header file.

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

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

  • Reference to the subsystem that handles the entitlement logic.

    private:
    UPROPERTY()
    UEntitlementsEssentialsSubsystem_Starter* EntitlementsSubsystem;
    void UOwnedCountWidgetEntry_Starter::NativeOnActivated()
    {
    // ...
    EntitlementsSubsystem = GetGameInstance()->GetSubsystem<UEntitlementsEssentialsSubsystem_Starter>();
    ensure(EntitlementsSubsystem);
    // ...
    }
  • Reference to its parent widget, W_StoreItemEntry. Since this widget is only used within that context, it safely assumes W_StoreItemEntry as its parent.

    private:
    UPROPERTY()
    UStoreItemListEntry* W_Parent;

Equipment inventory widget

This widget displays all equipable items, including ship customizations, visual effects, and power-ups. This widget is defined in the files below:

  • EquipmentInventoryWidget_Starter: A C++ class used to display the list of owned items and their details.

    • Header file: Source/AccelByteWars/TutorialModules/Monetization/EntitlementsEssentials/UI/EquipmentInventoryWidget_Starter.h
    • CPP file: Source/AccelByteWars/TutorialModules/Monetization/EntitlementsEssentials/UI/EquipmentInventoryWidget_Starter.cpp
    • Blueprint widgets:
      • Content/TutorialModules/Monetization/EntitlementEssentials/UI/W_ShipCustomization_Starter.uasset
      • Content/TutorialModules/Monetization/EntitlementEssentials/UI/W_FxCustomization_Starter.uasset
      • Content/TutorialModules/Monetization/EntitlementEssentials/UI/W_PowerUpCustomization_Starter.uasset

There are several variants of this inventory widget, but all of them are controlled by a single EquipmentInventoryWidget_Starter class. These widget variants are:

  • Ship customization widget

    The W_ShipCustomization_Starter widget displays list of owned ship shapes and colors to equip. Here is the preview of this widget:

    W_ShipCustomization_Starter preview

  • Visual effects customization

    The W_FxCustomization_Starter widget displays list of owned visual effects to equip, such as explosion and missile trail. Here is the preview of this widget:

    W_FxCustomization_Starter preview

  • Power ups customization

    The W_PowerUpCustomization_Starter widget displays list of owned power ups to equip. Here is the preview of this widget:

    W_PowerUpCustomization_Starter preview

The components used in these widgets are defined in the EquipmentInventoryWidget_Starter class header file.

  • Reference to the subsystem that handles the entitlement logic.

    protected:
    // ...
    UPROPERTY()
    UEntitlementsEssentialsSubsystem_Starter* EntitlementsSubsystem;
    void UEquipmentInventoryWidget_Starter::NativeOnActivated()
    {
    // ...
    EntitlementsSubsystem = GetGameInstance()->GetSubsystem<UEntitlementsEssentialsSubsystem_Starter>();
    ensure(EntitlementsSubsystem);
    // ...
    }
  • Reference to widget components and helper variables.

    protected:
    // ...
    UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
    UAccelByteWarsWidgetSwitcher* Ws_Root;

    UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
    UInventoryWidget* W_Inventory;

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

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    TSubclassOf<AActor> PreviewActorClass;

    UPROPERTY(BlueprintReadOnly)
    AActor* PreviewActor;

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    FName PreviewActorTag;

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    FTransform PreviewActorTransform;

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    FSlateBrush PreviewActorRenderTarget;

    UPROPERTY()
    UEntitlementsEssentialsSubsystem_Starter* EntitlementsSubsystem;

    UPROPERTY()
    TArray<UStoreItemDataObject*> CachedEntitlements;

    UPROPERTY()
    FPlayerEquipments CachedEquipments;

Ready the menu

  1. Open the OwnedCountWidgetEntry_Starter header file and add the following function declarations.

    private:
    // ...
    void RetrieveEntitlementWithForceRequest(const bool bForceRequest);
    void ShowOwnedCount(const FOnlineError& Error, const UStoreItemDataObject* Entitlement);
  2. Open the OwnedCountWidgetEntry_Starter CPP file and implement the RetrieveEntitlementWithForceRequest() function. For now, this function retrieves only the item represented by the widget. You'll expand this later to include entitlement retrieval.

    void UOwnedCountWidgetEntry_Starter::RetrieveEntitlementWithForceRequest(const bool bForceRequest)
    {
    SetVisibility(ESlateVisibility::Collapsed);

    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    W_Parent = GetFirstOccurenceOuter<UStoreItemListEntry>();
    if (W_Parent && LocalPlayer)
    {
    // Get item.
    const UStoreItemDataObject* ItemData = W_Parent->GetItemData();
    if (!ItemData)
    {
    return;
    // ...
    }
    }
  3. Implement the ShowOwnedCount() function. This displays the number of items owned, or an Owned indicator if the item is durable, based on the backend response.

    void UOwnedCountWidgetEntry_Starter::ShowOwnedCount(const FOnlineError& Error, const UStoreItemDataObject* Entitlement)
    {
    if (!Error.bSucceeded || !Entitlement)
    {
    return;
    }

    // Set owned count.
    FText Text;
    if (Entitlement->GetCount() > 0)
    {
    Text = Entitlement->GetIsConsumable() ?
    FText::FromString(FString::Printf(TEXT("%d x"), Entitlement->GetCount())) : TEXT_OWNED;
    }
    Tb_OwnedCount->SetText(Text);

    // Show the owned count if not empty.
    SetVisibility(Tb_OwnedCount->GetText().IsEmpty() ? ESlateVisibility::Collapsed : ESlateVisibility::Visible);
    }
  4. Find the NativeOnActivated() function and replace the existing implementation with the code below. This triggers RetrieveEntitlementWithForceRequest() when the widget is activated and binds the function to the item purchase widget delegate, so the item count updates immediately after a successful purchase.

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

    EntitlementsSubsystem = GetGameInstance()->GetSubsystem<UEntitlementsEssentialsSubsystem_Starter>();
    ensure(EntitlementsSubsystem);

    RetrieveEntitlementWithForceRequest(false);

    UItemPurchaseWidget::OnPurchaseCompleteMulticastDelegate.AddWeakLambda(this, [this](const APlayerController* PC)
    {
    RetrieveEntitlementWithForceRequest(true);
    });
    UItemPurchaseWidget_Starter::OnPurchaseCompleteMulticastDelegate.AddWeakLambda(this, [this](const APlayerController* PC)
    {
    RetrieveEntitlementWithForceRequest(true);
    });
    }
  5. Navigate to the NativeOnDeactivated() function and replace the existing implementation with the code below. This unbinds all delegates used by the widget to ensure there are no lingering references when the widget is deactivated.

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

    UItemPurchaseWidget::OnPurchaseCompleteMulticastDelegate.RemoveAll(this);
    UItemPurchaseWidget_Starter::OnPurchaseCompleteMulticastDelegate.RemoveAll(this);
    }
  6. Now, open the EquipmentInventoryWidget_Starter header file and add the following function declarations.

    protected:
    void SpawnPreviewActor();
    protected:
    // ...
    void QueryEquipmentItems();
    protected:
    // ...
    void DisplayEquipmentItems(FInventoryCategory* Category);
    protected:
    // ...
    void OnEquipmentItemClicked(UObject* Item, FInventoryCategory* Category);
    void OnEquipmentItemEquipped(UObject* Item, FInventoryCategory* Category);
    void OnEquipmentItemUnequipped(UObject* Item, FInventoryCategory* Category);
    protected:
    // ...
    void SaveEquipments();
  7. Open the EquipmentInventoryWidget_Starter CPP file to define the functions above. Let's start with the SpawnPreviewActor() function. This function spawns an actor that will be used to display the applied ship customizations.

    void UEquipmentInventoryWidget_Starter::SpawnPreviewActor()
    {
    if (PreviewActorClass && !PreviewActor)
    {
    // Try to get existing preview actor.
    TArray<AActor*> Actors;
    UGameplayStatics::GetAllActorsOfClassWithTag(this, PreviewActorClass, { PreviewActorTag }, Actors);
    if (!Actors.IsEmpty() && Actors[0]->IsValidLowLevel())
    {
    PreviewActor = Actors[0];
    }

    // Otherwise, try to spawn a new preview actor.
    if (!PreviewActor && GetWorld())
    {
    PreviewActor = GetWorld()->SpawnActor<AActor>(PreviewActorClass, PreviewActorTransform);
    if (PreviewActor)
    {
    PreviewActor->Tags.Add(PreviewActorTag);
    }
    }
    ensureMsgf(PreviewActor, TEXT("Preview actor is not found. Failed to spawn or find existing actor."));
    }

    if (PreviewActor)
    {
    W_Inventory->SetPreview(PreviewActorRenderTarget);
    }
    }
  8. Next, define the QueryEquipmentItems() function. Later, you will complete this function to query the player's item entitlements to be displayed on the widget. For now, add the code below to display a loading spinner indicating that the data is being loaded.

    void UEquipmentInventoryWidget_Starter::QueryEquipmentItems()
    {
    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    if (!LocalPlayer)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed to save customization. Local player is invalid.");
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Error);
    return;
    }

    const int32 LocalUserNum = LocalPlayer->GetControllerId();
    const FUniqueNetIdPtr UserId = LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
    // ...
    }
  9. Next, define the DisplayEquipmentItems() function to display inventory items based on the category selected by the player.

    void UEquipmentInventoryWidget_Starter::DisplayEquipmentItems(FInventoryCategory* Category)
    {
    if (!Category)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed to display customization items. Category is invalid.");
    return;
    }

    const FString CategoryId = Category->CategoryId.ToString();
    const EItemSkuPlatform PlatformSku = EItemSkuPlatform::AccelByte;
    TArray<UStoreItemDataObject*> FilteredEntitlements = CachedEntitlements;

    UObject* EquippedItemData = nullptr;
    const FString EquippedItemId = [&]() -> FString
    {
    switch (Category->ItemType)
    {
    case EItemType::Skin:
    return CachedEquipments.SkinId;
    case EItemType::Color:
    return CachedEquipments.ColorId;
    case EItemType::ExplosionFx:
    return CachedEquipments.ExplosionFxId;
    case EItemType::MissileTrailFx:
    return CachedEquipments.MissileTrailFxId;
    case EItemType::PowerUp:
    return CachedEquipments.PowerUpId;
    default:
    return FString();
    }
    }();

    FString EquippedItemSku = TEXT("");
    if (const UInGameItemDataAsset* EquippedItemDataAsset = UInGameItemUtility::GetItemDataAsset(EquippedItemId))
    {
    EquippedItemSku = EquippedItemDataAsset->SkuMap.Contains(PlatformSku) ? EquippedItemDataAsset->SkuMap[PlatformSku] : TEXT("");
    }

    // If there is a default item, add it as the first item in the list.
    if (const UInGameItemDataAsset* DefaultItemDataAsset = Category->DefaultItemDataAsset)
    {
    if (UStoreItemDataObject* DefaultItemData = NewObject<UStoreItemDataObject>())
    {
    DefaultItemData->Setup(
    DefaultItemDataAsset->DisplayName,
    FText::FromName(Category->CategoryId),
    UEnum::GetValueAsString(Category->ItemType),
    DefaultItemDataAsset->SkuMap[PlatformSku],
    TEXT(""),
    DefaultItemDataAsset->Icon->GetPathName(),
    DefaultItemDataAsset->SkuMap,
    {}, 1, false);

    EquippedItemData = DefaultItemData;
    FilteredEntitlements.Insert(DefaultItemData, 0);
    }
    }

    // Filter entitlements based on category.
    FilteredEntitlements.RemoveAll(
    [CategoryId, EquippedItemSku, PlatformSku, &EquippedItemData](UStoreItemDataObject* Item)
    {
    if (!Item)
    {
    return false;
    }

    const bool bIsValid = Item->GetCategory().ToString().Contains(CategoryId);
    if (bIsValid)
    {
    if (Item->GetSkuMap().Contains(PlatformSku) && Item->GetSkuMap()[PlatformSku].Equals(EquippedItemSku))
    {
    EquippedItemData = Item;
    }
    Item->SetIsShowItemCount(Item->GetIsConsumable());
    }
    return !bIsValid;
    });

    // Initialize preview actor based on equipped items.
    if (APlayerShipBase* ShipPreview = Cast<APlayerShipBase>(PreviewActor))
    {
    if (const UInGameItemDataAsset* SkinDataAsset = UInGameItemUtility::GetItemDataAsset(CachedEquipments.SkinId))
    {
    ShipPreview->SetAlphaTexture(SkinDataAsset->Icon);
    }
    if (const UInGameItemDataAsset* ColorDataAsset = UInGameItemUtility::GetItemDataAsset(CachedEquipments.ColorId))
    {
    ShipPreview->SetColorTexture(ColorDataAsset->Icon);
    }
    }

    // Set initial equipped item and update the list items.
    if (EquippedItemData)
    {
    Category->EquippedItem = EquippedItemData;
    }
    W_Inventory->SetListItems(FilteredEntitlements);
    }
  10. Define these functions to be called when an inventory item is clicked, equipped, or unequipped. When an item is clicked, the widget displays the item's information and its preview. When an item is equipped, the widget temporarily stores the equipped item. When an item is unequipped, the item is removed from the temporary data. This temporary data will be finalized as the equipped items once the user closes the menu.

    void UEquipmentInventoryWidget_Starter::OnEquipmentItemClicked(UObject* Item, FInventoryCategory* Category)
    {
    const UStoreItemDataObject* ItemData = Cast<UStoreItemDataObject>(Item);
    if (!Category || !ItemData)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed handle item clicked event. Item is invalid.");
    return;
    }

    const EItemSkuPlatform PlatformSku = EItemSkuPlatform::AccelByte;
    const UInGameItemDataAsset* ItemDataAsset =
    UInGameItemUtility::GetItemDataAssetBySku(PlatformSku, ItemData->GetSkuMap()[PlatformSku]);
    if (!ItemDataAsset)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed handle item clicked event. Item data asset is invalid.");
    return;
    }

    // Display preview based on item type.
    W_Inventory->SetPreviewDetails(ItemDataAsset->DisplayName, ItemDataAsset->Description);
    switch (Category->ItemType)
    {
    case EItemType::Skin:
    if (APlayerShipBase* ShipPreview = Cast<APlayerShipBase>(PreviewActor))
    {
    ShipPreview->SetAlphaTexture(ItemDataAsset->Icon);
    }
    break;
    case EItemType::Color:
    if (APlayerShipBase* ShipPreview = Cast<APlayerShipBase>(PreviewActor))
    {
    ShipPreview->SetColorTexture(ItemDataAsset->Icon);
    }
    break;
    default:
    if (ItemDataAsset->PreviewVideo)
    {
    W_Inventory->SetPreview(ItemDataAsset->PreviewVideo);
    }
    break;
    }
    }
    void UEquipmentInventoryWidget_Starter::OnEquipmentItemEquipped(UObject* Item, FInventoryCategory* Category)
    {
    const EItemSkuPlatform PlatformSku = EItemSkuPlatform::AccelByte;
    const UStoreItemDataObject* ItemData = Cast<UStoreItemDataObject>(Item);
    if (!Category || !ItemData || !ItemData->GetSkuMap().Contains(PlatformSku))
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed handle item unequipped event. Item is invalid.");
    return;
    }

    const UInGameItemDataAsset* ItemDataAsset =
    UInGameItemUtility::GetItemDataAssetBySku(PlatformSku, ItemData->GetSkuMap()[PlatformSku]);
    if (!ItemDataAsset)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed handle item unequipped event. Item data asset is invalid.");
    return;
    }

    // Update cached equipments based on item type.
    const FString& ItemId = ItemDataAsset->Id;
    switch (Category->ItemType)
    {
    case EItemType::Skin:
    CachedEquipments.SkinId = ItemId;
    break;
    case EItemType::Color:
    CachedEquipments.ColorId = ItemId;
    break;
    case EItemType::ExplosionFx:
    CachedEquipments.ExplosionFxId = ItemId;
    break;
    case EItemType::MissileTrailFx:
    CachedEquipments.MissileTrailFxId = ItemId;
    break;
    case EItemType::PowerUp:
    CachedEquipments.PowerUpId = ItemId;
    break;
    }
    }
    void UEquipmentInventoryWidget_Starter::OnEquipmentItemUnequipped(UObject* Item, FInventoryCategory* Category)
    {
    if (!Category)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed handle item equipped event. Item is invalid.");
    return;
    }

    // Unequip cached equipments based on item type.
    switch (Category->ItemType)
    {
    case EItemType::Skin:
    CachedEquipments.SkinId = TEXT("");
    break;
    case EItemType::Color:
    CachedEquipments.ColorId = TEXT("");
    break;
    case EItemType::ExplosionFx:
    CachedEquipments.ExplosionFxId = TEXT("");
    break;
    case EItemType::MissileTrailFx:
    CachedEquipments.MissileTrailFxId = TEXT("");
    break;
    case EItemType::PowerUp:
    CachedEquipments.PowerUpId = TEXT("");
    break;
    }
    }
  11. Define the SaveEquipments() function. Later, you will complete this function to save the player’s equipment to cloud storage. For now, add the code below to display a loading spinner indicating that the data is being saved.

    void UEquipmentInventoryWidget_Starter::SaveEquipments()
    {
    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    if (!LocalPlayer)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed to save customization. Local player is invalid.");
    return;
    }

    const int32 LocalUserNum = LocalPlayer->GetControllerId();

    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    if (!GameInstance)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed to save customization. Game instance is invalid.");
    return;
    }

    UPromptSubsystem* PromptSubsystem = GameInstance->GetSubsystem<UPromptSubsystem>();
    if (!PromptSubsystem)
    {
    UE_LOG_ENTITLEMENTS_ESSENTIALS(Warning, "Failed to save customization. Prompt subsystem is invalid.");
    return;
    }

    PromptSubsystem->ShowLoading(TEXT_SAVING_EQUIPMENTS);
    // ...
    }
  12. Next, bind the functions above to their respective widget components by adding the code below to the NativeOnActivated() function.

    void UEquipmentInventoryWidget_Starter::NativeOnActivated()
    {
    // ...
    W_Inventory->OnCategorySwitched.AddUObject(this, &ThisClass::DisplayEquipmentItems);
    W_Inventory->OnItemClicked.AddUObject(this, &ThisClass::OnEquipmentItemClicked);
    W_Inventory->OnItemEquipClicked.AddUObject(this, &ThisClass::OnEquipmentItemEquipped);
    W_Inventory->OnItemUnequipClicked.AddUObject(this, &ThisClass::OnEquipmentItemUnequipped);
    Btn_Back->OnClicked().AddUObject(this, &ThisClass::SaveEquipments);

    SpawnPreviewActor();

    QueryEquipmentItems();
    }
  13. Build your project and open it in the Unreal Engine Editor. In the Editor, go to /Content/TutorialModules/Monetization/EntitlementEssentials/. You will find a data asset called DA_EntitlementsEssentials. Open it and enable Is Starter Mode Active. Then, save the data asset. This will activate the widgets, allowing you to navigate through them when you play the game.

  14. Play the game in the Editor, log in, and navigate from the main menu to Store > Inventory, then select one of the inventory customization types. You will see that the menu is currently in a loading state. Later, you will change this behavior to actually load the entitlement item list.

Resources