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

OSSとSDKを使用してチャレンジを取得し報酬を請求する - チャレンジ - (Unreal Engineモジュール)

Last updated on February 4, 2026

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

サブシステムの展開

このチュートリアルでは、AccelByte Gaming Services (AGS) Online Subsystem (OSS) とSoftware Development Kit (SDK) を使用してチャレンジを取得し、報酬を請求する方法を学びます。

Byte Warsプロジェクトでは、ChallengeEssentialsSubsystemという名前のGame Instance Subsystemがすでに作成されています。このサブシステムには、チャレンジに関連する基本的な機能が含まれています。このサブシステムのスターターバージョンを使用して、チャレンジ機能をゼロから実装します。

スターターパックの内容

このチュートリアルに従うために、ChallengeEssentialsSubsystem_Starterという名前のスターターサブシステムクラスが用意されています。このクラスはリソースセクションで入手でき、以下のファイルで構成されています:

  • ヘッダーファイル: /Source/AccelByteWars/TutorialModules/Engagement/ChallengeEssentials/ChallengeEssentialsSubsystem_Starter.h
  • CPPファイル: /Source/AccelByteWars/TutorialModules/Engagement/ChallengeEssentials/ChallengeEssentialsSubsystem_Starter.cpp

ChallengeEssentialsSubsystem_Starterクラスは、いくつかの主要な機能を提供します:

  • AGS SDKチャレンジAPIインターフェースを取得する関数。この関数を使用してチャレンジ関連のリクエストを送信します。

    AccelByte::Api::ChallengePtr UChallengeEssentialsSubsystem_Starter::GetChallengeApi() const
    {
    AccelByte::FApiClientPtr ApiClient = UTutorialModuleOnlineUtility::GetApiClient(this);
    if (!ApiClient)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("AccelByte API Client is invalid."));
    return nullptr;
    }

    return ApiClient->GetChallengeApi().Pin();
    }
  • AGS OSSストアインターフェースを取得する関数。この関数を使用してチャレンジ報酬アイテム情報を取得します。

    FOnlineStoreV2AccelBytePtr UChallengeEssentialsSubsystem_Starter::GetStoreInterface() const
    {
    const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
    if (!ensure(Subsystem))
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and DefaultPlatformService under [OnlineSubsystem] in the Engine.ini set to AccelByte."));
    return nullptr;
    }

    return StaticCastSharedPtr<FOnlineStoreV2AccelByte>(Subsystem->GetStoreV2Interface());
    }

サブシステムクラスに加えて、ChallengeEssentialsModels.hという名前のファイルがあり、ヘルパークラス、デリゲート、定数が含まれています。このファイルは以下の場所にあります:

/Source/AccelByteWars/TutorialModules/Engagement/ChallengeEssentials/ChallengeEssentialsModels.h

