Open J0shhT opened 5 months ago
Hi @J0shhT,
I have attached a project which provides you with two possible ways to handle hidden object spawning. The primary difference in from your approach is that showing the object should happen while the object is being spawned but afterwards.
The primary area I adjusted:
private void _spawnNetworkObjectForClient(ulong clientId)
{
if (!SpawnWithServerAsOwner)
{
// Option 1: You can just spawn the object with the client already set as the owner
var instance = NetworkManager.SpawnManager.InstantiateAndSpawn(m_networkPrefab, clientId, true);
// You then should just show the object here and not in the spawn method of the object being spawned.
instance.NetworkShow(clientId);
}
else
{
// Option 2: You can spawn the object only on the server side with it not being visible to any client
// This is sort of a "round about" way to handle this and is a bit more troublesome than it is worth (imo)
var instance = Instantiate(m_networkPrefab.gameObject);
var networkObjectInstance = instance.GetComponent<NetworkObject>();
networkObjectInstance.Spawn();
// The not recommended path:
// You have to first "show" the object (i.e. send the CreateObjectMessage to the targeted client)
networkObjectInstance.NetworkShow(clientId);
// You then have to change the ownership to the targeted client
networkObjectInstance.ChangeOwnership(clientId);
}
// Option 1: Only 1 message is sent (i.e. the CreateObjectMessage when you show it to the client)
// Option 2: Two messages are sent (i.e. the CreateObjectMessage when it is shown and the ChangeOwnershipMessage)
}
As well, I didn't use the NgoBugComponent
you provided since you should only handle showing something after it has run through the spawn process (it just changes the "NetworkShow" from happening within the component to afterwards).
Here is the project with the modified code you provided (I renamed NgoBugSetup
to HiddenObjectSpawner
).
HiddenObjectSpawner.zip
As a side note, in NGO v2.0.0 there are new NetworkBehaviour
virtual methods and I believe the NetworkPostSpawn
would allow you to handle showing the object from a component attached to the network prefab.
Let me know if this resolves your issue?
Description
Calling
NetworkShow
on aNetworkObject
that is in the process of spawning (such as fromOnNetworkSpawn
) can cause a duplicateCreateObjectMessage
to be sent to the remote client. This only applies if the target client is not already an observer of theNetworkObject
(such as whenSpawnWithObservers
is set tofalse
).Reproduce Steps
NetworkShow
on its owner inOnNetworkSpawn
, like so:public class NgoBugComponent : NetworkBehaviour { public override void OnNetworkSpawn() { base.OnNetworkSpawn(); if (IsServer && !NetworkObject.IsNetworkVisibleTo(OwnerClientId)) NetworkObject.NetworkShow(OwnerClientId); } }
m_networkPrefab
serialized field to the network prefab that was created in step two.Actual Outcome
Two
CreateObjectMessage
are sent to the remote client for the sameNetworkObjectId
, which causes a warning for trying to spawn a network object that is already spawned. Two copies of the spawned network object exist in the scene for the remote client (the original and the duplicate).Expected Outcome
Only one
CreateObjectMessage
is sent to the remote client for theNetworkObjectId
. No warning occurs and only one copy of the network object exists in the scene for the remote client.Environment
Additional Context
After investigating this issue on my end by stepping through code in a debugger, I believe I identified the reason for this bug.
https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/eb213838233cbee2030891203bcb72d2354a5a7d/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L773-L781
Line 773 will eventually lead to
OnNetworkSpawn
being called, which in turn will result in our call toNetworkShow
. The code forNetworkShow
defers creating theCreateObjectMessage
until the end of the frame, but also adds the client ID to theNetworkObject.Observers
list:https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/eb213838233cbee2030891203bcb72d2354a5a7d/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L558-L559
The problem is, after the call to
NetworkManager.SpawnManager.SpawnNetworkObjectLocally
returns inNetworkObject.SpawnInternal
, it will then loop through the observers and add aCreateObjectMessage
for them (sinceNetworkShow
would have added them to the observers list). Then, at the end of the frame, anotherCreateObjectMessage
will be added due to the call toNetworkManager.SpawnManager.MarkObjectForShowingTo
insideNetworkShow
.I think a potential solution to this issue would be to only call
NetworkManager.SpawnManager.MarkObjectForShowingTo
insideNetworkShow
if theNetworkObject
has finished spawning (i.e.,SpawnInternal
has returned). Something like this:and then
m_FinishedSpawning
would be set totrue
at the end ofSpawnInternal
. A similar thing should probably be done forNetworkHide
as well.I haven't tested this fix myself - just a thought.