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

Use the Online Subsystem to get recent players - Recent players - (Unreal Engine module)

Last updated on October 23, 2024

Unwrap the Subsystem

This tutorial shows how to implement the functions to get the recent player list and the game session player list using the AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, there is already a game instance subsystem created named RecentPlayersSubsystem, which has the completed implementation. However, for this tutorial, you will use a starter version of that subsystem so you can implement the functions from scratch.

What's in the Starter Pack

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

  • Header file: /Source/AccelByteWars/TutorialModules/Social/RecentPlayers/RecentPlayersSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Social/RecentPlayers/RecentPlayersSubsystem_Starter.cpp

The RecentPlayersSubsystem_Starter class contains several helpers:

  • AGS OSS interfaces declarations: FriendsInterface and SessionInterface. You will use these interfaces to implement the recent players feature.

    protected:
    FOnlineFriendsAccelBytePtr FriendsInterface;
    IOnlineSessionPtr SessionInterface;
  • A helper function to get UniqueNetId from a PlayerController. You will need this function for the AGS OSS interfaces mentioned above.

    FUniqueNetIdPtr URecentPlayersSubsystem_Starter::GetUniqueNetIdFromPlayerController(const APlayerController* PlayerController)
    {
    if (!PlayerController)
    {
    return nullptr;
    }

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

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }

There is also a model file containing helper delegates. The file is located in /Source/AccelByteWars/TutorialModules/Social/RecentPlayers/RecentPlayersModels.h. You will use these delegates to handle callbacks.

DECLARE_DELEGATE_TwoParams(FOnGetRecentPlayersComplete, bool /*bWasSuccessful*/, TArray<UFriendData*> /*RecentPlayersData*/)
DECLARE_DELEGATE_TwoParams(FOnGetGameSessionPlayerListComplete, bool /*bWasSuccessful*/, TArray<UFriendData*> /*GameSessionPlayersData*/)

Prepare the helpers

In this section, you will prepare the functions to be used as helpers for the RecentPlayersSubsystem_Starter implementation.

  1. Open the RecentPlayersSubsystem_Starter class Header file and declare the function below. This function updates the player friend invitation status.

    private:
    void UpdatePlayersInviteStatus(const APlayerController* PlayerController, const FOnGetGameSessionPlayerListComplete& OnComplete, TArray<UFriendData*>& PlayersData);
  2. Open the RecentPlayersSubsystem_Starter class CPP file to define the UpdatePlayersInviteStatus() function by adding the code below. This function updates the player's friend invitation status by comparing the player's friend list with the blocked players list. The reason you need this function is that the blocked players list is handled by a different interface of the AGS OSS. Hence, you need to sync the status to make sure the player is only able to send friend requests to players who aren't blocked.

    void URecentPlayersSubsystem_Starter::UpdatePlayersInviteStatus(const APlayerController* PlayerController, const FOnGetGameSessionPlayerListComplete& OnComplete, TArray<UFriendData*>& PlayersData)
    {
    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    ensure(GameInstance);

    UFriendsSubsystem* FriendsSubsystem = GameInstance->GetSubsystem<UFriendsSubsystem>();
    ensure(FriendsSubsystem);

    FriendsSubsystem->GetFriendsInviteStatus(PlayerController, PlayersData, FOnGetPlayersInviteStatusComplete::CreateWeakLambda(this, [this, PlayerController, OnComplete, &PlayersData, GameInstance](bool bWasSuccessful, const FString& ErrorMessage)
    {
    if(bWasSuccessful)
    {
    //check blocked player list, as blocked player returned from Friends subsystem has friend status as EFriendStatus::Unknown
    UManagingFriendsSubsystem* ManagingFriendsSubsystem = GameInstance->GetSubsystem<UManagingFriendsSubsystem>();
    ensure(ManagingFriendsSubsystem);
    ManagingFriendsSubsystem->GetBlockedPlayerList(
    PlayerController,
    false,
    FOnGetBlockedPlayerListComplete::CreateWeakLambda(this, [this, OnComplete, &PlayersData](bool bWasSuccessful, TArray<UFriendData*> BlockedPlayers, const FString& ErrorMessage)
    {
    if(bWasSuccessful)
    {
    for(UFriendData* PlayerData: PlayersData)
    {
    if(BlockedPlayers.ContainsByPredicate([PlayerData](const UFriendData* Data)
    {
    return Data->UserId == PlayerData->UserId;
    }))
    {
    PlayerData->Status = EFriendStatus::Blocked;
    PlayerData->bCannotBeInvited = true;
    PlayerData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "Blocked", "Blocked").ToString();
    }
    }
    }

    OnComplete.ExecuteIfBound(bWasSuccessful, PlayersData);
    }));
    }
    else
    {
    OnComplete.ExecuteIfBound(false, GameSessionPlayersData);
    UE_LOG_RECENTPLAYERS(Warning, TEXT("Failed get invite status. Error message: %s"), *ErrorMessage);
    }
    }));
    }

