onavatardestroyed never called
I am trying to make a LocalPlayerRegistry script that has a list of all players in the Normcore Room with their Meta display name and clientID attached. I am using RealtimeAvatarManager to get a list of all players, but for some reason if Client A and Client B are in the same room, and Client B leaves, Client A never gets an OnAvatarDestroyed event from the RealtimeAvatarManager.
44 Replies
Hi @Alix Ollivier. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
Based on the knowledge sources, the
The most common cause for
XR Common Questions
XR Avatars Reference ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
avatarDestroyed event on RealtimeAvatarManager should be called when an avatar is destroyed, such as when a client leaves the room. The typical usage is to subscribe to avatarCreated and avatarDestroyed events in your script, as shown in the documentation:
If you are not receiving the avatarDestroyed event when another client disconnects, here are some things to check:
1. Prefab Setup: Ensure your avatar prefab includes a RealtimeAvatar component on the root. The RealtimeAvatarManager relies on this to track and manage avatars properly.
2. Ownership and Lifetime Flags: The default behavior is for the root RealtimeView of the avatar to have destroyWhenOwnerLeaves set to true. If you have changed this value or cleared the owner of the avatar, the server will not clean up the avatar properly, and the avatarDestroyed event may not fire as expected. Double-check that your avatar prefab’s root RealtimeView has destroyWhenOwnerLeaves enabled. This is mentioned explicitly in the documentation:
The root RealtimeView is set toXR Common Questions 3. Correct Event Subscription: Make sure you are subscribing to the event before connecting to the room and that you are not accidentally unsubscribing or missing the event due to script execution order. If all of the above are set up correctly and you are still not receiving the event, it may be worth checking for errors in the console or reviewing your prefab and scene setup for any deviations from the recommended configuration. Summary:destroyWhenOwnerLeavesby default. If you change this value, or if you clear the owner of the avatar itself, then it will not be cleaned up properly by the server.
The most common cause for
avatarDestroyed not firing is that the avatar’s root RealtimeView does not have destroyWhenOwnerLeaves set to true, or ownership is not set up correctly. Double-check these settings in your prefab and ensure your event subscription is active before connecting to the room.XR Common Questions
XR Avatars Reference ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Do you see the avatar get destroyed?
we see the avatar as disabled, but not destroyed
RealtimeAvatar’s OnDestroy() calls RealtimeAvatarManager’s avatarDestroyed event. the source code is included. it’s hard to imagine how the event doesn’t fire as long as the avatar prefab is destroyed
then you’ve got something else going on. Normcore doesn’t disable prefabs in response to anything
I expect it’s not being destroyed on the owning client side either then
yeah this seems to be the source of the issue, we messed with it somewhere. Thanks for the heads up!

@Max My cursor is telling me that this "Scene View Destroy" when Last Client Leaves" should be unchecked, does that seem right? I don't remember ever touching this and I could only find it through the unity debug, so I want to be cautious.

