Skip to main content

Use the SDK to play with party - Playing with party - (Unity module)

Last updated on November 7, 2025

Unwrap the wrapper

In this section, you will learn how to implement playing with a party using the AccelByte Gaming Services (AGS) Software Development Kit (SDK). In the Byte Wars project, there is already a wrapper class named PlayingWithPartyWrapper that contains the complete party playing implementations. In this tutorial, you will use the starter version of that wrapper so you can implement playing with a party from scratch.

Enable tutorial starter mode

To follow this tutorial, you first need to enable the starter mode. To do this, build your project and open it in the Unity Editor.

In the editor, go to Assets/Resources/Modules/Play/PlayingWithParty and open the PlayingWithPartyAssetConfig.asset. Then, make sure to activate the module in starter mode by ticking the Is Active checkbox and the Is Starter Active checkbox.

What's in the starter pack

To follow this tutorial, you will use the starter wrapper class called PlayingWithPartyWrapper_Starter. This class inherits from the SessionEssentialsWrapper class, which contains basic session implementations (create, join, invite to game sessions) and is defined in the following file:

  • C# file: Assets/Resources/Modules/Play/SessionEssentials/Scripts/SessionEssentialsWrapper.cs

The SessionEssentialsWrapper class itself inherits from the AccelByteWarsOnlineSession class, which is defined in the following file:

  • C# file: Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSession.cs

The PlayingWithPartyWrapper_Starter class is defined in the file below:

  • C# file: Assets/Resources/Modules/Play/PlayingWithParty/Scripts/PlayingWithPartyWrapper_Starter.cs

There is also a model class containing helper constant variables and structs defined in the file below:

  • C# file: Assets/Resources/Modules/Play/PlayingWithParty/Scripts/PlayingWithPartyWrapperModels.cs

The AccelByteWarsOnlineSession class is a base abstract class for session and party-related logic. It contains several predefined helper components, including:

  • Helper variables for referencing AGS SDK interfaces. These variables are assigned during initialization:

    protected static User User;
    protected static Lobby Lobby;
    protected static Session Session;

    protected virtual void Awake()
    {
    ...
    User ??= AccelByteSDK.GetClientRegistry().GetApi().GetUser();
    Lobby ??= AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
    Session ??= AccelByteSDK.GetClientRegistry().GetApi().GetSession();
    ...
    }
  • Helper property to get cached party session and party statuses.

    public static SessionV2PartySession CachedParty { get; protected set; } = null;

    public static bool IsInParty => CachedParty != null && CachedParty.members.Select(m => m.StatusV2 == SessionV2MemberStatus.JOINED).ToArray().Length > 1;

    public static bool IsPartyLeader => IsInParty && GameData.CachedPlayerState.PlayerId == CachedParty.leaderId;

The AccelByteWarsOnlineSession class is a base abstract class for session and party-related logic. It contains several predefined helper components, including:

  • Helper variables for referencing AGS SDK interfaces. These variables are assigned during initialization:

    protected static User User;
    protected static Lobby Lobby;
    protected static Session Session;

    protected virtual void Awake()
    {
    ...
    User ??= AccelByteSDK.GetClientRegistry().GetApi().GetUser();
    Lobby ??= AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
    Session ??= AccelByteSDK.GetClientRegistry().GetApi().GetSession();
    ...
    }