Implement getting recent players

In this section, you will implement functions to get the recent players list.

  1. Open the RecentPlayersSubsystem_Starter class Header file and declare the helper variable below. This variable will be used to cache the recent players list.

    private:
    // ...
    UPROPERTY()
    TArray<UFriendData*> RecentPlayersData{};
  2. Still in the Header file, declare the functions below. These functions will be used to query and get the recent players list.

    public:
    // ...
    void GetRecentPlayers(const APlayerController* PlayerController, const FOnGetRecentPlayersComplete& OnComplete = FOnGetRecentPlayersComplete());
    void QueryRecentPlayers(const APlayerController* PlayerController);
    FDelegateHandle BindRecentPlayerDelegate(FOnQueryRecentPlayersCompleteDelegate& Delegate);
    void UnBindRecentPlayerDelegate(FDelegateHandle& Delegate);
  3. Still in the Header file, declare the functions below. These functions will be used to query and get the recent players list.

    private:
    // ...
    void OnSessionDestroyed(FName SessionName, bool bWasSuccessful);
  4. Open the RecentPlayersSubsystem_Starter class CPP file to define the functions above. Define the GetRecentPlayers() function by adding the code below. This function collects the recent players list from the AGS OSS cache and then updates the friend invitation status.

    void URecentPlayersSubsystem_Starter::GetRecentPlayers(const APlayerController* PlayerController,
    const FOnGetRecentPlayersComplete& OnComplete)
    {
    const FUniqueNetIdPtr LocalPlayerId = GetUniqueNetIdFromPlayerController(PlayerController);
    if (!ensure(LocalPlayerId.IsValid()))
    {
    UE_LOG_RECENTPLAYERS(Warning, TEXT("Cannot get recent player. LocalPlayer NetId is not valid."));
    return;
    }

    TArray<TSharedRef<FOnlineRecentPlayer>> RecentPlayers;
    bool bSuccess = FriendsInterface->GetRecentPlayers(LocalPlayerId.ToSharedRef().Get(), TEXT(""), RecentPlayers);

    if(bSuccess)
    {
    UE_LOG_RECENTPLAYERS(Log, TEXT("Success to get recent player list."));

    RecentPlayersData.Empty();
    for(const TSharedRef<FOnlineRecentPlayer>& Player: RecentPlayers)
    {
    RecentPlayersData.Add(UFriendData::ConvertToFriendData(Player));
    }

    UpdatePlayersInviteStatus(PlayerController, OnComplete, RecentPlayersData);
    }
    }
  5. Next, define the QueryRecentPlayers() function by adding the code below. This function sends a request to the backend to query the recent players list.

    void URecentPlayersSubsystem_Starter::QueryRecentPlayers(const APlayerController* PlayerController)
    {
    const FUniqueNetIdPtr LocalPlayerId = GetUniqueNetIdFromPlayerController(PlayerController);
    if (!ensure(LocalPlayerId.IsValid()))
    {
    UE_LOG_RECENTPLAYERS(Warning, TEXT("Cannot query recent player. LocalPlayer NetId is not valid."));
    return;
    }

    FriendsInterface->QueryRecentPlayers(LocalPlayerId.ToSharedRef().Get(), TEXT(""));
    }
  6. Define the BindRecentPlayerDelegate() function by adding the code below. This function adds the passed delegate to be triggered upon completion of querying the recent players list. You will use this function with the UI to refresh the displayed recent players list.

    FDelegateHandle URecentPlayersSubsystem_Starter::BindRecentPlayerDelegate(FOnQueryRecentPlayersCompleteDelegate& Delegate)
    {
    return FriendsInterface->AddOnQueryRecentPlayersCompleteDelegate_Handle(Delegate);
    }
  7. Then, define the UnBindRecentPlayerDelegate() function by adding the code below. This function removes the passed delegate from the query recent players list delegate. You will use this function with the UI to stop refreshing the displayed recent players list.

    void URecentPlayersSubsystem_Starter::UnBindRecentPlayerDelegate(FDelegateHandle& Delegate)
    {
    FriendsInterface->ClearOnQueryRecentPlayersCompleteDelegate_Handle(Delegate);
    }
  8. Next, define the OnSessionDestroyed() function by adding the code below. This function will query the recent players list every time a game session ends.

    void URecentPlayersSubsystem_Starter::OnSessionDestroyed(FName SessionName, bool bWasSuccessful)
    {
    // refresh recent player if a game session is destroyed
    if(bWasSuccessful && SessionName.IsEqual(NAME_GameSession))
    {
    const UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    ensure(GameInstance);

    QueryRecentPlayers(GameInstance->GetFirstLocalPlayerController());
    }
    }
  9. Locate the predefined Initialize() function, which is the function to be called when the subsystem is initialized. Then, add the code below to bind the OnSessionDestroyed() function to be called every time a game session ends.

    void URecentPlayersSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(FOnDestroySessionCompleteDelegate::CreateUObject(this, &ThisClass::OnSessionDestroyed));
    }
  10. Finally, locate the predefined Deinitialize() function, which is the function to be called when the subsystem is deinitialized. Then, add the code below to clear the on game session ended delegate.

    void URecentPlayersSubsystem_Starter::Deinitialize()
    {
    // ...
    SessionInterface->ClearOnDestroySessionCompleteDelegates(this);
    }

