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

Unity SDK を使用してパーティサービスを実装する

Last updated on August 9, 2024

Quick Reference

AGS Shared Cloud

This topic is specific to the AccelByte Gaming Services (AGS) Shared Cloud tier.

References
using AccelByte.Api;
using AccelByte.Core;
using AccelByte.Models;
Get Lobby Service
Lobby lobby = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
Party Notification Events
lobby.InvitedToParty += result => {};
lobby.JoinedParty += result => {};
lobby.KickedFromParty += result =>{};
lobby.LeaveFromParty += result =>{};
lobby.RejectedPartyInvitation += result =>{};
Create Party
ResultCallback<PartyInfo> createPartyCallback = result =>
{
// If there is no error, display the success information
if (!result.IsError)
{
Debug.Log("Successfully create a party");
}
};
lobby.CreateParty(createPartyCallback);
Invite to Party
string userId;

lobby.InviteToParty(userId, result =>
{
if (!result.IsError)
{
Debug.Log("Successfully invite an invitee");
}
});
Kick from Party
string userId;

lobby.KickPartyMember(userId, result =>
{
if (result.IsError)
{
Debug.Log($"Successfully kick member from party");
}
});
Leave Party
lobby.LeaveParty(result =>
{
// If there is no error, display the success information
if (!result.IsError)
{
Debug.Log("Successfully leave from party");
}
});
Promote to Party Leader
string userId;

lobby.PromotePartyLeader(userId, result =>
{
if (!result.IsError)
{
Debug.Log("Successfully promoted member to be a party leader");
}
});
Join Party
PartyInvitation partyInvitation;

lobby.JoinParty(partyInvitation.partyID, partyInvitation.invitationToken, result =>
{
if (!result.IsError)
{
Debug.Log("Successfully joined the party");
}
});
Reject Party Invitation
PartyInvitation partyInvitation;

lobby.RejectPartyInvitation(partyInvitation.partyID, partyInvitation.invitationToken, result =>
{
if (result.IsError)
{
Debug.Log("Successfully rejected the party invitation");
}
});
Get Users' Info
string[] usersIdList;

AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(usersIdList, result => {
// Loop the result's Users Data, return in BaseUserInfo
foreach (BaseUserInfo user in result.Value.data)
{
// Do something with the user data
}
});

Quickstart Guide

In this tutorial, you will learn how to use Party services. This guide assumes that you have already implemented the Lobby and Friends services.

Since Party implementation can vary for each game, you can familiarize yourself with other concepts and classes in the LobbyModels.cs file inside the plugin SDK.

