NetCode refers to networked entities as "Ghosts" 👻. Ghosts must be declared before runtime, there is no way to currently update our ghost list from inside a system; it must be done through authoring.
Add a "GhostAuthoringComponent" to the prefabs (this is a special NetCode component). You can think of this as "registering" our Asteroid, Player, and Bullet prefab.
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.
Ghost authoring component
The ghost authoring component is based on specifying ghosts as Prefabs with the GhostAuthoringComponent on them. The GhostAuthoringComponent has a small editor which you can use to configure how NetCode synchronizes the Prefab.
Ghost Authoring Component
You must set the Name, Importance, Supported Ghost Mode, Default Ghost Mode and Optimization Mode property on each ghost. Unity 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:
All - this ghost supports both being interpolated and predicted.
Interpolated - this ghost only supports being interpolated, it cannot be spawned as a predicted ghost.
Predicted - this ghost only supports being predicted, it cannot be spawned as a interpolated ghost.
You can select from three different Default Ghost Mode types:
Interpolated - all ghosts Unity receives from the server are treated as interpolated.
Predicted - all ghosts Unity receives from the server are treated as predicted.
Owner predicted - the ghost is predicted for the client that owns it, and interpolated for all other clients. When you select this property, you must also add a GhostOwnerComponent and set its NetworkId field in your code. Unity compares this field to each clients’ network ID to find the correct owner.
You can select from two different Optimization Mode types:
Dynamic - the ghost will be optimized for having small snapshot size both when changing and when not changing.
Static - the ghost will not be optimized for having small snapshot size when changing, but it will not be sent at all when it is not changing.
To override the default client instantiation you can create a classification system updating after ClientSimulationSystemGroup and before GhostSpawnClassificationSystem which goes through the GhostSpawnBuffer buffer on the singleton entity with GhostSpawnQueueComponent and change the SpawnType.
Unity uses attributes in C# to configure which components and fields are synchronized as part of a ghost. You can see the current configuration in the GhostAuthoringComponent by selecting Update component list, but you cannot modify it from the inspector.
To change which versions of a Prefab a component is available on you use PrefabType in a GhostComponentAttribute on the component. PrefabType can be on of the these types:
InterpolatedClient - the component is only available on clients where the ghost is interpolated.
PredictedClient - the component is only available on clients where the ghost is predicted.
Client - the component is only available on the clients, both when the ghost is predicted and interpolated.
Server - the component is only available on the server.
AllPredicted - the component is only available on the server and on clients where 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, but it will have it when instantiated on the client.
A component can set OwnerPredictedSendType in the GhostComponentAttribute to control which clients the component is sent to when it is owner predicted. The available modes are:
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.
Interpolated - the component is sent to all clients.
If a component is not sent to a client NetCode will not modify the component on the client which did not receive it.
A component can also set SendDataForChildEntity to true or false in order to control if the component it sent when it is part of a child entity of a ghost with multiple entities.
For each component, you need to add an attribute to the values you want to sent. Add a [GhostField] attribute to the fields you want to send in an IComponentData. The GhostFieldAttribute can specify Quantization for floating point numbers. The floating point number will be multiplied by this number and converted to an integer in order to save bandwidth. Specifying a Quantization is mandatory for floating point numbers and not supported for integer numbers. To send a floating point number unquantized you have to explicitly specify [GhostField(Quantization=0)]. The GhostFieldAttribute also has a Interpolate property which controls if the field will be interpolated or not on clients which are not predicting the ghost. Finally the GhostFieldAttribute has a SubType property which can be set to an integer value to use special serialization rules supplied by the game for that specific field.
As well as adding attributes, you can specify rules for components which you do not have source access to by creating an assembly with a name ending with .NetCodeGen. This assembly should contain a class implementing the interface IGhostDefaultOverridesModifier. Implement the method public void Modify(Dictionary<string, GhostAuthoringComponentEditor.GhostComponent> overrides) and add an entry to the ictionary with the full name of the component as key and a value containing the desired override attributes.
When the client side receives a new ghost, the ghost type is determined by a set of classification systems and then a spawn system spawns it. There is no specific spawn message, and when the client receives an unknown ghost ID, it counts as an implicit spawn.
Because the client interpolates snapshot data, Unity cannot spawn entities immediately, unless it was preemptively spawned, such as with spawn prediction. This is because the data is not ready for the client to interpolate it. Otherwise, the object would appear and then not get any more updates until the interpolation is ready.
Therefore normal spawns happen in a delayed manner. Spawning is split into three main types as follows:
Delayed or interpolated spawning (Asteroids Prefab). The entity is spawned when the interpolation system is ready to apply updates. This is how remote entities are handled, because they are interpolated in a straightforward manner.
Predicted spawning for the client predicted player object (Player Prefab). The object is predicted so the input handling applies immediately. Therefore, it doesn't need to be delay spawned. While the snapshot data for this object arrives, the update system applies the data directly to the object and then plays back the local inputs which have happened since that time, and corrects mistakes in the prediction.
Predicted spawning for player spawned objects (Bullet Prefab). These are objects that the player input spawns, like in-game bullets or rockets that the player fires. The spawn code needs to run on the client, in the client prediction system. Spawn the predicted client version of the ghost prefab and add a PredictedGhostSpawnRequestComponent. Then when the first snapshot update for the entity arrives it will apply to that predict spawned object (no new entity is created). After this, the snapshot updates are applied the same as in the predicted spawning for client predicted player object model.
You need to implement some specific code to handle the predicted spawning for player spawned objects. You need to create a system updating in the ClientSimulationSystemGroup after GhostSpawnClassificationSystem. The system needs to go through the GhostSpawnBuffer buffer stored on a singleton with a GhostSpawnQueueComponent. For each entry in that list it should compare to the entries in the PredictedGhostSpawn buffer on the singleton with a PredictedGhostSpawnList component. If the two entries are the same the classification system should set the PredictedSpawnEntity property in the GhostSpawnBuffer and remove the entry from GhostSpawnBuffer.
NetCode spawns entities on the client with a Prefab stored in the GhostPrefabCollectionComponent singleton.
Prefabs in parenthesis in spawning types added my Moetsi
From the 3 types of spawning described in the section above, asteroids are delayed, interpolated, or spawning (re-read the section above if you missed it). The server will spawn the asteroids, and when the interpolation system is ready to apply updates, the client will then spawn them as well.
To the client, asteroids just "appear" (because NetCode syncs ghosted entities) and their movement is updated through Snapshots.
The client is not running any Physics code for the asteroid movement; the asteroids will move because of updated snapshots from the server. The server runs the systems that take in the PhysicsVelocity and updates the asteroids positions, these updates are then sent to the clients.
If it sounds like we are being repetitive, we are. It is important to understand the concept of interpolated ghosts to make sense of our implementations 💪.
If the concepts 'interpolated spawning' and 'predicted spawning' are making your head spin, read and watch the explainer content we provided in the "Overview" page of this DOTS NetCode section.
Now, let's implement:
Select the Asteroid prefab and add a GhostAuthoringComponent. Fill out the following fields:
Name = "Asteroid"
Importance = "100"
Supported Ghost Modes = All
Default Ghost Mode = Interpolated
Optimization Mode = Dynamic
Click "Update component list" to see the components that will be ghosted
You may get an error which you can ignore
The list of components that appear have a marker S/IC/PC
This stands for:
"Server" (the component is only available on the server)
"Interpolated Client" (the component is only available on clients where the ghost is interpolated)
"Predicted Client" (the component is only available on clients where the ghost is predicted)
You might be curious why there is a "RenderMesh" component, yet rendering is not needed on the server (because, as we know, the server does not have a presentation layer system). This is just because NetCode is having a tough time excluding meshes automatically (but this will be updated in future releases!)
When we add the GhostAuthoringComponent to our Player prefab and our Bullet prefab we will see how these values change
At the bottom of the component list you'll find the Rotation and Translation components. Expand these components:
Here you can see different fields configured for quantization and interpolation
You can in the Inspector override default values for components and where they get sent
This is newly introduced in NetCode v0.6
NetCode v0.6 provided a lot of awesome updates that allows us to change what data gets sent where