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

オンラインサブシステムを使用してプレイヤーをフレンド解除しブロックする - フレンドを管理する - (Unreal Engine モジュール)

Last updated on May 30, 2024

Unwrap the Subsystem

In this section, you will learn how to implement the functionality to unfriend a friend, block a player, unblock players, and get the blocked player list using AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, there is already a Game Instance Subsystem created named ManagingFriendsSubsystem. This subsystem contains managing friends-related functionalities. In this tutorial, you will use a starter version of that subsystem, so you can implement managing friends-related functionalities from scratch.

What's in the Starter Pack

To follow this tutorial, a starter subsystem class named ManagingFriendsSubsystem_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/ManagingFriends/ManagingFriendsSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Social/ManagingFriends/ManagingFriendsSubsystem_Starter.cpp

The ManagingFriendsSubsystem_Starter class also has several functionalities provided:

  • AGS OSS interfaces declarations: FriendsInterface and UserInterface. You will use these interfaces to implement managing friends-related functionalities later.

    protected:
    FOnlineUserAccelBytePtr UserInterface;
    FOnlineFriendsAccelBytePtr FriendsInterface;
  • Helper functions to get UniqueNetId from a PlayerController. You will need these helpers for the AGS OSS interfaces mentioned above.

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

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

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }
  • Helper functions to get the LocalUserNum from a PlayerController. You will need these helpers for the AGS OSS interfaces later, too.

    int32 UManagingFriendsSubsystem_Starter::GetLocalUserNumFromPlayerController(const APlayerController* PC) const
    {
    int32 LocalUserNum = 0;

    if (!PC)
    {
    return LocalUserNum;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (LocalPlayer)
    {
    LocalUserNum = LocalPlayer->GetControllerId();
    }

    return LocalUserNum;
    }

Besides the starter subsystem, there are prepared constants and delegates in the /Source/AccelByteWars/TutorialModules/Social/FriendsEssentials/FriendsEssentialsModels.h file. In that file, you can find the following:

  • Delegates to be used as a callback upon the caching blocked players' data completing.

    DECLARE_DELEGATE_ThreeParams(FOnGetBlockedPlayerListComplete, bool /*bWasSuccessful*/, TArray<UFriendData*> /*BlockedPlayers*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE(FOnGetCacheBlockedPlayersDataUpdated);
  • Delegates to be used as callback upon the unfriending process completing, the blocking player process completing, and the unblocking player process completing.

    DECLARE_DELEGATE_TwoParams(FOnUnfriendComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE_TwoParams(FOnBlockPlayerComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE_TwoParams(FOnUnblockPlayerComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);

Implement Get Blocked Players

In this section, you will implement functionalities to get the blocked player list.

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

    public:
    void GetBlockedPlayerList(const APlayerController* PC, const FOnGetBlockedPlayerListComplete& OnComplete = FOnGetBlockedPlayerListComplete());
  2. Create the definition for the function above. Open the ManagingFriendsSubsystem_Starter class CPP file and add the code below. The basic flow to get the blocked player list is to query the list from the backend. Once it's completed, the blocked player list will be cached locally.

    void UManagingFriendsSubsystem_Starter::GetBlockedPlayerList(const APlayerController* PC, const FOnGetBlockedPlayerListComplete& OnComplete)
    {
    if (!ensure(FriendsInterface))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot cache blocked player list. Friends Interface is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    const FUniqueNetIdPtr PlayerNetId = GetUniqueNetIdFromPlayerController(PC);

    // Try to get cached blocked player list first.
    TArray<TSharedRef<FOnlineBlockedPlayer>> CachedBlockedPlayerList;
    if (FriendsInterface->GetBlockedPlayers(PlayerNetId->AsShared().Get(), CachedBlockedPlayerList))
    {
    // Then, update the cached blocked players' information by querying their user information.
    TPartyMemberArray BlockedPlayerIds;
    for (const TSharedRef<FOnlineBlockedPlayer>& CachedBlockedPlayer : CachedBlockedPlayerList)
    {
    BlockedPlayerIds.Add(CachedBlockedPlayer.Get().GetUserId());
    }

    // Create callback to handle queried blocked players' user information.
    OnQueryUserInfoCompleteDelegateHandle = UserInterface->AddOnQueryUserInfoCompleteDelegate_Handle(
    LocalUserNum,
    FOnQueryUserInfoCompleteDelegate::CreateWeakLambda(this, [this, PlayerNetId, OnComplete](int32 LocalUserNum, bool bWasSuccessful, const TArray<FUniqueNetIdRef>& UserIds, const FString& Error)
    {
    UserInterface->ClearOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, OnQueryUserInfoCompleteDelegateHandle);

    /* Refresh blocked players data with queried blocked players' user information.
    * Then, return blocked players to the callback. */
    TArray<UFriendData*> BlockedPlayers;
    TArray<TSharedRef<FOnlineBlockedPlayer>> NewCachedBlockedPlayerList;
    FriendsInterface->GetBlockedPlayers(PlayerNetId->AsShared().Get(), NewCachedBlockedPlayerList);
    for (const TSharedRef<FOnlineBlockedPlayer>& NewCachedBlockedPlayer : NewCachedBlockedPlayerList)
    {
    // Update blocked player's avatar URL based on queried friend's user information.
    FString UserAvatarURL;
    TSharedPtr<FOnlineUser> UserInfo = UserInterface->GetUserInfo(LocalUserNum, NewCachedBlockedPlayer.Get().GetUserId().Get());
    UserInfo->GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, UserAvatarURL);

    // Add the updated blocked player to the list.
    UFriendData* BlockedPlayer = UFriendData::ConvertToFriendData(NewCachedBlockedPlayer);
    BlockedPlayer->AvatarURL = UserAvatarURL;
    BlockedPlayers.Add(BlockedPlayer);
    }

    OnComplete.ExecuteIfBound(true, BlockedPlayers, TEXT(""));
    }
    ));

    // Query blocked players' user information.
    UserInterface->QueryUserInfo(LocalUserNum, BlockedPlayerIds);
    }
    // If none, request to backend then get the cached the blocked player list.
    else
    {
    OnQueryBlockedPlayersCompleteDelegateHandle = FriendsInterface->AddOnQueryBlockedPlayersCompleteDelegate_Handle(
    FOnQueryBlockedPlayersCompleteDelegate::CreateWeakLambda(this, [this, OnComplete](const FUniqueNetId& UserId, bool bWasSuccessful, const FString& Error)
    {
    if (!bWasSuccessful)
    {
    OnComplete.ExecuteIfBound(false, TArray<UFriendData*>(), Error);
    return;
    }

    TArray<TSharedRef<FOnlineBlockedPlayer>> CachedBlockedPlayer;
    FriendsInterface->GetBlockedPlayers(UserId, CachedBlockedPlayer);

    // Return blocked players to the callback.
    TArray<UFriendData*> BlockedPlayers;
    for (const TSharedRef<FOnlineBlockedPlayer>& TempData : CachedBlockedPlayer)
    {
    BlockedPlayers.Add(UFriendData::ConvertToFriendData(TempData));
    }

    OnComplete.ExecuteIfBound(true, BlockedPlayers, TEXT(""));
    }
    ));

    FriendsInterface->QueryBlockedPlayers(PlayerNetId->AsShared().Get());
    }
    }

