Client connection through Relay working inconsistently?

I’ve been having an issue where semi-regularly my client attempts to connect to my host but seems to partially fail. I’m currently using Netcode for GameObjects 1.9.1 and Unity 2022.3.10f1.

As far as I can tell, the client makes a connection and is seen by the host (NetworkManager.ConnectedClientsList.Count increases to reflect this), and on the client side it shows NetworkManager.IsApproved as being true, although it shows NetworkManager.IsConnectedClient as being false?

The documentation I could find on these was a little sparse and only seems to say that the difference between the two is that IsConnectedClient indicates that the client is also “Synchronized with the server” whatever that means? (I believe this is the correct documentation: Class NetworkManager | Netcode for GameObjects | 1.9.1 )

As far as I can tell these changes in connection status are purely the results of calling NetworkManager.Singleton.StartClient() in my StartClient() function, but there are is no actual events thrown to signify what the issues are? I’ve tried looking at OnClientDisconnectCallback, OnTransportFailure, ConnectionApprovalCallback, and OnConnectionEvent, but the first three don’t seem to trigger in any of my cases, and OnConnectionEvent seems to give very little information (it fires a “client connected” event on a complete success and does nothing on a partial success).

In terms of the actual scenes, it basically acts as if the client didn’t connect, and the scenemanager fails to sync the client’s scene to the host’s scene, although any newly spawned networkObjects appear in the client’s scene (still on the main menu instead of in the loaded game scene).

I have the code below for my HostScene() and StartClient() code. None of the exceptions are being hit and it doesn’t seem consistent how frequently/when the partial failures are occurring. I’ve had it connect fine as many as 6 times in a row, and I’ve had it fail to connect correctly as many as probably 5 or so times in a row. I’ve also had it alternate 1 for 1 as many as 3 or 4 times. I have primarily tested using one unity editor and one standalone build on the same PC. I have also tested with two standalone builds on the same PC (all windows).

    public async void HostScene(string sceneName)
    {
        Allocation allocation;
        status.currentAwait = "Waiting for host allocation";
        try
        {
            allocation = await RelayService.Instance.CreateAllocationAsync(4);
        }
        catch (Exception e)
        {
            Debug.Log($"Failed to create allocation while hosting scene. Caught exception: {e}");
            return;
        }

        status.currentAwait = "Waiting for join code";
        try
        {
            FindObjectOfType<PersistentData>().JoinCode = await RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);
        }
        catch (Exception e)
        {
            Debug.Log($"Failed to get join code while hosting scene. Caught exception: {e}");
        }

        status.currentAwait = "";
        RelayServerData servDat = new RelayServerData(allocation, "dtls");
        NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(servDat);
        NetworkManager.Singleton.StartHost();
        NetworkManager.Singleton.SceneManager.LoadScene(sceneName, LoadSceneMode.Single);

    }
public async void StartClient(string joinCode)
    {
        JoinAllocation allocation;
        lastUsedJoinCode = joinCode;
        status.currentAwait = "Waiting to join";
        try
        {
            allocation = await RelayService.Instance.JoinAllocationAsync(joinCode);
        }
        catch (Exception e)
        {
            Debug.Log($"Failed to join allocation as client w/ joincode {joinCode}. Exception caught: {e}");
            return;
        }

        status.currentAwait = "";
        RelayServerData servDat = new RelayServerData(allocation, "dtls");
        NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(servDat);
        NetworkManager.Singleton.StartClient();
    }

When this occurs I’m getting this warning on the client side a handful of times with slightly different values:

And on the host side I’m getting this error:

Which both seem to me like errors based off the fact that the client isn’t properly loading the host’s scene? Although I’m a bit unclear here.

This sounds like you are not following requirements and best practices when it comes to scene loading and starting a game.

First, make sure NetworkManager is in a “launch” scene to prevent it from ever getting duplicated. Otherwise you will run into issues starting a new online session no matter what you do.

Here I believe your issue might be that you call SetRelayServerData on the client. This shouldn’t be done.

Be sure to check the IsConnected state only in or after OnNetworkSpawn in a NetworkBehaviour. If you run it in Awake or OnEnable/Start this may execute before the object was spawned and even before the client did actually connect.

As to events: be sure to register event callbacks before StartHost/StartClient or else the earliest ones may not trigger because some of them may be called instantaneously from within the Start* method.

Thank you very much the response.

I’m sure you’re likely right, I’m mostly trying to track down where I’ve failed to implement best practice. I went ahead and moved my NetworkManager.Singleton.SceneManager.LoadScene call into OnNetworkSpawn instead of calling it directly after StartHost, although this doesn’t seem to have had an impact on behavior (the partial connections don’t seem to have anything to do with the host’s state, since I can get a failed and successful connection to the same host instance by trying repeatedly)

I do have my NetworkManager in a startup scene so that it is not duplicated. In all of my editor testing there has only been one NetworkManager at a time.

I was under the impression that the documentation recommended using SetRelayServerData to set your relay data for the client? This tutorial seems to imply so but I couldn’t find any other sources supporting or refuting that. I went digging around a big in the UnityTransport documentation, and managed to cobble together a call to SetRelayClientData instead, but I experienced what appeared to be the exact same behavior when trying to join. If this isn’t intended usage I would love elaboration on this because I can’t seem to find any more details on how to do this.

I’m only using the IsConnected state as a diagnostic tool (I’m printing a bunch of NetworkManager properties to my screen so I can see their status at various times in my testing), I’m not actually using it for any connection logic.

I did misunderstand the timing on callback events (I thought that they were like the NetworkManager subsystems that are only instantiated after the NetworkManager starts listening), and have moved those earlier in code execution. This hasn’t resulted in any changes in terms of events firing, but the earlier timing seems beneficial since it works.

I also updated my Unity Transport package from 1.4 to 2.2 (although supposedly according to Unity Transport documentation that shouldn’t have been an issue) and changed the protocol type on the Unity Transport from “Unity Transport” to “Relay Unity Transport” (I really thought this was it when I saw this setting) but neither of those did anything.

I could always force a reconnect after X seconds if the client manages to get to an IsApproved but not IsConnectedClient state, but that feels like a very incorrect solution to my problem (and could also be fairly slow if there are a number of failed connections in a row)

I’m going back through the documentation on setting up NetworkManager, UnityTransport, and Relay Services in my project to see if I’ve made any mistakes in my settings but so far no luck there?

Directly loading a network scene after StartHost is not an issue. :wink:

Hmmm I must have confused it with something else that you‘re not supposed to change in the client‘s transport. In fact I use the exact same code.

If anyone stumbles upon this after the fact: I just settled on a workaround where I enter a local scene on the client side (screen just says connecting or something along those lines) and check if we’ve gotten to a broken state then attempt to reconnect. Not super happy with it but it doesn’t feel unresponsive and I didn’t feel like getting bogged down in a better solution.