OSS を使用して定期的なリーダーボードを表示する - 毎週のリーダーボード - (Unreal Engine モジュール)
注釈:本資料は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 クラスには、いくつかの機能が提供されています。
-
LeaderboardInterfaceとUserInterfaceという名前の 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);
定期的なリーダーボードランキングの取得を実装する
このセクションでは、定期的なリーダーボードランキングを取得する機能を実装します。
-
PeriodicBoardSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
void GetPeriodicRankings(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const int32 ResultLimit, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete()); -
定期的なリーダーボードランキング取得プロセスが完了したときに処理するコールバック関数を宣言します。
protected:
// ...
void OnGetPeriodicRankingsComplete(bool bWasSuccessful, const int32 LocalUserNum, const FOnlineLeaderboardReadRef LeaderboardObj, const FOnGetLeaderboardRankingComplete OnComplete); -
リーダーボードリストからユーザー情報をクエリするコールバック関数を宣言します。
protected:
// ...
void OnQueryUserInfoComplete(
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo,
const int32 LocalUserNum,
const FOnlineLeaderboardReadRef LeaderboardObj,
const FOnGetLeaderboardRankingComplete OnComplete); -
上記の関数を定義します。
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);
} -
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);
} -
定期的なリーダーボードランキングを取得するにはサイクル ID が必要なため、名前でサイクル ID を取得する関数を作成します。
PeriodicBoardSubsystem_Starterクラスのヘッダーファイルを再度開き、以下の関数を宣言します。public:
// ...
void GetLeaderboardCycleIdByName(const FString& InCycleName, const EAccelByteCycle& InCycleType, const FOnGetLeaderboardsCycleIdComplete& OnComplete = FOnGetLeaderboardsCycleIdComplete()); -
次に、
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());
})
);
}
プレイヤーの定期的なリーダーボードランキングの取得を実装する
範囲内の定期的なリーダーボードランキングの取得を実装しました。このセクションでは、特定のプレイヤーの定期的なリーダーボードランクを取得する機能を実装します。これにより、プレイヤーが特定のリーダーボードランキング範囲に含まれていない場合に、後で表示できるようになります。
-
PeriodicBoardSubsystem_Starterクラスのヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
void GetPlayerPeriodicRanking(const APlayerController* PC, const FString& LeaderboardCode, const FString& CycleId, const FOnGetLeaderboardRankingComplete& OnComplete = FOnGetLeaderboardRankingComplete()); -
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);
}
リソース
-
このチュートリアルセクションで使用されるファイルは、Unreal Byte Wars GitHub リポジトリで入手できます。
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/PeriodicLeaderboard/PeriodicBoardSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/PeriodicLeaderboard/PeriodicBoardSubsystem_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Engagement/LeaderboardEssentials/LeaderboardEssentialsModels.h