How to Use Unity Multiplayer ISession Events correctly to recive data?

Is there anyone here who has successfully used the ISession events in the new Multiplayer Service?

I’m currently trying to replace my fully functional old Lobby implementation with the new service. Creating and joining a session works so far. Now I’m trying to react to changes in the session for my session room—and I’m failing.

The ISession events (ISession.SessionPropertyChanged and ISession.PlayerJoined) are being triggered, but I’m not receiving any data. That means the values in ISession are outdated in the event, so after a player joins, the read-only list of players still only contains the host who created the session. However, the session’s player count correctly shows two players. Even calling await activeSession.RefreshAsync(); doesn’t help.

What am I doing wrong? In the old Lobby service, it was quite simple and fully functional to react to events—I even got the NetworkPlayer directly. Right now, I only receive the joined player’s ID as a string, but that’s useless since I’m missing all of their data to update the UI.

Hi @Hellhound_01,

The expectation would be that when the session raises a player joined event on the host, you should already have access to the newly added player in the readonly players list.
I did a quick test on our side and this seems to work as expected.

here is a brief outline of what I used to test the behavior:

Host creates the session

public async Task CreateSessionAsync()
{
    var session = await MultiplayerService.CreateSessionAsync(new SessionOptions()
    {
        Type = k_SessionType,
        MaxPlayers = k_MaxPlayers,
        IsPrivate = IsPrivate,
        Password = string.IsNullOrEmpty(Password) ? null : Password,
        IsLocked = IsLocked,
        SessionProperties = LocalSessionProperties,
        PlayerProperties = LocalPlayerProperties
    }.WithRelayNetwork());

    // Once you created the session, add subscribers to the event delegates you want to handle
    session.PlayerJoined += id =>
    {
        Logger.Log($"PlayerJoined: {id}");
        // session should have the players updated once we hit this delegate
        Assert.AreEqual(2, session.Players.Count); // this assert block is only here for testing, please remove it if you do not need it anymore
        Assert.IsNotNull(session.Players.FirstOrDefault(p=>p.Id == id));
    };
    //session.Changed += SessionChanged;
    //session.StateChanged += SessionStateChanged;
    //session.PlayerHasLeft += PlayerHasLeft;
    //session.SessionPropertiesChanged += SessionPropertiesChanged;
    //session.PlayerPropertiesChanged += SessionPlayerPropertiesChanged;
    //session.RemovedFromSession += RemovedFromSession;
    //session.Deleted += SessionDeleted;
}

client joins the session

public async Task JoinSessionByCodeAsync(string sessionCode)
{
    var session = await MultiplayerService.JoinSessionByCodeAsync(sessionCode,
                new JoinSessionOptions() 
                { 
                    Password = string.IsNullOrEmpty(Password) ? null : Password, 
                    PlayerProperties = LocalPlayerProperties 
                });

    // you have joined the session, at this point you can see the host and the client in the session

    //session.PlayerJoined += PlayerJoined;
    //session.Changed += SessionChanged;
    //session.StateChanged += SessionStateChanged;
    //session.PlayerHasLeft += PlayerHasLeft;
    //session.SessionPropertiesChanged += SessionPropertiesChanged;
    //session.PlayerPropertiesChanged += SessionPlayerPropertiesChanged;
    //session.RemovedFromSession += RemovedFromSession;
    //session.Deleted += SessionDeleted;
}

Packages used in test:

  • com.unity.services.multiplayer @ 1.1.1
  • com.unity.netcode.gameobjects @ 1.11.0

Hope this answers your question, feel free to come back to this thread if you have any further questions/concerns.

cheers,
Kálmán

Hi @kalman_balint,

thanks for your support; that helped me a lot. Through your snippet, I realized that during all my testing, I had accidentally removed the JoinSessionOptions with the player data in the join call. As a result, it couldn’t match the data correctly. Now, both the client and server receive exactly the data I wanted. If the JoinSessionOption had not been an optional parameter in the join call, I would never have made this mistake.

BTW, I’m using version 1.0.2.

I do have one more question: Is there any way to access the host’s data when querying available sessions? When creating a session, I set some SessionProperties values, which I display as search criteria when looking for sessions. In the lobby, there was a Data property that could be queried for this purpose. However, in ISession, I can’t find anything similar, which means I have no way to display presets made by host like the map, map type, level, etc.

Best regards,
Hellhound

1 Like

hi @Hellhound_01,

Happy to hear that you were able to solve the problem!
For the query part, you can use the query api that supports filtering the sessions based on the session properties that you defined when creating the session itself.

small example of how one could query sessions defining some custom properties

Create sessions with properties:

