Destroying Networked GameObjects. NetworkServer.Destroy Issues

I have GameObjects (like explosive barrels) that when destroyed should:
-explode in a fireball
-generate shrapnel
-play an audio file

Here’s example video.

I’m struggling with the proper way handle destruction of objects.

Issues:
1: Using NetworkServer.Destroy will destroy the objects on the host and clients. Using OnDestroy() to locally generate shrapnel, explosion, sound, etc. on each client works perfect. However, when you change scenes OnDestroy() is also called throwing errors “Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)”. I do not know of a way to distinguish if OnDestroy is being called from NetworkServer.Destroy, or if it’s being called from a scene change.

2: If I play an AudioClip on the GameObject that is destroyed then the effect does not play (because the object is destroyed). Either you must disable the object (disable renderer, scripts, collider, etc.) and “hide” the object so the audio clip continues to play, or alternatively destroy the object and generate a new invisible GameObject at the same location, attach an AudioSource, set the AudioSource settings, play the AudioClip, and then Destroy it when the clip is finished (I am doing this as opposed to AudioSource.PlayClipAtPoint so I can adjust AudioSource parameters before playing clip). Neither of these seem clean/correct. Is there a better way?

3: If you use any [ClientRpc] at the same time the object is destroyed it throws warnings such as “Did not find target for RPC message for 28”. In my case I have RPC’s to play audio clips, but these break if used at the same time the object is destroyed.

The only relatively clean solution I’ve found is to not use NetworkServer.Destroy() or OnDestroy() alltogether. Instead create a new custom destroy RPC.

[ClientRpc] 
private void RpcDestroy()
{
   Destroy(gameObject); //destroys locally on client
   //do local stuff here like generate explosion, shrapnel, and play sounds
}

But this seems wrong to circumvent the Unity NetworkServer.Destroy() and OnDestroy() functionality (My fear is not using NetworkServer.Destroy() on networked objects may create unintended consequences). Am I missing something?

OnNetworkDestroy should solve your problem! It’s a function on the NetworkBehaviour that’s only called when the NetworkServer destroys an object.

1 Like

Thanks!

OnNetworkDestroy() seems to be called when a scene/level is closed. I think this means that both OnDestroy() and OnNetworkDestroy() should never be used to instantiate objects such as shrapnel or invisible audio source objects or do any other “OnDeath” functionality.

I think avoiding NetworkServer.Destroy, OnDestroy, or OnNetworkDestroy altogether but instead using RpcDestroy seems to be the solution… as long as it doesn’t create unintended consequences.

I still have not found a solution to this issue. The OnDestroy/OnNetworkDestroy methods can be called a variety of ways and I am not sure how to tell if it’s called because…
-The application is stopped
-The scene is exited forcing all objects to be destroyed
-Destroy() is called (either on Client or Server)
-NetworkServer.Destroy() is called
-The network connection is terminated (this causes Spawned objects to be destroyed, and you to be sent to offline scene which destroys scene objects)

Has anybody else ran into problems with this? Any solutions? Thanks in advance.

Well, when the scene ends, kinda everything is destroyed, thats why those are called (also thats why DontDestroyOnLoad is named like that) and this only happens once, so if you Instantiate something when the scene end destruction wave happens, it is not “cleaned” causing trouble, then, you could simply do something like:

void Update() { //Or wherever this happens
      if (criteria-to-blow-up-are-met) { CmdExplode();}
}

[Command] void CmdExplode() {
      GameObject explosion = (GameObject)Instantiate(explosionPrefab, transform.position, transform.rotation);
      NetworkServer.Spawn(explosion);
      GameObject musicGameobject = (GameObject)Instantiate(invisibleMusicGameObjectPrefab, transform.position, transform.rotation);
      NetworkServer.Spawn(musicGameobject); //I suggest using the invisible music prefab
      NeworkServer.Destroy(this.gameobject); //Not sure, never used it before, I do the normal singleplayer Destroy(gameobject); and works for me.
}

No extra coroutines called, so no problems when closing the scene

I lack resources to provide a code that adapts to your script perfectly (like the name of the prefab, music, criteria met for the barrel to blow up, etc…)

Hope it helps

1 Like

