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

UIの追加 - ゲーム内ストアディスプレイ - (Unreal Engineモジュール)

Last updated on February 4, 2026

注釈:本資料はAI技術を用いて翻訳されています。

メニューの内容

このセクションでは、ストアディスプレイとセクションを表示するために使用するウィジェットを準備します。関連ファイルはリソースセクションにあります。

セクションウィジェットエントリ

  • SectionWidgetEntry: アイテムの詳細を表示するために使用されるC++クラスです。このメニューは、すぐに使用できるコンポーネントとして提供されているため、変更する必要はありません。
    • ヘッダーファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/Components/SectionWidgetEntry.h
    • CPPファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/Components/SectionWidgetEntry.cpp
    • Blueprintウィジェット: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/Components/W_SectionEntry.uasset

SectionWidgetEntryは、特定のストアセクション内のすべてのアイテムを表示するように設計されたUIコンポーネントです。セクションに時間制限がある場合は、カウントダウンタイマーを表示することもできます。セクションコンテンツの視覚的な表示を処理しますが、ストアディスプレイの統合は含まれていません。代わりに、このウィジェットを別のウィジェットの一部として使用します。

以下はSectionWidgetEntry Blueprintウィジェットのプレビューです:

Section widget entry preview

このウィジェットで使用されるコンポーネントは、SectionWidgetEntryクラスのヘッダーファイルで定義されています:

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;

このウィジェットには、Byte Wars統合用に調整された事前設定済みの変数と関数が含まれています。

  • ストアセクションデータがこのウィジェットに割り当てられたときに、アイテムエントリを設定する関数。
	virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
  • アイテムがクリックされたときにストアの詳細を開く関数。
protected:
void OnItemClicked(UObject* Item) const;
  • セクションがローテーションするまでの残り時間を追跡する変数。
private:
FTimespan TimeLeft;

セクション分けされたショップウィジェット

  • SectionedShopWidget_Starter: アイテムの詳細を表示するために使用されるC++クラスです。このメニューは、すぐに使用できるコンポーネントとして提供されているため、変更する必要はありません。
    • ヘッダーファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/SectionedShopWidget_Starter.h
    • CPPファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/UI/SectionedShopWidget_Starter.cpp
    • デイリーショップBlueprintウィジェット: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_DailyShop_Starter.uasset
    • フィーチャーショップBlueprintウィジェット: Content/TutorialModules/Monetization/InGameStoreDisplays/UI/W_FeaturedShop_Starter.uasset

SectionedShopWidget_Starterウィジェットには2つのBlueprintウィジェットがあります: デイリーストアディスプレイを表示するW_DailyShop_Starterと、フィーチャーストアディスプレイを表示するW_FeaturedShop_Starterです。これらのウィジェットは、ゲーム内ストアの基本モジュールで設定したメインアイテムストアウィジェットに統合されています。このモジュールがアクティブになると、これら2つのウィジェットがデイリーとフィーチャーの2つの新しいタブとして表示され、プレイヤーはゲーム内ストア内で厳選されたアイテムセクションを閲覧できるようになります。

以下はW_DailyShop_Starter Blueprintウィジェットのプレビューです:

Daily shop preview

以下はW_FeaturedShop_Starter Blueprintウィジェットのプレビューです:

Featured shop preview

これらのウィジェットで使用されるコンポーネントは、SectionedShopWidget_Starterヘッダーファイルで定義されています:

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

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

このウィジェットには、Byte Wars統合用に調整された事前設定済みの変数とヘルパー関数が含まれています。

  • ストアディスプレイのロジックを処理するサブシステムへの参照。
private:
UPROPERTY()
UInGameStoreDisplaysSubsystem_Starter* InGameStoreDisplaysSubsystem;
void USectionedShopWidget_Starter::NativeOnActivated()
{
// ...
InGameStoreDisplaysSubsystem = GetGameInstance()->GetSubsystem<UInGameStoreDisplaysSubsystem_Starter>();
ensure(InGameStoreDisplaysSubsystem);
// ...
}
  • このウィジェットが表示すべきディスプレイのデータを格納する変数。