You will start by adding simple party logic into the game.

  1. Create a new script called PartyHandler.cs and attach it to the AccelByteHandler gameObject.

  2. Add the following AccelByte libraries to the top of the script:

    using UnityEngine;
    using AccelByte.Api;
    using AccelByte.Models;
    using AccelByte.Core;
  3. Add some basic Party functionality in PartyHandler.cs so you can call these later from your UI:

    • Create a Party This function will return a result from the class type PartyInfo which contains Party data such as partyID, the leader's leaderID, a list of the party members' userIDs, a list of the invitees' userIDs, and the party invitation's invitationToken.
    public void CreateParty()
    {
    ResultCallback<PartyInfo> createPartyCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to create party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully create a party");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().CreateParty(createPartyCallback);
    }

    If you want to save the PartyInfo of the created party, you can set the value based on result.Value.

    if (result.IsError)
    ...
    else
    {
    ...
    partyInfo = result.Value;
    }
    • Invite Friend to Party This function requires the UserID of the player you want to invite in order to send an invitation, so add inviteeUserId as the function's parameter.
    public void InviteToParty(string inviteeUserId)
    {
    ResultCallback inviteToPartyCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to invite user to party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully invite an invitee");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().InviteToParty(inviteeUserId, inviteToPartyCallback);
    }
    • Kick Friend from Party This function requires the UserID of the party member you want to kick from the party, so add memberUserId as the function's parameter.
    public void KickParty(string memberUserId)
    {
    ResultCallback kickPartyMemberCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to kick user from party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log($"Successfully kick member {memberUserId} from party");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().KickPartyMember(memberUserId, kickPartyMemberCallback);
    }
    • Leave from Party Use this function to leave a party.
    public void LeaveParty()
    {
    ResultCallback leavePartyCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to leave party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully leave from party");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().LeaveParty(leavePartyCallback);
    }
    • Promote Friend to Party Leader This function requires the UserID of the party member you want to promote to party leader, so add memberUserId as the function's parameter.
    public void PromotePartyLeader(string memberUserId)
    {
    ResultCallback<PartyPromoteLeaderResponse> promotePartyLeaderCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to promote member to be a party leader: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully promote member to be a party leader");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().PromotePartyLeader(memberUserId, promotePartyLeaderCallback);
    }
  4. Most of the party invitation's UI will be a spawnable prefab. Create a new script called PartyInvitationPanel.cs and add the following to the top:

    using UnityEngine;
    using AccelByte.Core;
    using AccelByte.Models;

    This script will hold functions that handle the Party Invitation's related events. You can attach this in your Party Invitation prefab later on.

  5. Add some functions to handle party invitations in PartyInvitationPanel.cs:

    • Accept Party Invitation (Join Party) This function requires the UserID of the player you want to invite in order to send an invitation to that player, so add inviteeUserId as the function's parameter.
    public void JoinParty(PartyInvitation partyInvitation)
    {
    ResultCallback<PartyInfo> joinPartyCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to join party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully join the party");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().JoinParty(partyInvitation.partyID, partyInvitation.invitationToken, joinPartyCallback);
    }
    • Reject Party Invitation This function requires the UserID of the party member you want to kick from the party, so add memberUserId as the function's parameter.
    public void RejectPartyInvitation(PartyInvitation partyInvitation)
    {
    ResultCallback<PartyRejectResponse> rejectPartyCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to reject party invitation: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully reject the party invitation");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().RejectPartyInvitation(partyInvitation.partyID, partyInvitation.invitationToken, rejectPartyCallback);
    }

    You can use the result value from the AccelByteSDK.GetClientRegistry().GetApi().GetLobby().InvitedToParty event for this PartyInvitation value.

  6. To send a notification to a player on any activity related to the current party, find LobbyHandler.cs and add this under the ConnectToLobby() function. For now, we will just add a Debug.Log for each event.

    lobby.InvitedToParty += result => { Debug.Log($"Invited by: {result.Value.from}"); };
    lobby.JoinedParty += result => { Debug.Log("Invitee join a party"); };
    lobby.KickedFromParty += result => { Debug.Log("You're kicked from party"); };
    lobby.LeaveFromParty += result => { Debug.Log($"{result.Value.userID} leave the party"); };
    lobby.RejectedPartyInvitation += result => { Debug.Log("Invitee rejected a party invitation"); };
  7. You may wish to retrieve Party data. There are a few ways you can do this.

    If you want to retrieve Party data every time the data is updated, use this event:

    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().PartyDataUpdateNotif += result => {}

    This event will return a result with the class type PartyDataUpdateNotif which contains Party data such as PartyInfo. PartyDataUpdateNotif does not have an invitationToken, but does have updatedAt which indicates when the Party data is updated, and custom_attribute which is a Dictionary that you can use with any custom info.

    As with the last step, you can find this event in LobbyHandler.cs under the ConnectToLobby() function. In this case, since we only need to display Party data, just add a Debug.Log to notify the party member's UserID via the Console output.

    lobby.PartyDataUpdateNotif += result =>{ Debug.Log($"Current Party Members: {result.Value.members}");}

    You can also retrieve Party data using the GetPartyInfo() function. The PartyInfo class contains Party data such as the partyID, leaderID, members (UserID), invitees (UserID), and an invitationToken. To get this PartyInfo, use the following function:

    ResultCallback<PartyInfo> getPartyInfoCallback = result =>
    {
    // If there is an error, display the error information
    if (result.IsError)
    {
    Debug.Log($"Failed to leave party: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    Debug.Log("Successfully left party");
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().GetPartyInfo(getPartyInfoCallback);

    This function returns a result with the class type PartyInfo, and you can access its values with result.Value.<the object you need>. For example, if you want to retrieve the partyID, use result.Value.partyId.

    TIP

    You can only retrieve UserID from PartyDataUpdateNotif and PartyInfo, so you will need to use the GetUser() function to retrieve user data.

    AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(usersIdList, result => {
    // Loop the result's Users Data, return in BaseUserInfo
    foreach (BaseUserInfo user in result.Value.data)
    {
    Debug.Log($"User data: {user.displayName}");
    }
    });

    AccelByteSDK.GetClientRegistry().GetApi().GetUser().GetUserByUserId(userId, result => {
    Debug.Log($"User data: {result.Value}");
    });

    Depending on the situation, when displaying Party data, you may want to use BulkGetUserInfo() which also contains avatarUrl.

Congratulations! You have successfully learnt how to use the Party service!

Continue on for a step-by-step example of the UI and code implementation.

Step-by-step guide

UI Implementation
  1. The Party service is part of the Lobby, so start by creating a new panel or scene for the Lobby and add the following objects:

    • Header text
    • Exit button
    • Empty panel, for the Lobby Page content (optional)
  2. The Lobby panel will have more than just Party UIs, so split the Lobby panel with two new panels. Once completed, add a Friends Management button to redirect players to the Friends panel.

  3. Add Party-related UI objects including:

    • Party ID text
    • Create Party button
    • Party Member List panel
    • Player Entry panel (since the default party size is four players, create four panels and parent them to the Party Member List panel)
    • Leave Party button
  4. Create a pop-up or panel for the Party Invitation and ensure that it has the following UI elements:

    • Invitation text
    • Accept button
    • Reject button
  5. You will also need a notification box for log messages, so create a new panel and add the following:

    • Sub-header text
    • Scroll View
    • Log Message Display prefab

    The Log Message Display prefab will spawn if a log message is created. Create a new panel, ensure it has a Log Message text, and link it under the content of the Scroll View.

Code Implementation

Now that you have some basic Party functionality, you can implement these into your UI.

  1. Inside PartyHandler.cs, add Unity Engine's UI library:

    using UnityEngine;
    using UnityEngine.UI;
  2. Add references to your Party UI:

    [SerializeField]
    private GameObject LobbyWindow;

    [SerializeField]
    private Transform canvasTransform;
    [SerializeField]
    private Transform partyDisplayPanel;

    [SerializeField]
    private GameObject partyInvitationPrefab;

    #region Buttons

    [SerializeField]
    private Button friendsManagementButton;
    [SerializeField]
    private Button createPartyButton;
    [SerializeField]
    private Button leavePartyButton;
    [SerializeField]
    private Button exitButton;

    #endregion

    [SerializeField]
    private Text partyIdText;
  3. Create a new dictionary that will save your Party members' User IDs and display names.

    public Dictionary<string, string> partyMembers { private set; get; }
  4. Since FriendsPanel can be called from both the Menu and the Lobby, create a new enum for an exit mode. In FriendsManagementHandler.cs, create a new enum with its getter and setter.

    #region ExitMode
    public enum ExitMode
    {
    Menu,
    Lobby
    }

    private ExitMode ExitScreen
    {
    get => ExitScreen;
    set
    {
    switch (value)
    {
    case ExitMode.Menu:
    FriendsManagementWindow.SetActive(false);
    GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
    break;

    case ExitMode.Lobby:
    FriendsManagementWindow.SetActive(false);
    GetComponent<LobbyHandler>().LobbyWindow.SetActive(true);
    break;
    }
    }
    }
    #endregion
  5. To avoid the listener being set up twice, add a new boolean as a flag checker in FriendsManagementHandler.cs, and then change the Setup() function:

    public void Setup(ExitMode exitType)
    {
    // Reset the exit button's listener, then add the listener based on the exit screen type
    exitButton.onClick.RemoveAllListeners();
    exitButton.onClick.AddListener(() =>
    {
    ...
    ExitScreen = exitType;
    });
    }
  6. Create a function in PartyHandler.cs to set up your Party UI when the LobbyPanel is set to active.

    /// Setup Party UI in Lobby and prepare state
    public void SetupParty()
    {
    friendsManagementButton.onClick.AddListener(() =>
    {
    GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Lobby);
    LobbyWindow.SetActive(false);
    GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
    });
    createPartyButton.onClick.AddListener(() => { CreateParty(); });
    leavePartyButton.onClick.AddListener(() => { LeaveParty(); });
    exitButton.onClick.AddListener(() =>
    {
    LobbyWindow.SetActive(false);
    GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
    });
    }

  7. Before you continue with PartyHandler.cs, connect the LobbyPanel with the MenuPanel's LobbyButton. In MenuHandler.cs, add a reference for the LobbyButton.

    public Button LobbyButton;
  8. Inside the Create() function, add the LobbyButton's event listener and change the FriendsButton's Setup():

    public void Create()
    {
    LobbyButton.onClick.AddListener(() =>
    {
    GetComponent<PartyHandler>().SetupParty();
    Menu.gameObject.SetActive(false);
    GetComponent<LobbyHandler>().LobbyWindow.SetActive(true);
    });

    FriendsButton.onClick.AddListener(() =>
    {
    GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Menu);
    ...
    });
    }
  9. In the Unity Editor, on the MenuHandler.cs script component in the AccelByteHandler game object, drag the following objects to the appropriate variables.

  10. Go to FriendStatusPanel.cs and add an inviteToPartyButton listener in the SetupButton() function. Use the LobbyHandler's instance to get PartyHandler without needing to use the Find function.

    public void SetupButton()
    {
    inviteToPartyButton.onClick.AddListener(() =>
    {
    LobbyHandler.Instance.GetComponent<PartyHandler>().InviteToParty(_userData.userId);
    });
    ...
    }
  11. Update your PartyInvitationPanel.cs for when the PartyInvitationPopup is spawned, such as in the following example:

    // Add some UI references
    [SerializeField]
    private GameObject invitationPopUp;
    [SerializeField]
    private Text invitationText;
    [SerializeField]
    private Button acceptInvitationButton;
    [SerializeField]
    private Button rejectInvitationButton;

    // Create a function to setup the Popup and its listener
    public void Setup(PartyInvitation partyInvitation)
    {
    AccelByteSDK.GetClientRegistry().GetApi().GetUser().GetUserByUserId(partyInvitation.from, result =>
    {
    invitationText.text = result.Value.displayName + " invite you to join their party\nAccept invitation?";
    });

    acceptInvitationButton.onClick.AddListener(() => { JoinParty(partyInvitation);});
    rejectInvitationButton.onClick.AddListener(() => { RejectPartyInvitation(partyInvitation);});
    }
  12. Add the following in PartyInvitationPanel.cs to destroy the PartyInvitationPopup after the player accepts or rejects an invitation:

    public void JoinParty(PartyInvitation partyInvitation)
    {
    ...

    // Destroy the PopUp prefab
    Destroy(invitationPopUp);
    }

    public void RejectPartyInvitation(PartyInvitation partyInvitation)
    {
    ...

    // Destroy the PopUp prefab
    Destroy(invitationPopUp);
    }
  13. In the Unity editor, open the PartyInvitationPopup prefab. Add the PartyInvitationPanels.cs script as a component, then drag the objects to their exposed variables.

  14. In PartyHandler.cs, create a new function to Display Party data to the PartyListPanel using the BulkGetUserInfo() function to retrieve player data.

    /// Display all Party Data to PartyList UI
    public void DisplayPartyData(Result<PartyDataUpdateNotif> partyDataNotifResult)
    {
    // Update PartyID in UI
    partyIdText.text = "PartyID: " + partyDataNotifResult.Value.partyId;

    // Get all party members data based on _partyUserIds, then update data to UI
    AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(partyDataNotifResult.Value.members, result =>
    {
    if (result.IsError)
    {
    Debug.Log($"Failed to get party member's data: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    // Initialize dictionary
    partyMembers = new Dictionary<string, string>();

    // Result data's order => reversed order of _partyIserIds
    int _index = result.Value.data.Length;
    foreach (BaseUserInfo user in result.Value.data)
    {
    _index -= 1;
    // Get transform of PlayerEntryDisplay, which is child of PartyListPanel
    Transform playerEntryDisplay = partyDisplayPanel.GetChild(_index).transform;

    if (user.userId == partyDataNotifResult.Value.leader)
    {
    // Set LeaderStatusIndicator as active
    Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
    leaderStatusIndicator.gameObject.SetActive(true);
    }
    else
    {
    if (AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId == partyDataNotifResult.Value.leader)
    {
    // Set PartyLeaderButton (promote button) as active, then add listener when onclick button
    Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
    partyLeaderButton.gameObject.SetActive(true);
    partyLeaderButton.GetComponent<Button>().onClick.AddListener(() =>
    {
    PromotePartyLeader(user.userId);
    });

    // Set KickPartyButton as active, then add listener when onclick button
    Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
    kickPartyButton.gameObject.SetActive(true);
    kickPartyButton.GetComponent<Button>().onClick.AddListener(() =>
    {
    KickParty(user.userId);
    });
    }
    }

    // Set DisplayNameText as active, then change text to User's Display Name
    Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
    displayNameText.gameObject.SetActive(true);
    displayNameText.GetComponent<Text>().text = user.displayName;

    partyMembers.Add(user.userId, user.displayName);
    }
    }
    });
    }
  15. You may want to display Party Member data if the Lobby is still connected. You can do this by adding the Start() function and then add Display Party data functionality using GetPartyInfo().

    private void Start()
    {
    Lobby lobby = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
    if (lobby.IsConnected)
    {
    lobby.GetPartyInfo(partyInfoResult =>
    {
    // Update PartyID in the UI
    partyIdText.text = "PartyID: " + partyInfoResult.Value.partyID;

    ResetPlayerEntryUI();

    // Get all party members data based on _partyUserIds, then update data in the UI
    AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(partyInfoResult.Value.members, result =>
    {
    if (result.IsError)
    {
    Debug.Log($"Failed to get party member's data: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    // Initialize dictionary
    partyMembers = new Dictionary<string, string>();

    // Result data's order => reversed order of _partyIserIds
    int index = result.Value.data.Length;
    foreach (BaseUserInfo user in result.Value.data)
    {
    index -= 1;
    // Get transform of PlayerEntryDisplay, which is child of PartyListPanel
    Transform playerEntryDisplay = partyDisplayPanel.GetChild(index).transform;

    if (user.userId == partyInfoResult.Value.leaderID)
    {
    // Set LeaderStatusIndicator as active
    Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
    leaderStatusIndicator.gameObject.SetActive(true);
    }
    else
    {
    if (AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId == partyInfoResult.Value.leaderID)
    {
    // Set PartyLeaderButton (promote button) as active, then add listener when onclick button
    Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
    partyLeaderButton.gameObject.SetActive(true);
    partyLeaderButton.GetComponent<Button>().onClick.AddListener(() =>
    {
    PromotePartyLeader(user.userId);
    });

    // Set KickPartyButton as active, then add listener when onclick button
    Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
    kickPartyButton.gameObject.SetActive(true);
    kickPartyButton.GetComponent<Button>().onClick.AddListener(() =>
    {
    KickParty(user.userId);
    });
    }
    }

    // Set DisplayNameText as active, then change text to User's Display Name
    Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
    displayNameText.gameObject.SetActive(true);
    displayNameText.GetComponent<Text>().text = user.displayName;

    partyMembers.Add(user.userId, user.displayName);
    }
    }
    });
    });
    }
    }
  16. Prepare some Reset functions to reset the Party data and UI in PartyHandler.cs:

    /// Reset Party ID's UI
    public void ResetPartyId()
    {
    partyIdText.text = "PartyID: ###############################";
    partyMembers = null;
    }

    /// Reset Party List's UI
    public void ResetPlayerEntryUI()
    {
    foreach(Transform playerEntryDisplay in partyDisplayPanel)
    {
    // Set LeaderStatusIndicator as not active
    Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
    leaderStatusIndicator.gameObject.SetActive(false);

    // Set PartyLeaderButton (promote button) as not active, then remove all listener on button
    Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
    partyLeaderButton.gameObject.SetActive(false);
    partyLeaderButton.GetComponent<Button>().onClick.RemoveAllListeners();

    // Set KickPartyButton as not active, then remove all listener on button
    Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
    kickPartyButton.gameObject.SetActive(false);
    kickPartyButton.GetComponent<Button>().onClick.RemoveAllListeners();

    // Set DisplayNameText as not active and set value to default text
    Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
    displayNameText.gameObject.SetActive(false);
    displayNameText.GetComponent<Text>().text = "PlayerUsername";
    }
    }
  17. The Party UI must be reset when a player leaves their current party, when displaying data, or on a player's first connection to the Lobby. To do this, call the Reset function in PartyHandler.cs:

    public void LeaveParty()
    {
    ResultCallback leavePartyCallback = result =>
    {
    if (result.IsError)
    {
    ...
    }
    else
    {
    ...
    // Reset all Party-related UIs
    ResetPartyId();
    ResetPlayerEntryUI();
    }
    };
    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().LeaveParty(leavePartyCallback);
    }

    public void DisplayPartyData(Result<PartyDataUpdateNotif> partyDataNotifResult)
    {
    // Udate PartyID in UI
    ...

    // Reset the Party List's UI
    ResetPlayerEntryUI();

    ...
    }
  18. You will need the notification display to appear in the notification box. To do this, create a new script called LogMessagePanel.cs and attach it to the LogMessageDisplay prefab. Once completed, add the following to the script:

    using UnityEngine;
    using UnityEngine.UI;

    public class LogMessagePanel : MonoBehaviour
    {
    [SerializeField]
    private Text messageText;

    /// Update Notification Message's UI
    public void UpdateNotificationUI(string text, Color color)
    {
    messageText.text = text;
    messageText.color = color;
    }
    }
  19. In LobbyHandler.cs, add the following UI references:

    public GameObject LobbyWindow;

    #region Notification Box
    [Header("Notification Box")]
    [SerializeField]
    private Transform notificationBoxContentView;
    [SerializeField]
    private GameObject logMessagePrefab;
    #endregion
  20. Add the following function in LobbyHandler.cs. This function will be called to instantiate the log message in the notification box.

    /// Write the log message on the notification box
    public void WriteLogMessage(string text, Color color)
    {
    LogMessagePanel logPanel = Instantiate(logMessagePrefab, notificationBoxContentView).GetComponent<LogMessagePanel>();
    logPanel.UpdateNotificationUI(text, color);
    }
  21. In the Unity Editor, on your AccelByteHandler, drag the following objects to the exposed variables.

  22. In PartyHandler.cs, create functions to handle each update event in LobbyHandler.cs and to create notification messages.

    /// Called on update when a party invitation is received
    public void InvitePartyNotification(PartyInvitation partyInvitation)
    {
    Debug.Log($"Invited by: {partyInvitation.from}");
    PartyInvitationPanel invitationPanel = Instantiate(partyInvitationPrefab, canvasTransform).GetComponent<PartyInvitationPanel>();
    invitationPanel.Setup(partyInvitation);
    }

    /// Called on update when kicked from party
    public void KickPartyNotification()
    {
    Debug.Log("You're kicked from party");
    ResetPartyId();
    ResetPlayerEntryUI();
    }

    /// Called on update when a friend joins the party
    public void JoinedPartyNotification(JoinNotification joinNotification)
    {
    Debug.Log("Invitee join a party");
    AccelByteSDK.GetClientRegistry().GetApi().GetUser().GetUserByUserId(joinNotification.userID, result =>
    {
    if (result.IsError)
    {
    Debug.Log($"Failed to get user data: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
    LobbyHandler.Instance.WriteLogMessage($"[Party] {result.Value.displayName} join the party", Color.black);
    }
    });
    }

    /// Called on update when a friend leaves the party
    public void LeavePartyNotification(LeaveNotification leaveNotification)
    {
    if (leaveNotification.userID != AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId)
    {
    Debug.Log($"{leaveNotification.userID} leave the party");
    LobbyHandler.Instance.WriteLogMessage($"[Party] {partyMembers[leaveNotification.userID]} leave the party", Color.black);
    }
    }
  23. While still in PartyHandler.cs, add the following code under the PromotePartyLeader() function to notify players whether their Promote Party Leader action succeeds or fails.

    public void PromotePartyLeader(string memberUserId)
    {
    // Instantiate the notification message
    NotificationMessagePanel notificationPanel = Instantiate(notificationMessagePrefab, notificationContentView).GetComponent<NotificationMessagePanel>();

    AccelByteSDK.GetClientRegistry().GetApi().GetLobby().PromotePartyLeader(memberUserId, result =>
    {
    if (result.IsError)
    {
    ...
    // Update the messageText that failed to promote leader
    LobbyHandler.Instance.WriteLogMessage($"[Party] Failed to promote {partyMembers[memberUserId]} to be party leader", Color.black);
    }
    else
    {
    ...
    // Update the messageText that success to promote leader
    LobbyHandler.Instance.WriteLogMessage($"[Party] Successfully promote {partyMembers[memberUserId]} to be party leader", Color.black);
    }
    });
    }
  24. Create new functions in NotificationHandler.cs that will be called from the notifications events when there is an update.

    // Collection of party notifications
    #region Party
    /// Called when user gets party invitation
    public void OnInvitedToParty(Result<PartyInvitation> result)
    {
    GetComponent<PartyHandler>().InvitePartyNotification(result.Value);
    }

    /// Called when user joins the party
    public void OnJoinedParty(Result<JoinNotification> result)
    {
    GetComponent<PartyHandler>().JoinedPartyNotification(result.Value);
    }

    /// Called when user is kicked by party leader
    public void OnKickedFromParty(Result<KickNotification> result)
    {
    GetComponent<PartyHandler>().KickPartyNotification();
    }

    /// Called when user leaves the party
    public void OnLeaveFromParty(Result<LeaveNotification> result)
    {
    GetComponent<PartyHandler>().LeavePartyNotification(result.Value);
    }

    /// Called when user rejects party invitation
    public void OnRejectedPartyInvitation(Result<PartyRejectNotif> result)
    {
    Debug.Log("[Party-Notification] Invitee rejected a party invitation");
    }

    /// Called when party data is updated
    public void OnPartyDataUpdateNotif(Result<PartyDataUpdateNotif> result)
    {
    GetComponent<PartyHandler>().DisplayPartyData(result);
    }
    #endregion
  25. Change your notification update events in LobbyHandler.cs under the ConnectToLobby() function, such as in the following example:

    public void ConnectToLobby()
    {
    ...
    // Party
    lobby.InvitedToParty += notificationHandler.OnInvitedToParty;
    lobby.JoinedParty += notificationHandler.OnJoinedParty;
    lobby.KickedFromParty += notificationHandler.OnKickedFromParty;
    lobby.LeaveFromParty += notificationHandler.OnLeaveFromParty;
    lobby.RejectedPartyInvitation += notificationHandler.OnRejectedPartyInvitation;
    lobby.PartyDataUpdateNotif += notificationHandler.OnPartyDataUpdateNotif;
    ...
    }
  26. Update the RemoveLobbyListeners() function in LobbyHandler.cs to reset the notification update events, such as in the following example:

    public void RemoveLobbyListeners()
    {
    ...
    // Party
    lobby.InvitedToParty -= notificationHandler.OnInvitedToParty;
    lobby.JoinedParty -= notificationHandler.OnJoinedParty;
    lobby.KickedFromParty -= notificationHandler.OnKickedFromParty;
    lobby.LeaveFromParty -= notificationHandler.OnLeaveFromParty;
    lobby.RejectedPartyInvitation -= notificationHandler.OnRejectedPartyInvitation;
    lobby.PartyDataUpdateNotif -= notificationHandler.OnPartyDataUpdateNotif;
    }
  27. Save and return to the Unity Editor. In your scene, on the AccelByteHandler gameObject, drag the appropriate objects into the exposed variables of the PartyHandler script component.

Congratulations! You have now fully implemented the Party service.

Full code for reference

PartyHandler.cs
using UnityEngine;
using UnityEngine.UI;
using AccelByte.Core;
using AccelByte.Models;
using AccelByte.Api;
using System.Collections.Generic;

public class PartyHandler : MonoBehaviour
{
[SerializeField]
private GameObject LobbyWindow;

[SerializeField]
private Transform canvasTransform;
[SerializeField]
private Transform partyDisplayPanel;

[SerializeField]
private GameObject partyInvitationPrefab;

#region Buttons

[SerializeField]
private Button friendsManagementButton;
[SerializeField]
private Button createPartyButton;
[SerializeField]
private Button leavePartyButton;
[SerializeField]
private Button exitButton;

#endregion

[SerializeField]
private Text partyIdText;

public Dictionary<string, string> partyMembers
{
private set; get;
}

/// <summary>
/// Display all Party Data to PartyList UI
/// </summary>
/// <param name="partyDataNotifResult"> </param>
public void DisplayPartyData(Result<PartyDataUpdateNotif> partyDataNotifResult)
{
// Update PartyID in UI
partyIdText.text = "PartyID: " + partyDataNotifResult.Value.partyId;

// Reset the Party List's UI
ResetPlayerEntryUI();

// Get all party members data based on _partyUserIds, then update data to UI
AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(partyDataNotifResult.Value.members, result =>
{
if (result.IsError)
{
Debug.Log($"Failed to get party member's data: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
// Initialize dictionary
partyMembers = new Dictionary<string, string>();

// Result data's order => reversed order of _partyIserIds
int index = result.Value.data.Length;
foreach (BaseUserInfo user in result.Value.data)
{
index -= 1;
// Get transform of PlayerEntryDisplay, which is child of PartyListPanel
Transform playerEntryDisplay = partyDisplayPanel.GetChild(index).transform;

if (user.userId == partyDataNotifResult.Value.leader)
{
// Set LeaderStatusIndicator as active
Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
leaderStatusIndicator.gameObject.SetActive(true);
}
else
{
if (AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId == partyDataNotifResult.Value.leader)
{
// Set PartyLeaderButton (promote button) as active, then add listener when onclick button
Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
partyLeaderButton.gameObject.SetActive(true);
partyLeaderButton.GetComponent<Button>().onClick.AddListener(() =>
{
PromotePartyLeader(user.userId);
});

// Set KickPartyButton as active, then add listener when onclick button
Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
kickPartyButton.gameObject.SetActive(true);
kickPartyButton.GetComponent<Button>().onClick.AddListener(() =>
{
KickParty(user.userId);
});
}
}

// Set DisplayNameText as active, then change text to User's Display Name
Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
displayNameText.gameObject.SetActive(true);
displayNameText.GetComponent<Text>().text = user.displayName;

partyMembers.Add(user.userId, user.displayName);
}
}
});
}

/// <summary>
/// Setup Party UI in Lobby and prepare state
/// </summary>
public void SetupParty()
{
friendsManagementButton.onClick.AddListener(() =>
{
GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Lobby);
LobbyWindow.SetActive(false);
GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
});
createPartyButton.onClick.AddListener(() => { CreateParty(); });
leavePartyButton.onClick.AddListener(() => { LeaveParty(); });
exitButton.onClick.AddListener(() =>
{
LobbyWindow.SetActive(false);
GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
});
}

/// <summary>
/// Create party and update partyID to UI
/// </summary>
public void CreateParty()
{
ResultCallback<PartyInfo> createPartyCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to create party: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully create a party");
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().CreateParty(createPartyCallback);
}

/// <summary>
/// Invite friend to party
/// </summary>
/// <param name="inviteeUserId"> userId of the user that will received the invitation</param>
public void InviteToParty(string inviteeUserId)
{
ResultCallback inviteToPartyCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to invite user to party: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully invite an invitee");
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().InviteToParty(inviteeUserId, inviteToPartyCallback);
}

public void KickParty(string memberUserId)
{
ResultCallback kickPartyMemberCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to kick user from party: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log($"Successfully kick member {memberUserId} from party");
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().KickPartyMember(memberUserId, kickPartyMemberCallback);
}

/// <summary>
/// Leave party
/// </summary>
public void LeaveParty()
{
ResultCallback leavePartyCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to leave party: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully leave from party");

// Reset all Party-related UIs
ResetPartyId();
ResetPlayerEntryUI();
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().LeaveParty(leavePartyCallback);
}

/// <summary>
/// Promote member to be a party leader
/// </summary>
/// <param name="memberUserId"> userId of the member that will be promoted as party leader</param>
public void PromotePartyLeader(string memberUserId)
{
ResultCallback<PartyPromoteLeaderResponse> promotePartyLeaderCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to promote member to be a party leader: error code: {result.Error.Code} message: {result.Error.Message}");

// Update the messageText that failed to promote leader
LobbyHandler.Instance.WriteLogMessage($"[Party] Failed to promote {partyMembers[memberUserId]} to be party leader", Color.black);
}
else
{
Debug.Log("Successfully promote member to be a party leader");

// Update the messageText that success to promote leader
LobbyHandler.Instance.WriteLogMessage($"[Party] Successfully promote {partyMembers[memberUserId]} to be party leader", Color.black);
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().PromotePartyLeader(memberUserId, promotePartyLeaderCallback);
}

/// <summary>
/// Reset Party ID's UI
/// </summary>
public void ResetPartyId()
{
partyIdText.text = "PartyID: ###############################";
partyMembers = null;
}

/// <summary>
/// Reset Party List's UI
/// </summary>
public void ResetPlayerEntryUI()
{
foreach (Transform playerEntryDisplay in partyDisplayPanel)
{
// Set LeaderStatusIndicator as not active
Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
leaderStatusIndicator.gameObject.SetActive(false);

// Set PartyLeaderButton (promote button) as not active, then remove all listener on button
Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
partyLeaderButton.gameObject.SetActive(false);
partyLeaderButton.GetComponent<Button>().onClick.RemoveAllListeners();

// Set KickPartyButton as not active, then remove all listener on button
Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
kickPartyButton.gameObject.SetActive(false);
kickPartyButton.GetComponent<Button>().onClick.RemoveAllListeners();

// Set DisplayNameText as not active and set value to default text
Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
displayNameText.gameObject.SetActive(false);
displayNameText.GetComponent<Text>().text = "PlayerUsername";
}
}

/// <summary>
/// Called on update when a party invitation is received
/// </summary>
public void InvitePartyNotification(PartyInvitation partyInvitation)
{
Debug.Log($"Invited by: {partyInvitation.from}");
PartyInvitationPanel invitationPanel = Instantiate(partyInvitationPrefab, canvasTransform).GetComponent<PartyInvitationPanel>();
invitationPanel.Setup(partyInvitation);
}

/// <summary>
/// Called on update when kicked from party
/// </summary>
public void KickPartyNotification()
{
Debug.Log("You're kicked from party");
ResetPartyId();
ResetPlayerEntryUI();
}

/// <summary>
/// Called on update when a friend joins the party
/// </summary>
public void JoinedPartyNotification(JoinNotification joinNotification)
{
Debug.Log("Invitee join a party");
AccelByteSDK.GetClientRegistry().GetApi().GetUser().GetUserByUserId(joinNotification.userID, result =>
{
if (result.IsError)
{
Debug.Log($"Failed to get user data: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
LobbyHandler.Instance.WriteLogMessage($"[Party] {result.Value.displayName} join the party", Color.black);
}
});
}


/// <summary>
/// Called on update when a friend leaves the party
/// </summary>
public void LeavePartyNotification(LeaveNotification leaveNotification)
{
if (leaveNotification.userID != AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId)
{
Debug.Log($"{leaveNotification.userID} leave the party");
LobbyHandler.Instance.WriteLogMessage($"[Party] {partyMembers[leaveNotification.userID]} leave the party", Color.black);
}
}

private void Start()
{
Lobby lobby = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
if (lobby.IsConnected)
{
lobby.GetPartyInfo(partyInfoResult =>
{
// Update PartyID in the UI
partyIdText.text = "PartyID: " + partyInfoResult.Value.partyID;

ResetPlayerEntryUI();

// Get all party members data based on _partyUserIds, then update data in the UI
AccelByteSDK.GetClientRegistry().GetApi().GetUser().BulkGetUserInfo(partyInfoResult.Value.members, result =>
{
if (result.IsError)
{
Debug.Log($"Failed to get party member's data: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
// Initialize dictionary
partyMembers = new Dictionary<string, string>();

// Result data's order => reversed order of _partyIserIds
int index = result.Value.data.Length;
foreach (BaseUserInfo user in result.Value.data)
{
index -= 1;
// Get transform of PlayerEntryDisplay, which is child of PartyListPanel
Transform playerEntryDisplay = partyDisplayPanel.GetChild(index).transform;

if (user.userId == partyInfoResult.Value.leaderID)
{
// Set LeaderStatusIndicator as active
Transform leaderStatusIndicator = playerEntryDisplay.GetChild(0).transform;
leaderStatusIndicator.gameObject.SetActive(true);
}
else
{
if (AccelByteSDK.GetClientRegistry().GetApi().GetUser().Session.UserId == partyInfoResult.Value.leaderID)
{
// Set PartyLeaderButton (promote button) as active, then add listener when onclick button
Transform partyLeaderButton = playerEntryDisplay.GetChild(1).transform;
partyLeaderButton.gameObject.SetActive(true);
partyLeaderButton.GetComponent<Button>().onClick.AddListener(() =>
{
PromotePartyLeader(user.userId);
});

// Set KickPartyButton as active, then add listener when onclick button
Transform kickPartyButton = playerEntryDisplay.GetChild(2).transform;
kickPartyButton.gameObject.SetActive(true);
kickPartyButton.GetComponent<Button>().onClick.AddListener(() =>
{
KickParty(user.userId);
});
}
}

// Set DisplayNameText as active, then change text to User's Display Name
Transform displayNameText = playerEntryDisplay.GetChild(3).transform;
displayNameText.gameObject.SetActive(true);
displayNameText.GetComponent<Text>().text = user.displayName;

partyMembers.Add(user.userId, user.displayName);
}
}
});
});
}
}
}
PartyInvitationPanel.cs
using UnityEngine;
using AccelByte.Core;
using AccelByte.Models;
using UnityEngine.UI;

public class PartyInvitationPanel : MonoBehaviour
{
[SerializeField]
private GameObject invitationPopUp;
[SerializeField]
private Text invitationText;
[SerializeField]
private Button acceptInvitationButton;
[SerializeField]
private Button rejectInvitationButton;

/// <summary>
/// Setup PartyInvitation's Popup UI and event listener
/// </summary>
public void Setup(PartyInvitation partyInvitation)
{
AccelByteSDK.GetClientRegistry().GetApi().GetUser().GetUserByUserId(partyInvitation.from, result =>
{
invitationText.text = result.Value.displayName + " invite you to join their party\nAccept invitation?";
});

acceptInvitationButton.onClick.AddListener(() => { JoinParty(partyInvitation); });
rejectInvitationButton.onClick.AddListener(() => { RejectPartyInvitation(partyInvitation); });
}

/// <summary>
/// Accept the invitation and join the party
/// </summary>
public void JoinParty(PartyInvitation partyInvitation)
{
ResultCallback<PartyInfo> joinPartyCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to join party: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully join the party");
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().JoinParty(partyInvitation.partyID, partyInvitation.invitationToken, joinPartyCallback);

// Destroy the PopUp prefab
Destroy(invitationPopUp);
}

/// <summary>
/// Reject the party invitation
/// </summary>
public void RejectPartyInvitation(PartyInvitation partyInvitation)
{
ResultCallback<PartyRejectResponse> rejectPartyCallback = result =>
{
// If there is an error, display the error information
if (result.IsError)
{
Debug.Log($"Failed to reject party invitation: error code: {result.Error.Code} message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully reject the party invitation");
}
};
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().RejectPartyInvitation(partyInvitation.partyID, partyInvitation.invitationToken, rejectPartyCallback);

// Destroy the PopUp prefab
Destroy(invitationPopUp);
}
}
LogMessagePanel.cs
using UnityEngine;
using UnityEngine.UI;

public class LogMessagePanel : MonoBehaviour
{
[SerializeField]
private Text messageText;

/// <summary>
/// Update Notification Message's UI
/// </summary>
/// <param name="text"> text that will be shown in the party notification</param>
public void UpdateNotificationUI(string text, Color color)
{
messageText.text = text;
messageText.color = color;
}
}
LobbyHandler.cs
using UnityEngine;
using AccelByte.Api;
using AccelByte.Core;

public class LobbyHandler : MonoBehaviour
{
/// <summary>
/// Private Instance
/// </summary>
static LobbyHandler instance;

/// <summary>
/// The Instance Getter
/// </summary>
public static LobbyHandler Instance => instance;

/// <summary>
/// The Instance Getter
/// </summary>
private Lobby lobby;

[HideInInspector]
public NotificationHandler notificationHandler;

public GameObject LobbyWindow;

#region Notification Box
[Header("Notification Box")]
[SerializeField]
private Transform notificationBoxContentView;
[SerializeField]
private GameObject logMessagePrefab;
#endregion

private void Awake()
{
// Check if another Instance is already created, and if so delete this one, otherwise destroy the object
if (instance != null && instance != this)
{
Destroy(this);
return;
}
else
{
instance = this;
}

// Get the the object handler
notificationHandler = gameObject.GetComponent<NotificationHandler>();
}

/// <summary>
/// Connect to the <see cref="Lobby"/> and setup CallBacks
/// </summary>
public void ConnectToLobby()
{
//Get a reference to the instance of the Lobby
lobby = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();

//Init menu handler
GetComponent<MenuHandler>().Create();
GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);

//Connection
lobby.Connected += notificationHandler.OnConnected;
lobby.Disconnecting += notificationHandler.OnDisconnecting;
lobby.Disconnected += notificationHandler.OnDisconnected;

// Friends
lobby.FriendsStatusChanged += notificationHandler.OnFriendsStatusChanged;
lobby.FriendRequestAccepted += notificationHandler.OnFriendRequestAccepted;
lobby.OnIncomingFriendRequest += notificationHandler.OnIncomingFriendRequest;
lobby.FriendRequestCanceled += notificationHandler.OnFriendRequestCanceled;
lobby.FriendRequestRejected += notificationHandler.OnFriendRequestRejected;
lobby.OnUnfriend += notificationHandler.OnUnfriend;

// Party
lobby.InvitedToParty += notificationHandler.OnInvitedToParty;
lobby.JoinedParty += notificationHandler.OnJoinedParty;
lobby.KickedFromParty += notificationHandler.OnKickedFromParty;
lobby.LeaveFromParty += notificationHandler.OnLeaveFromParty;
lobby.RejectedPartyInvitation += notificationHandler.OnRejectedPartyInvitation;
lobby.PartyDataUpdateNotif += notificationHandler.OnPartyDataUpdateNotif;

//Connect to the Lobby
if (!lobby.IsConnected)
{
lobby.Connect();
}
}

public void RemoveLobbyListeners()
{
//Remove delegate from Lobby
//Connection
lobby.Connected -= notificationHandler.OnConnected;
lobby.Disconnecting -= notificationHandler.OnDisconnecting;
lobby.Disconnected -= notificationHandler.OnDisconnected;

// Friends
lobby.FriendsStatusChanged -= notificationHandler.OnFriendsStatusChanged;
lobby.FriendRequestAccepted -= notificationHandler.OnFriendRequestAccepted;
lobby.OnIncomingFriendRequest -= notificationHandler.OnIncomingFriendRequest;
lobby.FriendRequestCanceled -= notificationHandler.OnFriendRequestCanceled;
lobby.FriendRequestRejected -= notificationHandler.OnFriendRequestRejected;
lobby.OnUnfriend -= notificationHandler.OnUnfriend;

// Party
lobby.InvitedToParty -= notificationHandler.OnInvitedToParty;
lobby.JoinedParty -= notificationHandler.OnJoinedParty;
lobby.KickedFromParty -= notificationHandler.OnKickedFromParty;
lobby.LeaveFromParty -= notificationHandler.OnLeaveFromParty;
lobby.RejectedPartyInvitation -= notificationHandler.OnRejectedPartyInvitation;
lobby.PartyDataUpdateNotif -= notificationHandler.OnPartyDataUpdateNotif;
}

public void DisconnectFromLobby()
{
if (lobby != null && lobby.IsConnected)
{
lobby.Disconnect();
}
}

/// <summary>
/// Write the log message on the notification box
/// </summary>
public void WriteLogMessage(string text, Color color)
{
LogMessagePanel logPanel = Instantiate(logMessagePrefab, notificationBoxContentView).GetComponent<LogMessagePanel>();
logPanel.UpdateNotificationUI(text, color);
}

private void OnApplicationQuit()
{
// Attempt to Disconnect from the Lobby when the Game Quits
DisconnectFromLobby();
}
}
MenuHandler.cs
using UnityEngine;
using UnityEngine.UI;

public class MenuHandler : MonoBehaviour
{
public Transform Menu;

public Button FriendsButton;
public Button LobbyButton;

private bool initialize = false;

public void Create()
{
if (initialize)
{
return;
}

LobbyButton.onClick.AddListener(() =>
{
GetComponent<PartyHandler>().SetupParty();
Menu.gameObject.SetActive(false);
GetComponent<LobbyHandler>().LobbyWindow.SetActive(true);
});

FriendsButton.onClick.AddListener(() =>
{
GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Menu);
Menu.gameObject.SetActive(false);
GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
});

initialize = true;
}
}
NotificationHandler.cs
using UnityEngine;
using AccelByte.Models;
using AccelByte.Core;

public class NotificationHandler : MonoBehaviour
{
#region Notifications

// Collection of connection notifications
#region Connections
/// <summary>
/// Called when lobby is connected
/// </summary>
public void OnConnected()
{
Debug.Log("Lobby Connected");
}

/// <summary>
/// Called when connection is disconnecting
/// </summary>
/// <param name="result"> Contains data of message</param>
public void OnDisconnecting(Result<DisconnectNotif> result)
{
Debug.Log($"Lobby Disconnecting {result.Value.message}");
}

/// <summary>
/// Called when connection is being disconnected
/// </summary>
/// <param name="result"> Contains data of websocket close code</param>
public void OnDisconnected(WsCloseCode result)
{
Debug.Log($"Lobby Disconnected: {result}");
}
#endregion

// Collection of friend notifications
#region Friends
/// <summary>
/// Called when friend status is changed
/// </summary>
/// <param name="result"> Contains data of user id, availability, status, etc</param>
public void OnFriendsStatusChanged(Result<FriendsStatusNotif> result)
{
GetComponent<FriendsManagementHandler>().UpdateFriends(result.Value);
}

/// <summary>
/// Called when friend request is accepted
/// </summary>
/// <param name="result"> Contains data of friend's user id</param>
public void OnFriendRequestAccepted(Result<Friend> result)
{
Debug.Log($"Accepted a Friend Request from user {result.Value.friendId}");
}

/// <summary>
/// Called when there is incomming friend request
/// </summary>
/// <param name="result"> Contains data of friend's user id</param>
public void OnIncomingFriendRequest(Result<Friend> result)
{
Debug.Log($"Received a Friend Request from user {result.Value.friendId}");
}

/// <summary>
/// Called when friend is unfriend
/// </summary>
/// <param name="result"> Contains data of friend's user id</param>
public void OnUnfriend(Result<Friend> result)
{
Debug.Log($"Unfriended User {result.Value.friendId}");
}

/// <summary>
/// Called when friend request is canceled
/// </summary>
/// <param name="result"> Contains data of sender user id</param>
public void OnFriendRequestCanceled(Result<Acquaintance> result)
{
Debug.Log($"Cancelled a Friend Request from user {result.Value.userId}");
}

/// <summary>
/// Called when friend request is rejected
/// </summary>
/// <param name="result"> Contains data of rejector user id</param>
public void OnFriendRequestRejected(Result<Acquaintance> result)
{
Debug.Log($"Rejected a Friend Request from user {result.Value.userId}");
}
#endregion

// Collection of party notifications
#region Party
/// <summary>
/// Called when user gets party invitation
/// </summary>
/// <param name="result"> Contains data of inviter, party id, and invitation token</param>
public void OnInvitedToParty(Result<PartyInvitation> result)
{
GetComponent<PartyHandler>().InvitePartyNotification(result.Value);
}

/// <summary>
/// Called when user joins the party
/// </summary>
/// <param name="result"> Contains data of joined user id</param>
public void OnJoinedParty(Result<JoinNotification> result)
{
GetComponent<PartyHandler>().JoinedPartyNotification(result.Value);
}

/// <summary>
/// Called when user is kicked by party leader
/// </summary>
/// <param name="result"> Contains data of party leader's user id, party id, and kicked user id</param>
public void OnKickedFromParty(Result<KickNotification> result)
{
GetComponent<PartyHandler>().KickPartyNotification();
}

/// <summary>
/// Called when user leaves the party
/// </summary>
/// <param name="result"> Contains data of party leader's user id and leaver user id</param>
public void OnLeaveFromParty(Result<LeaveNotification> result)
{
GetComponent<PartyHandler>().LeavePartyNotification(result.Value);
}

/// <summary>
/// Called when user rejects party invitation
/// </summary>
/// <param name="result"> Contains data of party id, party leader's user id, and rejector user id</param>
public void OnRejectedPartyInvitation(Result<PartyRejectNotif> result)
{
Debug.Log("[Party-Notification] Invitee rejected a party invitation");
}

/// <summary>
/// Called when party data is updated
/// </summary>
/// <param name="result"> Contains data of updated party</param>
public void OnPartyDataUpdateNotif(Result<PartyDataUpdateNotif> result)
{
GetComponent<PartyHandler>().DisplayPartyData(result);
}
#endregion

#endregion
}
FriendsManagementHandler.cs
using AccelByte.Api;
using AccelByte.Models;
using AccelByte.Core;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using System.Collections.Generic;
using Button = UnityEngine.UI.Button;

public class FriendsManagementHandler : MonoBehaviour
{
private Dictionary<string, FriendStatusPanel> friendUIDictionary = new Dictionary<string, FriendStatusPanel>();

[Header("Panels")]
public GameObject FriendsManagementWindow;

[FormerlySerializedAs("FriendsPanel")]
[SerializeField]
private RectTransform friendsPanel;
[SerializeField]
private RectTransform requestsPanel;
[SerializeField]
private RectTransform blockedPanel;

#region Buttons
[Header("Buttons")]

[SerializeField]
private Button friendsTabButton;
[SerializeField]
private Button pendingTabButton;
[SerializeField]
private Button blockedTabButton;

[SerializeField]
private Button exitButton;

#endregion

[Header("Friends Panel")]
[SerializeField]
private Transform friendsDisplayPanel;

[FormerlySerializedAs("FriendsDisplayPrefab")]
[SerializeField]
private GameObject friendsDisplayPrefab;

[SerializeField]
private Button friendsSearchButton;

[SerializeField]
private Transform friendsDisplayPlaceholder;

private bool isInitialized = false;

#region Search
[Header("Search Panel")]
[SerializeField]
private GameObject addFriendsPrefab;
[SerializeField]
private Transform friendsSearchPanel;

[SerializeField]
private InputField friendsSearchInputField;
[SerializeField]
private RectTransform friendsSearchScrollView;
[SerializeField]
private Button friendsSearchQuitButton;

[SerializeField]
private Transform friendsSearchPlaceholder;

#endregion

#region Pending
[Header("Pending Panel")]
[SerializeField]
private RectTransform pendingIncomingRequestsContent;
[SerializeField]
private RectTransform pendingOutgoingRequestsContent;

[SerializeField]
private GameObject pendingIncomingRequestsPrefab;
[SerializeField]
private GameObject pendingOutgoingRequestsPrefab;

[SerializeField]
private Transform pendingIncomingRequestPlaceholder;
[SerializeField]
private Transform pendingOutgoingRequestPlaceholder;

[SerializeField]
private Text pendingIncomingRequestText;
[SerializeField]
private Text pendingOutgoingRequestText;

#endregion

#region Blocked
[Header("Blocked")]
[SerializeField]
private RectTransform blockedContent;

[SerializeField]
private GameObject blockedUserPrefab;

[SerializeField]
private Transform blockedDisplayPlaceholder;

#endregion

#region ExitMode
public enum ExitMode
{
Menu,
Lobby
}

private ExitMode ExitScreen
{
get => ExitScreen;
set
{
switch (value)
{
case ExitMode.Menu:
FriendsManagementWindow.SetActive(false);
GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
break;

case ExitMode.Lobby:
FriendsManagementWindow.SetActive(false);
GetComponent<LobbyHandler>().LobbyWindow.SetActive(true);
break;
}
}
}
#endregion

private bool setupStatus = false;

public enum FriendsMode
{
Default,
Friends,
Pending,
Blocked
};

private FriendsMode displayMode = FriendsMode.Default;

private FriendsMode DisplayMode
{
get => displayMode;
set
{
switch (value)
{
case FriendsMode.Default:
friendsPanel.gameObject.SetActive(true);
requestsPanel.gameObject.SetActive(false);
blockedPanel.gameObject.SetActive(false);
break;

case FriendsMode.Friends:
friendsPanel.gameObject.SetActive(true);
requestsPanel.gameObject.SetActive(false);
blockedPanel.gameObject.SetActive(false);
GetFriends();
break;

case FriendsMode.Pending:
requestsPanel.gameObject.SetActive(true);
friendsPanel.gameObject.SetActive(false);
blockedPanel.gameObject.SetActive(false);
DisplayPending();
break;

case FriendsMode.Blocked:
blockedPanel.gameObject.SetActive(true);
friendsPanel.gameObject.SetActive(false);
requestsPanel.gameObject.SetActive(false);
DisplayBlocked();
break;
}
}
}

/// <summary>
/// Setup UI and prepare State
/// </summary>
/// <param name="exitType"> name of the destination panel</param>
public void Setup(ExitMode exitType)
{
// Reset the exit button's listener, then add the listener based on the exit screen type
exitButton.onClick.RemoveAllListeners();
exitButton.onClick.AddListener(() =>
{
FriendsManagementWindow.SetActive(false);
GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
ExitScreen = exitType;
});

// Check whether the FriendsPanel is already set up or not
if (setupStatus)
{
DisplayMode = FriendsMode.Default;
return;
}
// Run the setup if it hasn't
else
{
setupStatus = true;

DisplayMode = FriendsMode.Friends;

// Reset listeners, so it won't be triggered more than once
friendsTabButton.onClick.RemoveAllListeners();
pendingTabButton.onClick.RemoveAllListeners();
blockedTabButton.onClick.RemoveAllListeners();

// Add the listeners
friendsTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Friends);
pendingTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Pending);
blockedTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Blocked);
friendsSearchPanel.gameObject.SetActive(false);
friendsDisplayPlaceholder.gameObject.SetActive(true);
friendsSearchQuitButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(false));
friendsSearchButton.onClick.AddListener(DisplaySearch);
friendsSearchInputField.onEndEdit.AddListener(SearchForFriends);
friendsSearchButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(true));
}
}