The PlayingWithPartyWrapperModels class includes several helpers, including:

  • Strings to display party-related pop-up messages.

    public static readonly string PartyMembersGameSessionStatusesKey = "party_member_game_session_statuses";

    public static readonly string PartyMatchmakingStartedMessage = "Party Matchmaking Started by Party Leader";
    public static readonly string PartyMatchmakingSuccessMessage = "Party Matchmaking Found, Joining Match";
    public static readonly string PartyMatchmakingFailedMessage = "Party Matchmaking failed";
    public static readonly string PartyMatchmakingCanceledMessage = "Party Matchmaking is canceled by party leader";
    public static readonly string PartyMatchmakingExpiredMessage = "Party Matchmaking expired";
    public static readonly string PartyMatchmakingSafeguardMessage = "Matchmaking with this game mode is not supported when in a party";

    public static readonly string JoinPartyGameSessionMessage = "Joining Party Leader Game Session";
    public static readonly string JoinPartyGameSessionFailedMessage = "Failed to Join Party Game Session";
    public static readonly string JoinPartyGameSessionCanceledMessage = "Party game session is canceled by party leader";
    public static readonly string JoinPartyGameSessionWaitServerMessage = "Joined Party Game Session. Waiting for Server";
    public static readonly string JoinPartyGameSessionServerErrorMessage = "Party Game Session failure. Cannot find game server.";
    public static readonly string JoinPartyGameSessionSafeguardMessage = "Cannot join session. Insufficient slots to join with party";

    public static readonly string PartyGameSessionLeaderSafeguardMessage = "Cannot play online session since party members are on other session";
    public static readonly string PartyGameSessionMemberSafeguardMessage = "Only party leader can start online session";

Implement game sessions with party

