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

OSS を使用して定期的なリーダーボードを表示する - 毎週のリーダーボード - (Unreal Engine モジュール)

Last updated on February 4, 2026

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

サブシステムの展開

このチュートリアルでは、AccelByte Gaming Services (AGS) Online Subsystem (OSS) を使用して定期的なリーダーボードを取得する方法を学びます。Byte Wars プロジェクトには、PeriodicBoardSubsystem クラスという名前の Game Instance Subsystem がすでに作成されています。このサブシステムには、定期的なリーダーボード関連の機能が含まれています。このサブシステムのスターターバージョンを使用して、定期的なリーダーボード機能をゼロから実装します。

スターターパックの内容

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

  • Header file: /Source/AccelByteWars/TutorialModules/Engagement/PeriodicLeaderboard/PeriodicBoardSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Engagement/PeriodicLeaderboard/PeriodicBoardSubsystem_Starter.cpp

PeriodicBoardSubsystem_Starter クラスには、いくつかの機能が提供されています。

  • LeaderboardInterfaceUserInterface という名前の AGS OSS インターフェース宣言。これらのインターフェースを使用して、後でリーダーボード関連の機能を実装します。

    protected:
    // ...
    FOnlineUserAccelBytePtr UserInterface;
    FOnlineLeaderboardAccelBytePtr LeaderboardInterface;
  • PlayerController から UniqueNetId を取得するヘルパー関数。上記の AGS OSS インターフェースを使用するには、このヘルパーが必要です。

    FUniqueNetIdPtr UPeriodicBoardSubsystem_Starter::GetUniqueNetIdFromPlayerController(const APlayerController* PC) const
    {
    if (!ensure(PC))
    {
    return nullptr;
    }

    ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (!ensure(LocalPlayer))
    {
    return nullptr;
    }

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }
  • PlayerController から LocalUserNum を取得するヘルパー関数。AGS OSS インターフェースを使用するには、このヘルパーも必要です。

    int32 UPeriodicBoardSubsystem_Starter::GetLocalUserNumFromPlayerController(const APlayerController* PC) const
    {
    if (!PC)
    {
    return INDEX_NONE;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (!LocalPlayer)
    {
    return INDEX_NONE;
    }

    return LocalPlayer->GetControllerId();
    }

全期間リーダーボードと同じ定数、デリゲート、その他のヘルパーを使用します。これらは /Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardEssentialsModels.h ファイルに格納されています。そのファイルには、以下のヘルパーがあります:

  • 個々のプレイヤーのリーダーボード情報(表示名、ランク、スコアなど)を含む LeaderboardRank という名前のヘルパークラス。後でリーダーボードエントリを表示するために必要です。

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

    public:
    FUniqueNetIdRepl UserId;
    int32 Rank;
    FString DisplayName;
    float Score;

    void Init(const FUniqueNetIdRepl InUserId, const int32 InRank, const FString InDisplayName, const float InScore)
    {
    UserId = InUserId;
    Rank = InRank;
    DisplayName = InDisplayName;
    Score = InScore;
    }
    };
  • リーダーボードデータ取得プロセスが完了したときのコールバックとして使用できるデリゲート。

    DECLARE_DELEGATE_TwoParams(FOnGetLeaderboardRankingComplete, bool /*bWasSuccessful*/, const TArray<ULeaderboardRank*> /*Rankings*/);
  • リーダーボードサイクル ID 取得プロセスが完了したときのコールバックとして使用できるデリゲート。

    // ...
    DECLARE_DELEGATE_TwoParams(FOnGetLeaderboardsCycleIdComplete, bool /*bWasSuccesful*/, const FString& CycleId);

定期的なリーダーボードランキングの取得を実装する

