Odd Double Instantiation Bug

Hi,

I’ve got a double instantiation problem.
I’m using RiptideNetworking (C# networking library) to create a multiplayer game, and just having a bit of trouble. Note, the library itself and its demo work fine, it’s my changes that broke something (but I don’t know where!)

All of my code can be found here: https://github.com/jordandalby/NetworkBug

Pipeline:

  • Player presses Host button, calling NetworkUI::HostClicked
  • NetworkUI::HostClicked calls NetworkManager::StartHost
  • NetworkManager::StartHost starts the server and connects the host to it with Client::Connect
  • When client connects, NetworkManager:: DidConnect is called
  • NetworkManager:: DidConnect switches to the game scene and instantiates a player, but there are two players instead of one. See DidConnect and Player.Spawn below

DidConnect:

private void DidConnect(object sender, EventArgs e)
{
    StartCoroutine(SwitchScenesAndInstantiateFirstPlayer());
}

IEnumerator SwitchScenesAndInstantiateFirstPlayer()
{
    string username = NetworkUI.instance.username.text;
    AsyncOperation asyncLoadLevel = SceneManager.LoadSceneAsync("Game", LoadSceneMode.Single);
    while (!asyncLoadLevel.isDone)
    {
        yield return null;
    }

    yield return new WaitForEndOfFrame();

    Player.Spawn(Client.Id, username, true);
}

Player.Spawn:

    internal static void Spawn(ushort id, string username, bool shouldSendSpawn = false)
    {
        Player player;
        if (id == NetworkManager.Singleton.Client.Id)
            player = Instantiate(new GameObject().AddComponent<Player>(), Vector3.zero, Quaternion.identity).GetComponent<Player>();
        else
            player = Instantiate(new GameObject().AddComponent<Player>(), Vector3.zero, Quaternion.identity).GetComponent<Player>();

        player.Id = id;
        player.username = username;
        player.name = $"Player {id} ({username})";

        List.Add(id, player);
        if (shouldSendSpawn)
            player.SendSpawn();
    }

A few notes:

  • NetworkManager is a Singleton with DontDestroyOnLoad
  • Player USED to have DontDestroyOnLoad, but I had this same problem so I tried to find a workaround, but it seems that this issue persists even after DontDestroyOnLoad is removed.
  • A scene called Boot is run first, which has the NetworkManager instance, allowing it to persist on every subsequent scene, Boot is never visited again.
  • There is only ever 1 instance of NetworkManager (NetworkManager alerts when that’s not the case)
  • Player.Spawn only gets called once, I’ve debugged that to check.
  • One of the instantiated players ignores the player.name = ... assignment, suggesting further that it isn’t instantiated by Player.Spawn

What I’ve tried:

  • Taking Boot out of the equation and simply having NetworkManager in the GameConnection screen by default (no DontDestroyOnLoad)
  • Not switching scenes before calling Player.Spawn
  • Not using a coroutine for DidConnect

I suppose at this point it has to be a Unity thing (some system that I misunderstand).
A little lost from there, what could be the problem?

You can debug log Awake or OnEnable to see the stack trace of code execution I think

Good idea,
the first object comes from: UnityEngine.GameObject:AddComponent<Player> ()
and the second comes from: UnityEngine.Object:Instantiate<Player> (Player,UnityEngine.Vector3,UnityEngine.Quaternion)

I can’t see why that would create two GameObjects of the player though.

EDIT: This was the problem though, the Player.Spawn script:

Player player = Instantiate(new GameObject.AddComponent<Player>(), Vector3.zero, Quaternion.identity).GetComponent<Player>();

The cause of this is obvious, new GameObject instantiates the GameObject too, creating an initial object, then Instantiate just copies that object.
Thanks @DevDunk !