In this section, you will learn how to create and join game sessions with a party.

  1. You already implemented creating and joining game sessions in the Introduction to Session module. When creating a game session, you can pass an additional parameter to include all party members to be invited. In the CreateMatchSession() function of that module wrapper, add the code to set the teams parameter. With this parameter, the backend will send the game session invitation to all players included in it. This way, when the party leader creates a game session, the members will be invited as well.

    public override void CreateGameSession(
    SessionV2GameSessionCreateRequest request,
    ResultCallback<SessionV2GameSession> onComplete)
    {
    // ..

    // Add party session id for playing with party feature.
    if (CachedParty != null && !string.IsNullOrEmpty(CachedParty.id))
    {
    request.teams = new SessionV2TeamData[]
    {
    new SessionV2TeamData
    {
    TeamId = CachedParty.id,
    userIds = CachedParty.members.Where(m => m.StatusV2 == SessionV2MemberStatus.JOINED).Select(m => m.id).ToArray()
    }
    };
    }

    // ..
    }
  2. When the party members receive the game session invitation from the backend, we want the player to join the game session. To do this, open the PlayingWithPartyWrapper_Starter class and create the function below.

    private void OnInvitedToPartyGameSession(Result<SessionV2GameInvitationNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to handle party game session invitation event. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }

    BytewarsLogger.Log($"Received party game session session invitation.");

    /* If the player is a party member, join the party leader's game session
    * only if the player is not currently in any session, including offline single-player mode. */
    if (GameData.GameModeSo?.GameMode != GameModeEnum.MainMenu &&
    CachedSession == null && !IsPartyLeader)
    {
    Session.GetGameSessionDetailsBySessionId(result.Value.sessionId, (sessionResult) =>
    {
    if (sessionResult.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to get game session details. Error {sessionResult.Error.Code}: {sessionResult.Error.Message}");
    return;
    }

    // If the session is not from party leader, ignore it.
    if (!sessionResult.Value.members.Select(m => m.id).Contains(CachedParty?.leaderId))
    {
    return;
    }

    MenuManager.Instance.PromptMenu.ShowLoadingPrompt(PlayingWithPartyModels.JoinPartyGameSessionMessage);

    JoinGameSession(sessionResult.Value.id, (joinResult) =>
    {
    if (joinResult.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to join party leader game session. Error {joinResult.Error.Code}: {joinResult.Error.Message}");
    MenuManager.Instance.PromptMenu.HidePromptMenu();
    MenuManager.Instance.PushNotification(new PushNotificationModel()
    {
    Message = PlayingWithPartyModels.JoinPartyGameSessionFailedMessage
    });
    return;
    }

    // Handle travel to server or host.
    SessionV2GameSession gameSession = joinResult.Value;
    switch (gameSession.configuration.type)
    {
    case SessionConfigurationTemplateType.DS:
    if (gameSession.dsInformation == null || gameSession.dsInformation.status != SessionV2DsStatus.AVAILABLE)
    {
    Lobby.SessionV2DsStatusChanged += OnDSStatusChangedReceived;
    MenuManager.Instance.PromptMenu.ShowLoadingPrompt(
    PlayingWithPartyModels.JoinPartyGameSessionWaitServerMessage, true,
    PromptMenuCanvas.DefaultCancelMessage, () =>
    {
    Lobby.SessionV2DsStatusChanged -= OnDSStatusChangedReceived;
    LeaveGameSession(gameSession.id, null);
    });
    }
    else
    {
    TravelToDS(gameSession, AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(gameSession));
    }
    break;
    case SessionConfigurationTemplateType.P2P:
    TravelToP2PHost(gameSession, AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(gameSession));
    break;
    default:
    BytewarsLogger.LogWarning($"Failed to travel. Unsupported game session server configuration type.");
    break;
    }
    });
    });
    }
    }
  3. From the function above, it also listens to an event in case the game session doesn't have an available game server to join yet. So, let's create the function to handle this event by adding the code below.

    private void OnDSStatusChangedReceived(Result<SessionV2DsStatusUpdatedNotification> result)
    {
    if (result.IsError)
    {
    BytewarsLogger.LogWarning(
    $"Failed to handle dedicated server status changed event. " +
    $"Error {result.Error.Code}: {result.Error.Message}");
    Lobby.SessionV2DsStatusChanged -= OnDSStatusChangedReceived;
    MenuManager.Instance.PromptMenu.ShowPromptMenu(
    PromptMenuCanvas.DefaultErrorPromptMessage,
    SessionEssentialsModels.FailedToFindServerMessage,
    PromptMenuCanvas.DefaultOkMessage, null);
    return;
    }

    SessionV2GameSession session = result.Value.session;
    SessionV2DsInformation dsInfo = session.dsInformation;

    // Check if the requested game mode is supported.
    InGameMode requestedGameMode = AccelByteWarsOnlineSessionModels.GetGameSessionGameMode(session);
    if (requestedGameMode == InGameMode.None)
    {
    BytewarsLogger.LogWarning(
    $"Failed to handle dedicated server status changed event. " +
    $"Session's game mode is not supported by the game.");
    Lobby.SessionV2DsStatusChanged -= OnDSStatusChangedReceived;
    MenuManager.Instance.PromptMenu.ShowPromptMenu(
    PromptMenuCanvas.DefaultErrorPromptMessage,
    SessionEssentialsModels.FailedToFindServerMessage,
    PromptMenuCanvas.DefaultOkMessage, null);
    return;
    }

    // Check the dedicated server status.
    switch (dsInfo.StatusV2)
    {
    case SessionV2DsStatus.AVAILABLE:
    Lobby.SessionV2DsStatusChanged -= OnDSStatusChangedReceived;
    TravelToDS(session, requestedGameMode);
    break;
    case SessionV2DsStatus.FAILED_TO_REQUEST:
    case SessionV2DsStatus.ENDED:
    case SessionV2DsStatus.UNKNOWN:
    Lobby.SessionV2DsStatusChanged -= OnDSStatusChangedReceived;
    BytewarsLogger.LogWarning(
    $"Failed to handle dedicated server status changed event. " +
    $"Session failed to request for dedicated server due to unknown reason.");
    MenuManager.Instance.PromptMenu.ShowPromptMenu(
    PromptMenuCanvas.DefaultErrorPromptMessage,
    SessionEssentialsModels.FailedToFindServerMessage,
    PromptMenuCanvas.DefaultOkMessage, null);
    break;
    default:
    BytewarsLogger.Log($"Received dedicated server status change. Status: {dsInfo.StatusV2}");
    break;
    }
    }
  4. Unlike creating a game session, you cannot pass any parameters when sending a join session request. Thus, when the party leader joins an existing game session, they need to manually invite the party members if you want them to join the same game session. To do this, create the function below in the PlayingWithPartyWrapper_Starter class.

    private void OnPartyGameSessionJoined(Result<SessionV2GameJoinedNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to handle party game session joined event. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }

    BytewarsLogger.Log($"Received game session joined event.");

    if (IsPartyLeader)
    {
    HashSet<string> partyMemberIds = CachedParty?.members.Where(m => m.StatusV2 == SessionV2MemberStatus.JOINED).Select(m => m.id).ToHashSet();
    HashSet<string> sessionMemberIds = result.Value.members.Select(m => m.id).ToHashSet();

    // Send manual invites to party members not yet invited.
    string[] uninvitedPartyMemberIds = partyMemberIds?.Where(id => !sessionMemberIds.Contains(id)).ToArray();
    if (uninvitedPartyMemberIds.Length > 0)
    {
    foreach (string memberId in uninvitedPartyMemberIds)
    {
    SendGameSessionInvite(result.Value.sessionId, memberId, null);
    }
    }
    }
    }
  5. Bind the functions above to their respective events when the wrapper is initialized. You can do this in the OnEnable() function.

    private void OnEnable()
    {
    ...
    if (Lobby != null)
    {
    // Bind game session events.
    Lobby.SessionV2InvitedUserToGameSession += OnInvitedToPartyGameSession;
    Lobby.SessionV2UserJoinedGameSession += OnPartyGameSessionJoined;
    }
    }
  6. Finally, unbind the functions above from their respective events when the wrapper is deinitialized. You can do this in the OnDisable() function.

    private void OnDisable()
    {
    ...
    if (Lobby != null)
    {
    // Unbind game session events.
    Lobby.SessionV2InvitedUserToGameSession -= OnInvitedToPartyGameSession;
    Lobby.SessionV2UserJoinedGameSession -= OnPartyGameSessionJoined;
    }
    }