このファイルには以下のヘルパーが含まれています:

  • チャレンジゴール情報を保存するためのChallengeGoalDataという名前のヘルパークラス。このクラスをUIコンポーネントと共に使用して、チャレンジゴールのリストを表示します。

    UCLASS()
    class ACCELBYTEWARS_API UChallengeGoalData : public UObject
    {
    GENERATED_BODY()

    public:
    FAccelByteModelsChallengeGoal Goal;
    FAccelByteModelsChallengeGoalProgress Progress;
    TArray<FChallengeGoalRewardData> Rewards{};
    FString EndDateTime = TEXT("");

    FString GetEndTimeDuration()
    {
    FDateTime ParsedEndDateTime{};
    if (!FDateTime::ParseIso8601(*EndDateTime, ParsedEndDateTime))
    {
    return TEXT("");
    }

    /* Return duration in "dd hh mm" format.
    * If duration is less than a minute, return "< 1m" instead. */
    const FTimespan Duration = ParsedEndDateTime - FDateTime::UtcNow();
    FString DurationStr{};
    if (Duration.GetDays() > 0)
    {
    DurationStr += FString::Printf(TEXT("%dd "), Duration.GetDays());
    }
    if (Duration.GetHours() > 0)
    {
    DurationStr += FString::Printf(TEXT("%dh "), Duration.GetHours());
    }
    if (Duration.GetMinutes() > 0)
    {
    DurationStr += FString::Printf(TEXT("%dm"), Duration.GetMinutes());
    }
    else
    {
    DurationStr = TEXT("< 1m");
    }

    return DurationStr;
    }
    };
  • チャレンジ報酬情報を保存するためのChallengeGoalRewardDataという名前のヘルパークラス。このクラスをUIコンポーネントと共に使用して、報酬のリストを表示します。

    USTRUCT()
    struct ACCELBYTEWARS_API FChallengeGoalRewardData
    {
    GENERATED_BODY()

    FString Sku = TEXT("");
    FString Name = TEXT("");
    FString IconUrl = TEXT("");
    int32 Quantity = 0;
    };
  • リクエストコールバックを処理するためのデリゲート。

    DECLARE_DELEGATE_TwoParams(FOnEvaluateChallengeProgressComplete, bool bWasSuccessful, const FString& ErrorMessage);
    DECLARE_DELEGATE_ThreeParams(FOnGetChallengeCodeComplete, bool bWasSuccessful, const FString& ErrorMessage, const FAccelByteModelsChallenge& Result);
    DECLARE_DELEGATE_ThreeParams(FOnGetChallengeGoalsComplete, bool bWasSuccessful, const FString& ErrorMessage, const TArray<UChallengeGoalData*>& Result);
    DECLARE_DELEGATE_TwoParams(FOnQueryRewardItemsBySkusRecursivelyComplete, bool bWasSuccessful, const FString& ErrorMessage);
    DECLARE_DELEGATE_ThreeParams(FOnQueryRewardItemsInformationComplete, bool bWasSuccessful, const FString& ErrorMessage, const TArray<UChallengeGoalData*>& Result);
    DECLARE_DELEGATE_TwoParams(FOnClaimChallengeGoalRewardsComplete, bool bWasSuccessful, const FString& ErrorMessage);
  • ログとメッセージ表示のための定数。

    #define ACCELBYTEWARS_LOCTEXT_NAMESPACE "AccelByteWars"
    #define INVALID_CHALLENGE_INTERFACE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Invalid Challenge Interface", "Invalid Challenge Interface")
    #define EMPTY_CHALLENGE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Empty Challenge", "No Challenge Found")
    #define EMPTY_CLAIMABLE_CHALLENGE_REWARD_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Empty Claimable Challenge Reward", "No Claimable Challenge Reward Found")
    #define CLAIMED_CHALLENGE_REWARD_LABEL NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Claimed Challenge Reward Label", "Claimed")
    #define CLAIMING_CHALLENGE_REWARD_LABEL NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Claiming Challenge Reward Label", "Claiming")
    #define CLAIMABLE_CHALLENGE_REWARD_LABEL NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Claimable Challenge Reward Label", "Claim")
    #define ALLTIME_CHALLENGE_TITLE_LABEL NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "All Time Challenges", "All Time Challenges")
    #define PERIODIC_CHALLENGE_TITLE_LABEL NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "{0} Challenges", "{0} Challenges")
    #define CLAIMING_ALL_CHALLENGE_REWARDS_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Claim All Challenge Rewards", "Claiming All Rewards")

期間別にチャレンジを取得する

以前、Admin Portalでチャレンジとそのローテーション/期間を設定しました。このセクションでは、選択した期間に基づいてチャレンジ情報を取得する機能を実装する方法を学びます。

  1. ChallengeEssentialsSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します:

    public:
    void GetChallengeByPeriod(
    const EAccelByteModelsChallengeRotation Period,
    const FOnGetChallengeCodeComplete& OnComplete = FOnGetChallengeCodeComplete());
  2. 次に、ChallengeEssentialsSubsystem_StarterクラスのCPPファイルを開き、上記の関数を定義します。この関数は、利用可能なすべてのチャレンジを取得するリクエストを送信し、指定された期間に一致するものをフィルタリングします。

    void UChallengeEssentialsSubsystem_Starter::GetChallengeByPeriod(
    const EAccelByteModelsChallengeRotation Period,
    const FOnGetChallengeCodeComplete& OnComplete)
    {
    const AccelByte::Api::ChallengePtr ChallengeApi = GetChallengeApi();
    if (!ChallengeApi)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge. Challenge API Client is invalid."));
    OnComplete.ExecuteIfBound(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString(), FAccelByteModelsChallenge{});
    return;
    }

    // Get the list of challenges and return the one that matches with the requested period.
    ChallengeApi->GetChallenges(
    AccelByte::THandler<FAccelByteModelsGetChallengesResponse>::CreateWeakLambda(this, [Period, OnComplete](const FAccelByteModelsGetChallengesResponse& Result)
    {
    for (const FAccelByteModelsChallenge& Challenge : Result.Data)
    {
    // Skip inactive challenge.
    if (Challenge.Status == EAccelByteModelsChallengeStatus::RETIRED)
    {
    continue;
    }

    // Challenge codes in Byte Wars use the <engine>-<period> format, e.g., unreal-alltime or unreal-weekly.
    if (Challenge.Code.Contains("unreal") && Challenge.Rotation == Period)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to get challenge with %s period. Challenge code: %s"), *FAccelByteUtilities::GetUEnumValueAsString(Period), *Challenge.Code);
    OnComplete.ExecuteIfBound(true, TEXT(""), Challenge);
    return;
    }
    }

    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge. No challenge found with %s period."), *FAccelByteUtilities::GetUEnumValueAsString(Period));
    OnComplete.ExecuteIfBound(false, EMPTY_CHALLENGE_MESSAGE.ToString(), FAccelByteModelsChallenge{});
    }),
    AccelByte::FErrorHandler::CreateWeakLambda(this, [OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge. Error %d: %s"), ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, ErrorMessage, FAccelByteModelsChallenge{});
    }),
    EAccelByteModelsChallengeSortBy::UPDATED_AT_DESC,
    EAccelByteModelsChallengeStatus::NONE);
    }
  3. 上記の関数によって返される結果には、チャレンジ名やチャレンジコードなどのチャレンジ情報が含まれます。