Also, as previously you used OnDestroy(), or OnNetworkDestroy(), that means you simply change all instances of NetworkServer.Destroy(); to CmdExplode();, as those are where the criteria to blow is met.

1 Like

Thanks for the code. Creating a new networked explosion object and spawning it from the server when the object is destroyed (and criteria met) should work… but it’s part of what I was trying to avoid. The client knows that it will explode and generate a sound… then there should be no reason to send all this networked explosion & sound this information across the network (for bandwidth reasons). But I agree it should work.

So it seems like there are a few options
1: During OnDestroy() have sufficient information to know why it’s being destroyed (application close, scene change, forced destroy, connection lost, etc.). This is ideal but Unity does not appear to support this.
2: Workaround A: When an object is destroyed + criteria met then network spawn all the stuff (i.e. an explosion, a sound, shrapnel, etc.). This uses a lot of additional network bandwidth (especially for stuff like bullets being destroyed that may happen a lot).
3: Workaround B: Before you call Destroy() send a network message MessageCustomDestroy using channel#0 (reliable sequenced). This message will arrive just before the UNET destroy message. So if CustomDestroy() is called then you know you destroyed the object yourself and you can do special stuff (instantiate explosion & sound locally). But if OnDestroy() is called without CustomDestroy() being called first then you know the object was destroyed due to other reasons (i.e. scene change, app close, connection lost, etc.). I need to test this but I think I may try this workaround.

So something like this

private bool isCustomDestroy = false;

public void CustomDestroy() //This method will destroy the object and also set the isCustomDestroy to true on all clients
{
   //generate & send MessageCustomDestroy to all observers
   Destroy(gameObject);
{

public void ReadMessageCustomDestroy() //this is called by the MessageCustomDestroy
{
   isCustomDestroy = true;
}

private void OnDestroy()
{
   //isCustomDestroy will be accessible in this method allowing you to make decisions.
}


...something along these lines?

Well Im not that experienced with functions and messages, as the need hasnt come to me yet, but you should give that a try, after all, there is nohing to loose, but id recommend to have a backup of the script when doing experimental stuff.

About the music, why not including it in the explosion prefab?

1 Like

I do sounds a different way for networking & avoiding duplication reasons. Each sound is loaded along and given an ID# along with some information (sound level, falloff distance parameters, etc.). I do this because I reuse sounds in many locations and I don’t want to have to set their properties everywhere they are used (that’s bad practice). I also do this to network sounds. For example I call PlayNetworkSound(uint SoundID#, vec3 location) or for 2D sounds PlayNetworkSound(uint SoundID#). When the message is received on client it instantiates an object at that location and plays a sound. Sounds can also be played locally (i.e. called during OnDestroy). But the sound (usually) is detached from the object. The reason I detach sounds is so the object can be destroyed without cutting off the sound file in mid playback (i.e. an arrow hitting a wall and playing an impact sound at that location… either you need to have a detached sound player or make the arrow invisible w/ a delayed destroy). If you instantiate a new detached sound object you need to know if the object is being destroyed yourself or if the scene is closing… because if the scene/application is closing then you DONT want to generate/detach new objects or scene cleanup will fail. Easy to do in singleplayer, just struggling to find a good networked way of doing this.

So maybe it’s best to have an message for that calls OnCustomDestroy()? So something like this…

public void OnCustomDestroy() //this is only called when the user forces an objects destruction.  will never occur on scene exit, network disconnect, or application close
{
 //instantiate explosion prefab locally (no networking or spawn)
 //play explosion sound locally (no networking or spawn)
 Destroy(gameObject)
}

public void OnDestroy() //this will be called on application exit, scene exit, Destroy(), NetworkServer.Destroy(), or after OnCustomDestroy()
{
   //common code here that is ALWAYS called regardless how the object is destroyed.  This code should NEVER spawn objects or scene cleanup will fail when exiting a scene.
}

And as long as the CustomDestroyMessage uses channel0 reliable sequenced it should beat the NetworkIdentity automatic destroy message (that cannot be stopped due to UNET sealed classes) to the client (in which case the UNET destroy message will be ignored because the object is already destroyed?)

I’ll try this and post how it goes.