/// <summary>
/// Get Friends and Display them
/// </summary>
public void GetFriends()
{
// Cleanup First
LoopThroughTransformAndDestroy(friendsDisplayPanel.transform, friendsDisplayPlaceholder);

AccelByteSDK.GetClientRegistry().GetApi().GetLobby().LoadFriendsList(result =>
{
if (!result.IsError)
{
// Check if no friends were returned
if (result.Value.friendsId.Length <= 0)
{
// Display the Placeholder Text
friendsDisplayPlaceholder.gameObject.SetActive(true);
return;
}

User user = AccelByteSDK.GetClientRegistry().GetApi().GetUser();

// Send requests to create UI for each friend
Debug.Log("Loaded Friends List Successfully");

var userIds = new System.Collections.Generic.List<string>();
foreach (string friendID in result.Value.friendsId)
{
userIds.Add(friendID);

user.GetUserByUserId(friendID, x =>
{
CreateFriendUI(x.Value);
});
}

AccelByteSDK.GetClientRegistry().GetApi().GetLobby().BulkGetUserPresence(userIds.ToArray(), bulkResult =>
{
if (!bulkResult.IsError)
{
foreach (var friend in bulkResult.Value.data)
{
user.GetUserByUserId(friend.userID, x =>
{
Debug.Log($"Friend : {x.Value.displayName}, Status : {friend.activity}, and Availability : {friend.availability.ToString()}");
});
}
}
});
}
else
{
Debug.LogWarning("Error in Getting Friends");
}
});
}