Implement matchmaking with a party

In this section, you will learn how to perform matchmaking with a party.

  1. To enable matchmaking with a party, you need to implement the party system first. For more information, please revisit the Introduction to Party module.

  2. Then, when creating a match ticket, you need to pass the party ID to the optional match ticket parameters. Revisit the Quick Match with Dedicated Servers and Quick Match with Peer-to-Peer modules to learn how to create a match ticket for matchmaking. In the StartMatchmaking() function of those modules' wrappers, add the code below to set the match ticket's sessionId parameter with the player's party ID before starting the matchmaking process.

    public override void StartMatchmaking(
    string matchPool,
    ResultCallback<MatchmakingV2CreateTicketResponse> onComplete)
    {
    ...

    MatchmakingV2CreateTicketRequestOptionalParams optionalParams = new() { attributes = new() };

    ...

    // Add party session id for playing with party feature.
    if (CachedParty != null && !string.IsNullOrEmpty(CachedParty.id))
    {
    optionalParams.sessionId = CachedParty.id;
    }

    ...
    }
  3. By setting the party ID to the match ticket's sessionId parameter, the backend will matchmake the entire party and automatically invite each member. When a member receives the invitation, they need to acknowledge it and join the game session. You already handled this in the previous section.

  4. Now that the matchmaking setup is complete, let's create several functions to handle party matchmaking events. Open the PlayingWithPartyWrapper_Starter class and create the function below to be called when matchmaking is started. This function will display a prompt to the party members indicating that the party leader has initiated matchmaking.

    private void OnPartyMatchmakingStarted(Result<MatchmakingV2MatchmakingStartedNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to start party matchmaking. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }

    BytewarsLogger.Log($"Party matchmaking started.");

    /* Show notification that the party matchmaking is started.
    * Only show the notification if the player is a party member.*/
    if (!IsPartyLeader)
    {
    MenuManager.Instance.PromptMenu.ShowLoadingPrompt(PlayingWithPartyModels.PartyMatchmakingStartedMessage);
    }
    }
  5. Still in the same class, create the function below to display a prompt message to the party members indicating whether the matchmaking has completed successfully or failed.

    private void OnPartyMatchmakingFound(Result<MatchmakingV2MatchFoundNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to party matchmaking. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }
    else
    {
    BytewarsLogger.Log($"Party matchmaking found. Currently joining the match.");
    }

    /* Show notification that the party matchmaking is completed.
    * Only show the notification if the player is a party member.*/
    if (!IsPartyLeader)
    {
    if (result.IsError)
    {
    MenuManager.Instance.PromptMenu.HidePromptMenu();
    MenuManager.Instance.PushNotification(new PushNotificationModel()
    {
    Message = PlayingWithPartyModels.PartyMatchmakingFailedMessage,
    UseDefaultIconOnEmpty = false
    });
    }
    else
    {
    MenuManager.Instance.PromptMenu.ShowLoadingPrompt(PlayingWithPartyModels.PartyMatchmakingSuccessMessage);
    }
    }
    }
  6. Then, create the function below to display a prompt to the party members indicating that the party leader has canceled the matchmaking.

    private void OnPartyMatchmakingCanceled(Result<MatchmakingV2MatchmakingCanceledNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to cancel party matchmaking. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }

    BytewarsLogger.Log($"Party matchmaking canceled.");

    /* Show notification that the party matchmaking is canceled.
    * Only show the notification if the player is a party member.*/
    if (!IsPartyLeader)
    {
    MenuManager.Instance.PromptMenu.HidePromptMenu();
    MenuManager.Instance.PushNotification(new PushNotificationModel()
    {
    Message = PlayingWithPartyModels.PartyMatchmakingCanceledMessage,
    UseDefaultIconOnEmpty = false
    });
    }
    }
  7. Next, create the function below to display a prompt to the party members indicating that the matchmaking failed because the match ticket has expired.

    private void OnPartyMatchmakingExpired(Result<MatchmakingV2TicketExpiredNotification> result)
    {
    if (!IsInParty)
    {
    return;
    }

    if (result.IsError)
    {
    BytewarsLogger.LogWarning($"Failed to handle party matchmaking expired event. Error {result.Error.Code}: {result.Error.Message}");
    return;
    }

    BytewarsLogger.Log($"Party matchmaking expired.");

    if (!IsPartyLeader)
    {
    MenuManager.Instance.PromptMenu.HidePromptMenu();
    MenuManager.Instance.PushNotification(new PushNotificationModel()
    {
    Message = PlayingWithPartyModels.PartyMatchmakingExpiredMessage,
    UseDefaultIconOnEmpty = false
    });
    }
    }
  8. Bind the functions above to their respective events when the wrapper is initialized. You can do this in the OnEnable() function.

    private void OnEnable()
    {
    ...
    if (Lobby != null)
    {
    // Bind party matchmaking events.
    Lobby.MatchmakingV2MatchmakingStarted += OnPartyMatchmakingStarted;
    Lobby.MatchmakingV2MatchFound += OnPartyMatchmakingFound;
    Lobby.MatchmakingV2MatchmakingCanceled += OnPartyMatchmakingCanceled;
    Lobby.MatchmakingV2TicketExpired += OnPartyMatchmakingExpired;
    }
    }
  9. Finally, unbind the functions above from their respective events when the wrapper is deinitialized. You can do this in the OnDisable() function.

    private void OnDisable()
    {
    ...
    if (Lobby != null)
    {
    // Unbind party matchmaking events.
    Lobby.MatchmakingV2MatchmakingStarted -= OnPartyMatchmakingStarted;
    Lobby.MatchmakingV2MatchFound -= OnPartyMatchmakingFound;
    Lobby.MatchmakingV2MatchmakingCanceled -= OnPartyMatchmakingCanceled;
    Lobby.MatchmakingV2TicketExpired -= OnPartyMatchmakingExpired;
    }
    }

Resources