チャレンジゴールを取得する

このセクションでは、チャレンジゴール、その進捗状況、および関連する報酬を取得する方法を学びます。

  1. ChallengeEssentialsSubsystem_Starterクラスのヘッダーファイルを開き、チャレンジゴールのリストを取得するための以下の関数を宣言します:

    public:
    // ...
    void GetChallengeGoalList(
    const FUniqueNetIdPtr UserId,
    const FAccelByteModelsChallenge& Challenge,
    const FOnGetChallengeGoalsComplete& OnComplete = FOnGetChallengeGoalsComplete());
    private:
    void OnGetChallengeGoalListComplete(
    bool bIsSucceeded,
    const FString& ErrorMessage,
    const TArray<UChallengeGoalData*> Goals,
    const FOnGetChallengeGoalsComplete OnComplete);
  2. 同じファイルで、報酬アイテム情報をクエリするための以下の関数を宣言します:

    private:
    // ...
    void QueryRewardItemsInformation(
    const FUniqueNetIdPtr UserId,
    const TArray<UChallengeGoalData*> Goals,
    const FOnQueryRewardItemsInformationComplete& OnComplete);
    private:
    // ...
    void QueryRewardItemsBySkusRecursively(
    const FUniqueNetIdPtr UserId,
    TArray<FString> ItemSkusToQuery,
    const FOnQueryRewardItemsBySkusRecursivelyComplete& OnComplete);
    private:
    // ...
    void OnQueryRewardItemsInformationComplete(
    bool bWasSuccessful,
    const FString& Error,
    const TArray<UChallengeGoalData*> Goals,
    const FOnQueryRewardItemsInformationComplete OnComplete);
  3. 次に、チャレンジ評価リクエストを送信するための以下の関数を宣言します:

    public:
    // ...
    void EvaluateChallengeProgress(
    const FAccelByteModelsChallengeEvaluateProgressOptionalParameter& Param,
    const FOnEvaluateChallengeProgressComplete& OnComplete = FOnEvaluateChallengeProgressComplete()) const;
  4. 次に、ChallengeEssentialsSubsystem_StarterクラスのCPPファイルを開き、上記の関数を定義します。この関数は、プレイヤーのチャレンジ進捗を評価するリクエストを送信します。評価リクエストは、後でチャレンジゴールリストを取得する際に正しい進捗データが含まれるようにするために必要です。

    void UChallengeEssentialsSubsystem_Starter::EvaluateChallengeProgress(
    const FAccelByteModelsChallengeEvaluateProgressOptionalParameter& Param,
    const FOnEvaluateChallengeProgressComplete& OnComplete) const
    {
    const AccelByte::Api::ChallengePtr ChallengeApi = GetChallengeApi();
    if (!ChallengeApi)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to evaluate challenge progress. Challenge API Client is invalid."));
    OnComplete.ExecuteIfBound(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString());
    return;
    }

    ChallengeApi->EvaluateChallengeProgress(
    Param,
    AccelByte::FVoidHandler::CreateWeakLambda(this, [this, OnComplete]()
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to evaluate challenge progress."));
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }),
    AccelByte::FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to evaluate challenge progress. Error %d: %s"), ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, ErrorMessage);
    }));
    }
  5. 次に、ChallengeEssentialsSubsystem_StarterクラスのCPPファイルを開きます。まず、GetChallengeGoalList()関数を定義します。この関数は、チャレンジコードによってプレイヤーのチャレンジ進捗を評価するリクエストを送信し、ゴールの進捗を取得してから、QueryRewardItemsInformation()を呼び出して報酬アイテムの詳細を取得します。最後に、OnGetChallengeGoalListComplete()を呼び出して結果を返します。

    void UChallengeEssentialsSubsystem_Starter::GetChallengeGoalList(
    const FUniqueNetIdPtr UserId,
    const FAccelByteModelsChallenge& Challenge,
    const FOnGetChallengeGoalsComplete& OnComplete)
    {
    if (!UserId)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge goal list. User ID is invalid."));
    OnGetChallengeGoalListComplete(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString(), {}, OnComplete);
    return;
    }

    const AccelByte::Api::ChallengePtr ChallengeApi = GetChallengeApi();
    if (!ChallengeApi)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge goal list. Challenge API Client is invalid."));
    OnGetChallengeGoalListComplete(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString(), {}, OnComplete);
    return;
    }

    // Request to evaluate to update challenge goals progresses.
    FAccelByteModelsChallengeEvaluateProgressOptionalParameter Param
    {
    TArray<FString>{ Challenge.Code }
    };
    EvaluateChallengeProgress(
    Param,
    FOnEvaluateChallengeProgressComplete::CreateWeakLambda(this, [this, ChallengeApi, Challenge, UserId, OnComplete](bool bWasSuccessful, const FString& ErrorMessage)
    {
    if (!bWasSuccessful)
    {
    OnGetChallengeGoalListComplete(false, ErrorMessage, {}, OnComplete);
    return;
    }

    // Get the goal list and their progress.
    ChallengeApi->GetChallengeProgress(
    Challenge.Code,
    AccelByte::THandler<FAccelByteModelsChallengeProgressResponse>::CreateWeakLambda(this, [this, Challenge, UserId, OnComplete](const FAccelByteModelsChallengeProgressResponse& Result)
    {
    // Construct new goal object and add it to the list.
    TArray<UChallengeGoalData*> GoalDataList{};
    for (const FAccelByteModelsChallengeGoalProgress& Progress : Result.Data)
    {
    if (UChallengeGoalData* GoalData = NewObject<UChallengeGoalData>())
    {
    GoalData->Goal = Progress.Goal;
    GoalData->Progress = Progress;
    GoalData->EndDateTime = (Challenge.Rotation == EAccelByteModelsChallengeRotation::NONE) ? TEXT("") : Result.Meta.Period.EndTime.ToIso8601();
    GoalDataList.Add(GoalData);
    }
    }

    // Query reward item information for all goals.
    QueryRewardItemsInformation(
    UserId,
    GoalDataList,
    FOnQueryRewardItemsInformationComplete::CreateWeakLambda(this, [this, OnComplete](bool bWasSuccessful, const FString& ErrorMessage, const TArray<UChallengeGoalData*>& Result)
    {
    // Operation is complete, return the result.
    OnGetChallengeGoalListComplete(bWasSuccessful, ErrorMessage, Result, OnComplete);
    }));
    }),
    AccelByte::FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    OnGetChallengeGoalListComplete(false, ErrorMessage, {}, OnComplete);
    }));
    }));
    }
  6. QueryRewardItemsInformation()関数を定義します。この関数は、報酬アイテム情報がすでにOSSにキャッシュされているかどうかを確認します。キャッシュされていない場合は、QueryRewardItemsBySkusRecursively()関数を使用して報酬アイテムの詳細を取得するリクエストを送信します。その後、レスポンスはOnQueryRewardItemsInformationComplete()関数によって処理されます。

    void UChallengeEssentialsSubsystem_Starter::QueryRewardItemsInformation(
    const FUniqueNetIdPtr UserId,
    const TArray<UChallengeGoalData*> Goals,
    const FOnQueryRewardItemsInformationComplete& OnComplete)
    {
    if (!UserId)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items info. User ID is invalid."));
    OnQueryRewardItemsInformationComplete(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString(), Goals, OnComplete);
    return;
    }

    const FOnlineStoreV2AccelBytePtr StoreInterface = GetStoreInterface();
    if (!StoreInterface)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items info. Store interface is invalid."));
    OnQueryRewardItemsInformationComplete(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString(), Goals, OnComplete);
    return;
    }

    // Collect reward item SKUs to query.
    TArray<FString> RewardItemSkusToQuery{};
    for (const UChallengeGoalData* GoalData : Goals)
    {
    if (!GoalData) continue;
    for (const FAccelByteModelsChallengeGoalReward& Reward : GoalData->Goal.Rewards)
    {
    // The reward item ID from backend response is actually the item's SKU.
    RewardItemSkusToQuery.AddUnique(Reward.ItemId);
    }
    };

    // Check if the reward items information is already cached.
    TArray<FString> CachedOfferIds{};
    TArray<FOnlineStoreOfferAccelByteRef> CachedOffers{};
    StoreInterface->GetOffers(CachedOffers);
    Algo::Transform(CachedOffers, CachedOfferIds, [](const FOnlineStoreOfferAccelByteRef Item) { return Item->Sku; });
    RewardItemSkusToQuery.RemoveAll([&CachedOfferIds](const FString& Item) { return CachedOfferIds.Contains(Item); });

    // Return success if all reward items are already cached.
    if (RewardItemSkusToQuery.IsEmpty())
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to query reward items info. All infos are already cached."));
    OnQueryRewardItemsInformationComplete(true, TEXT(""), Goals, OnComplete);
    return;
    }

    // Query reward items information by SKUs recursively.
    QueryRewardItemsBySkusRecursively(
    UserId,
    RewardItemSkusToQuery,
    FOnQueryRewardItemsBySkusRecursivelyComplete::CreateUObject(this, &ThisClass::OnQueryRewardItemsInformationComplete, Goals, OnComplete));
    }
  7. 次に、QueryRewardItemsBySkusRecursively()関数を定義します。この関数は、SKUごとにアイテム情報をクエリするリクエストを1つずつ送信します。

    void UChallengeEssentialsSubsystem_Starter::QueryRewardItemsBySkusRecursively(
    const FUniqueNetIdPtr UserId,
    TArray<FString> ItemSkusToQuery,
    const FOnQueryRewardItemsBySkusRecursivelyComplete& OnComplete)
    {
    // All item is queried, the operation is completed.
    if (ItemSkusToQuery.IsEmpty())
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to query reward items info by SKUs."));
    OnComplete.ExecuteIfBound(true, TEXT(""));
    return;
    }

    if (!UserId)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items info by SKUs. User ID is invalid."));
    OnComplete.ExecuteIfBound(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString());
    return;
    }

    const FOnlineStoreV2AccelBytePtr StoreInterface = GetStoreInterface();
    if (!StoreInterface)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items info by SKUs. Store interface is invalid."));
    OnComplete.ExecuteIfBound(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString());
    return;
    }

    const FString CurrentSku = ItemSkusToQuery[0];
    ItemSkusToQuery.RemoveAt(0);

    StoreInterface->QueryOfferBySku(
    UserId.ToSharedRef().Get(),
    CurrentSku,
    FOnQueryOnlineStoreOffersComplete::CreateWeakLambda(this, [this, UserId, ItemSkusToQuery, OnComplete]
    (bool bWasSuccessful, const TArray<FUniqueOfferId>& OfferIds, const FString& Error)
    {
    // Abort if failed to query item by SKU.
    if (!bWasSuccessful)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items by SKUs. Error: %s"), *Error);
    OnComplete.ExecuteIfBound(false, Error);
    return;
    }

    // Recurse to next item SKU
    QueryRewardItemsBySkusRecursively(UserId, ItemSkusToQuery, OnComplete);
    }));
    }
  8. 次に、OnQueryRewardItemsInformationComplete()関数を定義します。この関数は、SKU、名前、アイコン、数量などの報酬情報を収集し、割り当てられたデリゲートに結果を渡します。

    void UChallengeEssentialsSubsystem_Starter::OnQueryRewardItemsInformationComplete(
    bool bWasSuccessful,
    const FString& Error,
    const TArray<UChallengeGoalData*> Goals,
    const FOnQueryRewardItemsInformationComplete OnComplete)
    {
    if (!bWasSuccessful)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to query reward items info. Error: %s"), *Error);
    OnComplete.ExecuteIfBound(false, Error, {});
    return;
    }

    // Construct goal reward data based on queried information.
    for (UChallengeGoalData* GoalData : Goals)
    {
    if (!GoalData) continue;
    for (const FAccelByteModelsChallengeGoalReward& Reward : GoalData->Goal.Rewards)
    {
    // Get item offer from cache by using the item SKU (the reward item ID is the item's SKU).
    TSharedPtr<FOnlineStoreOfferAccelByte> Offer = GetStoreInterface()->GetOfferBySkuAccelByte(Reward.ItemId);

    FChallengeGoalRewardData RewardData;
    RewardData.Sku = Reward.ItemId;
    RewardData.Name = Reward.ItemName;
    RewardData.Quantity = (int32)Reward.Qty;

    FString IconKey = TEXT("IconUrl");
    if (Offer && Offer->DynamicFields.Contains(IconKey))
    {
    RewardData.IconUrl = *Offer->DynamicFields.Find(IconKey);
    }

    GoalData->Rewards.Add(RewardData);
    }
    }

    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to query reward items info."));
    OnComplete.ExecuteIfBound(true, TEXT(""), Goals);
    }
  9. 最後に、OnGetChallengeGoalListComplete()関数を定義します。これはゴール取得プロセスの最後のステップです。完了状況と請求された報酬に基づいてゴールリストをソートし、割り当てられたデリゲートを使用して結果を返します。

    void UChallengeEssentialsSubsystem_Starter::OnGetChallengeGoalListComplete(
    bool bIsSucceeded,
    const FString& ErrorMessage,
    const TArray<UChallengeGoalData*> Goals,
    const FOnGetChallengeGoalsComplete OnComplete)
    {
    if (!bIsSucceeded)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to get challenge goal list. Error: %s"), *ErrorMessage);
    OnComplete.ExecuteIfBound(false, ErrorMessage, {});
    return;
    }

    // Sort the result based on the unclaimed rewards and completion status.
    TArray<UChallengeGoalData*> Result = Goals;
    Result.Sort([](UChallengeGoalData& Goal1, UChallengeGoalData& Goal2)
    {
    const EAccelByteModelsChallengeGoalProgressStatus CompleteStatus = EAccelByteModelsChallengeGoalProgressStatus::COMPLETED;
    const bool bIsCompleted = (Goal1.Progress.Status == CompleteStatus) > (Goal2.Progress.Status == CompleteStatus);
    const bool bIsClaimed = Goal1.Progress.ToClaimRewards.IsEmpty() < Goal2.Progress.ToClaimRewards.IsEmpty();
    return bIsClaimed || bIsCompleted;
    });

    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to get challenge goal list."));
    OnComplete.ExecuteIfBound(true, ErrorMessage, Result);
    }