public void UpdateFriends(FriendsStatusNotif notification)
{
// Find the friend and update it's UI
if (friendUIDictionary.ContainsKey(notification.userID))
{
friendUIDictionary[notification.userID].SetOnlineStatus(notification);
}
// Otherwise we should handle this in some way, possibly creating a Friend UI piece
else
{
Debug.Log("Unregistered Friend received a Notification");
}
}

private void ListQueriedusers(PagedPublicUsersInfo pagedInfo)
{
// Cleanup First
LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);

if (pagedInfo.data.Length <= 0)
{
friendsSearchPlaceholder.gameObject.SetActive(true);
}
else
{
friendsSearchPlaceholder.gameObject.SetActive(false);
foreach (PublicUserInfo info in pagedInfo.data)
{
FriendsAddPanel addPanel = Instantiate(addFriendsPrefab, friendsSearchScrollView).GetComponent<FriendsAddPanel>();
addPanel.Create(info);
}
}
}

private void SearchForFriends(string query)
{
AccelByteSDK.GetClientRegistry().GetApi().GetUser().SearchUsers(query, result =>
{
if (!result.IsError)
{
ListQueriedusers(result.Value);
}
else
{
Debug.LogWarning($"Unable to Query Users Code: {result.Error.Code}, Message: {result.Error.Message}");
}
});
}