Implement Block Player

In this section, you will implement the functionality to block a player. Blocking a player will result in the blocked player not being able to send a friend request to or match-make with the blocker.

  1. Open the ManagingFriendsSubsystem_Starter class Header file and declare the following functions.

    public:
    void BlockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnBlockPlayerComplete& OnComplete = FOnBlockPlayerComplete());
  2. You also need to create a callback function to handle when the block player process completes.

    protected:
    void OnBlockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnBlockPlayerComplete OnComplete);
  3. Define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the BlockPlayer() function. This function will block a player and call the OnBlockPlayerComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::BlockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnBlockPlayerComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot block a player. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(BLOCK_PLAYER_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnBlockPlayerCompleteDelegateHandle = FriendsInterface->AddOnBlockedPlayerCompleteDelegate_Handle(LocalUserNum, FOnBlockedPlayerCompleteDelegate::CreateUObject(this, &ThisClass::OnBlockPlayerComplete, OnComplete));
    FriendsInterface->BlockPlayer(LocalUserNum, BlockedPlayerUserId.GetUniqueNetId().ToSharedRef().Get());
    }
  4. Define the OnBlockPlayerComplete() function that will be called when the block player process completes. This function simply prints a log to show whether the block player process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnBlockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnBlockPlayerComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnBlockedPlayerCompleteDelegate_Handle(LocalUserNum, OnBlockPlayerCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to block a player."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_BLOCK_PLAYER);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to block a player. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }

Implement Unblock Player

In this section, you will implement the functionality to unblock a player.

  1. Open the ManagingFriendsSubsystem_Starter class Header file and declare the following functions:

    public:
    void UnblockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnUnblockPlayerComplete& OnComplete = FOnUnblockPlayerComplete());
  2. You also need to create a callback function to handle when the unblock player process completes.

    protected:
    void OnUnblockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnUnblockPlayerComplete OnComplete);
  3. Define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the UnblockPlayer() function. This function will unblock a player and call the OnUnblockPlayerComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::UnblockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnUnblockPlayerComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot unblock a player. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(UNBLOCK_PLAYER_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnUnblockPlayerCompleteDelegateHandle = FriendsInterface->AddOnUnblockedPlayerCompleteDelegate_Handle(LocalUserNum, FOnBlockedPlayerCompleteDelegate::CreateUObject(this, &ThisClass::OnUnblockPlayerComplete, OnComplete));
    FriendsInterface->UnblockPlayer(LocalUserNum, BlockedPlayerUserId.GetUniqueNetId().ToSharedRef().Get());
    }
  4. Define the OnUnblockPlayerComplete() function that will be called once the unblock player process completes. This function simply prints a log to show whether the unblock player process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnUnblockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnUnblockPlayerComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnUnblockedPlayerCompleteDelegate_Handle(LocalUserNum, OnUnblockPlayerCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to unblock a player."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_UNBLOCK_PLAYER);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to unblock a player. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }

Implement Unfriend

In this section, you will implement the functionality to unfriend a friend.

  1. Open the ManagingFriendsSubsystem_Starter class Header file and declare the following functions.

    public:
    void Unfriend(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnUnfriendComplete& OnComplete = FOnUnfriendComplete());
  2. You also need to create a callback function to handle when the unfriend process completes.

    protected:
    void OnUnfriendComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnUnfriendComplete OnComplete);
  3. Define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the Unfriend() function. This function will unfriend a friend and call the OnUnfriendComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::Unfriend(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnUnfriendComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot unfriend a friend. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(UNFRIEND_FRIEND_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnUnfriendCompleteDelegateHandle = FriendsInterface->AddOnDeleteFriendCompleteDelegate_Handle(LocalUserNum, FOnDeleteFriendCompleteDelegate::CreateUObject(this, &ThisClass::OnUnfriendComplete, OnComplete));
    FriendsInterface->DeleteFriend(LocalUserNum, FriendUserId.GetUniqueNetId().ToSharedRef().Get(), TEXT(""));
    }
  4. Define the OnUnfriendComplete() function that will be called when the unfriend process is completes. This function simply prints a log to show whether the unfriend process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnUnfriendComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnUnfriendComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnDeleteFriendCompleteDelegate_Handle(LocalUserNum, OnUnfriendCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to unfriend a friend."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_UNFRIEND_FRIEND);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to unfriend a friend. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }

Listen for On Blocked Player List Updated

When a player is blocked or unblocked, the cached blocked player list will be updated automatically by the AGS OSS. In this section, you will learn how to bind a delegate to be executed when the blocked player list is updated. It will be useful when you need to update the displayed entries widgets later.

  1. Open the ManagingFriendsSubsystem_Starter class Header file and create the following function declarations:

    public:
    void BindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC, const FOnGetCacheBlockedPlayersDataUpdated& Delegate);
    void UnbindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC);
  2. Open the ManagingFriendsSubsystem_Starter class CPP file and create the definitions for the functions above. Start with the BindOnCachedBlockedPlayersDataUpdated() function.

    void UManagingFriendsSubsystem_Starter::BindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC, const FOnGetCacheBlockedPlayersDataUpdated& Delegate)
    {
    ensure(FriendsInterface);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Add on blocked players changed delegate.
    OnBlockedPlayersChangeDelegateHandles.Add(LocalUserNum, FriendsInterface->AddOnBlockListChangeDelegate_Handle(LocalUserNum, FOnBlockListChangeDelegate::CreateWeakLambda(this, [Delegate](int32, const FString&) { Delegate.ExecuteIfBound(); })));
    }
  3. Create the definition for the UnbindOnCachedBlockedPlayersDataUpdated() function.

    void UManagingFriendsSubsystem_Starter::UnbindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC)
    {
    ensure(FriendsInterface);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Clear on blocked players changed delegate.
    FDelegateHandle TempHandle = OnBlockedPlayersChangeDelegateHandles[LocalUserNum];
    if (TempHandle.IsValid())
    {
    FriendsInterface->ClearOnFriendsChangeDelegate_Handle(LocalUserNum, TempHandle);
    }
    }
  4. Essentially, with these two functions, you can bind a delegate to be executed when the blocked player list is updated by using the BindOnCachedBlockedPlayersDataUpdated() function. To unbind that delegate, you can use the UnbindOnCachedBlockedPlayersDataUpdated() function. You will use these functions to update displayed entries widgets in the next tutorial.

Resources