5 - Property Changes
Overview
This section shows how to synchronize additional data over the network in addition to the player's position using Networked Properties
.
Networked Properties
Fusion synchronizes the transforms of NetworkObjects
when you add a NetworkTransform
component to them. Other states such as variables in your scripts are not synchronized over the network. To have state synchronized over the network a [Networked]
Property is needed. Networked Properties synchronize their state from the StateAuthority
to all other clients.
If a client changes a Networked Property on an object over which it has no StateAuthority
the change is not synchronized over the network but instead applied as a local prediction and can be overridden by changes from the StateAuthority
in the future. Be careful to only update Networked Properties on the StateAuthority
if you want it to update on every client.
A simple example for a Network Property would be a player's color. First create a new script and name it PlayerColor
. Add a Networked property and a public field to reference the MeshRender
of the object to it.
In this example, the goal is to color the player cube white when a ball is fired and then fade it towards blue.
Declaring Networked Properties
To trigger the effect, the host will toggle a single bit in a networked variable. Since no more than one ball can ever be spawned in a single tick (by a given player), each new spawn will change the value of the bit to something that is different from the previous tick and thus trigger a change detection.
Before adding the code, note that this design can fail - as already mentioned, changes can go undetected, especially if they are of the flip/flop kind. To make it more resilient to this, one could replace the bool
with a byte
or an int
and bump it on each invocation instead. In the end, it is a question of how important the visual effect is vs. how much bandwidth it consumes.
Open the Player
class and add a new Networked Property:
C#
[Networked]
public bool spawnedProjectile { get; set; }
When defining networked properties, Fusion will replace the provided get
and set
stubs with custom code to access the network state. This means that the application cannot use these methods to deal with changes in property values, and creating separate setter methods will only work locally.
To solve this problem, changes can be detected via a ChangeDector
. Add a new ChangeDector
to the script and initialize it in Spawned
like this:
C#
private ChangeDetector _changeDetector;
public override void Spawned()
{
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
}
Also add a material field to reference the cube material and set the field in Awake
:
C#
public Material _material;
private void Awake()
{
...
_material = GetComponentInChildren<MeshRenderer>().material;
}
Then add a Render
function with the following:
C#
public override void Render()
{
foreach (var change in _changeDetector.DetectChanges(this))
{
switch (change)
{
case nameof(spawnedProjectile):
_material.color = Color.white;
break;
}
}
}
This code iterates over all changes that happened to the Networked Properties
since the last call to the ChangeDetector
. So in this case since the last Render
. If a change in the color value is detected on any client the MeshRenderer
is updated.
This is useful for spawning local visual effects in response to state changes or perform other tasks that does not directly impact gameplay logic. This is an important caveat since property changes may occur multiple times due to re-simulation (or, twice, to be exact, one on prediction and again if the prediction was incorrect) or it may be skipped entirely if the property changes back and forth between two values faster than network states are being sent (or a packet is dropped).
The primary benefit of using change callbacks over common messages like RPCs is that the callback is executed immediately after the tick in which the value is changed, where as an RPC may arrive later when the game is in an completely different state.
Lerping the color
The color should be updated in Render()
as a linear interpolation from the current color towards blue. This is done in Render()
rather than Update()
because it is guaranteed to run after FixedUpdateNetwork()
and it uses Time.deltaTime
rather than Runner.DeltaTime
because it is running in Unity's render loop and not as part of the Fusion simulation.
Add the following line to the end of the Render
function.
C#
_material.color = Color.Lerp(_material.color, Color.blue, Time.deltaTime);
All that remains is to trigger the callback by toggling the spawnedProjectile
property after calling Runner.Spawn()
:
C#
...
Runner.Spawn(_prefabBall, transform.position+_forward, Quaternion.LookRotation(_forward));
spawnedProjectile = !spawnedProjectile;
...
Keep in mind that there are two places where Spawn()
is called and both should toggle spawnedProjectile
.
But Why?
Q: But why not simply set the color immediately when calling spawn?
While this would work for the host and for the client with Input Authority because both are predicting based on the player's input, it would not work for proxies.
Q: But what if the color was a networked property that was simply applied locally on all clients in Render()
, surely there would be no need for a change detector?
That would indeed work, but it would need to be animated on the host and it would generate a lot of unnecessary traffic. Generally, visual effects should be triggered by the state authority and then left to run autonomously on each client. As much as everyone loves sparks, nobody cares if a particular spark is flying in one direction or the other.
Next Host Mode Basics 6 - Remote Procedure Calls
Back to top