private void DisplaySearch()
{
friendsSearchPanel.gameObject.SetActive(true);
LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);
friendsSearchPlaceholder.gameObject.SetActive(true);
}

private void CreateFriendUI(PublicUserData userData)
{
FriendStatusPanel panel = Instantiate(friendsDisplayPrefab, friendsDisplayPanel).GetComponent<FriendStatusPanel>();
panel.Create(userData);
if (!friendUIDictionary.ContainsKey(userData.userId))
{
friendUIDictionary.Add(userData.userId, panel);
}

// Set up the FriendStatusPanel's related button
panel.SetupButton();
}

/// <summary>
/// A utility function to Destroy all Children of the parent transform. Optionally do not remove a specific Transform
/// </summary>
/// <param name="parent">Parent Object to destroy children</param>
/// <param name="doNotRemove">Optional specified Transform that should NOT be destroyed</param>
private static void LoopThroughTransformAndDestroy(Transform parent, Transform doNotRemove = null)
{
// Loop through all the children and add them to a List to then be deleted
List<GameObject> toBeDeleted = new List<GameObject>();
foreach (Transform t in parent)
{
//except the Do Not Remove transform if there is one
if (t != doNotRemove)
{
toBeDeleted.Add(t.gameObject);
}
}
// Loop through list and Delete all Children
for (int i = 0; i < toBeDeleted.Count; i++)
{
Destroy(toBeDeleted[i]);
}
}