チャレンジ報酬を請求する

このセクションでは、チャレンジ報酬を請求する方法を学びます。

  1. ChallengeEssentialsSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します:

    public:
    // ...
    void ClaimChallengeGoalRewards(
    const TArray<FString>& RewardIDs,
    const FOnClaimChallengeGoalRewardsComplete& OnComplete = FOnClaimChallengeGoalRewardsComplete());
  2. 次に、ChallengeEssentialsSubsystem_StarterクラスのCPPファイルを開き、上記の関数を定義します。この関数は、報酬IDのリストを渡して報酬を請求するリクエストを送信します。その後、割り当てられたデリゲートを通じて結果を返します。

    void UChallengeEssentialsSubsystem_Starter::ClaimChallengeGoalRewards(
    const TArray<FString>& RewardIDs,
    const FOnClaimChallengeGoalRewardsComplete& OnComplete)
    {
    const AccelByte::Api::ChallengePtr ChallengeApi = GetChallengeApi();
    if (!ChallengeApi)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to claim challenge goal reward. Challenge API Client is invalid."));
    OnComplete.ExecuteIfBound(false, INVALID_CHALLENGE_INTERFACE_MESSAGE.ToString());
    return;
    }

    ChallengeApi->ClaimReward(
    FAccelByteModelsChallengeRewardClaimRequest{ RewardIDs },
    AccelByte::THandler<TArray<FAccelByteModelsChallengeReward>>::CreateWeakLambda(this, [OnComplete](const TArray<FAccelByteModelsChallengeReward>& Result)
    {
    // Abort if there is no claimable rewards.
    if (Result.IsEmpty())
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to claim challenge rewards. No claimable reward found."));
    OnComplete.ExecuteIfBound(false, EMPTY_CLAIMABLE_CHALLENGE_REWARD_MESSAGE.ToString());
    return;
    }

    UE_LOG_CHALLENGE_ESSENTIALS(Log, TEXT("Success to claim challenge rewards."));
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }),
    AccelByte::FErrorHandler::CreateWeakLambda(this, [OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_CHALLENGE_ESSENTIALS(Warning, TEXT("Failed to claim challenge rewards. Error %d: %s"), ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, ErrorMessage);
    }));
    }

リソース