public async Task CreateLocalSessionAsync()
{
	// sample properties on a session
	var localSessionProperties = new Dictionary<string, SessionProperty>()
	{
      {"key1", new SessionProperty("serialized value", VisibilityPropertyOptions.Public, PropertyIndex.String1)},
      {"key2", new SessionProperty("Some value", VisibilityPropertyOptions.Public, PropertyIndex.String2)},
	};

	Session = await MultiplayerService.CreateSessionAsync(new SessionOptions()
	{
		Type = k_SessionType,
		MaxPlayers = k_MaxPlayers,
		IsPrivate = IsPrivate,
		Password = string.IsNullOrEmpty(Password) ? null : Password,
		IsLocked = IsLocked,
		SessionProperties = localSessionProperties,
		PlayerProperties = LocalPlayerProperties
	}.WithDirectNetwork("0.0.0.0", GetLocalIPAddress(), Port);
}

Query sessions:

public async Task QueryAsync(int numberOfResultsToReturn)
{
 	var options = new QuerySessionsOptions() { Count = numberOfResultsToReturn };
	
	// Add filter options that describe your session properties
	options.FilterOptions.Add(new FilterOption(FilterField.StringIndex1, "Some value", FilterOperation.GreaterOrEqual));
	options.FilterOptions.Add(new FilterOption(FilterField.LastUpdated, DateTime.Today.ToString(), FilterOperation.LessOrEqual));

	// you can also sort the results
	options.SortOptions.Add(new SortOption(SortOrder.Ascending,SortField.StringIndex1));

	// the query results will return only the sessions that match the defined query options
	QueryResults = await MultiplayerService.Instance.QuerySessionsAsync(options);
}

Hope this will help you.

Cheers,
Kálmán

Hello @kalman_balint ,

thanks for your response. But I think I expressed myself a bit poorly.

I’m not having trouble with filtering—I can efficiently search for sessions (faster than before with the Lobby API) and access the results. However, the problem is that in the query results, I only receive the session ID and name, but not any additional data such as the session properties set by the host.

In the Lobby API query results, there was a Data entry that allowed access to host-defined data, like match type or the selected map. Did I overlook something, or has this feature been removed?

I used this data before joining a session to display match information to the player, making their selection easier.

Best regards
Hellhound

1 Like

Hi @Hellhound_01

Sorry for misunderstanding the question :sweat_smile:. Sadly the Data property that is available on the Lobby is not available on the SessionInfo itself at the moment. We are working on adding this property to the Sessions API in a future release.

Meanwhile as a workaround you can actually use the underlying Lobby API to query the Session associated lobby directly. This will enable you to get access to the Data properties when you are querying for Lobbies. Sessions are built on top of the lobby concept so there is a 1-1 mapping in between the two.

Here is an example of how you could use the two APIs from the MPSSDK to achieve that:

Create a porperty somewhere

// example session property that lives in some shared class
SessionProperty Blue => new SessionProperty("blue", VisibilityPropertyOptions.Public, PropertyIndex.String1);

In the host/server’s side of things everything can be done as usual:

// Assuming you have initilized the services, and signed in
public async Task CreateHostSession()
{
    // create a session with the host player
    var sessionOptions = new SessionOptions()
    {
        Type = "typeOfTheSession",
        MaxPlayers = 2,
        IsPrivate = false,
        Password = "TopSecretPassword",
        IsLocked = false,
        SessionProperties = new Dictionary<string, SessionProperty> { ["colour"] = _blue },
        PlayerProperties = new Dictionary<string, PlayerProperty>()
    };
    // this sets up a lobby in the background
    var hostSession = await MuliplayerService.Instance.CreateSessionAsync(sessionOptions);
}

Your client code could look like this:

// in your client since we require access to the Data property that is available only on the lobbies
public async Task<Lobby> QueryLobby_ByProperty(string propertyKey, string propertyValue)
{
    // Instead of querrying the session infos we directly search for the underlying lobby through the lobby API
    var queryFilters = new List<QueryFilter>()
    {
        // add any filter you may need
        new QueryFilter(QueryFilter.FieldOptions.Name, string.Empty, QueryFilter.OpOptions.NE)
    };

    var lobbyQueryOptions = new QueryLobbiesOptions(){ Filters = queryFilters };
    var lobbies = await LobbyService.Instance.QueryLobbiesAsync(lobbyQueryOptions);

    // you have access to the Data property as with the standalone SDK
    var lobbyIndex = lobbies.Results.FindIndex(lobby=>lobby.Data.TryGetValue(propertyKey, out var pValue) && string.Equals( pValue, propertyValue, StringComparison.OrdinalIgnoreCase );

    return lobbyIndex == -1? null : lobbies[lobbyIndex];
}

// to join to a Session using the lobby query results:
public async Task JoinSession_ThatHasProperty()
{
    var lobby = QueryLobby_ByProperty("colour", _blue.Value);

    // because of the inherent mapping between Sessions and Lobbies you can directly use the Lobby id to join to the associated Session
    var clientSession = await clientMultipalyerService
                .JoinSessionByIdAsync(lobby.Id, new JoinSessionOptions(){ Password ="TopSecretPassword" });
}

FYI the latest release of the SDK 1.1.4 provides access to the SessionProperties (formerly Data on Lobbies).