Using the same scene as in the previous lesson, we now take a look at another way to make Clients communicate: Network Commands. Network Commands are commonly referred to as "RPCs" (Remote Procedure Calls) in other networking frameworks. You can think of them as sending messages to objects, instead of syncing the value of a variable.
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
Q or D-pad up: Wave
Building on top of previous examples, let's now focus on two key player actions. Press Space to jump, or Q to greet other players. For both of these actions to play their animation, we need to send a command over the network to invoke Animator.SetTrigger()
on the other Client.
Like before, select the player Prefab located in the /Prefabs/Characters
folder, and browse its Hierarchy until you find the child GameObject called Workman.
Open the coherence Configure window on the third tab, Methods:
You can see how the method Animator.SetTrigger(string)
has been marked as a Network Command. With this done, it is now possible to invoke it over the network using code.
You can find the code doing so in the Wave
class (located in /Scripts/Player/Wave.cs
):
Analysing this line of code, we can recognize 5 key parts:
First, notice how the command is invoked on a specific CoherenceSync
(that sync
property).
We want to invoke this command on a component that is an Animator
.
We invoke a method called "Animator.SetTrigger".
With MessageTarget.Other
, we are asking to send this message only to network entities other than the one that has the CoherenceSync
we chose to use.
We pass the string "Wave"
as the first parameter of the method to invoke.
Because we don't invoke this on the one with authority, you will notice that just before invoking the Network Command, we also call SetTrigger
locally in the usual way:
An alternative to this would have been to call CoherenceSync.SendCommand()
with MessageTarget.All
.
In this example we used Network Commands to trigger a transition in an animation state machine, but they can be used to call any instantaneous behavior that has to be replicated over the network. As an example of this, it is also used in the Persistence lesson to change a number in a UI element across all Clients.
Using the same scene as in the previous lesson, let's see how to easily sync animation over the network.
Animation | Bindings
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
We haven't mentioned it before, but the character Prefab does a lot more than just syncing its position and rotation.
When you move around, you will notice that animation is also replicated across Clients. This is done via synced Animator parameters (and Network Commands, but we cover these in the next lesson).
Very much like in the example about position and rotation, just sending these across the network allows us to keep animation states in sync, making it look like network-instantiated Prefabs on other Clients are performing the same actions.
Open the player Prefab located in the /Prefabs/Characters
folder. Browse its Hierarchy until you find the child GameObject called Workman. You will notice it has an Animator
component.
Select this GameObject and open the Animator window.
As is usually the case, animation is controlled by a few Animator parameters of different types (int, bool, float, etc.).
Make sure to keep the GameObject with the Animator component selected, and open the coherence Configure window:
You will see that a group of animation parameters are being synced. It's that simple: just checking them will start sending the values across, once the game starts, just like other regular public properties.
Did you notice that we are able to configure bindings even if this particular GameObject doesn't have a CoherenceSync
component on it? This is done via the one attached to the root of the player Prefab. These parameters on child GameObjects are what we call deep bindings. Learn more in the Complex hierarchies lesson, or on this page.
There is only one piece missing: animation Triggers. We use one to trigger the transition to the Jump state.
Since Triggers are not a variable holding a value that changes over time, but rather an action that happens instantaneously, we can't just enable in the Config window like with other animator parameters. We will see how to sync them in the next lesson, using Network Commands.
This scene demonstrates the simplest networking scenario possible with coherence. Characters sync their position and rotation, which immediately creates a feeling of presence. Someone else is connected!
CoherenceSync | Bindings | Component behaviours | Authority
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
Upon connecting, a script instantiates a character for you. Now you can move and jump around, and you will see other characters move too.
To be able to connect, you need to also run a local Replication Server, that can be started via coherence > Local Replication Server > Run for Worlds.
coherence takes care of keeping network entities in sync on all Clients. When another Client connects, an instance of your character is instantiated in their scene, and an instance of their character is instantiated into yours. We refer to this as network instantiation.
When you click Connect in the sample UI, the CoherenceBridge
opens a connection. The PlayerHandler GameObject on the root of the hierarchy controls character instantiation by responding to that connection event.
Its PlayerHandler
script implements something like this:
On connection, a character is created. On disconnection, the same script destroys the character's instance. Note how instantiating and removing a network entity is done just with regular Unity Instantiate
and Destroy
.
Now let's take a look at the Prefab that is being instantiated. You can find it in the /Prefabs/Characters
folder.
By opening coherence's Configuration window (either by clicking on the Configure button on the CoherenceSync
component, or by going to coherence > GameObject Setup > Configure), you can see what is synced over the network.
When this window opens on the Variables tab you will notice that, at the very top, Transform.position
and Transform.rotation
are checked:
This is the data being transferred over the network for this object. Each Client sends the position and rotation of the character that they have authority over to every other connected Client, every time there is a change to it that is significant enough. We call these bindings.
Each connected Client receives these values and applies them to the Transform
component of their own instance of the remote player character.
In First Steps, all the variables are set to public by default. The network code that coherence automatically generates can only access public variables and methods, without them being public syncing would not work.
In your own projects, keep it in mind to always set synced variables to public!
To ensure that Clients don't modify the properties of entities they don't have authority over, we need to make sure that they are not running on the character instances that are non-authoritative.
coherence offers a rapid way to make this happen. If you open the Components tab of the Configuration window, you will see that 3 components are configured to do something special:
In particular:
The PlayerInput
and KinematicMove
scripts get disabled.
The Rigidbody
component is made kinematic.
While in Play Mode, try selecting a remote player character. You will notice that some of its script have been disabled by coherence:
You can learn more about Component Actions here.
One important concept to get familiar with is the fact that every networked entity exists as a GameObject on every Client currently connected. However, only one of them has what we call authority over the network entity, and can control its synced variables.
For instance, if we play this scene with two Clients connected, each one will have 2 player instances in their respective worlds:
This is something to keep in mind as you decide which components have to keep running or be disabled on remote instances, in order to not have the same code running unnecessarily on various Clients. This could create a conflict or put the two GameObjects in a very different state, generating unwanted results.
In the Unity Editor, when connected, the name of a GameObject and the icon next to it informs you about its current authority state (see image above).
There are two types of authority in coherence: State and Input. For the sake of simplicity, in this project we often refer just to a generic "authority", and what we mean is State authority. Go here for more info on authority.
If you want to see which entities are currently local and which ones are remote, we included a debug visualisation in the project. Hit the Tab key (or click the Joystick) to switch to a view that shows authority. You can keep playing the game while in this view, and see how things change (try the Physics scene!).