このセクションでは、定期的なリーダーボードランキングを取得する機能を実装します。

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

    public:
    // ...
    void GetPeriodicRankings(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const int32 ResultLimit, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete());
  2. 定期的なリーダーボードランキング取得プロセスが完了したときに処理するコールバック関数を宣言します。

    protected:
    // ...
    void OnGetPeriodicRankingsComplete(bool bWasSuccessful, const int32 LocalUserNum, const FOnlineLeaderboardReadRef LeaderboardObj, const FOnGetLeaderboardRankingComplete OnComplete);
  3. リーダーボードリストからユーザー情報をクエリするコールバック関数を宣言します。

    protected:
    // ...
    void OnQueryUserInfoComplete(
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo,
    const int32 LocalUserNum,
    const FOnlineLeaderboardReadRef LeaderboardObj,
    const FOnGetLeaderboardRankingComplete OnComplete);
  4. 上記の関数を定義します。PeriodicBoardSubsystem_Starter クラスの CPP ファイルを開き、まず GetPeriodicRankings() 関数を定義します。この関数は、定義された範囲と定期サイクル内のリーダーボードランキングを取得するリクエストを送信します。完了すると、OnGetPeriodicRankingsComplete() 関数を呼び出してコールバックを処理します。

    void UPeriodicBoardSubsystem_Starter::GetPeriodicRankings(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const int32 ResultLimit, const FOnGetLeaderboardRankingComplete& OnComplete)
    {
    if (!ensure(LeaderboardInterface.IsValid()) || !ensure(UserInterface.IsValid()))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get periodic leaderboard rankings. Leaderboard Interface or User Interface is not valid."));
    return;
    }

    if (!ensure(PC))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get periodic leaderboard rankings. PlayerController is null."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    FOnlineLeaderboardReadRef LeaderboardObj = MakeShared<FOnlineLeaderboardRead, ESPMode::ThreadSafe>();

    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    LeaderboardObj->LeaderboardName = LeaderboardCode;
    #else
    LeaderboardObj->LeaderboardName = FName(LeaderboardCode);
    #endif

    // Get the periodic leaderboard within the range of 0 to ResultLimit.
    OnLeaderboardReadCompleteDelegateHandle = LeaderboardInterface->AddOnLeaderboardReadCompleteDelegate_Handle(FOnLeaderboardReadCompleteDelegate::CreateUObject(this, &ThisClass::OnGetPeriodicRankingsComplete, LocalUserNum, LeaderboardObj, OnComplete));
    LeaderboardInterface->ReadLeaderboardCycleAroundRank(0, ResultLimit, CycleId, LeaderboardObj);
    }
  5. OnGetPeriodicRankingsComplete()OnQueryUserInfoComplete() 関数を定義します。定期的なリーダーボードランキング取得リクエストが完了すると、リーダーボードメンバーのユーザー ID とそのスコアポイントのリストが返されます。各メンバーのユーザー情報(表示名など)を取得するには、それらをクエリする必要があります。ユーザー情報がクエリされると、この関数は定期的なリーダーボードランキングのリストを割り当てられたコールバックに返します。

    void UPeriodicBoardSubsystem_Starter::OnGetPeriodicRankingsComplete(
    bool bWasSuccessful,
    const int32 LocalUserNum,
    const FOnlineLeaderboardReadRef LeaderboardObj,
    const FOnGetLeaderboardRankingComplete OnComplete)
    {
    ensure(UserInterface);
    ensure(LeaderboardInterface);

    LeaderboardInterface->ClearOnLeaderboardReadCompleteDelegate_Handle(OnLeaderboardReadCompleteDelegateHandle);

    if (!bWasSuccessful)
    {
    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Failed to get periodic leaderboard rankings with code: %s"), *LeaderboardObj->LeaderboardName);
    #else
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Failed to get periodic leaderboard rankings with code: %s"), *LeaderboardObj->LeaderboardName.ToString());
    #endif
    OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
    return;
    }

    // Collect periodic leaderboard members' player id.
    TPartyMemberArray LeaderboardMembers;
    for (const FOnlineStatsRow& Row : LeaderboardObj->Rows)
    {
    if (Row.PlayerId.IsValid())
    {
    LeaderboardMembers.Add(Row.PlayerId->AsShared());
    }
    }

    // Query periodic leaderboard members' user information.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    LocalUserNum,
    LeaderboardMembers,
    FOnQueryUsersInfoCompleteDelegate::CreateUObject(this, &ThisClass::OnQueryUserInfoComplete, LocalUserNum, LeaderboardObj, OnComplete));
    }
    else
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Startup subsystem is invalid"));
    OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
    }
    }
    void UPeriodicBoardSubsystem_Starter::OnQueryUserInfoComplete(
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo,
    const int32 LocalUserNum,
    const FOnlineLeaderboardReadRef LeaderboardObj,
    const FOnGetLeaderboardRankingComplete OnComplete)
    {
    if (!ensure(UserInterface))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get periodic leaderboard. User Interface is not valid."));
    return;
    }

    if (!Error.bSucceeded)
    {
    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    UE_LOG_PERIODIC_LEADERBOARD(
    Warning,
    TEXT("Failed to get periodic leaderboard with code: %s. Error: %s"),
    *LeaderboardObj->LeaderboardName, *Error.ErrorMessage.ToString());
    #else
    UE_LOG_PERIODIC_LEADERBOARD(
    Warning,
    TEXT("Failed to get periodic leaderboard with code: %s. Error: %s"),
    *LeaderboardObj->LeaderboardName.ToString(), *Error.ErrorMessage.ToString());
    #endif
    OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
    return;
    }

    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    UE_LOG_PERIODIC_LEADERBOARD(
    Warning,
    TEXT("Success in getting the periodic leaderboard rankings with code: %s"),
    *LeaderboardObj->LeaderboardName);
    #else
    UE_LOG_PERIODIC_LEADERBOARD(
    Warning,
    TEXT("Success in getting the periodic leaderboard rankings with code: %s"),
    *LeaderboardObj->LeaderboardName.ToString());
    #endif

    // Return periodic leaderboard information along with its members' user info.
    TArray<ULeaderboardRank*> Rankings;
    for (const FOnlineStatsRow& Row : LeaderboardObj->Rows)
    {
    if (!Row.PlayerId.IsValid())
    {
    continue;
    }

    // Get the member's display name.
    const TSharedPtr<FOnlineUser> LeaderboardMember = UserInterface->GetUserInfo(
    LocalUserNum, Row.PlayerId->AsShared().Get());
    const FString DisplayName = !LeaderboardMember->GetDisplayName().IsEmpty() ?
    LeaderboardMember->GetDisplayName() :
    FText::Format(DEFAULT_LEADERBOARD_DISPLAY_NAME, FText::FromString(Row.NickName.Left(5))).ToString();

    // Get the member's stat value.
    float Score = 0;
    if (Row.Columns.Contains(TEXT("Cycle_Point")))
    {
    // The stat key is "Cycle_Point" if it was retrieved from FOnlineLeaderboardAccelByte::ReadLeaderboardCycleAroundRank().
    Row.Columns[TEXT("Cycle_Point")].GetValue(Score);
    }
    else if (Row.Columns.Contains(TEXT("Point")))
    {
    // The stat key is "Point" if it was retrieved from FOnlineLeaderboardAccelByte::ReadLeaderboardsCycle()
    Row.Columns[TEXT("Point")].GetValue(Score);
    }

    // Add a new ranking object.
    ULeaderboardRank* NewRanking = NewObject<ULeaderboardRank>();
    NewRanking->Init(Row.PlayerId, Row.Rank, DisplayName, Score);
    Rankings.Add(NewRanking);
    }

    OnComplete.ExecuteIfBound(true, Rankings);
    }
  6. 定期的なリーダーボードランキングを取得するにはサイクル ID が必要なため、名前でサイクル ID を取得する関数を作成します。PeriodicBoardSubsystem_Starter クラスのヘッダーファイルを再度開き、以下の関数を宣言します。

    public:
    // ...
    void GetLeaderboardCycleIdByName(const FString& InCycleName, const EAccelByteCycle& InCycleType, const FOnGetLeaderboardsCycleIdComplete& OnComplete = FOnGetLeaderboardsCycleIdComplete());
  7. 次に、PeriodicBoardSubsystem_Starter クラスの CPP ファイルで上記の関数を定義します。この関数はサイクル ID をクエリし、指定された名前と一致するものを返します。

    void UPeriodicBoardSubsystem_Starter::GetLeaderboardCycleIdByName(const FString& InCycleName, const EAccelByteCycle& InCycleType, const FOnGetLeaderboardsCycleIdComplete& OnComplete)
    {
    AccelByte::FApiClientPtr ApiClient = UTutorialModuleOnlineUtility::GetApiClient(this);
    if (!ApiClient)
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Failed to get Cycle ID of cycle with name %s. AccelByte API Client is invalid."), *InCycleName);
    OnComplete.ExecuteIfBound(false, FString());
    return;
    }

    AccelByte::Api::StatisticPtr StatisticApi = ApiClient->GetStatisticApi().Pin();
    if (!ApiClient)
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Failed to get Cycle ID of cycle with name %s. Statistic API Client is invalid."), *InCycleName);
    OnComplete.ExecuteIfBound(false, FString());
    return;
    }

    StatisticApi->GetListStatCycleConfigs(
    InCycleType,
    THandler<FAccelByteModelsStatCycleConfigPagingResult>::CreateWeakLambda(this, [InCycleName, OnComplete](const FAccelByteModelsStatCycleConfigPagingResult& Result)
    {
    FString FoundCycleId;
    for (auto& Cycle : Result.Data)
    {
    if (Cycle.Name.Equals(InCycleName))
    {
    FoundCycleId = Cycle.Id;
    break;
    }
    }

    if (!FoundCycleId.IsEmpty())
    {
    UE_LOG_PERIODIC_LEADERBOARD(Log, TEXT("Cycle ID of cycle with name %s is %s."), *InCycleName, *FoundCycleId);
    OnComplete.ExecuteIfBound(true, FoundCycleId);
    }
    else
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cycle ID of cycle with name %s is not found."), *InCycleName);
    OnComplete.ExecuteIfBound(false, FoundCycleId);
    }
    }),
    FErrorHandler::CreateWeakLambda(this, [InCycleName, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Failed to get Cycle ID of cycle with name %s. Error %d: %s"), *InCycleName, ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, FString());
    })
    );
    }

