Use the OSS to show leaderboards - All time leaderboard - (Unreal Engine module)
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
andUserInterface
. You will use these interfaces to implement leaderboard-related functionalities later.protected:
// ...
FOnlineUserAccelBytePtr UserInterface;
FOnlineLeaderboardAccelBytePtr LeaderboardInterface;A helper function to get
UniqueNetId
from aPlayerController
. 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 aPlayerController
. 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;
void Init(const FUniqueNetIdRepl InUserId, const int32 InRank, const FString InDisplayName, const float InScore)
{
UserId = InUserId;
Rank = InRank;
DisplayName = InDisplayName;
Score = InScore;
}
};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.
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());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);Declare a callback function to query user information from the leaderboard list.
protected:
// ...
void OnQueryUserInfoComplete(
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo,
const int32 LocalUserNum,
const FOnlineLeaderboardReadRef LeaderboardObj,
const FOnGetLeaderboardRankingComplete OnComplete);Define the functions above. Open the
LeaderboardSubsystem_Starter
class CPP file and define theGetRankings()
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 theOnGetRankingsComplete()
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);
}Define the
OnGetRankingsComplete()
and theOnQueryUserInfoComplete()
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.
if (UStartupSubsystem* StartupSubsystem = GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
LocalUserNum,
LeaderboardMembers,
FOnQueryUsersInfoCompleteDelegate::CreateUObject(this, &ThisClass::OnQueryUserInfoComplete, LocalUserNum, LeaderboardObj, OnComplete));
}
else
{
OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
}
}void ULeaderboardSubsystem_Starter::OnQueryUserInfoComplete(
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo,
const int32 LocalUserNum,
const FOnlineLeaderboardReadRef LeaderboardObj,
const FOnGetLeaderboardRankingComplete OnComplete)
{
if (!ensure(UserInterface))
{
UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Cannot get leaderboard. User Interface is not valid."));
return;
}
if (!Error.bSucceeded)
{
UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Failed to get leaderboard with code: %s. Error: %s"), *Error.ErrorCode, *Error.ErrorMessage.ToString());
OnComplete.ExecuteIfBound(false, TArray<ULeaderboardRank*>());
return;
}
UE_LOG_LEADERBOARD_ESSENTIALS(Warning, TEXT("Success in getting the 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.0f;
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->Init(Row.PlayerId, Row.Rank, DisplayName, Score);
Rankings.Add(NewRanking);
}
OnComplete.ExecuteIfBound(true, Rankings);
}
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.
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());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 theOnGetRankingsComplete()
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
The files used in this tutorial section are available in the Unreal Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardSubsystem_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardEssentialsModels.h