that has no affect on prefabs
only on views that exist in the scene
@Normcore Assistant I made a debug script that drops the stack when an avatar leaves the room.
When I'm on Client A and Client B is in the room, then they leave, this is the stack that I get:
Let me check the knowledge base for you again. One moment! :books:
----------------------
I'm sorry, but the stack trace you mentioned was not included in your message. If you provide the actual stack trace or additional details, I can help analyze it based on the available Normcore documentation.
From the knowledge sources, the most common reasons for
avatarDestroyed not firing as expected are:
- The root RealtimeView of the avatar prefab does not have destroyWhenOwnerLeaves set to true, or ownership is not set up correctly. This is required for the server to clean up the avatar and trigger the event on other clients when someone leaves the room XR Common Questions.
- The RealtimeAvatar component must be present on the root of the prefab for RealtimeAvatarManager to track it properly XR Avatars Reference.
If you can share the stack trace, I can provide more targeted feedback. Otherwise, please review the above points in your setup.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai
Why is my RealtimePool calling SetActive on my Player Prefab instead of Destroy() when I leave a scene?
at UnityEngine.GameObject.SetActive_Injected (System.IntPtr , System.Boolean ) [0x00000] in <a25ed31d46d841d0bef9ea31576356a5>:0
at UnityEngine.GameObject.SetActive (System.Boolean value) [0x00000] in <a25ed31d46d841d0bef9ea31576356a5>:0
at Normal.Realtime.RealtimePool+Pool.ReturnToPool (UnityEngine.GameObject prefabInstance) [0x0000c] in .\Library\PackageCache\com.normalvr.normcore@4655f1e6de50\Normal.Realtime.Shared\Pooling\RealtimePool.cs:49
at Normal.Realtime.RealtimePool.DestroyRealtimePrefab (UnityEngine.GameObject prefabInstance) [0x0001a] in .\Library\PackageCache\com.normalvr.normcore@4655f1e6de50\Normal.Realtime.Shared\Pooling\RealtimePool.cs:177
Let me check the knowledge base for you again. One moment! :books:
----------------------
The behavior you're seeing—where
RealtimePool calls SetActive(false) on your player prefab instead of destroying it—is the intended design of Normcore's prefab pooling system. When using RealtimePool, prefabs are not destroyed when they are no longer needed. Instead, they are deactivated and returned to the pool for future reuse. This is done to improve performance by avoiding the overhead of destroying and recreating GameObjects, which can cause frame drops due to garbage collection and instantiation costs.
From the documentation:
When a realtime prefab is destroyed: 1. The root GameObject is disabled instead of getting destroyed. 2. The instance is returned to its pool for future reuse.>
warning Prefabs are never destroyed, which meansPrefab Pooling So, when you leave a scene or a player disconnects,OnDestroy()will not be called on pooled prefab instances. We recommend moving this logic over toOnEnable()andOnDisable()or theIRealtimePoolCallbacksmethods (described below) instead.
RealtimePool will call SetActive(false) on the prefab instance to return it to the pool, rather than destroying it. This is why you see SetActive in your stack trace instead of Destroy().
If you need to run cleanup logic when a prefab is returned to the pool, use the IRealtimePoolCallbacks interface and implement the PrefabWillReturnToPool() method on your component, as described in the documentation.
If you want the prefab to be destroyed instead of pooled, you would need to avoid using RealtimePool for that prefab.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai Earlier, Max said the following:
RealtimeAvatar’s OnDestroy() calls RealtimeAvatarManager’s avatarDestroyed event. the source code is included. it’s hard to imagine how the event doesn’t fire as long as the avatar prefab is destroyed
then you’ve got something else going on. Normcore doesn’t disable prefabs in response to anything
How does this conflict with what you just told me?
Let me check the knowledge base for you again. One moment! :books:
----------------------
Max’s statement and the behavior you’re seeing with
When a
With pooling enabled, Normcore changes the destruction behavior. Instead of destroying the prefab, it disables it by calling
There is no conflict—Max is describing the default, non-pooled behavior. When you use
RealtimePool are both correct, but they apply to different scenarios:
- Normcore’s default behavior (without pooling):When a
RealtimeAvatar prefab is destroyed (using Realtime.Destroy()), its OnDestroy() method is called, which in turn triggers the avatarDestroyed event on RealtimeAvatarManager. This is the standard flow, and as Max said, “it’s hard to imagine how the event doesn’t fire as long as the avatar prefab is destroyed.” Normcore itself does not disable prefabs in this default flow; it destroys them, ensuring the event fires as expected.
- When using RealtimePool:With pooling enabled, Normcore changes the destruction behavior. Instead of destroying the prefab, it disables it by calling
SetActive(false) and returns it to the pool. This means OnDestroy() is not called, and therefore, any logic (including firing the avatarDestroyed event) that depends on OnDestroy() will not execute. This is explicitly documented:Prefabs are never destroyed, which meansPrefab Pooling Summary:OnDestroy()will not be called on pooled prefab instances. We recommend moving this logic over toOnEnable()andOnDisable()or theIRealtimePoolCallbacksmethods (described below) instead.
There is no conflict—Max is describing the default, non-pooled behavior. When you use
RealtimePool, you must adapt your logic to use pooling callbacks or OnDisable() instead of relying on OnDestroy(), because pooling disables rather than destroys prefabs.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai is the issue that for some reason my RealtimePool is collecting my Normcore Avatar when it's not supposed to?
Let me check the knowledge base for you again. One moment! :books:
----------------------
Based on the knowledge sources, if your
RealtimePool is collecting (i.e., disabling and pooling) your Normcore Avatar prefab when you expect it to be destroyed, this is likely because the prefab pooling system is enabled for your avatar prefab. When pooling is active, Normcore will call SetActive(false) instead of destroying the prefab, which prevents OnDestroy() from being called and can interfere with events like avatarDestroyed that rely on destruction logic. This is the documented and intended behavior of RealtimePool:
When a realtime prefab is destroyed: 1. The root GameObject is disabled instead of getting destroyed. 2. The instance is returned to its pool for future reuse.>
warning Prefabs are never destroyed, which meansPrefab Pooling If you do not want your avatar prefab to be pooled, you should ensure thatOnDestroy()will not be called on pooled prefab instances. We recommend moving this logic over toOnEnable()andOnDisable()or theIRealtimePoolCallbacksmethods (described below) instead.
RealtimePool is not managing that prefab, or adjust your logic to use pooling callbacks or OnDisable() instead of OnDestroy().
The knowledge sources do not provide explicit details on how to exclude specific prefabs from pooling, but they do confirm that pooling changes the destruction behavior and that this can affect event firing. If you need the default destruction behavior for avatars, you should avoid pooling them.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@maxweisel is my RealtimePool supposed to be called on NormcoreAvatars?
the pool script will be called for every realtime instantiate and destroy on that realtime instance, you can add a test for avatars if you don't want them pooled or add an ondisable to your avatars to detect when it's being pooled or use another realtime for avatars
So would the pool ever disable my destroy in favor of a set active(false) call?
not sure what you mean, OnDisable gets called in either situation
the pooling script intercepts Realtime.Destroy calls so that it can pool the object instead
Max mentioned earlier in this chat that an avatar prefab should be destroyed, not disabled. When you run your games, do you see a disabled Normcore clone every time someone leaves a room?
I'm not getting on avatar destroyed calls when a player disconnects from a room, and the stack trace I posted above is showing that it's because it's being disabled by the realtime pool instead.
that's if you don't use pooling
How do I disable pooling for my RealtimeAvatars?
you can edit the pooling script or add another realtime instance for avatars
Ah, my confusion over this whole thing was because I disabled my Realtimepool script and it still functioned the same, I had to remove it from my scene and now the avatardestroyed works appropriately. So I need to modified my realtimepoolbootstrap script to not work on avatars.
Thanks for the help @Mechabit ! You're a great asset to the Normcore community.
@Normcore Assistant , I want to modify my RealtimePool script so that it handles avatar prefabs differently from all other prefabs. I don't want to modify the script directly because it'll get replaced next time we update Normcore. Is there a flag I can call on my avatars to make the RealtimePool not follow them?
Let me check the knowledge base for you again. One moment! :books:
----------------------
I'm sorry, but the knowledge sources do not mention any built-in flag or setting that you can apply to specific prefabs (such as avatars) to exclude them from being managed by
- Fork and customize the
RealtimePool. The documentation only describes how RealtimePool works globally and how to preload, clear, or asynchronously manage pools, but it does not provide a way to opt-out individual prefabs from pooling through a flag or inspector setting.
If you need to handle avatar prefabs differently, you would need to either:
- Avoid adding them to the pool in your project setup, or- Fork and customize the
RealtimePool script as suggested for advanced pooling needs, since all source code is included with Normcore and can be modified for your use case Advanced prefab pooling.
Unfortunately, there is no documented way to exclude specific prefabs from pooling without modifying the pooling logic itself.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@Normcore Assistant if I fork a script, will I need to update it every time we get a new Normcore version?
Let me check the knowledge base for you again. One moment! :books:
----------------------
If you fork a script like
RealtimePool to customize its behavior, you may need to manually update your forked version whenever you upgrade to a new version of Normcore, especially if the original script receives bug fixes or new features. The documentation notes that all source code for RealtimePool is included and can be forked for advanced pooling needs, but it does not specify an automatic way to keep your fork in sync with future updates. Therefore, maintaining a forked script typically requires you to review and merge changes from new Normcore releases as needed Advanced prefab pooling.
If you need to avoid this maintenance, consider using the default pooling behavior or structuring your project to minimize the need for custom forks.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@Normcore Assistant How could I make a new Realtime Instance just for avatars that doesn't include the realtimepool?
Let me check the knowledge base for you again. One moment! :books:
----------------------
To create a new
The knowledge sources do not provide step-by-step instructions for this setup, but they do confirm that multiple
Realtime instance just for avatars that does not include the RealtimePool, you can add a separate Realtime component to a different GameObject in your scene and ensure that you do not add a RealtimePool component to that GameObject.
Each Realtime instance manages its own connection to a room and its own set of synchronized objects. You can assign specific prefabs (like your avatars) to use this dedicated Realtime instance by configuring the RealtimeView's "Realtime Instance" property or by passing the useInstance parameter in Realtime.Instantiate():
This way, your avatars will be managed by a Realtime instance that does not use pooling, while other objects can use a different Realtime instance with pooling enabled if needed. The documentation confirms that you can have multiple Realtime instances in a scene, each managing its own set of objects and settings Realtime component reference.
Note:The knowledge sources do not provide step-by-step instructions for this setup, but they do confirm that multiple
Realtime instances are supported and that each manages its own configuration and objects.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@Normcore Assistant if I have 2 realtime instances in a scene, does the amount of data/hours we spend double?
Let me check the knowledge base for you again. One moment! :books:
----------------------
If you have two
Pricing Summary:
If your two
Realtime instances in a scene, each one creates its own connection to a room server. The amount of data and room hours you use will depend on how many room servers you are connected to and how much data is being synchronized through each connection.
If both Realtime instances connect to the same room, you are still only using one room server, but you may be sending more data if both instances are synchronizing different sets of objects.
If each Realtime instance connects to a different room, you will be billed for both room servers (room hours) and the bandwidth used by each connection. So, in this case, your usage could double.
From the documentation:
Normcore allows you to connect to multiple room servers simultaneously. You can use them to split large MMORPG spaces into smaller shards or to group traffic to host large events like concerts.>
Room hours: Room hours refer to the total number of hours that a room server is running and clients are connected.Essentials Common Questions
Bandwidth: Bandwidth refers to the amount of data sent to and from Normcore's servers.
Pricing Summary:
If your two
Realtime instances connect to different rooms, your room hour and bandwidth usage will increase accordingly. If they connect to the same room, you are still billed for one room server, but your bandwidth may increase depending on the data being synchronized.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@185325614775926784> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
RealtimePool + RealtimeAvatar is an interesting use case I must have missed when I made RealtimePool. Yes it would make sense for us to support a per-prefab "opt out from pooling" setting out of the box. I'll bring it up with the team.
I'm using realtimeavatar as a way to track all the players in my scene so I can have a list for mods to ban particular players. Should I be using something else for this sort of function?
For anyone who faces this issue in the future, I made a ForkedRealtimePool script which avoids all avatars!
sweet! thanks for posting this!
I'll get a more official fix queued up in Normcore too. We have some improvements coming to RealtimePrefabDelegate that will allow them to be chained (and fire their own events) which would resolve issues like this.