A ghost is a networked object that the server simulates. During every frame, the server sends a snapshot of the current state of all ghosts to the client. The client presents them, but cannot directly control or affect them because the server owns them.
The ghost snapshot system synchronizes entities which exist on the server to all clients. To make it perform properly, the server processes per ECS chunk rather than per entity. On the receiving side the processing is done per entity. This is because it is not possible to process per chunk on both sides, and the server has more connections than clients.
Why use Ghosts? Ghosts vs. RPCs Ghost Snapshot Synchronization ("Eventual Consistency") Use-CasesGhost can be authored in the editor by creating a Prefab with a GhostAuthoringComponent.
The GhostAuthoringComponent has a small editor which you can use to configure how Netcode synchronizes the Prefab.
You must set the Name, Importance, Supported Ghost Mode, Default Ghost Mode and Optimization Mode property on each ghost.
Netcode for Entities uses the Importance property to control which entities are sent when there is not enough bandwidth to send all. A higher value makes it more likely that the ghost will be sent.
You can select from three different Supported Ghost Mode types:
You can select from three different Default Ghost Mode types:
You can select from two different Optimization Mode types:
Netcode for Entities uses C# attributes to configure which components and fields are synchronized as part of a ghost. There are three fundamental attributes you can use:
NetCode Attribute Usage GhostFieldAttribute TheGhostFieldAttribute
should be used to mark which component (or buffer) fields should be serialised.
[GhostField]
, it becomes replicated, and will be transmitted as part of the ghost data. GhostEnabledBitAttribute Similarly, the GhostEnabledBitAttribute
should be used on an IEnableableComponent
struct definition, to denote that the enabled bit for this component should be serialized.
[GhostEnabledBit]
, its enabled-bit will be replicated, and thus transmitted as part of the ghost data. GhostComponentAttribute The GhostComponentAttribute
should be used on a ComponentType
struct definition to:
To signal the Netcode for Entities that a component should be serialised, you need to add a [GhostField]
attribute to the values you want to send.
public struct MySerialisedComponent : IComponentData
{
[GhostField]public int MyIntField;
[GhostField(Quantization=1000)]public float MyFloatField;
[GhostField(Quantization=1000, Smoothing=SmoothingAction.Interpolate)]public float2 Position;
public float2 NonSerialisedField;
...
}
For a component to support serialization, the following conditions must be met:
public
.public
members are considered. Adding a [GhostField]
to a private member has no effect.GhostField
can specify Quantization
for floating point numbers (and other supported types, see Ghost Type Templates). The floating point number will be multiplied by this Quantization
value, and converted to an integer, in order to save bandwidth. Specifying a Quantization
is mandatory for floating point numbers. To send a floating point number unquantized; you have to explicitly specify [GhostField(Quantization=0)]
.GhostField
Composite
flag controls how the delta compression computes the change fields bitmask for non primitive fields (i.e. structs). When set to true
, the delta compression will generate only 1 bit to indicate if the entire struct contains any changes. If Composite
is false, each field will have its own change-bit. Thus, use Composite=true
if all fields are typically modified together (example: GUID
).GhostField
SendData
flag (which defaults to true
) can be used to instruct code-generation to not include this field in the serialization data. This is particularly useful for non primitive members (like structs), which will have all fields serialized by default.GhostField
also has a Smoothing
property, which controls how the field will be updated, when the ghost is in GhostMode.Interpolated
. I.e. When a given client is not predicting said ghost. Possible values are:
SmoothingAction.Clamp
- Every time a snapshot is received, jump (i.e. clamp) the client value to the latest snapshot value.SmoothingAction.Interpolate
- Every frame, interpolate the field between the last two snapshot values. If no data is available for the next tick, clamp to the latest value.SmoothingAction.InterpolateAndExtrapolate
- Every frame, interpolate the field between the last two snapshot values. If no data is available for the next tick, the next value is linearly extrapolated using the previous two snapshot values. Extrapolation is limited (i.e. clamped) via ClientTickRate.MaxExtrapolationTimeSimTicks
.GhostField
MaxSmoothingDistance
allows you to disable interpolation when the values change more than the specified limit between two snapshots. This is useful for dealing with teleportation, for example.GhostField
has a SubType
property which can be set to an integer value to use special serialization rules supplied for that specific field.Authoring dynamic buffer serialization[!NOTE] Speaking of teleportation: To support short range teleportation, you'd need some other replicated bit to distinguish a teleport from a move (lerp).
Dynamic buffers serialization is natively supported. Unlike components, to replicate a buffer, all public fields must be marked with at [GhostField]
attribute.
[!NOTE] This restriction has been added to guarantee that: In the case where an element is added to the buffer, when it is replicated to the client, all fields on said element will have meaningful values. This restriction may be removed in the future (e.g. by instead, defaulting this undefined behaviour to
default(T)
).
public struct SerialisedBuffer : IBufferElementData
{
[GhostField]public int Field0;
[GhostField(Quantization=1000)]public float Field1;
[GhostField(Quantization=1000)]public float2 Position;
public float2 NonSerialisedField; // This is an explicit error!
private float2 NonSerialisedField; // We allow this. Ensure you set this on the client, before reading from it.
[GhostField(SendData=false)]public int NotSentAndUninitialised; // We allow this. Ensure you set this on the client, before reading from it.
...
}
Furthermore, in line with the IComponentData
:
public
.public
members are considered. Adding a [GhostField]
to a private member has no effect.GhostField.SendData
you can instrument the serialisation code to skip certain field. In such a case:
Dynamic buffers fields don't support SmoothingAction
s. Thus, the GhostFieldAttribute.Smoothing
and GhostFieldAttribute.MaxSmoothingDistance
properties will be ignored when used on buffers.
ICommandData, being a subclass of IBufferElementData, can also be serialized from the server to clients. As such, the same rules for buffers apply: if the command buffer must be serialized, then all fields must be annotated.
[GhostComponent()]
public struct MyCommand : ICommandData
{
[GhostField] public NetworkTick Tick {get; set;}
[GhostField] public int Value;
}
The same applies when using automated input synchronization with IInputComponentData.
public struct MyCommand : IInputComponentData
{
[GhostField] public int Value;
}
The command data serialization is particularly useful for implementing RemotePlayerPrediction.
Ghost Field InheritanceIf a [GhostField]
is specified for a non primitive field type, the attribute (and some of its properties) are automatically inherited by all the sub-fields which do not themselves implement a [GhostField]
attribute. For example:
public struct Vector2
{
public float x;
[GhostField(Quantization=100)] public float y;
}
public struct MyComponent : IComponentData
{
//Value.x will inherit the quantization value specified by the parent definition (1000).
//Value.y will maintain its original quantization value (100).
[GhostField(Quantized=1000)] public Vector2 Value;
}
The following properties are not inherited:
The GhostComponentAttribue
does not indicate or signal that a component is replicated (that's what the other two attributes are for). Instead, it should be used to instruct the runtime how to handle the component when it comes to:
SendDataForChildEntity
denotes whether or not to replicate this component when it is attached to a child of a ghost entity. Replicating child of ghost entities is significantly slower than replicating those ghost root entities. Thus, we default to false
. This flag also applies to the [GhostEnabledBit]
.PrefabType
allows you to remove the component from the specific version of the ghost prefab. Ghost prefabs have three versions: Interpolated Client (IC)
, Predicted Client (PC)
, and Server (S)
. Example: Removing rendering related components from the server world's version of this ghost.GhostSendType
denotes whether or not the component should be sent when the ghost is GhostMode.Predicted
vs GhostMode.Interpolated
. Example: Only sending PhysicsVelocity
when you're actually predicting the physics of a ghost.SendToOwnerType
denotes whether or not to replicate this data when the ghost is owned, vs not owned, vs either. Example: Replicating input commands only to other players (as you already know your own).[GhostComponent(PrefabType=GhostPrefabType.All, SendTypeOptimization=GhostSendType.OnlyInterpolatedClients, SendDataForChildEntity=false)]
public struct MyComponent : IComponentData
{
[GhostField(Quantized=1000)] public float3 Value;
}
PrefabType Details
To change which versions of a Ghost Prefab a component is available on, use PrefabType
in a GhostComponentAttribute
on the component. PrefabType
can be on of the these types:
InterpolatedClient
- the component is only available on clients, and only when the ghost is interpolated.PredictedClient
- the component is only available on clients, and only when the ghost is predicted.Client
- the component is only available on clients, regardless of the GhostMode
(e.g. either predicted or interpolated).Server
- the component is only available on the server.AllPredicted
- the component is only available on the server, and on clients, only when the ghost is predicted.All
- the component is available on the server and all clients.For example, if you add [GhostComponent(PrefabType=GhostPrefabType.Client)]
to RenderMesh
, the ghost wonât have a RenderMesh
when it is instantiated on the server world, but it will have it when instantiated on the client world.
SendTypeOptimization Details[!NOTE] Runtime Prediction Switching therefore has the potential to add and remove components on a ghost, live.
A component can set SendTypeOptimization
in the GhostComponentAttribute
to control which clients the component is sent to, whenever a ghost type is known at compile time. The available modes are:
None
- the component is never sent to any clients. Netcode will not modify the component on the clients which do not receive it.Interpolated
- the component is only sent to clients which are interpolating the ghost.Predicted
- the component is only sent to clients which are predicting the ghost.All
- the component is sent to all clients.A component can also set SendDataForChildEntity to true in order to change the default (of not serializing children), allowing this component to be serialized when on a child.
A component can also set SendToOwner in the GhostComponentAttribute to specify if the component should be sent to client who owns the entity. The available values are:
How to add serialization support for custom Types[!NOTE] By setting either the
SendTypeOptimisation
and/orSendToOwner
(to specify to which types of client(s) the component should be replicated to), will not affect the presence of the component on the prefab, nor modify the component on the clients which did not receive it.
The types you can serialize via GhostFieldAttribute
are specified via templates. You can see the default supported types [here](ghost-types-templates.md#Supported Types)
In addition to the default out-of-the-box types you can also:
GhostFieldAttribute
.Please check how to [use and write templates](ghost-types-templates.md#Defining additional templates) for more information on the topic.
Ghost Component Variants[!NOTE] Writing templates is non-trivial. If it is possible to replicate the type simply by adding GhostFields, it's often easier to just do so. If you do not have access to a type, create a Variant instead (see section below).
The GhostComponentVariationAttribute is special attribute tha can be used to declare at compile time a "replication schema" for a type, without the need to markup the fields in the original type, or the original type itself.
[!NOTE]This new declared type act as proxy from a code-generation perspective. Instead of using the original type, the code-generation system use the declared "variant" to generate a specific version of the serialization code.
[!NOTE] Ghost components variants for
IBufferElementData
are not fully supported.
The GhostComponentVariationAttribute
has some specific use-cases in mind:
Unity.Entities.LocalTransform
replicated.Unity.Entities.LocalRotation
, or the full quaternion
.RenderMesh
) from certain prefab types (e.g. from the Server) by overriding or adding a GhostComponentAttribute
to the type, without changing the original declaration. [GhostComponentVariation(typeof(LocalTransform), "Transform - 2D")]
[GhostComponent(PrefabType=GhostPrefabType.All, SendTypeOptimization=GhostSendType.AllClients)]
public struct PositionRotation2d
{
[GhostField(Quantization=1000, Smoothing=SmoothingAction.InterpolateAndExtrapolate, SubType=GhostFieldSubType.Translation2D)]
public float3 Position;
[GhostField(Quantization=1000, Smoothing=SmoothingAction.InterpolateAndExtrapolate, SubType=GhostFieldSubType.Rotation2D)]
public quaternion Rotation;
}
In the example above, the PositionRotation2d
Variation will generate serialization code for LocalTransform
, using the properties and the attribute present in the variant declaration.
The attribute constructor takes a few arguments:
Type type
of the ComponentType
you want to specify the variant for (ex: LocalTransform
).string variantName
, which will allow you to better interpret GhostAuthoringInspectionComponent
UI.Then, for each field in the original struct (in this case, LocalTransform
) that you wish to replicate, you should add a GhostField
attribute like you usually do, and define the field identically to that of the base struct.
[!NOTE] Only members that are present in the component type are allowed. Validation occurs at compile time, and exceptions are thrown in case this rule is not respected.
An optional GhostComponentAttribute
attribute can be added to the variant to further specify the component serialization properties.
It is possible to declare multiple serialization variant for a component (example: 2D and 3D variations for LocalRotation
).
Preventing a component from supporting Variations[!NOTE] If you only define only one Variant for a
ComponentType
, it becomes the default serialization strategy for that type automatically.
There are cases where you'd like to prevent a component from having its serialization modified via Variants. Example: From the NetCode package itself, we must always replicate the GhostComponent
for netcode systems to work properly, so we don't let user-code (you) modify serialization rules for it).
Thus, to prevent a component from supporting variation, use the DontSupportPrefabOverridesAttribute attribute. An error will be reported at compile time, if a GhostComponentVariation
is defined for that type.
Using the GhostAuthoringInspectionComponent
MonoBehaviour
in conjunction with the GhostAuthoringComponent
MonoBehaviour
, it is possible to select what serialization variants to use on a per-prefab basis. You can choose a Variant for each individual component (including the ability to set the special-case variant: DontSerializeVariant
).
All variants for that specific component type present in the project will be show in a dropbox selection.
To modify how children of Ghost prefabs are replicated, add a GhostAuthoringInspectionComponent
to each individual child.
Assigning a default variant to use for a Type[!NOTE] The
GhostAuthoringInspectionComponent
is also an incredibly valuable debugging tool. Add it to a Ghost Prefab (or one of its children) to view all replicated types on said Ghost, and to diagnose why a specific type is not replicating in the way you'd expect.
In cases where multiple variants are present for a type, Netcode may be unable to infer which Variant should be used. If the "Default Serializer" for the Type is replicated, it'll default to it. If not, it is considered a conflict, and you'll get runtime exceptions when creating any netcode world (including Baking worlds). We use a built-in, deterministic, fallback method to guess which variant you likely want, but, in general, it is the users responsibility to indicate what Variant should be the default here.
To setup which variant to use as the default
for a given type, you need to create a system that inherits from DefaultVariantSystemBase class, and implements the RegisterDefaultVariants
method.
using System.Collections.Generic;
using Unity.Entities;
using Unity.Transforms;
namespace Unity.NetCode.Samples
{
sealed class DefaultVariantSystem : DefaultVariantSystemBase
{
protected override void RegisterDefaultVariants(Dictionary<ComponentType, Rule> defaultVariants)
{
defaultVariants.Add(typeof(LocalTransform), Rule.OnlyParents(typeof(TransformDefaultVariant)));
}
}
}
This example code would make sure the default LocalTransform
variant to us as default is the TransformDefaultVariant
. For more information, please refer to the DefaultVariantSystemBase documentation.
Special Variant Types Special Built-in Variant Details[!NOTE] This is the recommended approach to setup the default Variant for a Ghost "project-wide". Prefer
DefaultVariantSystemBase
over direct Variant manipulation (via theGhostAuthoringInspectionComponent
overrides).
ClientOnlyVariant
Use this to specify that a given ComponentType
should only appear on client worlds. ServerOnlyVariant
The inverse. DontSerializeVariant
Use this to disable serialization of a Type entirely. I.e. Use it to ignore replication attributes ([GhostField]
and [GhostEnabledBit]
).
using System.Collections.Generic;
using Unity.Entities;
using Unity.Transforms;
namespace Unity.NetCode.Samples
{
sealed class DefaultVariantSystem : DefaultVariantSystemBase
{
protected override void RegisterDefaultVariants(Dictionary<ComponentType, Rule> defaultVariants)
{
defaultVariants.Add(typeof(SomeClientOnlyThing), Rule.ForAll(typeof(ClientOnlyVariant)));
defaultVariants.Add(typeof(SomeServerOnlyThing), Rule.ForAll(typeof(ServerOnlyVariant)));
defaultVariants.Add(typeof(NoNeedToSyncThis), Rule.ForAll(typeof(DontSerializeVariant)));
}
}
}
You can also manually pick the DontSerializeVariant
in the ghost component on ghost prefabs (via the GhostAuthoringInspectionComponent
).
It is possible to override the following meta-data on per-prefab basis, by using the GhostAuthoringInspectionComponent editor.
The GhostAuthoringInspectionComponent
should be added to the GameObject
you would like to customise. Once added, the editor will show which components present in the runtime entity are replicated.
The editor allow you to: change the following properties:
PrefabType
in which the component should be present/replicated.SendToOptimization
for this component (if applicable)It is possible to prevent a component from supporting per-prefab overrides by using the DontSupportPrefabOverrides attribute.
When present, the component can't be customized in the inspector, nor can a programmer add custom or default variants for this type (as that will trigger errors during ghost validation).
For example: The Netcode for Entities package requires the GhostOwner to be added to all ghost types, sent for all ghost types, and serialized using the default variant. Thus, we add the [DontSupportPrefabOverride]
attribute to it.
[!NOTE] Components on child entities are not serialised by default, thus by default when you look to
GhostAuthoringInspectionComponent
on a child GameObject you will see that the selected variant for the type is theDontSerializeVariant
.
To understand what is being put on the wire in the Netcode, you can use the snapshot visualization tool, NetDbg tool.
To open the tool, go to menu: Multiplayer > Open NetDbg, and the tool opens in a browser window. It displays a vertical bar for each received snapshot, with a breakdown of the snapshotâs ghost types, size etc.
To see more detailed information about the snapshot, click on one of the bars.
[!NOTE] This tool is a prototype. In future versions of the package, it will integrate with the Unity Profiler so you can easily correlate network traffic with memory usage and CPU performance.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4