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

OSS を使用してリーダーボードを表示する - 全期間のリーダーボード - (Unreal Engine モジュール)

Last updated on May 30, 2024

Unwrap the Subsystem

In this tutorial, you will learn how to get the leaderboard using the AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, there is already a Game Instance Subsystem created named LeaderboardSubsystem. This subsystem contains basic leaderboard-related functionality. You will use a starter version of that subsystem, so you can implement the leaderboard functionalities from scratch.

What's in the Starter Pack

To follow this tutorial, a starter subsystem class has been prepared named LeaderboardSubsystem_Starter. This class is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardSubsystem_Starter.cpp

The LeaderboardSubsystem_Starter class has several functionalities provided.

  • AGS OSS interfaces declarations named LeaderboardInterface and UserInterface. You will use these interfaces to implement leaderboard-related functionalities later.

    protected:
    FOnlineUserAccelBytePtr UserInterface;
    FOnlineLeaderboardAccelBytePtr LeaderboardInterface;
  • A helper function to get UniqueNetId from a PlayerController. You will need this helper to use the AGS OSS interfaces mentioned above.

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

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

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }
  • A helper function to get LocalUserNum from a PlayerController. You will also need this helper to use the AGS OSS interfaces.

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

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

    return LocalPlayer->GetControllerId();
    }

Besides the starter subsystem, also prepared are some constants, delegates, and other helpers in the /Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardEssentialsModels.h file. In that file, you can find the following helpers:

  • A helper class named LeaderboardRank which contains the individual player rank information such as display name, rank, and score. You will need this to display the leaderboard entries later.

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

    public:
    FUniqueNetIdRepl UserId;
    int32 Rank;
    FString DisplayName;
    float Score;
    };
  • Delegates that can be used as a callback when the get leaderboard data process completes.

    DECLARE_DELEGATE_TwoParams(FOnGetLeaderboardRankingComplete, bool /*bWasSuccessful*/, const TArray<ULeaderboardRank*> /*Rankings*/);

Get leaderboard rankings

In this section, you will implement functionality to get leaderboard rankings.

  1. Open the LeaderboardSubsystem_Starter class Header file and declare the following function:

    public:
    void GetRankings(const APlayerController* PC, const FString& LeaderboardCode, const int32 ResultLimit, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete());
  2. Declare a callback function to handle when the get leaderboard rankings process completes:

    protected:
    void OnGetRankingsComplete(bool bWasSuccessful, const int32 LocalUserNum, const FOnlineLeaderboardReadRef LeaderboardObj, const FOnGetLeaderboardRankingComplete OnComplete);
  3. Define the functions above. Open the LeaderboardSubsystem_Starter class CPP file and define the GetRankings() function first. This function will send a request to get leaderboard rankings around the defined range. In this case, you want to get the leaderboard from rank zero to a certain rank limit. Once it completes, it will call the OnGetRankingsComplete() function to handle the callback.

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

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

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    FOnlineLeaderboardReadRef LeaderboardObj = MakeShared<FOnlineLeaderboardRead, ESPMode::ThreadSafe>();
    LeaderboardObj->LeaderboardName = FName(LeaderboardCode);

    // Get the leaderboard within the range of 0 to ResultLimit.
    OnLeaderboardReadCompleteDelegateHandle = LeaderboardInterface->AddOnLeaderboardReadCompleteDelegate_Handle(FOnLeaderboardReadCompleteDelegate::CreateUObject(this, &ThisClass::OnGetRankingsComplete, LocalUserNum, LeaderboardObj, OnComplete));
    LeaderboardInterface->ReadLeaderboardsAroundRank(0, ResultLimit, LeaderboardObj);
    }
  4. Define the OnGetRankingsComplete() function. When the get leaderboard rankings request process completes, it will return a list of leaderboard members' user IDs and their score points. To get each members' user information (e.g., display name), you need to query them. Once the user information is queried, this function will return the list of leaderboard rankings to the assigned callback.

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

    LeaderboardInterface->ClearOnLeaderboardReadCompleteDelegate_Handle(OnLeaderboardReadCompleteDelegateHandle);

    if (!bWasSuccessful)
    {
    UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Failed to get leaderboard rankings with code: %s"), *LeaderboardObj->LeaderboardName.ToString());
    OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
    return;
    }

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

    // Query leaderboard members' user information.
    OnQueryUserInfoCompleteDelegateHandle = UserInterface->AddOnQueryUserInfoCompleteDelegate_Handle(
    LocalUserNum,
    FOnQueryUserInfoCompleteDelegate::CreateWeakLambda(this, [this, LeaderboardObj, OnComplete](int32 LocalUserNum, bool bWasSuccessful, const TArray<FUniqueNetIdRef>& UserIds, const FString& ErrorStr)
    {
    if (!ensure(UserInterface))
    {
    UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Cannot get leaderboard. User Interface is not valid."));
    return;
    }
    UserInterface->ClearOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, OnQueryUserInfoCompleteDelegateHandle);

    if (!bWasSuccessful)
    {
    UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Failed to get leaderboard with code: %s. Error: %s"), *LeaderboardObj->LeaderboardName.ToString(), *ErrorStr);
    OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
    return;
    }

    UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Success to get leaderboard rankings with code: %s"), *LeaderboardObj->LeaderboardName.ToString());

    // Return 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(FName("AllTime_Point")))
    {
    // The stat key is "AllTime_Point" if it was retrieved from FOnlineLeaderboardAccelByte::ReadLeaderboardsAroundRank().
    Row.Columns[FName("AllTime_Point")].GetValue(Score);
    }
    else if (Row.Columns.Contains(FName("Point")))
    {
    // The stat key is "Point" if it was retrieved from FOnlineLeaderboardAccelByte::ReadLeaderboards()
    Row.Columns[FName("Point")].GetValue(Score);
    }

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

    OnComplete.ExecuteIfBound(true, Rankings);
    }
    ));

    UserInterface->QueryUserInfo(LocalUserNum, LeaderboardMembers);
    }

Get player leaderboard ranking

You have implemented getting leaderboard rankings within some range. In this section, you will implement functionality to get the local player's leaderboard rank so you can display it later if the player is not included in a certain leaderboard rankings range.

  1. Open the LeaderboardSubsystem_Starter class Header file and declare the following function:

    public:
    void GetPlayerRanking(const APlayerController* PC, const FString& LeaderboardCode, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete());
  2. Open the LeaderboardSubsystem_Starter class CPP file and define the function above. This function will send a request to get a certain player's leaderboard rank. The callback will be handled by the OnGetRankingsComplete() function, too.

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

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

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

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    FOnlineLeaderboardReadRef LeaderboardObj = MakeShared<FOnlineLeaderboardRead, ESPMode::ThreadSafe>();
    LeaderboardObj->LeaderboardName = FName(LeaderboardCode);

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

Resources