Respawning Problem with Unity Netcode

I’ve been working on implementing a respawn system using Unity Netcode for my game objects. The respawn process works as expected when the host kills the client, but I’m encountering a problem when the client kills the host. The host respawns correctly, but it floats in the air (because i am spawning it 1f on y axis) on the client and some of the scripts attached to it, such as PlayerMovement, GunSystem, ClientNetworkTransform, and Target, seem to be disabled.

I’ve provided relevant code snippets from my Target script and GunSystem script below. The Target script handles health, respawning, and notifying clients about health changes, while the GunSystem script initiates damage to the target through raycasting.

Target Script:

[ServerRpc(RequireOwnership = false)]
    public void UpdateHealthServerRpc(float damage, ulong clientId)
    {
        health.Value -= damage;

        NotifyHealthChangeClientRpc(damage, new ClientRpcParams{
            Send = new ClientRpcSendParams
            {
                TargetClientIds = new ulong[] {clientId}
            }
        });

        Die(clientId);
    }

    public void Die(ulong clientId)
    {
        if(health.Value < 0f)
        {
            Debug.Log("Destroying");
            gameObject.GetComponent<NetworkObject>().Despawn();
           
            StartCoroutine(Delay(5f));

            RespawnPlayer(clientId);
        }
    }

    public void RespawnPlayer(ulong clientId)
    {
        NetworkObject playerPrefab = Instantiate(NetworkManager.LocalClient.PlayerObject, new Vector3(0f, 1f, 0f), Quaternion.identity);
        playerPrefab.SpawnAsPlayerObject(clientId);
    }

    [ClientRpc]
    public void NotifyHealthChangeClientRpc(float damage, ClientRpcParams clientRpcParams = default)
    {
        Debug.Log("Client got damage");
    }

GunSystem script:

//RayCast
        if (Physics.Raycast(rayCastOrigin.position, direction, out rayHit, range, whatIsEnemy))
        {
            Debug.Log(rayHit.collider.name);

            if (rayHit.collider.CompareTag("Enemy"))
            {
                var playerHit = rayHit.collider.GetComponent<NetworkObject>();
                if(playerHit != null)
                {
                    playerHit.GetComponent<Target>().UpdateHealthServerRpc(damage, playerHit.OwnerClientId);
                }               
            }
        }

In general you want to avoid destroying and spawning multiplayer objects as much as possible, especially the players as their objects are linked to the NetworkManager’s connected client list and that gets messy. It’s easier to disable the player’s control inputs and visuals and then teleport the player and re-enable everything when they “respawn” instead.

Be aware that you may run into issues with ownership when teleporting or moving players with clientnetworktransforms in code, and you may run into an issue where the networkrigidbody and networktransform both try to sync locations in the same frame so your teleport gets overwritten. That was a fun one to diagnose.

Thank you for your guidance! I followed your suggestion and made some changes to the Target script.

public void Die(ulong clientId)
    {
        if(health.Value < 0f)
        {
            Debug.Log("Destroying");
            //gameObject.GetComponent<NetworkObject>().Despawn();
            NetworkObject playerPrefab = NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;
            playerPrefab.GetComponent<PlayerMovement>().enabled = false;
            playerPrefab.GetComponent<MeshRenderer>().enabled = false;
            playerPrefab.GetComponent<GunSystem>().enabled = false;
         
            //StartCoroutine(Delay(5f));

            RespawnPlayer(clientId);
        }
    }

    public void RespawnPlayer(ulong clientId)
    {
        // NetworkObject playerPrefab = Instantiate(NetworkManager.LocalClient.PlayerObject, new Vector3(0f, 1f, 0f), Quaternion.identity);
        // playerPrefab.SpawnAsPlayerObject(clientId);
        NetworkObject playerPrefab = NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;
        playerPrefab.transform.position = new Vector3(0f, 1f, 0f);
        playerPrefab.GetComponent<MeshRenderer>().enabled = true;
        playerPrefab.GetComponent<PlayerMovement>().enabled = true;
        playerPrefab.GetComponent<GunSystem>().enabled = true;
        playerPrefab.GetComponent<Target>().health.Value = 100f;

    }

However, I encountered an issue related to ClientNetworkTransform and NetworkRigidBody. The client can kill the host successfully, but the host isn’t able to kill clients. Although client’s health gets restored to 100, but the position doesn’t change.

Could be the bug I described above, it’s an issue where the clientnetworktransform and the networkrigidbody both set the position so the change of position gets instantly reverted. Without seeing the rest of your code it’d be hard to diagnose.

Also, how does the server actually tell the clients that a character has died or respawned? I can see that clients can tell the server via a serverrpc to deal damage to the character, and various scripts then get disabled and re-enabled but this only happens on the server. You need to instead dispatch these events to all clients through a ClientRpc that enables and disables the scripts.