Implement getting game session players list

In this section, you will implement functionality to get the game session player list.

  1. Open the RecentPlayersSubsystem_Starter class Header file and declare the helper variable below. This variable will be used to cache the game session players list.

    private:
    // ...
    UPROPERTY()
    TArray<UFriendData*> GameSessionPlayersData{};
  2. Still in the Header file, declare the functions below. These functions will be used get the game session players list.

    public:
    // ...
    void GetGameSessionPlayerList(const APlayerController* PlayerController, const FOnGetGameSessionPlayerListComplete& OnComplete = FOnGetGameSessionPlayerListComplete());
    FString GetGameSessionPlayerStatus(UFriendData* Player);
  3. Open the RecentPlayersSubsystem_Starter class CPP file to define the functions above. Define the GetGameSessionPlayerList() function by adding the code below. This function collects and returns the game session player list via the passed delegate parameter.

    void URecentPlayersSubsystem_Starter::GetGameSessionPlayerList(const APlayerController* PlayerController, const FOnGetGameSessionPlayerListComplete& OnComplete)
    {
    const FNamedOnlineSession* GameSession = SessionInterface->GetNamedSession(NAME_GameSession);
    if(!GameSession)
    {
    return;
    }

    const TSharedPtr<FOnlineSessionInfo> SessionInfo = GameSession->SessionInfo;
    if (!SessionInfo.IsValid())
    {
    return;
    }

    const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
    if (!AbSessionInfo.IsValid())
    {
    return;
    }

    const TSharedPtr<FAccelByteModelsV2BaseSession> AbBaseSessionInfo = AbSessionInfo->GetBackendSessionData();
    if (!AbBaseSessionInfo.IsValid())
    {
    return;
    }


    const FUniqueNetIdAccelByteUserPtr TargetUserABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(GetUniqueNetIdFromPlayerController(PlayerController));

    if(!TargetUserABId.IsValid())
    {
    UE_LOG_RECENTPLAYERS(Warning, TEXT("Local userid invalid"));
    return;
    }

    GameSessionPlayersData.Empty();

    TArray<FAccelByteModelsV2SessionUser> AbMembers = AbBaseSessionInfo->Members;
    TArray<FUniqueNetIdRef> UniqueNetIds;
    for (const FAccelByteModelsV2SessionUser& AbMember : AbMembers)
    {
    // skip local player
    if(TargetUserABId->GetAccelByteId().Equals(AbMember.ID))
    {
    continue;
    }

    FAccelByteUniqueIdComposite CompositeId;
    CompositeId.Id = AbMember.ID;

    FUniqueNetIdAccelByteUserRef AccelByteUser = FUniqueNetIdAccelByteUser::Create(CompositeId);
    UniqueNetIds.Add(AccelByteUser);

    const FOnlineSubsystemAccelByte* Subsystem = static_cast<FOnlineSubsystemAccelByte*>(Online::GetSubsystem(GetWorld()));
    if (!ensure(Subsystem))
    {
    UE_LOG_RECENTPLAYERS(Warning, TEXT("The online subsystem is invalid."));
    return;
    }

    const TSharedPtr<const FAccelByteUserInfo> User = Subsystem->GetUserCache()->GetUser(AccelByteUser.Get());
    if(User.IsValid())
    {
    UFriendData* PlayerData = UFriendData::ConvertToFriendData(User.ToSharedRef());
    GameSessionPlayersData.Add(PlayerData);
    }
    else
    {
    UE_LOG_RECENTPLAYERS(Warning, TEXT("User is invalid"));
    }
    }

    UE_LOG_RECENTPLAYERS(Log, TEXT("Success to get game session player list."));

    UpdatePlayersInviteStatus(PlayerController, OnComplete, GameSessionPlayersData);
    }
  4. Next, define the GetGameSessionPlayerStatus() function by adding the code below. This function returns the player status in the game session (e.g., joined or left game session).

    FString URecentPlayersSubsystem_Starter::GetGameSessionPlayerStatus(UFriendData* Player)
    {
    FString StatusAsString = TEXT("");
    const FNamedOnlineSession* GameSession = SessionInterface->GetNamedSession(NAME_GameSession);
    const TSharedPtr<FOnlineSessionInfo> SessionInfo = GameSession->SessionInfo;
    if (!SessionInfo.IsValid())
    {
    return StatusAsString;
    }

    const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
    if (!AbSessionInfo.IsValid())
    {
    return StatusAsString;
    }

    const TSharedPtr<FAccelByteModelsV2BaseSession> AbBaseSessionInfo = AbSessionInfo->GetBackendSessionData();
    if (!AbBaseSessionInfo.IsValid())
    {
    return StatusAsString;
    }

    TArray<FAccelByteModelsV2SessionUser> AbMembers = AbBaseSessionInfo->Members;
    const FUniqueNetIdAccelByteUserPtr TargetUserABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(Player->UserId);
    FAccelByteModelsV2SessionUser* OnlineUser = AbMembers.FindByPredicate([TargetUserABId](FAccelByteModelsV2SessionUser User)
    {
    return User.ID.Equals(TargetUserABId->GetAccelByteId());
    });
    if(OnlineUser != nullptr)
    {
    StatusAsString = UEnum::GetValueAsString<EAccelByteV2SessionMemberStatus>(OnlineUser->StatusV2);
    StatusAsString = StatusAsString.RightChop(StatusAsString.Find(TEXT("::")) + 2);
    }

    return StatusAsString;
    }
    備考

    You can refer to the EAccelByteV2SessionMemberStatus enumeration for a detailed list of all player game session status types. This enum outlines the various statuses a player may have during a game session.

Resources