プレイヤーの定期的なリーダーボードランキングの取得を実装する

範囲内の定期的なリーダーボードランキングの取得を実装しました。このセクションでは、特定のプレイヤーの定期的なリーダーボードランクを取得する機能を実装します。これにより、プレイヤーが特定のリーダーボードランキング範囲に含まれていない場合に、後で表示できるようになります。

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

    public:
    // ...
    void GetPlayerPeriodicRanking(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete());
  2. PeriodicBoardSubsystem_Starter クラスの CPP ファイルを開き、上記の関数を定義します。この関数は、特定のプレイヤーの定期的なリーダーボードランクを取得するリクエストを送信します。コールバックも OnGetPeriodicRankingsComplete() 関数で処理されます。

    void UPeriodicBoardSubsystem_Starter::GetPlayerPeriodicRanking(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const FOnGetLeaderboardRankingComplete& OnComplete)
    {
    if (!ensure(LeaderboardInterface.IsValid()) || !ensure(UserInterface.IsValid()))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get player periodic leaderboard ranking. Leaderboard Interface or User Interface is not valid."));
    return;
    }

    if (!ensure(PC))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get player periodic leaderboard ranking. PlayerController is null."));
    return;
    }

    const FUniqueNetIdPtr PlayerNetId = GetUniqueNetIdFromPlayerController(PC);
    if (!ensure(PlayerNetId.IsValid()))
    {
    UE_LOG_PERIODIC_LEADERBOARD(Warning, TEXT("Cannot get player periodic leaderboard ranking. Player's UniqueNetId is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    FOnlineLeaderboardReadRef LeaderboardObj = MakeShared<FOnlineLeaderboardRead, ESPMode::ThreadSafe>();

    #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
    LeaderboardObj->LeaderboardName = LeaderboardCode;
    #else
    LeaderboardObj->LeaderboardName = FName(LeaderboardCode);
    #endif

    // Get the player's periodic leaderboard ranking.
    OnLeaderboardReadCompleteDelegateHandle = LeaderboardInterface->AddOnLeaderboardReadCompleteDelegate_Handle(FOnLeaderboardReadCompleteDelegate::CreateUObject(this, &ThisClass::OnGetPeriodicRankingsComplete, LocalUserNum, LeaderboardObj, OnComplete));
    LeaderboardInterface->ReadLeaderboardsCycle(TPartyMemberArray{ PlayerNetId->AsShared() }, LeaderboardObj, CycleId);
    }

リソース