private:
// ...
UPROPERTY()
TArray<USectionDataObject*> SectionDatas;
  • このウィジェットが表示すべきディスプレイを指定する変数。値はW_DailyShop_StarterではDailyに、W_FeaturedShop_StarterではFeaturedにすでに設定されています。この値は、エディターを通じてBlueprintで表示および変更できます。
private:
// ...
UPROPERTY(EditAnywhere)
FString TargetDisplayName;
  • インデックスとマッピングに基づいてセクションの色を設定する関数。これはByte Warsの機能です。サービス統合とは関係ありません。
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)
};

メニューの準備

特定のストアディスプレイからオファーを表示するには、ウィジェットは一連のステップに従う必要があります。まず、利用可能なすべてのディスプレイをクエリし、次に結果をフィルタリングして、表示を担当するディスプレイを見つけます。次に、そのディスプレイ内のすべてのセクションを取得し、続いて各セクションに含まれるオファーをクエリします。以下の図は、このロジックの完全なフローを示しています。

  1. SectionedShopWidget_Starterヘッダーファイルを開き、以下の変数宣言を追加します。

    • bRefreshing: 複数のプロセスが同時に実行されるのを防ぎます。
    • QueryOrGetOffersInSectionCount: 受信したセクションのオファーの数を追跡します。
    private:
    // ...
    bool bRefreshing = false;
    int QueryOrGetOffersInSectionCount = 0;
  2. 以下の関数宣言を追加します。

    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. SectionedShopWidget_Starter CPPファイルを開き、OnParentRefreshButtonClicked()関数を実装します。今のところ、これはウィジェットをローディング状態に設定するだけです。後でここにディスプレイクエリ呼び出しを追加します。

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

    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
    // ...
    }
  4. OnQueryOrGetDisplaysCompleted()関数を実装します。これは受信したディスプレイをフィルタリングし、TargetDisplayName変数に一致するものだけを使用します。後でここにセクションクエリを呼び出します。

    void USectionedShopWidget_Starter::OnQueryOrGetDisplaysCompleted(
    TArray<TSharedRef<FAccelByteModelsViewInfo>>& Displays,
    const FOnlineError& Error)
    {
    if (Error.bSucceeded)
    {
    for (const TSharedRef<FAccelByteModelsViewInfo>& Display : Displays)
    {
    /**
    * ターゲットディスプレイのみのセクションクエリをトリガー
    * 管理ポータルで設定されたディスプレイ名を見つけるためにNameを使用
    * 管理ポータルで設定されたローカライズされた名前を取得するためにTitleを使用
    */
    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. OnQueryOrGetSectionsCompleted()関数を実装します。これはセクションデータをSectionDatasに格納します。この時点では、オファーデータはまだ含まれていません。また、QueryOrGetOffersInSectionCount変数をインクリメントします。これにより、ゲームはデータを表示する前に待機する応答の数を追跡できます。後でこの関数でオファークエリを呼び出します。

    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. OnQueryOrGetOffersInSectionCompleted関数を実装します。これはSectionDatasのオファーデータを更新し、QueryOrGetOffersInSectionCountがゼロになったらオファーを表示します。

    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)
    {
    // すべてのデータが空かどうかを確認
    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. NativeOnActivated()関数を見つけて、現在の実装を以下のコードに置き換えます。これにより、OnParentRefreshButtonClicked()関数がアイテムストアの更新ボタンにバインドされます。

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

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

    Ws_Root->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
    // ...
    // 親の(ストアの基本)更新関数をこの更新関数にバインド
    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. NativeOnDeactivated()関数の実装を以下のコードに置き換えて、ウィジェットが閉じられたときにすべてのバインディングがクリアされるようにします。

    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. プロジェクトをビルドし、完了したらUnreal Editorで開きます。

  10. Content/TutorialModules/Monetization/InGameStoreDisplays/DA_InGameStoreDisplays.uassetを開き、Is Starter Mode Activeを有効にします。データアセットを保存します。

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

  11. エディターでPlayをクリックします。この時点で、Store > Item Shop > FeaturedおよびStore > Item Shop > Dailyに移動できるようになります。この段階では、これらのメニューはローディング画面のみを表示します。

リソース