Adding UI - In-Game Store Essentials - (Unreal Engine module)
注釈:本資料はAI技術を用いて翻訳されています。
このセクションの内容
このセクションでは、ストアアイテムとその詳細メニューを表示するために使用するウィジェットを準備する方法を学びます。関連ファイルはリソースセクションで入手できます。
アイテム詳細ウィジェット
StoreItemDetailWidget: アイテムの詳細を表示するために使用されるC++クラスです。このメニューはすぐに使用できるコンポーネントとして提供されているため、変更する必要はありません。- ヘッダーファイル:
Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/StoreItemDetailWidget.h - CPPファイル:
Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/StoreItemDetailWidget.cpp - Blueprintウィジェット:
Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_StoreItemDetail.uasset
- ヘッダーファイル:
以下はStoreItemDetailWidget Blueprintウィジェットのプレビューです。この段階では、表示される詳細がショップウィジェットの内容と似ているため冗長に見えるかもしれません。しかし、このウィジェットは追加のモジュールがUIを表示できるプレースホルダーとして機能します。実際のアイテム購入はここで行われ、後のモジュールで実装します。

このウィジェットで使用されるコンポーネントは、StoreItemDetailWidgetクラスのヘッダーファイルで定義されています。
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;
このウィジェットを初期化するには、Setup()関数を使用します。この関数は、提供されたストアアイテムデータに基づいてUIコンポーネントを更新します。
public:
// ...
void Setup(const UStoreItemDataObject* Object);
ショップウィジェット
ShopWidget_Starter: コアストア機能を駆動するC++クラスです。- ヘッダーファイル:
Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.h - CPPファイル:
Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.cpp - 通貨ショップ用Blueprintウィジェット:
Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_CurrencyShop_Starter.uasset - アイテムショップ用Blueprintウィジェット:
Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_ItemShop_Starter.uasset
- ヘッダーファイル:
ShopWidget_Starterは2つのBlueprintウィジェットで使用されます。違いは、それぞれが表示するアイテムカテゴリです:W_CurrencyShop_Starterは通貨アイテムを表示し、W_ItemShop_Starterは通常のストアアイテムを表示します。それ以外の動作は同じです。
ショップウィジェットには4つの状態があります:
- Success - 利用可能なストアアイテムとカテゴリフィルタータブを表示します。
- Empty - アイテムが利用できないことを示します。
- Loading - ストアがデータを取得中であることを示します。
- Error - ストアアイテムの取得中に問題が発生したことを示します。
これらの状態変更は、事前定義された状態を持つカスタムWidget SwitcherであるAccelByteWars Widget Switcherを介して管理されます。
ショップウィジェットにはRefreshとBackボタンもあります。以下はW_CurrencyShop_StarterとW_ItemShop_Starterウィジェットの両方のプレビューです:


