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

Implement Subsystem - In Game Store Displays - (Unreal Engine module)

Last updated on February 4, 2026

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

サブシステムの展開

Byte Wars は、AccelByte Gaming Services (AGS) Online Subsystem (OSS) をラップするために InGameStoreDisplaysSubsystem という Game Instance Subsystem を使用します。このサブシステムは、Unreal Engine の IOnlineStoreV2 インターフェースの AccelByte 実装である FOnlineStoreV2AccelByte を使用します。このチュートリアルでは、サブシステムのスターターバージョンを使用して、必要な関数をゼロから実装します。

このサブシステムは、Store Displays、そのセクション、および関連するオファーを取得するプロセスを管理します。各パーツには、キャッシュからデータを取得するための独自の関数があります。ただし、これらのキャッシュを更新するには、2つの個別のクエリが必要です。1つはストアフロントをクエリするもので、すべてのディスプレイ、そのセクション、およびストアアイテムIDを返しますが、完全なアイテムデータは返しません。もう1つはオファーをクエリするもので、完全なストアアイテムの詳細を取得します。サブシステムは、単一の統合された関数を公開することで、この複雑さを抽象化します。他のオブジェクトは、この関数を呼び出して onComplete ハンドラーを提供するだけで、基礎となるクエリシーケンスを気にする必要はありません。以下の図は、この動作の仕組みを示しています。

スターターパックの内容

このチュートリアルに従うために、InGameStoreDisplaysSubsystem_Starter という名前のスターターサブシステムクラスが用意されています。これは Resources セクションで見つけることができます。以下のファイルが含まれています。

  • ヘッダーファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/InGameStoreDisplaysSubsystem_Starter.h
  • CPP ファイル: Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/InGameStoreDisplaysSubsystem_Starter.cpp

InGameStoreDisplaysSubsystem_Starter クラスには、いくつかの便利なコンポーネントが含まれています。

  • AGS Software Development Kit (SDK) 機能へのアクセスを提供する AGS OSS FOnlineStoreV2AccelByte インターフェースの宣言と初期化。
private:
FOnlineStoreV2AccelBytePtr StoreInterface;
void UInGameStoreDisplaysSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);

const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
ensure(Subsystem);
const IOnlineStoreV2Ptr StorePtr = Subsystem->GetStoreV2Interface();
ensure(StorePtr);
StoreInterface = StaticCastSharedPtr<FOnlineStoreV2AccelByte>(StorePtr);
ensure(StoreInterface);
}
  • Player Controller から一意の Net ID を取得する関数。これは、このサブシステムの主要なユーザーであるウィジェットが Player Controller を使用してプレイヤーを参照するのに対し、OSS インターフェースはユーザーを識別するために一意の Net ID を必要とするため、必要です。
private:
// ...
FUniqueNetIdPtr GetUniqueNetIdFromPlayerController(const APlayerController* PlayerController) const;
  • FOnlineStoreOfferAccelByteRef データ構造体(インターフェースで使用)を UStoreItemDataObject(ゲームで使用)に変換する関数。
private:
// ...
UStoreItemDataObject* ConvertStoreData(const FOnlineStoreOfferAccelByteRef Offer) const;

さらに、Source/AccelByteWars/TutorialModules/Monetization/InGameStoreDisplays/InGameStoreDisplaysModel.h にモデルファイルがあり、構造体、呼び出し元のリクエストを保存するために使用されるデリゲート、およびセクションデータを保存するために使用されるクラスが定義されています。

DECLARE_DELEGATE_TwoParams(FOnQueryOrGetDisplaysComplete, TArray<TSharedRef<FAccelByteModelsViewInfo>>&, const FOnlineError&)
DECLARE_DELEGATE_TwoParams(FOnQueryOrGetSectionsInDisplayComplete, TArray<TSharedRef<FAccelByteModelsSectionInfo>>&, const FOnlineError&)
DECLARE_DELEGATE_TwoParams(FOnQueryOrGetOffersInSectionComplete, TArray<UStoreItemDataObject*>&, const FOnlineError&)

UCLASS()
class USectionDataObject : public UObject
{
GENERATED_BODY()

public:
FAccelByteModelsSectionInfo SectionInfo;
FLinearColor SectionColor;

UPROPERTY()
TArray<UStoreItemDataObject*> Offers;

bool operator== (const USectionDataObject& Other) const
{
return SectionInfo.SectionId.Equals(Other.SectionInfo.SectionId);
}
};