private void DisplayPending()
{
LoopThroughTransformAndDestroy(pendingIncomingRequestsContent.transform, pendingIncomingRequestPlaceholder);
LoopThroughTransformAndDestroy(pendingOutgoingRequestsContent.transform, pendingOutgoingRequestPlaceholder);

Lobby lobby = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
// Get all Incoming Friend Requests
lobby.ListIncomingFriends(result =>
{
// Check for an Error
if (result.IsError)
{
Debug.LogWarning($"Unable to get Incoming Requests Code: {result.Error.Code}, Message: {result.Error.Message}");

// Set the placeholder Text to be active so it doesn't look broken
pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
}
else
{
if (result.Value.friendsId.Length <= 0)
{
pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
}
else
{
pendingIncomingRequestPlaceholder.gameObject.SetActive(false);
}

// Loop through all the UserID's returned by the Friends callback and get their PublicUserData
User user = AccelByteSDK.GetClientRegistry().GetApi().GetUser();
foreach (string userID in result.Value.friendsId)
{
// Request the PublicUserData for the specific Friend
user.GetUserByUserId(userID, userResult =>
{
// If there's an error, report it and do nothing else
if (userResult.IsError)
{
Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
}
// If we have valid data, log the display name
else
{
Debug.Log($"Friend request from : {userResult.Value.displayName}");
FriendsOutgoingPanel outgoingPanel = Instantiate(pendingOutgoingRequestsPrefab, pendingOutgoingRequestsContent).GetComponent<FriendsOutgoingPanel>();
outgoingPanel.Create(userResult.Value);
}
});
}
}
});

// Get all outgoing friend requests
lobby.ListOutgoingFriends(result =>
{
// Check for an error
if (result.IsError)
{
Debug.LogWarning($"Unable to get Outgoing Friend Lists Code: {result.Error.Code}, Message: {result.Error.Message}");
}
else
{
if (result.Value.friendsId.Length <= 0)
{
pendingOutgoingRequestPlaceholder.gameObject.SetActive(true);
}
else
{
pendingOutgoingRequestPlaceholder.gameObject.SetActive(false);
}

User user = AccelByteSDK.GetClientRegistry().GetApi().GetUser();
// Loop through all the UserID's returned by the Friends callback and get their PublicUserData
foreach (string userID in result.Value.friendsId)
{
// Request the PublicUserData for the specific Friend
user.GetUserByUserId(userID, userResult =>
{
// If it's an error, report it and do nothing else
if (userResult.IsError)
{
Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
}
// If we have valid data, log the display name
else
{
Debug.Log($"Pending Friend Request to : {userResult.Value.displayName}");
FriendsOutgoingPanel outgoingPanel = Instantiate(pendingOutgoingRequestsPrefab, pendingOutgoingRequestsContent).GetComponent<FriendsOutgoingPanel>();
outgoingPanel.Create(userResult.Value);
}
});
}
}
});
}