これらのウィジェットで使用されるコンポーネントは、ヘッダーファイルで宣言されています:
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;
このウィジェットには、Byte Wars統合用に調整された事前設定済みの変数とヘルパー関数が含まれています。
- ストアアイテムを取得するカテゴリパスを定義する変数。
private:
UPROPERTY(EditAnywhere)
FString RootPath;
- ストアロジックを処理するサブシステムへの参照。
private:
// ...
UPROPERTY()
UInGameStoreEssentialsSubsystem_Starter* StoreSubsystem;
void UShopWidget_Starter::NativeOnActivated()
{
// ...
StoreSubsystem = GetGameInstance()->GetSubsystem<UInGameStoreEssentialsSubsystem_Starter>();
ensure(StoreSubsystem);
// ...
}
- ウィジェットアクションに応答するためのデリゲート。
public:
inline static TMulticastDelegate<void(const APlayerController*)> OnActivatedMulticastDelegate;
FSimpleMulticastDelegate OnRefreshButtonClickedDelegates;
- ウィジェットの状態を切り替える関数。
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);
}
- ストアアイテムが選択されたときに使用される詳細ウィジェットへの参照。
private:
// ...
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteWarsActivatableWidget> DetailWidgetClass;
W_WalletOuterと呼ばれるウィジェットコンポーネントへの参照と、その子を取得する関数。これは、プレイヤーの通貨を表示するためにWallet Essentialsモジュールのウィジェットを格納するために使用されます。このウィジェットについては後のモジュールで学習します。
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget* W_WalletOuter;
UAccelByteWarsActivatableWidget* GetBalanceWidget() const;
メニューの準備
-
ShopWidget_Starterヘッダーファイルを開き、以下の関数宣言を追加します。これらはストアレスポンスを処理します。protected:
void OnGetOrQueryCategoriesComplete(TArray<FOnlineStoreCategory> Categories);
void OnRefreshCategoriesComplete(TArray<FOnlineStoreCategory> Categories);
void OnGetOrQueryOffersComplete(const TArray<UStoreItemDataObject*> Offers) const; -
CPPファイルで、
OnGetOrQueryCategoriesComplete()関数を実装して、タブリストにアイテムカテゴリを入力し、デフォルトの「All」タブを追加します。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);
}
}
} -
引き続きCPPファイルで、
OnRefreshCategoriesComplete()関数を実装して、更新後に以前に選択されたカテゴリを復元します。void UShopWidget_Starter::OnRefreshCategoriesComplete(TArray<FOnlineStoreCategory> Categories)
{
const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
if (!LocalPlayer)
{
return;
}
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);
}
} -
OnGetOrQueryOffersComplete()関数を実装して、アイテムタイルビューを入力します。void UShopWidget_Starter::OnGetOrQueryOffersComplete(const TArray<UStoreItemDataObject*> Offers) const
{
Tv_ContentOuter->SetListItems(Offers);
SwitchContent(Tv_ContentOuter->GetNumItems() <= 0 ?
EAccelByteWarsWidgetSwitcherState::Empty : EAccelByteWarsWidgetSwitcherState::Not_Empty);
} -
ShopWidget_Starterヘッダーファイルに戻り、以下の関数宣言を追加します。protected:
// ...
void OnStoreItemClicked(UObject* Item) const;
void OnRefreshButtonClicked();
UFUNCTION()
void SwitchCategory(FName Id); -
ShopWidget_StarterCPPファイルに移動し、OnStoreItemClicked()関数を実装してアイテム詳細ビューを開きます。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);
} -
OnRefreshButtonClicked()関数を実装して、ウィジェットの状態を更新し、更新イベントをブロードキャストし、Walletモジュールのウィジェットを再アクティブ化してセットアップロジックをトリガーします。実際の更新関数は後のページで呼び出します。void UShopWidget_Starter::OnRefreshButtonClicked()
{
const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
if (!LocalPlayer)
{
return;
}
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();
}
} -
SwitchCategory()関数を実装して、選択されたカテゴリのアイテムリストをクリアします。この関数は、後で指定されたカテゴリに基づいてストアアイテム取得リクエストを呼び出すために使用します。void UShopWidget_Starter::SwitchCategory(FName Id)
{
if (const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer())
{
Tv_ContentOuter->ClearListItems();
// ...
}
} -
ShopWidget_StarterCPPファイルで、NativeOnActivated()関数を見つけて、以下のハイライトされた行を追加します。これらのコードは3つのことを行います:- 実装した関数をタイルビューエントリ、更新ボタン、タブリストエントリにバインドします。
- このメニューは後でこの関数でクエリを呼び出すことを想定しているため、メニューの状態をローディングに設定します。
OnActivatedMulticastDelegateデリゲートを呼び出して、このメニューがアクティブ化されたことを他のオブジェクトに通知します。
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);
Tv_ContentOuter->ClearListItems();
// ...
// Reset UI.
SwitchContent(EAccelByteWarsWidgetSwitcherState::Loading);
// ...
OnActivatedMulticastDelegate.Broadcast(GetOwningPlayer());
} -
NativeOnDeactivated()関数を見つけて、以下のハイライトされた行を追加します。このコードは2つのことを行います:- ボタンにバインドされた関数をクリアします。
- ウィジェットが閉じられたときにクリーンな終了を確実にするために、タイルビューをクリアします。
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();
} -
プロジェクトをビルドし、完了したらUnreal Editorで開きます。
-
Content/TutorialModules/Monetization/InGameStoreEssentials/DA_InGameStoreEssentials.uassetを開き、Is Starter Mode Activeを有効にします。Data Assetを保存します。
-
エディタでPlayをクリックします。この時点で、Store > Item ShopとStore > Currency Shopに移動できるようになります。ただし、今のところそのメニューにはローディング画面のみが表示されます。
リソース
- このチュートリアルセクションで使用されるファイルは、Byte Wars GitHubリポジトリで入手できます。
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Monetization/InGameStoreEssentials/UI/ShopWidget_Starter.cpp
- AccelByteWars/Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_CurrencyShop_Starter.uasset
- AccelByteWars/Content/TutorialModules/Monetization/InGameStoreEssentials/UI/W_ItemShop_Starter.uasset
- Content/TutorialModules/Monetization/InGameStoreEssentials/DA_InGameStoreEssentials.uasset