Calling NetworkBehavior.SpawnWithOwnership() during OnNetworkStart() breaks any NetworkBehavior's on the spawned object

Issue Description

I have a player script (that, to nobody’s surprise, is attached to the default player prefab) that spawns a “unit” that is supposed to be owned by the player as soon as the player joins the game.

For Reference, This Is How My Player Script Is Programmed

using UnityEngine;
using Unity.Netcode;
using Sirenix.OdinInspector;

public class Player : NetworkBehaviour
{
    [SerializeField, AssetsOnly, Required(“The Player Needs To Have A DefaultUnitToSpawn Assigned!”, InfoMessageType.Error)]
    private NetworkObject defaultUnitToSpawn;

    public override void OnNetworkSpawn()
    {
        if (IsServer)
        {
            Instantiate(defaultUnitToSpawn.gameObject, Vector3.up * 0.5f, Quaternion.identity)
                .GetComponent<NetworkObject>()
                .SpawnWithOwnership(OwnerClientId);
        }
    }
}

The defaultUnitToSpawn is a prefab that has a separate NetworkBehavior script attached. After that unit is spawned, the NetworkBehavior will not allow me to call any ServerRPC’s on either the host or the client, and the NetworkBehavior also will return false when you check the .IsOwner() property of the Behavior. However, the stranger thing is that the NetworkObject component the NetworkBehavior is attached to does correctly show that the client owns the unit object.

In other words, while the NetworkObject on my unit correctly identifies that it is owned by the client, my NetworkBehavor goes “rogue” and becomes, for a lack of a better term, “unowned”

ISSUE DEMONSTRATION

My NetworkBehavior script, UnitMover, has two checkboxes.

  • The top checkbox, labled Does This Behavior Think, shows what calling .IsOwner() on the Network Behavior returns.
  • The bottom checkbox, labeled Does The Networked Object Think, shows what calling the .IsOwner() on the NetworkObject above returns.

Server’s Perspective

Client Perspective

Attempted Remedies

  • Spawning The Unit During OnNetworkPostSpawn()
    • Result: Nothing (new) happened, everything played out exactly the same.
  • Spawning The Unit 3 Secounds After OnNetworkSpawn()
    • Result: Everything was fixed, and both the NetworkBehavior and the NetworkObject behaved correctly.

Have you tried using Start rather than OnNetworkSpawn as I’m also seeing some strange behaviour.

Coincidentally earlier I did try switching scenes in OnNetworkSpawn in an in-scene object and the call was ignored. That was due to OnNetworkSpawn being called before Start and it’s meant to be the other way around for in-scene objects, I’ve not looked into why that is.

This actually does work for my issue! I am not sure if this is intended behavior or not, because OnNetworkSpawn feels like the better function to use (since the documentation says that it is called after the object is set up), hopefully someone smarter than me can explain to me whether or not this behavior is intended, it feels like this should be the job of OnNetworkSpawn and right now it appears like my feeling is off.

Yeah I’d not noticed any issues until yesterday and reproducing what you’re doing. There is some initialisation done internally after the OnNetworkSpawn call so it may be down to that.

You might find the NetworkBehaviour documentation page helpful when it comes to order of operations for NetworkBehaviours.
Dynamically spawned (i.e. not in-scene place which means the object is instantiated during scene loading), follows the actual UnityEngine order of operations which is:

  • Frame of Instantiation: Awake is invoked, then the NetworkBehaviour spawn methods (pre through post post).
  • Frame after Instantiation: UnityEngine invoked the Start method.

If the server already knows what unit to spawn when using a client-server network topology then you can either spawn the unity during Start as was mentioned or you might end up having better long term results handling this like:
Client-Server Network Topology

public class MyPlayerSpawnLogicClientServer: NetworkBehaviour
{
    public GameObject DefaultUnitToSpawn;

    protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
    {
        if (networkManager.IsServer) 
        {
            networkManager.SceneManager.OnSynchronizeComplete += OnSynchronizeComplete;
        }
        base.OnNetworkPreSpawn(ref networkManager);
    }

    private void OnSynchronizeComplete(ulong clientId)
    {
        NetworkManager.SceneManager.OnSynchronizeComplete -= OnSynchronizeComplete;
        var unitInstance = Instantiate(DefaultUnitToSpawn, Vector3.up * 0.5f, Quaternion.identity);
        unitInstance?.GetComponent<NetworkObject>()?.SpawnWithOwnership(OwnerClientId);
    }
}

Which the above should assure that you aren’t instantiating the unit until the player has completely synchronized everything in the session (in the event there are any dependencies the unit my need to access during spawn).

The client-server network topology does make it a little complex than the distributed authority topology where you can just do this:
Distributed Authority Network Topology

public class MyPlayerSpawnLogicDistributedAuthority : NetworkBehaviour
{
    public GameObject DefaultUnitToSpawn;

    protected override void OnNetworkSessionSynchronized()
    {
        if (HasAuthority)
        {
            var unitInstance = Instantiate(DefaultUnitToSpawn, Vector3.up * 0.5f, Quaternion.identity);
            unitInstance?.GetComponent<NetworkObject>()?.Spawn();
        }
    }
}

Where both should assure the client has finished synchronizing before spawning.
Of course, if you wanted to daisy chain spawning after you are spawned it becomes a different scenario and really depends upon what you are trying to accomplish…if you wanted to provide a culled down replication project with the basic elements of what you are trying to accomplish it is much easier to provide a “recommended” way of handling it.

Since the player prefab is being dynamically spawned, you can use the MonoBehaviour.Start approach but that will not assure you of the client being 100% synchronized (i.e. all scenes loaded and objects spawned locally)…which if you don’t plan on needed to access anything “netcode” related within any NetworkBehaviours within the unit spawned then the MonoBehaviour.Start approach should be fine.

1 Like

Thanks Noel, I’ve not looked into taking advantage of some of the SceneManager events.

I’ve found in testing with in-scene objects occasionally OnNetworkSpawn is triggered before Start, for example with the class in this post, is this a potential issue?

Not sure how I missed the link you gave me, I had 0 clue this was intended behavior! Thank you for your time helping me (and sorry for the long delay before a response), your explanation (and link) helped me understand what OnNetworkSpawn is actually for!

One last question, in the future, should have I marked this as a thread as a question instead of a bug? At the time I thought it was a bug, but after posting I realized I might want to mark anything I find like this as a question in the future just because I am very unlikely to be the first person to discover a bug and I don’t want to hide actually important bug posts.

Question or bug is fine… if you think it is a bug but it ends up being a question that is perfectly fine.

1 Like