private void DisplayBlocked()
{
// Cleanup First
LoopThroughTransformAndDestroy(blockedContent.transform, blockedDisplayPlaceholder);

// Get Blocked List
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().GetListOfBlockedUser(result =>
{
if (result.IsError)
{
Debug.LogWarning($"Unable to get Blocked Player List Code: {result.Error.Code}, Message: {result.Error.Message}");

blockedDisplayPlaceholder.gameObject.SetActive(true);
}
else
{
blockedDisplayPlaceholder.gameObject.SetActive(false);
User user = AccelByteSDK.GetClientRegistry().GetApi().GetUser();
// Loop through all the UserID's returned by the callback and get their PublicUserData
foreach (BlockedData blockedUser in result.Value.data)
{
// Request the PublicUserData for the specific User
user.GetUserByUserId(blockedUser.blockedUserId, userResult =>
{
// If it's an Error, report it and do nothing else
if (userResult.IsError)
{
Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
}
else
{
// If we have valid data, instantiate the prefab for the specific UI piece and call relevant functions
FriendsBlockedPanel blockedPanel = Instantiate(blockedUserPrefab, blockedContent).GetComponent<FriendsBlockedPanel>();
// Pass the PublicUserData into this function
blockedPanel.Create(userResult.Value);
}
});
}
}
});
}
}
FriendStatusPanel.cs
using AccelByte.Models;
using AccelByte.Core;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;

