Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question OnNetworkSpawn and IsLocalPlayer

Discussion in 'Netcode for GameObjects' started by jackward84, Sep 8, 2023.

  1. jackward84

    jackward84

    Joined:
    Jan 26, 2017
    Posts:
    87
    Hi,

    I'm trying to make it so that when a player disconnects, their player object remains in the world and when they reconnect it is given back to them. I found some strange behaviour while doing this and not sure it's the expected behaviour from NGO.

    I broke this down into the most simple setup I could and created a 2d project where the player object is just a circle with a networkobject and a network behaviour class attached to it.

    Then I run the server like this:


    using Unity.Netcode;
    using UnityEngine;

    public class StartHost : MonoBehaviour
    {
    private NetworkObject despawnedPo;

    void Start()
    {
    NetworkManager.Singleton.StartHost();
    }

    private void Update()
    {
    if (Input.GetKeyUp(KeyCode.Z))
    {
    despawnedPo = NetworkManager.Singleton.ConnectedClients[0].PlayerObject;
    despawnedPo.Despawn(destroy: false);
    }
    else if (despawnedPo != null && Input.GetKeyUp(KeyCode.X))
    {
    despawnedPo.SpawnAsPlayerObject(NetworkManager.ServerClientId);
    despawnedPo = null;
    }
    }
    }


    With a player object class like this:


    using Unity.Netcode;
    using UnityEngine;

    public class Controls : NetworkBehaviour
    {
    private Transform _transform;
    private CharacterController _characterController;

    private void Awake()
    {
    _transform = GetComponent<Transform>();
    _characterController = GetComponent<CharacterController>();
    }

    public override void OnNetworkSpawn()
    {
    Debug.Log($"Spawned! IsLocalPlayer={IsLocalPlayer}");
    base.OnNetworkDespawn();
    }

    public override void OnNetworkDespawn()
    {
    Debug.Log($"Despawned! IsLocalPlayer={IsLocalPlayer}");
    base.OnNetworkDespawn();
    }

    void Update()
    {
    if (!IsLocalPlayer)
    {
    return;
    }
    if (Input.GetKeyDown(KeyCode.LeftArrow))
    {
    _characterController.Move(Vector3.right * -1);
    }
    else if (Input.GetKeyDown(KeyCode.RightArrow))
    {
    _characterController.Move(Vector3.right);
    }
    }
    }


    Running as the host, this leads to very strange behaviour, and not what you'd expect.

    The server starts and the log will say:
    "Spawned! IsLocalPlayer=true"

    You press z and you get
    "Despawned! IsLocalPlayer=true"

    All good so far. But the first bit of weird behaviour: the host still has control of the Controls object, despite ownership being taken away. OK that's fine, just some weird behaviour with hosts being "LocalPlayer" for despawned objects.

    But it gets stranger, and this is where I can't make any sense of the behaviour anymore:

    You press x to respawn and you get the message:
    "Spawned! IsLocalPlayer=false"

    Only at this point do you lose control of the player object, which is super strange because, well.. I'm the owner of it now since it just spawned it with "SpawnAsPlayerObject"? But looking at the object closely, it is no longer marked as a PlayerObject at all, which is why IsLocalPlayer is false.

    I would have expected the behaviour:

    On despawn => IsPlayerObject becomes false and IsLocalPlayer becomes false.
    On respawn => IsPlayerObject becomes true and IsLocalPlayer becomes true.
     
    Last edited: Sep 8, 2023
  2. lavagoatGG

    lavagoatGG

    Joined:
    Apr 16, 2022
    Posts:
    229
    in OnNetworkSpawn you call

    base.OnNetworkDespawn();

    Shouldn't it be
    base.OnNetworkSpawn();
    ?
     
  3. jackward84

    jackward84

    Joined:
    Jan 26, 2017
    Posts:
    87
    You're right that it is backward, but it actually makes no difference to the behaviour. It's the same regardless, base.OnNetworkSpawn() (and despawn) are empty methods.
     
  4. jackward84

    jackward84

    Joined:
    Jan 26, 2017
    Posts:
    87
    I got it working by basically doing this:
    • Set the NetworkObject to
      DontDestroyWithOwner = true
    • When the client disconnects, don't do anything special. Don't call Despawn on it.
    • When (if) the client reconnects, run this code in the same frame
    Code (CSharp):
    1. var oldPlayerObj = SomeCodeToFindTheNetworkObject(clientId);
    2. oldPlayerObj.Despawn(destroy: false);
    3. oldPlayerObj.SpawnAsPlayerObject(clientId);
    As far as I'm aware, there is no way to assign a player object to a client without it first having been despawned.
    ChangeOwnership()
    is not good enough because it doesn't assign the PlayerObject to the client. Despawning/respawning is a good idea anyway because then you can still have the client setup their controls in
    OnNetworkSpawn
    .

    As of yet, I have not found any problems with doing this, other than some wacky IsLocalPlayer rules, which you can get around with special checks.

    The reason you don't want to call
    Despawn(destroy: false)
    when the client disconnects, is that the networkobject will become invisible to everyone except the host. The NetworkObject component also cannot be referenced with NetworkObjectReference while despawned, which causes all kinds of problems.
     
    Last edited: Sep 8, 2023