Host Migration on netcode for gameobjects

Hello everyone,

I’m taking my first steps to create a host migration system, which is crucial for my game as it operates as a death match. In other words, if a player loses, they are automatically disconnected. If that player happens to be the host, they should pass the host role to another client. Imagine my surprise when I discovered that Unity Networking does not support host migration. Now that I’ve practically finished the entire game, I’m feeling a bit desperate, haha! I’ve attached my two scripts where I handle all the lobby and relay processing, and this is my host migration function. Unfortunately, I’ve been working on this for two weeks now and can’t seem to get it to work. For now, I just want to do something very simple: only the host can change roles and make one of the other clients a host. Could you please help me figure out what I’m doing wrong and give me some guidance on how to fix it so I can release my game?

Thank you.

public static async Task MigrateHost()
    {
        Debug.LogError("Migrating host from 1");
        // Check if there's a valid lobby and if the player is the current host
        if (_currentLobby != null && _currentLobby.HostId == Authentication.PlayerId)
        {
            Debug.LogError("Migrating host from 2");
            // Select a new player as the host, if there are other players in the lobby
            if (NetworkManager.Singleton.ConnectedClientsList.Count > 1)
            {
                // Select a new host randomly from the connected clients list
                var newHostClientId = ulong.MinValue; // Placeholder value for new host's client ID
                foreach (var client in NetworkManager.Singleton.ConnectedClientsList)
                {
                    // Check if the client is not the current host
                    if (client.ClientId != NetworkManager.Singleton.LocalClientId)
                    {
                        newHostClientId = client.ClientId;
                        break;
                    }
                }

                // Check if a new host was found
                if (newHostClientId != ulong.MinValue)
                {
                    // Get the PlayerId of the new host from the lobby players list
                    var newHost = _currentLobby.Players.FirstOrDefault(player => player.Id == Authentication.PlayerId);
                    if (newHost != null)
                    {
                        Debug.LogError($"Migrating host from {Authentication.PlayerId} to {newHost.Id}");

                        // Relay initialization
                        Debug.LogError("Starting a new Relay allocation...");

                        // Create a new Relay allocation
                        var newAllocation = await RelayService.Instance.CreateAllocationAsync(_currentLobby.MaxPlayers);

                        Debug.LogError($"New Relay allocation created. Allocation ID: {newAllocation.AllocationId}");

                        // Update the lobby with the new host and the new Relay connection data
                        await Lobbies.Instance.UpdateLobbyAsync(_currentLobby.Id, new UpdateLobbyOptions
                        {
                            HostId = newHost.Id
                        });

                        Debug.LogError($"Host migrated to player {newHost.Id}. Relay connection data updated.");

                        // Reset the transport with the new Relay connection data
                        Transport.SetHostRelayData(newAllocation.RelayServer.IpV4, (ushort)newAllocation.RelayServer.Port, newAllocation.AllocationIdBytes, newAllocation.Key, newAllocation.ConnectionData);

                        Debug.LogError("Relay connection data set in the transport. Host migration complete.");

                        // Get all clients connected to the previous host
                        var connectedClients = NetworkManager.Singleton.ConnectedClientsList;

                        // Disconnect each client from the previous host and connect them to the new host
                        foreach (var client in connectedClients)
                        {
                            // Disconnect the client from the previous host
                            NetworkManager.Singleton.DisconnectClient(client.ClientId);

                            // Wait for a short period to ensure the client is properly disconnected
                            await Task.Delay(500);

                            // Assign the new host as the owner of the player object of each client
                            if (client.PlayerObject != null)
                            {
                                client.PlayerObject.ChangeOwnership(newHostClientId);
                            }
                            else
                            {
                                Debug.LogError($"Player object not found for client {client.ClientId}");
                            }
                        }

                        Debug.LogError("All clients migrated to the new host.");

                        // Initialize the new host
                        NetworkManager.Singleton.StartHost();

                        Debug.LogError("New host initialized.");
                    }
                    else
                    {
                        Debug.LogError("Failed to find the new host in the lobby players list.");
                    }
                }
                else
                {
                    Debug.LogError("Failed to find a new host from the connected clients list.");
                }
            }
            else
            {
                Debug.LogError("No other players in the lobby to become the host");
            }
        }
        else
        {
            Debug.LogError("You are not the current host or there is no valid lobby");
        }
    }

9765375–1398909–LobbyOrchestrator.cs (6.04 KB)
9765375–1398912–MatchmakingService.cs (10.1 KB)

First time I‘ve seen anyone actually attempt to make host migration with NGO. I wouldn‘t even know where to start with this or how to go about it. Skimming over your code I have little hope that this will even remotely work but that‘s just a hunch based on how much state there is in a network session. You cannot just pull the host (authority) out of it and shove a new host in.

You could start a new session with the same players but then you‘d have to re-initialize the entire network state (positions, variables, etc) first. That means the game would have to be designed to serialize and deserialize all network state. Do you have that?

One thing is for certain:

This is a built-in failure point. 500 ms is arbitrary, you have no way of knowing whether this will always work for every client.

Hello in my game everything is done locally or through events that the host itself triggers, the only thing I share with all clients is the current position of each client, if the host goes down the new host will take this information and reposition for all clients the positions, that is the only thing we have to sync is the transforms, so I’ve already set up a system that makes a direct ping from the server to the clients, and that creates a priority list to know who will be the next host if the first one goes down, and if this client realizes that he hasn’t received a ping from the host for X number of time it automatically starts the host migration for him and redoes the list to add the next client to the priority, the problem is in this function that should do the host migration, if I manage to do the migration successfully the rest I sync by myself

What you’re trying should be doable, at least on paper. What I can’t see from the code is how the client is informed that they’re the new host and how the relay allocation is shared with other clients. I’m not very familiar with UGS but with MigrateHost being run by the original host won’t it be the one that calls StartHost on line 79?

1 Like

Yes, the StartHost on line 79 is incorrect. The one who should initialize and call StartHost on the singleton is the new host. I’m fixing that, and yes, I haven’t figured out yet how to make the new relay allocation on the client that becomes the new host.

Just to point out that you have not provided any details as to how this code fails to do what it’s supposed to, eg the current state and point of failure.

My apologies for the oversight. Currently, all the initialization parts of the code are functioning perfectly, as demonstrated in the two scripts I attached in the post. However, the migration function is not working at all. At the moment, I am not concerned about the game states or any other aspects. My sole objective is to enable the current host to transfer their hosting responsibilities to another client whenever they desire. This is all I wish to achieve, ensuring that all clients in the session are seamlessly reconnected to the new host.

You are paraphrasing what you said already. :wink:

What does “not working at all” look like, behave like? What’s the first thing that doesn’t work as expected?

I think with key parts missing knowing the errors will be more meaningful once they’re in place.

I’d recommend experimenting with host migration in a new project as there’s a host of problems to solve first before trying it out in your main project.

Below is a very simplified breakdown of the initial sequence of events which is mostly guesswork without knowing more about UGS:


I’d be tempted to let the Lobby be the driver of this especially as this will handle host timeouts as well but you’ll have to see how you get on with it.

2 Likes

If the Lobby can’t handle this because it can’t keep state or make decisions by itself, then you may have to also use the Cloud Code service to create the Lobby allocation and migrate clients.

and where you call MigrateHost() ?