public class FriendStatusPanel : MonoBehaviour
{
PublicUserData userData;

[SerializeField]
private Image profilePicture;
[SerializeField]
private Image statusDisplay;

[SerializeField]
private Button chatButton;
[SerializeField]
private Button inviteToPartyButton;
[SerializeField]
private Button unfriendButton;
[SerializeField]
private Button blockButton;

[SerializeField]
private Text displayNameText;
[SerializeField]
private Text onlineStatusText;

public void SetOnlineStatus(FriendsStatusNotif notification)
{
switch (notification.availability)
{
case UserStatus.Offline:
onlineStatusText.text = "Offline";
statusDisplay.color = Color.black;
break;
case UserStatus.Online:
onlineStatusText.text = "Online";
statusDisplay.color = Color.green;
break;
case UserStatus.Busy:
onlineStatusText.text = "Busy";
statusDisplay.color = Color.yellow;
break;
case UserStatus.Invisible:
onlineStatusText.text = "Offline";
statusDisplay.color = Color.black;
break;
default:
onlineStatusText.text = $"INVALID UNHANDLED {notification.availability}";
statusDisplay.color = Color.magenta;
break;
}
Debug.Log($"Friend Status for {notification.userID} changed to {notification.availability}");
}

public void Create(PublicUserData publicUserData)
{
userData = publicUserData;
displayNameText.text = userData.displayName;
}

/// <summary>
/// Setup UI Button Listener
/// </summary>
public void SetupButton()
{
unfriendButton.onClick.AddListener(() =>
{
AccelByteSDK.GetClientRegistry().GetApi().GetLobby().Unfriend(userData.userId, result =>
{
if (result.IsError)
{
Debug.Log($"Failed to unfriend a friend: code: {result.Error.Code}, message: {result.Error.Message}");
}
else
{
Debug.Log("Successfully unfriend a friend!");
LobbyHandler.Instance.GetComponent<FriendsManagementHandler>().GetFriends();
}
});
});
}
}