Ghost groups
Use ghost groups to synchronize replication timings across multiple ghost instances, and prevent common gameplay state errors.
Ghost group usage
Configure a ghost group
To create a ghost group, you need to define a ghost group root, then define said ghost group root's children.
- Add a
GhostGroup
buffer to a ghost prefab at authoring time using the Ghost Group toggle in the GhostAuthoringComponent's Inspector window. This defines the ghost group root, and allows ghost group membership (by other ghost instances). - For each ghost group child instance;
- Add the
GhostChildEntity
component to said child. - Add the child ghost
Entity
to theGhostGroup
buffer on the root.
- Add the
Ghost group behaviour
- All ghost group children added to the ghost group root are guaranteed to be sent whenever the root ghost entity is.
- The root ghost entity is implicitly defined as the ghost instance with the
GhostGroup
buffer component, and without theGhostChildEntity
component.
Ghost group limitations
- There can only be one root ghost entity per group.
- Ghost groups do not support nesting. I.e. A ghost group entry cannot be a member of multiple ghost groups.
Relevancy
isn't supported for ghost group child entities while they are within a ghost group. They inherit the relevancy of their root ghost entity, and marking a child as irrelevant has no effect.
Note
Known issue as of 03/2025: If a relevant ghost group root becomes irrelevant, its children will not currently be made irrelevant - they will be left stranded.
GhostOptimizationMode.Static
isn't supported for ghost groups. You also can't mark children asStatic
while the root isDynamic
(or vice-versa). AllGhostGroup
ghosts are forcedDynamic
, even if authored asStatic
.- The
Importance
,GhostImportance
Importance Scaling
, andMax Send Rate
of child ghosts is ignored when they are part of a group. Only theGhostGroup
root ghost chunks values are used. GhostGroup
serialization is significantly slower (relatively speaking), as we must traverse to the chunk of each individual child (similar to how replicatingGhostField
's on components onUnity.Transforms.Child
child ghost entities is slower). Consider the potential impact on performance when using ghost groups.- Serializing
GhostGroup
entries can cause a single snapshot to be larger than the default (ofNetworkParameterConstants.MaxMessageSize
), which may increase the frequency of snapshot packet fragmentation.
Note
For performance reasons, errors are not reported for incorrect GhostGroup
usage.
Ghost group example use case
You have a Player
character controller ghost in a First-Person Shooter, that can pick-up, drop, and carry three individual Gun
ghost instances.
When carried by the player, each gun is attached to different points on the characters body (using a faked parenting approach), and can drive the characters hand animation state.
Your assets may look like this:
'Player' Ghost Prefab
Importance:100, Max Send Rate:60, Owner Predicted, Has Owner, Dynamic,
RelevantWithinRadius:1km, DynamicBuffer<GhostGroup> (Count:0)
'Gun' Ghost Prefab
Importance:10, MaxSendRate:10, Owner Predicted, Has Owner, Static,
RelevantWithinRadius:200m
Without ghost groups
Without ghost groups, you may experience the following issues during gameplay:
- When other players observe you picking up and firing a gun, they see your characters hand animation update before the gun physically moves into your hand.
- The held gun may not perfectly follow the players hand position and rotation, either.
- They may even see the gun firing FX appear from the gun on the ground (or in the process of being picked up).
- Distant players may appear to be empty-handed (due to differences in Relevancy and/or Importance leading to gun spawn delays).
- Exceptions may be thrown inside client
Gun
systems if they (incorrectly) assume that aGun
entity'sHoldingPlayer
entity reference will always exist, but it's been deleted in a previous snapshot before the deletion of thisGun
was replicated (or vice-versa).
With ghost groups
To use ghost groups in this example:
- Add the
GhostGroup
buffer to thePlayer
ghost (by checking the GhostGroup option on the GhostAuthoringComponent's Inspector window). - At runtime, when picking up a gun instance, add said
Gun
ghost entity to thePlayer
'sGhostGroup
buffer... - ...and add the
GhostChildEntity
component to saidGun
instance. - Similarly, when dropping a gun, remove it from the
Player
'sGhostGroup
buffer, and remove theGhostChildEntity
component from the (now dropped) gun.
This makes the Player
ghost instance the ghost group root, and each picked up Gun
ghost instance a ghost group child.
Each Gun
ghost instance will now be replicated every time the Player
ghost instance is, preventing the issues described in the without ghost groups section.
It also means:
- Each
Gun
'sImportance
is now effectively 100 and theirMax Send Rate
is 60 (the same as thePlayer
ghost group root). - Each
Gun
ghost instance is no longer static-optimized (and therefore, won't be forced intoUseSingleBaseline:true
). - Each
Gun
instance is only considered relevant to a connection if its ghost group root is and spawns the moment thePlayer
itself spawns (once within 1km of each connection's ownPlayer
).