struct FQueryOrGetSectionsParam
{
const FUniqueNetIdPtr UserId;
const FString& DisplayId;
const FOnQueryOrGetSectionsInDisplayComplete OnComplete;
};

struct FQueryOrGetOffersParam
{
const FUniqueNetIdPtr UserId;
const FString SectionId;
const FOnQueryOrGetOffersInSectionComplete OnComplete;
};

ディスプレイ、セクション、オファーのクエリを実装する

  1. InGameStoreDisplaysSubsystem_Starter ヘッダーファイルを開き、以下の関数宣言を追加します。これらの関数は、ディスプレイ、セクション、またはオファーを取得するために他のオブジェクトから呼び出されます。

    public:
    /**
    * @brief Retrieve store displays info from endpoint / cache if exist
    * @param PlayerController User's player controller to use
    * @param OnComplete Executed on response received
    * @param bForceRefresh if true, force call endpoint
    */
    void QueryOrGetDisplays(
    const APlayerController* PlayerController,
    const FOnQueryOrGetDisplaysComplete& OnComplete,
    const bool bForceRefresh = false);

    /**
    * @brief Retrieve store display's sections info from endpoint / cache if exist
    * @param PlayerController User's player controller to use
    * @param DisplayId Display Id to retrieve from
    * @param OnComplete Executed on response received
    * @param bForceRefresh if true, force call endpoint
    */
    void QueryOrGetSectionsForDisplay(
    const APlayerController* PlayerController,
    const FString& DisplayId,
    const FOnQueryOrGetSectionsInDisplayComplete& OnComplete,
    const bool bForceRefresh = false);

    /**
    * @brief Retrieve store section's offers info from endpoint / cache if exist
    * @param PlayerController User's player controller to use
    * @param SectionId Section Id to retrieve from
    * @param OnComplete Executed on response received
    * @param bForceRefresh if true, force call endpoint
    */
    void QueryOrGetOffersInSection(
    const APlayerController* PlayerController,
    const FString& SectionId,
    const FOnQueryOrGetOffersInSectionComplete& OnComplete,
    const bool bForceRefresh = false);
  2. キャッシュからデータを取得するために、以下の関数宣言を追加します。

    private:
    // ...
    TArray<TSharedRef<FAccelByteModelsViewInfo>> GetDisplays() const;
    TArray<TSharedRef<FAccelByteModelsSectionInfo>> GetSectionsForDisplay(
    const FUniqueNetId& UserId,
    const FString& DisplayId) const;
    TArray<UStoreItemDataObject*> GetOffersForSection(const FUniqueNetId& UserId, const FString& SectionId) const;
  3. リクエストパラメータを保存するために、以下の変数を追加します。これらは後でクエリレスポンスを受信した後に完了デリゲートをトリガーするために使用されます。

    private:
    // ...
    TArray<FOnQueryOrGetDisplaysComplete> QueryOrGetDisplaysOnCompleteDelegates;
    TArray<FQueryOrGetSectionsParam> QueryOrGetSectionsOnCompleteDelegates;
    TArray<FQueryOrGetOffersParam> QueryOrGetOffersOnCompleteDelegates;
  4. バックエンドクエリをトリガーし、そのレスポンスを処理するために、以下の関数と変数の宣言を追加します。

    private:
    // ...
    bool bIsQueryStoreFrontRunning = false;
    void QueryStoreFront(const FUniqueNetId& UserId);
    void OnQueryStoreFrontComplete(
    bool bWasSuccessful,
    const TArray<FString>& ViewIds,
    const TArray<FString>& SectionIds,
    const TArray<FUniqueOfferId>& OfferIds,
    const TArray<FString>& ItemMappingIds,
    const FString& Error);

    bool bIsQueryOffersRunning = false;
    void OnQueryOffersComplete(
    bool bWasSuccessful,
    const TArray<FUniqueOfferId>& OfferIds,
    const FString& Error);
  5. InGameStoreDisplaysSubsystem_Starter CPP ファイルを開き、QueryOrGetDisplays() 関数を実装します。この関数はすべてのディスプレイを取得します。最初にキャッシュをチェックし、キャッシュが空の場合はストアフロントクエリをトリガーします。

    void UInGameStoreDisplaysSubsystem_Starter::QueryOrGetDisplays(
    const APlayerController* PlayerController,
    const FOnQueryOrGetDisplaysComplete& OnComplete,
    const bool bForceRefresh)
    {
    const FUniqueNetIdPtr UserId = GetUniqueNetIdFromPlayerController(PlayerController);
    if (!UserId.IsValid())
    {
    return;
    }

    // check cache
    if (TArray<TSharedRef<FAccelByteModelsViewInfo>> Displays = GetDisplays(); !Displays.IsEmpty() && !bForceRefresh)
    {
    OnComplete.ExecuteIfBound(Displays, FOnlineError::Success());
    return;
    }

    // cache empty, trigger query
    QueryOrGetDisplaysOnCompleteDelegates.Add(OnComplete);
    QueryStoreFront(*UserId.Get());
    }
  6. 指定されたディスプレイのセクションを取得するために、QueryOrGetSectionsForDisplay() 関数を実装します。ディスプレイのセクションのキャッシュをチェックし、見つからない場合はストアフロントクエリをトリガーします。

    void UInGameStoreDisplaysSubsystem_Starter::QueryOrGetSectionsForDisplay(
    const APlayerController* PlayerController,
    const FString& DisplayId,
    const FOnQueryOrGetSectionsInDisplayComplete& OnComplete,
    const bool bForceRefresh)
    {
    const FUniqueNetIdPtr UserId = GetUniqueNetIdFromPlayerController(PlayerController);
    if (!UserId.IsValid())
    {
    return;
    }

    // check cache
    if (TArray<TSharedRef<FAccelByteModelsSectionInfo>> Sections = GetSectionsForDisplay(*UserId.Get(), DisplayId);
    !Sections.IsEmpty() && !bForceRefresh)
    {
    OnComplete.ExecuteIfBound(Sections, FOnlineError::Success());
    return;
    }

    // cache empty, trigger query
    QueryOrGetSectionsOnCompleteDelegates.Add({UserId, DisplayId, OnComplete});
    QueryStoreFront(*UserId.Get());
    }
  7. 指定されたセクションのオファーを取得するために、QueryOrGetOffersInSection() 関数を実装します。キャッシュをチェックし、見つからない場合はオファークエリをトリガーします。前の関数とは異なり、この関数は OSS インターフェースへのクエリを直接トリガーします。このクエリはすべてのストアアイテムを取得するため、前のクエリがまだ実行中の間に新しいリクエストが新しいクエリをトリガーする必要はなく、bIsQueryOffersRunning フラグによって制御されます。

    void UInGameStoreDisplaysSubsystem_Starter::QueryOrGetOffersInSection(
    const APlayerController* PlayerController,
    const FString& SectionId,
    const FOnQueryOrGetOffersInSectionComplete& OnComplete,
    const bool bForceRefresh)
    {
    const FUniqueNetIdPtr UserId = GetUniqueNetIdFromPlayerController(PlayerController);
    if (!UserId.IsValid())
    {
    return;
    }

    // check cache
    if (TArray<UStoreItemDataObject*> Offers = GetOffersForSection(*UserId.Get(), SectionId);
    !Offers.IsEmpty() && !bForceRefresh)
    {
    OnComplete.ExecuteIfBound(Offers, FOnlineError::Success());
    return;
    }

    // cache empty, trigger query
    QueryOrGetOffersOnCompleteDelegates.Add({UserId, SectionId, OnComplete});
    if (bIsQueryOffersRunning)
    {
    return;
    }
    bIsQueryOffersRunning = true;
    StoreInterface->QueryOffersByFilter(
    *UserId.Get(),
    FOnlineStoreFilter(),
    FOnQueryOnlineStoreOffersComplete::CreateUObject(this, &ThisClass::OnQueryOffersComplete));
    }
  8. CPP ファイルで、キャッシュされたディスプレイを返すために GetDisplays() 関数を実装します。

    TArray<TSharedRef<FAccelByteModelsViewInfo>> UInGameStoreDisplaysSubsystem_Starter::GetDisplays() const
    {
    TArray<TSharedRef<FAccelByteModelsViewInfo, ESPMode::ThreadSafe>> Displays;
    StoreInterface->GetDisplays(Displays);
    return Displays;
    }
  9. ディスプレイのキャッシュされたセクションを返すために、GetSectionsForDisplay() 関数を実装します。

    TArray<TSharedRef<FAccelByteModelsSectionInfo>> UInGameStoreDisplaysSubsystem_Starter::GetSectionsForDisplay(
    const FUniqueNetId& UserId,
    const FString& DisplayId) const
    {
    TArray<TSharedRef<FAccelByteModelsSectionInfo, ESPMode::ThreadSafe>> Sections;
    StoreInterface->GetSectionsForDisplay(UserId, DisplayId, Sections);
    return Sections;
    }
  10. キャッシュされたオファーを取得するために、GetOffersForSection() 関数を実装します。

    TArray<UStoreItemDataObject*> UInGameStoreDisplaysSubsystem_Starter::GetOffersForSection(
    const FUniqueNetId& UserId,
    const FString& SectionId) const
    {
    TArray<FOnlineStoreOfferAccelByteRef> Offers;
    StoreInterface->GetOffersForSection(UserId, SectionId, Offers);

    TArray<UStoreItemDataObject*> OfferObjects;
    for (const FOnlineStoreOfferAccelByteRef& Offer : Offers)
    {
    OfferObjects.Add(ConvertStoreData(Offer));
    }
    return OfferObjects;
    }
  11. すべてのディスプレイ、セクション、およびそれらのセクション内のアイテムIDをクエリするために、QueryStoreFront() 関数を実装します。このクエリは完全なアイテムの詳細を除くすべてを取得するため、前のクエリがまだ実行中の間は再度トリガーされるべきではなく、bIsQueryStoreFrontRunning フラグによって管理されます。

    void UInGameStoreDisplaysSubsystem_Starter::QueryStoreFront(const FUniqueNetId& UserId)
    {
    if (bIsQueryStoreFrontRunning)
    {
    return;
    }
    bIsQueryStoreFrontRunning = true;

    FString StoreId;
    FString ViewId;
    FString Region;
    constexpr EAccelBytePlatformMapping PlatformMapping = EAccelBytePlatformMapping::NONE;
    StoreInterface->QueryStorefront(
    UserId,
    StoreId,
    ViewId,
    Region,
    PlatformMapping,
    FOnQueryStorefrontComplete::CreateUObject(this, &ThisClass::OnQueryStoreFrontComplete));
    }
  12. QueryStoreFront() 関数からのレスポンスを処理するために、OnQueryStoreFrontComplete() 関数を実装します。この関数はレスポンスを処理し、保存されたリクエストパラメータをトリガーします。

    void UInGameStoreDisplaysSubsystem_Starter::OnQueryStoreFrontComplete(
    bool bWasSuccessful,
    const TArray<FString>& ViewIds,
    const TArray<FString>& SectionIds,
    const TArray<FUniqueOfferId>& OfferIds,
    const TArray<FString>& ItemMappingIds,
    const FString& Error)
    {
    bIsQueryStoreFrontRunning = false;

    FOnlineError OnlineError;
    OnlineError.bSucceeded = bWasSuccessful;
    OnlineError.ErrorMessage = FText::FromString(Error);

    for (const FOnQueryOrGetDisplaysComplete& Param : QueryOrGetDisplaysOnCompleteDelegates)
    {
    TArray<TSharedRef<FAccelByteModelsViewInfo>> Displays = GetDisplays();
    Param.ExecuteIfBound(Displays, OnlineError);
    }
    QueryOrGetDisplaysOnCompleteDelegates.Empty();

    for (FQueryOrGetSectionsParam& Param : QueryOrGetSectionsOnCompleteDelegates)
    {
    TArray<TSharedRef<FAccelByteModelsSectionInfo>> Sections = GetSectionsForDisplay(*Param.UserId.Get(), Param.DisplayId);
    Param.OnComplete.ExecuteIfBound(Sections, OnlineError);
    }
    QueryOrGetSectionsOnCompleteDelegates.Empty();
    }
  13. QueryOrGetOffersInSection() 関数からのレスポンスを処理するために、OnQueryOffersComplete() 関数を実装します。

    void UInGameStoreDisplaysSubsystem_Starter::OnQueryOffersComplete(
    bool bWasSuccessful,
    const TArray<FUniqueOfferId>& OfferIds,
    const FString& Error)
    {
    bIsQueryOffersRunning = false;

    FOnlineError OnlineError;
    OnlineError.bSucceeded = bWasSuccessful;
    OnlineError.ErrorMessage = FText::FromString(Error);

    for (FQueryOrGetOffersParam& Param : QueryOrGetOffersOnCompleteDelegates)
    {
    TArray<UStoreItemDataObject*> Offers = GetOffersForSection(*Param.UserId.Get(), Param.SectionId);
    Param.OnComplete.ExecuteIfBound(Offers, OnlineError);
    }
    QueryOrGetOffersOnCompleteDelegates.Empty();
    }

リソース