Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 117 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

SDK 0.9

Loading...

Overview

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Get Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Authority and communication

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Persistence

Loading...

Loading...

Loading...

Loading...

Optimization

Loading...

Loading...

Loading...

Loading...

Loading...

Connected entities

Loading...

Loading...

Loading...

Loading...

Simulators

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tutorial project

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Game Services

Loading...

Loading...

Loading...

Developer Portal

Loading...

Loading...

Loading...

Loading...

Loading...

API reference

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Schema reference

Loading...

Loading...

Loading...

Loading...

Resources

What is coherence?

Overview

coherence is a network engine, platform and a series of tools to help anyone create a multiplayer game.

  • Fast network engine with cloud scaling, state replication, persistence and auto load balancing.

  • Easy to develop, iterate and operate connected games and experiences.

  • SDK allows developers to make multiplayer games using Windows, Linux or Mac, targeting desktop, console, mobile, VR or the web.

  • Game engine plugins and visual tools will help even non-coders create and quickly iterate on a connected game idea.

  • Scalable from small games to large virtual worlds running on hundreds of servers.

  • Game-service features like user account, key-value stores and matchmaking.

Network engine

At the core of coherence lies a fast network engine based on bitstreams and a data-oriented architecture, with numerous optimization techniques like delta compression, quantization and network LOD-ing ("Level of Detail") to minimize bandwidth and maximize performance.

Authority models

The network engine supports multiple authority models:

  • Client authority

  • Server authority

  • Server authority with client prediction

  • Authority handover (request, steal)

  • Distributed authority (multiple simulators with seamless transition)

  • Deterministic client prediction with rollback ("GGPO"): coming in a future release

Persistence

coherence supports persistence out of the box.

This means that the state of the world is preserved no matter if clients or simulators are connected to it or not. This way, you can create shared worlds where visitors have a lasting impact.

Engine support

The coherence SDK only supports Unity at the moment. Unreal Engine support is planned. For more specific details and announcements, please check the Unreal Engine Support page. For custom engine integration, please contact our developer relations team.

Welcome

Games are better when we play together.

Get started

coherence is a network engine, platform and a series of tools to help anyone create a multiplayer game. Our mission is to give any game developer, regardless of how technical they are, the power to make a connected game.

Update an existing project

If you are an existing user and looking to update, check out the latest Release Notes. And maybe the SDK update guide as well!

Tutorial project

The Network Playground is a collection of scenes showing you how to use various features of the coherence Unity SDK. It shows you how to synchronize transforms, physics, persistence, animations, AI navigation and send network commands.

Existing or new Unity project

You can follow our step-by-step guide to learn how to install coherence in Unity, set up your scene, prefabs, interactions, as well as deploy your project to be shared with your friends.

Join the community

Join our community Discord for community chatter and support.

  • Join our official Developer Discord channel.

  • Contact us at [email protected]

How does coherence work?

Basic terms

Replication Server ("Replicator")

A lean and performant server that keeps the state of the world and replicates it efficiently between various Simulators and Game Clients. The Replicator usually runs in the coherence Cloud, but developers can start it locally from the command line or the Unity Editor.

Game Client

A build of the game. To connect to coherence, it will use the coherence SDK.

Simulation Server ("Simulator")

A version of the Game Client without the graphics ("headless client") optimized and configured to perform server-side simulation of the game world. When we say something is simulated on the server, we mean it is simulated on one or several Simulators.

Schema

A text file defining the structure of the world from the network's point of view. The schema is shared between the Replicators, Simulators and Game Clients. The world is generally divided in components and archetypes.

**** Code generation

The process of generating code specific to the game engine that takes care of network synchronization and other network-specific code. This is done using a CLI tool called Protocol Code Generator that takes the schema file and generates code for various engines (e.g. C# for Unity).

State replication

The process of making sure the state of the world is eventually the same on the Replicator, Simulators and Game Clients, depending on their areas of interest.

State Replication

coherence works by sharing game world data via a Replication Server in the cloud and passing it to the connected Clients.

The Clients and Simulators can define areas of interest (LiveQueries), levels of detail, varying simulation and replication frequencies and other optimization techniques to control how much bandwidth and CPU power is used in different situations.

The game world can be run using multiple Simulators that split up simulation functions or areas of the world accordingly.

Fast authority transfer and remote commands allow different authority models, including Client authority, Server authority, distributed authority and combinations like Client prediction with input queues.

The platform handles scaling, synchronization, persistence and load balancing automatically.

Peer-to-peer support (without a Replicator) is planned in a future release. Please see the Peer-to-peer page for updates.

Get the Tutorial Project
Install coherence

Settings window

Build and run a Server

The coherence Settings window is located in coherence > Settings.

Settings have descriptive tooltips that help understand what they do.

Rooms and Worlds

coherence provides two types of online replication services: Rooms and Worlds. Read about the different uses cases for each

Rooms

Rooms are best for session-based gameplay where the match between players takes place in a short-lived environment.

Use case

A good example is a first person shooter multiplayer match. The match takes place between two teams in a single game session, and players enter through a lobby and matchmaking. When the match is concluded, the multiplayer environment the match took place in is closed and players return to a lobby.

This is one example of how Rooms can be used, but it is by no means the only use case. The important distinction between Rooms and Worlds (see below) is that Rooms are relatively short-lived and are meant to be created and closed by the Game Client through the coherence SDK.

See Rooms API.

Current limits for Rooms are as follows: Players

The default setting is 10 players hosted, but you can specify your own value anywhere between 2 and 100 players.

To support more than a 100 players per room, write to [email protected]

Entities

1000 by default, currently up to a 65535.

Worlds

Worlds, as opposed to Rooms, are long-lived and permanent multiplayer environments provided by coherence. Using the Developer Portal, your project will easily define and manage your World configurations.

See Manage Worlds.

Use case

A good example of a World is a permanent environment for an Massively Multiplayer Game (MMO). Regardless of the number of players connected, the environment is always available, and players can connect and disconnect at will.

Entities can be permanently saved in the World so that even if there are no active connections, they still persist when players do connect.

See Worlds API.

Rooms and Worlds work together

Your project does not have to choose one-or-the-other. A project in coherence can contain both World and Rooms.

Use case

A good example of this scenario is again, our MMO. Although players connect to a permanent and persistent World, they may enter a dungeon instance with other players. These dungeon instances can be Rooms.

The primary difference in the configuration and usage of Room and Worlds is that Worlds are managed in the Developer Portal, whereas Rooms are created and managed through the SDK.

Requirements

Game engine support

coherence currently supports Unity. For custom engine integration, please contact our developer relations team. For updates regarding Unreal Engine support, please check the Unreal Engine support page.

Unity requirements

  • Unity 2020.3.36f1 or later.

  • A Windows, Linux or macOS system.

Storage

Persistent objects are stored

All persistent objects remain in the World for the entire lifetime of the Replication Server and, periodically, the Replication Server records the state of the World and saves it to physical storage. If the Replication Server is restarted, then the saved persistent objects are reloaded when the Replication Server resumes.

Parent-child relationships

Overview

Objects with the CoherenceSync component can be connected to other objects with CoherenceSync components to form a parent-child relationship. For example, an object can be linked to a hand, a hand to an arm, and the arm to a spine.

When an object has a parent in the network hierarchy, its transform (position and orientation) will update in local space, which means its transform is relative to the parent's transform.

A child object will only be visible in a LiveQuery if its parent is within the query's boundaries.

Usage

Creating an Entity hierarchy is very simple. All you need to do is add a GameObject with a CoherenceSynccomponent as a direct child of another GameObject with a CoherenceSynccomponent. You can add and remove parent-child relationships at runtime (even from the editor).

Destruction or disconnection of the parent object will also destroy and remove all children of this object. Those objects' state needs to be treated on the Client side to be reinstantiated on the next connection.

Hierarchies with intermediary transforms

Sometimes, it is not practical to add CoherenceSyncobjects to all the links in the chain. For example, if a weapon is parented to a hand controlled by an Animator, we do not need to synchronize the entire skeleton over the network. In that case, see CoherenceNode.

Level of detail considerations

If the child object is using LODs, it will base its distance calculations on the world position of its parent. For more details, see the Level of detail documentation.

Level of detail

Refer to the Level of detail and Archetypes and LODing sections for more information.

Troubleshooting

Tips on how to handle common problems

Can't build on platforms that Unity Hub reports as installed

On macOS, some versions of the Unity Hub don't install platform modules properly. This issue is fixed on version Unity Hub version 3.3.0. If you want to use a prior version, install one module at a time.

BuildConfiguration assets missing defining script

SDK 0.9 release removed dependency to the experimental package Platforms. As a result, your existing BuildConfiguration assets will have its defining script missing. To recreate the Simulator build configuration with the new pipeline, you can do it from the Simulators Module in coherence Hub.

If you run into issues that are not mentioned here, take a look at our Known Issues list at the end of Release Notes.

Overview

Sometimes you want to synchronize Entities that are connected to other Entities. These relationships can be references between Entities, but they can also involve direct parent-child relationship between game objects, or more nuanced use cases.

Here's a guide for what technique to use, depending on the situation:

  • If you have an Entity that needs to keep a nullable reference to another Entity, use a normal Entity reference. This includes any existing MonoBehaviour that has GameObject or Transform fields that you want to synchronize over the network.

  • If the Entities are placed in a hierarchy, use the techniques for parent-child relationships.

Cloud API

List of the Cloud APIs

Overview

The coherence Cloud API allows us to access online services like game accounts, key-value store, matchmaking, and others. It also allows you to get the addresses (IP and port number) of the Servers the players can connect to.

The Cloud API requires you to use tokens connected to your coherence project.

List of endpoints

  • Game account

  • Key-value store

  • Matchmaking

Overview

Persistent vs. session-based entities

When we connect to a Game World with a Game Client, the traditional approach is that all Entities originating on our Client are session-based. This means that when the Client disconnects, they will disappear from the network World for all players.

A persistent object, however, will remain on the Replication Server even when the Client or Simulator that created or last simulated it, is gone.

This allows us to create a living world where player actions leave lasting effects.

In a virtual world, examples of persistent objects are:

  • A door anyone can open, close or lock

  • User-generated or user-configured objects left in the world to be found by others

  • Game progress objects (e.g. in PvE games)

  • Voice or video messages left by users

  • NPC's wandering around the world using an AI logic

  • Player characters on "auto pilot" that continue affecting the world when the player is offline

  • And many, many more

A persistent object with no Simulator is called an orphan. Orphans can be configured to be auto-adopted by Clients or Simulators on a FCFS basis.

Create a free account

Cloud deployment - video tutorial

Now that we have tested our project locally, it's time to upload it to the cloud and share it with our friends and colleagues. To be able to do that, we need to create a free account with coherence.

Sign up or log in

In your web browser, navigate to https://coherence.io/dev.

Create an account or log into an existing one.

Login through Unity

Open Unity

Open Unity and open the Coherence Hub window. Then open the Portal tab.

Login to Portal

Login to Portal

After pressing Login you will be taken to the login page. Simply login as usual, and return to Unity.

Login

Select Organization and Project

You are now logged into the Portal through Unity. Select the correct Organization and Project, and you are ready to start creating.

Choose Organization and Project

Simulator load balancing

coherence allows us to use multiple Simulators to split up a large game world with many entities between them. This is called spatial load balancing. Please refer to the section about simulators for more information.

Our cloud services currently only support associating one Simulator to a Room or World. This will be extended in the near future. Enterprise customers can still run multiple Simulators in their own cloud environment.

Simulation frequency

Without a special configuration, Entity data is captured at the highest possible frequency and sent to the Replication Server. This often generates more data than is needed to efficiently replicate the Entity's state across the network.

Global simulator frequency

On a Simulator, we can limit the framerate globally using Unity's built-in static variable targetFrameRate.

Application.targetFrameRate = 10;

coherence will automatically limit the target framerate of uploaded Simulators to 30 frames per second. We plan to make it possible to lift this restriction in the future. Check back for updates in the next couple of releases.

Per-binding sampling frequency

Replication frequency can be configured for each binding individually in the Prefab Optimize window. The Sample Rate controls how many times per second values are sampled and synced over the network.

Use the Sample Rate to keep down bandwidth costs when scaling up.

High sample rates increase replication accuracy and reduce latency, but consume more bandwidth. The upper limit at which samples can be quantized is 60hz, so sample rates beyond that are generally not recommended. It is not possible to change sampling frequency at runtime.

Values that don't change over time do not consume any bandwidth. Only bindings with updated values will be synced over the network.

Dashboard

After creating an Organization and Project, (see Create a free account), your Developer Portal home page will be the Dashboard.

Here you can:

  • find your Portal token

  • view the recent **** resources you've created: schemas, Simulators, and Rooms

4. Animation and variables

Animation and variables

This scene will show you how easy it is to set up Networking in your Unity project and sync GameObject transforms and animations via the Animator parameters and customer variables.

In this example, each Client will have its own player character to move, the beloved Unity RobotKyle asset. Type your name into the connect dialog box and connect. You can move around with WASD. When another player connects, their name is carried across and their transform and the animation state is replicated.

General setup

In the Hierarchy of the scene you can see the two core Prefabs Core Scene Setup and Coherence Setup. Both are present in all scenes and described in detail on Start Tutorial page.

In this scene...

coherence Kyle is taken from the Unity asset "Robot Kyle", with added components Rigidbody, Character Controller, and Animator with the two animation states - Idle and Walk. The animation states are controlled by a speed parameter from the Agent script. The scene also contains a Name Badge script which gets the Connect Dialog GameObject from the Core Scene Setup and sets the color depending if it's local or networked.

Attached to Coherence Kyle is a coherenceSync component which replicates the parameters Transform; position and rotation, Animator; Speed[float] and NameBadge; Name [string]. The authority and persistence settings are set to their default values and the On Network Instantiation event is used to change the color of the networked entities.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

You can read more about synchronizing animations in the Animations section.

Configure Rooms

From the Developer Portal, you can configure how Rooms are created through the SDK in the coherence cloud.

Configuring rooms in the Developer Portal

From the left sidebar, select Rooms. On this page you can:

  • choose the regions you want to allow your rooms to be created in

  • enable Simulators for your Rooms

  • view a list of recently created Rooms

Simulators

From the Developer Portal, you can configure what size you want your Simulator instances to be. To attach a Simulator to a Room, send the "Simulator slug" uploaded through the SDK with the Rooms creation request. When using the PlayResolver to create Rooms, the Simulator uploaded through the SDK is automatically assigned in the creation request.

World Simulators

World Simulators are started and shut down with the World. They can be enabled and assigned in the Worlds section of the Developer Portal.

World simulation servers are started with the command line parameters described in the Simulators section.

MonoBridge

The MonoBridge is a system that makes sure every GameObject is linked to its networked representation. It essentially interfaces between the GameObject world and the coherence SDK code running "under the hood".

When you place a GameObject in your scene, the MonoBridge detects it and makes sure all the synchronization can be done via the CoherenceSync component.

At runtime, you can inspect which Entites the MonoBridge is currently tracking.

A MonoBridge is associated with the scene it's instantiated on, and keeps track of Entities that are part of that scene. This also allows for multiple connections at the same time coming from the game or within the Unity Editor.

When using a Global MonoBridge (Singleton), the MonoBridge is still associated to the scene it was originally instantiated on, even when the GameObject deattaches from the scene and becomes part of DontDestroyOnLoad.

Matchmaking

Motivation

coherence provides a powerful matchmaking API with various different setups (multiple teams, team sizes, etc.).

Configuration

Before using the matchmaking service you have to configure it on the Developer Portal. Currently there are two things to set up: the timeout and the teams.

The timeout is in seconds. There can be any number of teams and any number of players per team.

For example, a chess game would need only one team with two players, while in a soccer game you'd need two teams with eleven players per team.

Using the matchmaking

Please refer to the Cloud API: Matchmaking.

6. Network commands

Sending network commands

This scene will show you how easy it is to set up Networking in your Unity project and send network commands to other Clients. Network commands are like sending direct messages to objects instead of syncing variable values.

In this example each Client has one character they can control with "click to move" input. They can right-click on another Entity to send a command and that Entity will instantiate an Exclamation mark above their head.

General setup

In the Hierarchy of the scene you can see three core Prefabs:

Core Scene Setup and Coherence Setup are present in all scenes and described in detail on Start Tutorial page.

Coherence Entity is the Prefab that will change per scene with different functionality. It has a standard CharacterControllerand Rigidbody as well as an Agent script which will handle movement functionality through the Input Manager in the Core Scene Setup Prefab.

In this scene...

Coherence Entity can send commands to other entities through the Coherence Handler component. In the coherenceSync component we can open the bindings window and find a Methods tab used for command setup. There we can find a method called ReceiveCommand and beside it, an icon describing who the command will be sent to (only to the objects' authority or alternatively to all Clients).

In the game view in Play mode, commands can be sent to other Entities via the right click button. An exclamation mark asset will pop up above the right-clicked Entity for all Clients.

If we were to set this command to Authority Only then only the objects' authority would receive this method call.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

Simulator slugs

When using the Simulators tab in the coherence Hub, you can specify a Simulator slug. This is simply a unique identifier for a Simulator. This value is automatically saved in RuntimeSettings when an upload is complete, and Room creation requests will use this value to identify which Simulator should be started alongside your room.

The Simulator slug can be any string value, but we recommend using something descriptive. If the same slug is used between two uploads, the later upload will overwrite the previous Simulator.

Use the Simulators tab to specify all Simulator build settings. The Simulator slug is only needed for Simulators that are uploaded into the coherence Cloud.

A list of uploaded Simulators and their corresponding slugs can be found in the Developer Portal:

1. Transforms

Transforms

Welcome to the first scene of the coherence Network Playground. This scene will show you how easy it is to set up networking in your Unity project and sync GameObject transforms across the network.

In this example, each Client will have a player character to move by clicking on the map to make the Entity move to that location. Each Client will only have control of its local Entity.

General setup

In the Hierarchy of the Scene you can see three core Prefabs.

Core Scene Setup and Coherence Setup are present in all (Network Playground) Scenes and described in detail on Start Tutorial page.

Coherence Entity Character is the Prefab that will change per Scene with different functionality. It has a standard CharacterControllerand Rigidbody as well as an Agent script which will handle movement through the Input Manager in the Core Scene Setup Prefab.

In this scene...

Coherence Entity Character (remember to always change the Prefab, not the instance) is located in the Resources folder. The UnityEngine.Transform and position are ticked to sync. All other settings (persistence and authority) use the default settings. This Entity will be session-based, no authority handover and no adoption will take place, when a Client leaves.

The On Network Instantiation event is used to change the color of the mesh and recalculate the RigidBody collisions. That's it.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how the replication works for each of them.

Deploy Replication Server

Now we can finally deploy our schema and Replication Server into the cloud.

Make sure you have created a World before trying to deploy the Replication Server. To create a World, follow the steps described in .

Upload schemas

In the coherence Hub window, select the coherence Cloud tab, and click on Upload to coherence Cloud in the Schemas section.

The status in the Schemas section should now be In Sync.

If the status does not say "In Sync", or if you encounter any other issues with the server interface, refer to the section.

Your project schema is now deployed with the correct version of the Replication Server already running in the cloud. You will be able to see this in your cloud dashboard status.

Run project

The Connect Dialog uses the to fetch all the regions available for your project. This depends on the project configuration (e.g. the regions that you have selected for your project in the Portal).

You can now build the project again and send the build to your friends for testing.

You will be able to play over the internet without worrying about firewalls and local network connections.

Before connecting, make sure everybody selects the same region, and that this region is not local.

We are working on a WebGL / WebAssembly option that will automatically upload the browser-playable build to your own personal webpage that you can share with your friends. For more information about our roadmap, please contact our .

Room Simulators

Simulators per room can be enabled in the dashboard for the project. The Simulator used is matched according to the in the RuntimeSettings scriptable object file. This is set automatically when you upload a Simulator.

For each new Room, a Simulator will be created with the command line parameters described in the section. The Simulator is shutdown automatically when the Room is closed.

Simulation frame

The simulation frame represents an internal clock that every Client syncs with a . This clock runs at a 60Hz frequency which means that the resolution of a single simulation frame is ~16ms.

There are 3 different simulation frame types used within the coherence:

Server simulation frame

The latest simulation frame received from the Replication Server. Accessible via CoherenceMonoBridge.NetworkTime.ServerSimulationFrame.

Client simulation frame

Local Client simulation frame that progresses with local time. Accessible via CoherenceMonoBridge.NetworkTime.ClientSimulationFrame.

Every Client tries to match the Client simulation frame with the Server simulation frame by continuously monitoring the distance between the two and adjusting the NetworkTime.NetworkTimeScale based on the distance, ping, delta time, and several other factors starting from the first simulation frame captured when the client first connects in NetworkTime.ConnectionSimulationFrame

The Time.timeScale is automatically set to the value of NetworkTime.NetworkTimeScale if the CoherenceMonoBridge.controlTimeScale is set to true (default value).

In perfect conditions, all Clients connected to a single session should have exactly the same ClientSimulationFrame value at any point in the real-world time.

The value of the ClientSimulationFrame can jump by more than 1 between two engine frames if the frame rate is low enough.

The Client simulation frame is used to timestamp any outgoing Entity changes to achieve a consistent view of the World for all players. The receiving side uses it for of the synced values.

Client fixed simulation frame

Local simulation frame that progresses in user-controlled fixed steps. Accessible via CoherenceMonoBridge.ClientFixedSimulationFrame.

By default, the fixed step value is set to the Time.fixedDeltaTime.

Just like the basic Client simulation frame, it uses the NetworkTime.NetworkTimeScale to correct the drift. The fixed simulation frame is used as a base for the fixed-step, network-driven simulation loop that is run via CoherenceMonoBridge.OnFixedNetworkUpdate. This loop is used internally to power the and the code.

Unlike ClientSimulationFrame the CoherenceMonoBridge.OnFixedNetworkUpdate loop never skips frames - it is guaranteed to run for every single frame increment.

LiveQuery

LiveQuery

The way you get information about the World is through LiveQueries. We set criteria for what part of the World we are interested in at each given moment. That way, the Replicator won’t send information about everything that is going on in the Game World everywhere, at all times.

Instead, we will just get information about what’s within a certain area, kind of like moving a torch to look around in a dark cave.

More complex areas of interest types are coming in future versions of coherence.

Adding a LiveQuery to the scene

A LiveQuery is a cube that defines the area of interest in a particular part of the world. It is defined by its position and its extent (half the side of the cube). There can be multiple LiveQueries in a single scene.

Moving a LiveQuery

The classic approach is to put a LiveQuery on the camera and set the extent to correspond to the far clipping plane or visibility distance.

Moving the GameObject containing the LiveQuery will also notify the Replication Server that the query for that particular Game Client has moved.

Key-value store

Motivation

coherence provides an API and a database for storing key-value pairs from within game sessions.

Description

The key-value store provides a simple way to store and retrieve data for the currently logged in player. For example, you could store the player's score, email address, or any other data.

This feature requires a .

Limitations

The keys must be alphanumerical strings, underscore or dash. Currently, only strings are accepted as values. If you need to store numbers or complex types like arrays, you have to convert them to strings.

The total amount of stored data (keys + values) cannot exceed 256 KB per player (TBD).

There is no limit how often the data is stored or retrieved.

Using key-value store

Please refer to the .

2. Physics

Physics

This scene will show you how to use coherence to sync GameObject transforms and Physics objects across the network.

In this example, each Client will have its own player character to move. Left-clicking on the map will make the Entity move to that location. Right-clicking will spawn local physics-based objects that all player characters can interact with. Each Client will only have control over its local Entity.

General setup

In the Hierarchy of the scene you can see three core Prefabs:

Core Scene Setup and Coherence Setup are present in all scenes and described in detail on page.

Coherence Entity is the Prefab that will change per scene with different functionality. It has a standard CharacterControllerand Rigidbody as well as an Agent script which will handle movement functionality through the Input Manager in the Core Scene Setup Prefab.

Coherence Connection Events handles overall scene connectivity. Additionally, it removes all Entities with coherenceSync from the scene to demo disconnecting/reconnecting via the interface without refreshing the scene.

In this scene...

Coherence Entity Character (remember to always change the Prefab, not the instance) is located in the Resources folder. The UnityEngine.Transform and position are ticked to sync. All other settings (persistence and authority) use the default settings. This Entity will be session-based, no authority handover and no adoption will take place, when a Client leaves.

The On Network Instantiation event is used to change the color of the mesh.

The Physics Entity Spawner is a simple script to instantiate a Coherence Entity Physics Prefab with a coherenceSync component that replicates the transform and position. The component also changes the material based on whether it is locally simulated or synced over the network.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

Get the Tutorial Project

coherence only supports Unity at the moment. Unreal Engine support is planned. For more specific details and announcements, please check the page. For custom engine integration,.

Network Playground Project

The Network Playground is a starter project for you to dive into. It contains the latest SDK, all the required preview packages and a bunch of extra resources for you to learn how to make multiplayer games with coherence.

Step 1: Download the Network Playground Unity Project

You will need Unity Version 2020.3.36f1 or later.

Download the Network Playground Project .

Network Playground Scenes

Each scene in this Network Playground Project shows you something new:

  • Scene 1. Synchronizing Transforms

  • Scene 2. Physics

  • Scene 3. Persistence

  • Scene 4. Synchronizing Animations and Custom Variables

  • Scene 5. AI Navigation

  • Scene 6. Commands

  • Scene 7. Team based

  • Scene 8. Connected Entities

Remember to use at least two clients when you test the multiplayer functionality in Playground.

Overview

What is a Simulator?

A Simulation Server or a Simulator is a version of the Game Client without the graphics ("headless client") optimized and configured to perform a server-side simulation of the Game World. When we say something is simulated on the server, we mean it is simulated on one or several Simulators.

Simulators can also be independent from the game code. A Simulator could be a standalone application written in any language, including C#, Go or C++ , for instance. We will post more information about how to achieve this here in the future. For now, if you would like to create a Simulator outside of Unity, please .

Why do you need a Simulator?

A Simulator can have various uses, including:

  • Server-side simulation of game logic that cannot be tampered with

  • Offloading processing from Game Clients

  • Splitting up a large Game World with many Entities between them

Here are some examples of things a Simulator could be taking care of:

  • Running all the important game logic

  • Running NPC AI

  • Simulating the player character (by receiving only inputs from the Clients through )

Associating Simulators with Rooms and Worlds

Although it is possible to upload many Simulator binaries with different versions of the coherence SDK and your game logic, currently our cloud services support associating one running Simulator per World or Room. This will be extended in the near future.

See and .

Manage Worlds

From the Developer Portal you can create, edit and configure your Worlds

Creating Worlds

Click the New World button at the top right of the Worlds view in the Developer Portal.

To create a World:

  • Enter a unique name

  • (optional) choose an SDK version. The latest version is recommended, but this should match the SDK version installed for your project

  • Enter tags separated by commas

  • Choose which region the World should be started in

  • Choose the size of the Replicator

  • (optional) Choose the schema this World should start with. Usually the latest schema uploaded is the preferred choice, and this is the default.

Configuring a schema

See the chapter to get more information about how to configure a schema.

8. Connected Entities

In this scene...

This scene demonstrates usage of connected Entities.

This scene shares the moving Entities that were in a few previous scenes but also has added functionality.

Right-clicking on a non-local Entity causes the local Entity to start moving towards it while also displaying an arrow pointed at the target. When the Entity reaches this target it will parent itself under it as a child, setting the target as its connected Entity.

From this point onward, until the Entity disconnects by moving or otherwise, the Entity will be smaller and parented to this target. When the target moves the local Entity will move with it as one.

This type of connection can also cause issues. For example if the Client controlling the parent Entity disconnects, destroying its Entity, any Client whose Entity was a child of that destroyed Entity will have its Entity destroyed as well.

To learn more, see .

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

5. AI navigation

AI navigation, session-based NPC's

This scene will show you how easy it is to set up Networking in your Unity project and sync non-player characters that move around the World via Unity's Navmesh system.

In this example each Client can spawn as many navigation agents as they wish, and these navigation agents will move intermittently to different locations on the grid. All navigation agents will be replicated across Clients with specific colors that signify if they are local or networked.

General setup

In the Hierarchy of the scene you can see three core Prefabs:

Core Scene Setup and Coherence Setup are present in all scenes and described in detail on page.

Coherence Connection Events handles overall Scene connectivity. Additionally, it removes all Entities with coherenceSync from the scene to demo disconnection/reconnection via the Interface without refreshing the Scene.

In this scene...

Spawner is a simple script to instantiate a Coherence Entity Nav Agent Prefab with a coherenceSync component that replicates the transform and position. The component also changes the material based on if it is locally simulated or synced over the network.

Coherence Entity Nav Agent has a Nav Mesh Agent component controlled via the Navigation Agent script which every few seconds sets a new destination on the grid. It's not required to sync anything other than the Transform; position, rotation parameter as the Nav Mesh Agent settings only need to simulate locally.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

Worlds

Worlds API

Worlds functionality can also be accessed through the PlayResolver just like rooms. Worlds work a differently however and are a bit simpler.

Worlds

First we need to fetch the available Worlds. Unlike Rooms, Worlds cannot be created by a Client and need to be setup in the Developer Portal.

FetchWorlds in PlayResolver.cs allows us to fetch the available Worlds for our project. This task returns a list of Worlds in the form of WorldsData objects and a boolean that indicates if the operation was successful.

This method also calls EnsurePlayConnection which initializes the needed mechanisms in thePlayResolver if necessary. EnsurePlayConnection can also be called directly for initialization.

FetchLocalWorld in PlayResolver.cs returns the local World for a local running World Server.

The WorldsConnectDialog populates a dropdown with the Worlds returned by both of these methods so we can select a World.

After we've selected a World we can connect to it using:

JoinWorld in PlayResolver.cs connects the Client that we pass to the method to the World we pass to the method.

The isSimulator optional parameter is used for Simulators and can be ignored for regular Client connections (see ).

The WorldsConnectDialog is an example implementation for Worlds usage.

When connected to a Room or a World, the Client can access the currently connected endpoint by accessing the Coherence.IClient.LastEndpointData property of the CoherenceMonoBridge, e.g. myBridge.Client.LastEndpointData.

Key-value store

The key-value store provides a simple persistence layer for the players.

Coherence.Runtime.KvStore

The player needs need to be to use the key-value store.

This class provides the methods to set, get and unset key-value pairs. This is executed within the context of the currently logged in player.

Example

Limitations

Size: there are no limits to the number of stored key/values as long as the total size is less than 256 kB.

Requests: Set/Get/Unset can be called unlimited amount of times but the execution may be throttled.

Overview

Bandwidth is limited

No matter how fast the internet becomes, conserving bandwidth will always be important. Some Game Clients might be on poor quality mobile networks with low upload and download speeds, or have high ping to the Replication Server and/or other Clients, etc.

Additionally, sending more data than is required consumes more memory and unnecessarily burdens the CPU and potentially GPU, which could add to performance issues, and even to quicker battery drainage.

Optimization techniques

In order to optimize the data we are sending over the network, we can employ various techniques built into the core of coherence.

  • Delta-compression (automatic). When possible, only send differences in data, not the entire state every frame.

  • Compression and quantization (automatic and configurable). Various data types can be compressed to consume less bandwidth that they naturally would.

  • Simulation frequency (configurable). Most Entities do not need to be simulated at 60+ frames per second.

  • Levels of detail (configurable). Entities need to consume less and less bandwidth the farther away they move from the observer.

  • Area of interest. Only replicate what we can see.

API tokens and keys

Portal token

The token you get when creating a project on the .

You paste it in the Project Settings.

Runtime key

Once you have pasted the Portal token successfully, you need to fetch the runtime key as well.

You can fetch the runtime key by clicking on the down-arrow button on the right side of the input field.

game account
Cloud API: Key-value store
contact our developer relations team
Input Queues
Room Simulators
World Simulators
async Task<(IReadOnlyList<WorldData>, bool)> FetchWorlds()
async Task<WorldData> FetchLocalWorld()
void JoinWorld(IClient client, WorldData data, bool isSimulator = false)
Simulators
public static class KvStore
{
    // Sets a value
    // key: lowercase letters, numbers, underscore, dash
    // val: any string (null is not allowed)
    public static void Set(string key, string val)
    
    // Gets a value
    // key: lowercase letters, numbers, underscore, dash
    public static string Get(string key)
    
    // Unsets a value, removing it from the store
    // key: lowercase letters, numbers, underscore, dash
    public static void Unset(string key)
}
Coherence.Runtime.KvStore.Set("foo", "1");

var foo = Coherence.Runtime.KvStore.Get("foo");
Debug.Log(string.Format("foo={0}", foo)); // foo=1

Coherence.Runtime.KvStore.Unset("foo");
logged in
Simulator slug
Simulators
Enable simulator per room
Replication Server
interpolation
CoherenceInput
GGPO
Start Tutorial
Schema reference
Managing Worlds in the Developer Portal
The New World form
Start Tutorial
Developer Portal
Connected Entities

Value sync callbacks

It is often useful to know when a synced property has changed its value. It can be easily achieved using the OnValueSyncedAttribute. This attribute lets you define a method that will be called each time a value of a synced member (field or property) changes in the non-simulated version of an entity.

Usage

Let's start with a simple example:

using Coherence.Toolkit;
using UnityEngine;
using UnityEngine.UI;

public class Player : MonoBehaviour
{
    [OnValueSynced(nameof(UpdateHealthLabel))]
    public float Health;

    public Text HealthLabel;

    public void UpdateHealthLabel(float oldHealth, float newHealth)
    {
        HealthLabel.text = newHealth.ToString();
        Debug.Log($"Player HP changed by: {newHealth - oldHealth}");
    }
}

Whenever the value of the Health field gets updated (synced with its simulated version) the UpdateHealthLabel will be called automatically, changing the health label text and printing a log with a health difference.

This comes in handy in projects that use authoritative Simulators. The Client code can easily react to changes in the Player entity state introduced by the Simulator, updating the visual representation (which the Simulator doesn't need).

The OnValueSyncedAttribute requires using baked scripts.

Remember that the callback method will be called only for a non-simulated instance of an Entity. Use on a simulated (owned) instance requires calling the selected method manually whenever the value of a given field/member changes. We recommend using properties with a backing field for this.

Limitations

The OnValueSynced feature can be used only on members of user-defined types, that is, there's no way to be notified about a change in the value of a Unity type member, like transform.position. This might however change in the future, so stay tuned!

Value sync callbacks are currently only supported for value types. That means the following types are not supported: byte[], CoherenceSync, GameObject, Transform and RectTransform.

coherence SDK

SDK Overview

The coherence SDK is a set of Prefabs & scripts to help you create multiplayer games super fast.

It makes it easy for anyone to create a multiplayer game by having flexible, intuitive and powerful visual components.

Here are the main building blocks of the SDK.

CoherenceSync (Component)

CoherenceSync is a component that should be attached to every networked Game Object. It may be your player, an NPC or an inanimate object such as a ball, a projectile or a banana. Anything that needs to be synchronized over the network. You can select which of the attached components you would like to sync across the network as well as individual public properties.

Settings (Window)

The coherence Settings window is located in coherence > Settings.

LiveQuery (Component)

LiveQuery, as the name suggests, queries a location set by the developer so that coherence can simulate anything within its extent. In our Starter Project, the LiveQuery position is static with an extent large enough to cover the entire playable level. If the World was very large and potentially set over multiple Simulation Servers, the LiveQuery could be attached to the playable character or camera.

MonoBridge (Component)

The coherence MonoBridge passes information between the CoherenceSync component and the networked ECS components.

Sample UI

The sample UI Prefab holds all of the UI and connection functionality to connect to the running project locally or via a server. You can completely rewrite this if you like, it's there to get you up and running quickly.

The built-in coherence scripts are configured to execute in a specific order, using the following DefaultExecutionOrder setup:

  • -1000 CoherenceMonoBridge

  • -900 CoherenceSync

  • -800 CoherenceInput

  • 1000 CoherenceMonoBridgeSender

Rooms

Rooms API

Rooms functionality can be accessed through the PlayResolver which includes all the methods needed to use rooms.

Regions

To manage Rooms we must first decide which region we are working with.

async Task<(IReadOnlyList<string>, bool)> FetchRegions()

FetchRegions in PlayResolver.cs allows us to fetch the regions available for our project. This task returns a list of regions (as strings) and a boolean that indicates if the operation was successful.

async Task<string> FetchLocalRegions()

FetchLocalRegions in PlayResolver.cs returns the local region string for a local running Rooms Server, or null if the operation is un-successful (if the Server isn't running for example).

Every other Rooms API will require a region string that indicates the relevant region for the operation so these strings should not be changed before using them for other operations.

The RoomsConnectDialog populates a dropdown with the region strings returned by both of these methods directly for easy selection.

These methods also call EnsurePlayConnection which initializes the needed mechanisms in the PlayResolver if necessary. EnsurePlayConnection can also be called directly for initialization.

Room management

After we have the available regions we can start managing Rooms, for instance:

async Task<RoomData> CreateRoom(string region, RoomCreationOptions options = null)

CreateRoom in PlayResolver.cs allows us to create a Room in the region we send it.

the RoomCreationOptions is used to optionally specify:

  • a Room name

  • the maximum number of Clients allowed for the Room

  • a list of tags for Room filtering and other uses

  • a key-value collection for the Room

This task returns the RoomData for the created Room assuming the operation was successful.

async Task<IReadOnlyList<RoomData>> FetchRooms(string region, string[] tags = null)

FetchRooms in PlayResolver.cs allows us to search for available Rooms in a region. We can also optionally specify tags for filtering the Rooms.

This task returns a list of RoomData objects for the Rooms available for our specifications.

void JoinRoom(IClient client, RoomData room, bool isSimulator = false)

JoinRoom in PlayResolver.cs connects the client that we pass to the method to the Room we pass to the method. This RoomData object can be either the one we get back from CreateRoom or one of the ones we got from FetchRooms.

When joining a Room, the method is optionally supplied if the connecting Client is a Simulator, as well.

The RoomsConnectDialog demonstrates both of these cases in CreateRoom when called with true for autoJoin and in JoinRoom respectively.

Game account

Motivation

coherence provides an API for creating player game accounts that uniquely identify players across multiple devices. An account is required in order to use the rest of the online services, like the key-value store and matchmaking.

Account Types

There are two types of accounts that are currently supported - guest accounts and user accounts.

Guest Accounts

Guest accounts provide an easy way to start using the coherence online services without providing any user interface for user names or password. Everything is controlled with the API, and is completely transparent to the player.

The session data for the account is stored locally on the device so it is important to know that uninstalling the game will also wipe out all the data and the account will be no longer accessible even if the player installs the game again.

User Accounts

User accounts require explicit authorization by the player. Currently, only user name and password are supported as means for authentication. The user interface for entering the credentials must be provided by the game. Check the API how to use this feature.

In the future, there will be support for many more authentication mechanisms like Steam, Google Play Games, Sign in with Apple, etc.

Using game accounts

Please refer to the Cloud API: Game Accounts.

Overview

What is the Developer Portal?

The Developer Portal is an online dashboard where the cloud services behind your coherence-based game can be managed. It can be found at https://dev.coherence.io or from the Developer Portal link above.

The Developer Portal includes:

  • Organization and Project creation and management

  • Resource configuration and management

  • Enabling / disabling features

  • Cost analysis

  • Team management

Here are some examples of tasks to perform on the Developer Portal:

  • Create your organization and project for your game

  • Start/stop/restart your cloud-based Replication Server or Simulator

  • Enable coherence features such as player authentication, key-value store, persistence, and build sharing

  • Invite teammates to your project

  • View your resource usage and billing forecasts

Is the Developer Portal required for development?

While a local ReplicationServer is available as part of the Unity SDK, in order to host multiplayer services like the Replication Server in the cloud, your team must have a project in the Developer Portal. It is up to your project needs when to begin using the cloud services.

Getting started

Please see the page Create a free account in the Get Started section.

Manage Worlds
troubleshooting
runtime key
developer relations team
Unreal Engine Support
please contact our developer relations team
here

Areas of interest

LiveQuery

The way you get information about the world is through LiveQueries. We set criteria for what part of the world we are interested in at each given moment. That way, the Replicator won’t send information about everything that is going on in the Game World everywhere, at all times.

Instead, we will just get information about what’s within a certain area, kind of like moving a torch to look around in a dark cave.

More complex area of interest types are coming in future versions of coherence.

Adding a LiveQuery to the scene

A LiveQuery is a cube that defines the area of interest in a particular part of the World. It is defined by its position and its extent (half the side of the cube). There can be multiple LiveQueries in a single scene.

Moving a LiveQuery

A classic approach is to put a LiveQuery on the camera and set the extent to correspond to the far clipping plane or visibility distance.

Moving the GameObject containing the LiveQuery will also notify the Replication Server that the query for that particular Game Client has moved.

Adding a TagQuery

In addition to the LiveQuery, coherence also supports filtering objects by tag. This is useful when you have some special objects that should always be visible regardless of World position.

To create a TagQuery, right click a GameObject in the scene and select coherence > TagQuery from the context menu.

All networked GameObjects with matching tags will now be visible to the Client. The coherence tag can be any string and can be configured separately from the Unity tag in the Advanced Settings section of the CoherenceSync component.

Tags and TagQueries can be updated at any time while the application is running, either from the Unity inspector or setting CoherenceSync.coherenceTag and CoherenceTagQuery.coherenceTag with code.

Currently, only a single tag per GameObject and TagQuery is supported. To include objects with different tags, you can create multiple TagQuery objects for each tag.

In the future, we plan to integrate TagQueries with LiveQueries allowing combined query restrictions, e.g., only show objects with tag "red" within an extent of 50.

Queries can also be used for cheat prevention, see Server authoritative setup for more information.

Configuring persistence

Lifetime configuration

The CoherenceSync editor interface allows us to define the Lifetime of a networked object. The following options are available:

  • Session Based. No persistence. The Entity will disappear when the Client or Simulator disconnects.

  • Persistent. The Entity will remain on the Server until a simulating Client deletes it.

Uniqueness and UUID

Unique persistent objects need to be identified so that the system can know how to treat duplicate persistent objects.

Manually assigning a UUID means that each instance of this persistent object Prefab is considered the same object regardless of where on the network it is instantiated. So, for example, if two Clients instantiate the same Prefab object with the same persistence UUID then only one is considered official and the other is replaced by the Replication Server.

The CoherenceUUID behaviour is used to uniquely identify a Prefab.

It has several functions: you can generate a new ID for your object, and you can set auto-generate UUID on the scene to true, so each time the object will receive a new ID.

Auto-generate UUID in scene is not working for persistent objects.

Deleting a persistent object

A persistent object can be deleted only by the Client or Simulator that has authority over it. For indirect remote deletion, see the section about network commands.

Deleting a persistent object is done the same as with any network object - by destroying its GameObject.

Destroy(coherenceSync.gameObject);

Animations

coherence only replicates animation parameters, not state. Latency can create scenarios where different Clients reproduce different animations. Take this into account when working with Animator Controllers that require precise timings.

Unity Animator's parameters are bindable out of the box, with the exception of triggers.

Speed, Pose and Jump parameters are available bindings on CoherenceSync as variables

Triggers

Triggers can be invoked over the network using commands. Here's an example where we inform networked Clients that we have played a jump animation:

using UnityEngine;
using Coherence;
using Coherence.Toolkit;
using System.Collections.Generic;

public class JumpController : MonoBehaviour
{
    CoherenceSync coherenceSync;
    Animator animator;

    void Awake()
    {
        coherenceSync = GetComponent<CoherenceSync>();
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        if (!coherenceSync.HasInputAuthority)
        {
            return;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            MakePlayerJump();
        }
    }

    void MakePlayerJump()
    {
        coherenceSync.SendCommand<JumpController>(nameof(PlayJumpAnimation), MessageTarget.All, coherenceSync);
    }

    // bind to this method via the Bindings window
    public void PlayJumpAnimation(CoherenceSync jumpSync)
    {
        animator.SetTrigger("Jump");
    }
}

Now, bind to the PlayJumpAnimator.

Share builds

coherence allows you to upload and share the builds of your games to your team, friends or adoring fans via an easy access play link.

Right now we support desktop (PC, Mac, Linux) and also WebGL, where you can host and instantly play your multiplayer game and share it around the world.

Build the game

Build your game to a local folder on your desktop as you would normally.

In the coherence menu in Unity select Share > Build Upload.

Upload to coherence

In this window you can select which platform the build is for. You also need to browse to the local path folder.

Click Upload or Begin Upload and coherence will confirm that it's okay to compress and upload the build to the Portal dashboard.

Share your build

Now that build has been updated (signified by the green tick), you can share it by enabling and sharing the public URL. Anyone with this link can access the build.

WebGL

If you uploaded a WebGL build, the public link allows for instant play.

7. Team-based

In this scene...

This scene will show you how coherence can be used to make a basic team-based game.

In this example each Client has one character they can control with click to move input. When connecting the player will be prompted to select a team they wish to join.

This selection will be synced via a TeamIndex field in the Sample Team Agent component.

The Target Area object is a unique GameObject that is shared among Clients and moves around the grid, specifying a particular part of the field every time it jumps. When arriving at its final position, this object checks which team has the most players inside the specified area and awards that team a point.

The team colors and score are managed by another unique object called Team Assigner. This object has a synced string variable called encodedScores which is used to sync the team scores between Clients.

Because both the Team Assigner and Target Area are persistent we can disconnect from the Server and the game state will be preserved as long as the Server is alive, even if no Clients are connected at all.

Notice that the number of teams and their colors, set in the Team Assigner, are not synced. This means it could be possible to create different Clients with different colors without them affecting each other.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

Scene setup

Setup video tutorial

It's quick and easy to set up a networked scene from scratch using the coherence SDK. This example will show you the basic steps to sync up some moving characters.

Add these components to your scene to prepare it for network synchronization.

1. Add MonoBridge

coherence > Scene Setup > Create MonoBridge

This object takes care of connected GameObject lifetimes and allows us to develop using traditional MonoBehaviour scripts.

2. Add LiveQuery - hierarchy

coherence > Scene Setup > Create LiveQuery

Creates a LiveQuery which queries the area around the local player to get the required information from the Replication Server. You can surround your entire scene in one query or can attach it to an object such as the player or a camera.

Set the radius of the LiveQuery

3. Add the sample UI

coherence > Scene Setup > Add Sample UI

Creates a Canvas (and Event System if not already present in the scene) with a sample UI that helps you connect to a local or remote Replication Server. You can create your own connection dialog, this one is just a quick way to get started.

Using the coherence Hub window

Using the coherence Hub window gives you an overview of everything related to networking in your project. The Overview tab will show you the current status and which actions you need to perform for everything to work.

coherence > coherence Hub

Automatic issue solving

If we have identified any issues in your project, the Overview tab provides a one-click solution to solving it. In the example below, simply click the error icon (that's the red icon with an exclamation mark in it) and coherence will solve the issue for you.

Finding and solving issues

Sample UI

The coherence Sample UI is a Prefab that you can add to your scene. It handles interaction with coherence services, is made up of a Unity UI Canvas and includes everything needed to handle connection to coherence.

The UI component on the root of the Prefab allows us to switch between using Rooms or Worlds. Each of these methods has a dedicated dialog for connection.

The Auto Simulator Connection component is used by Simulator builds to connect to the relevant Replication Server.

See chapter Simulators to learn more about them.

The Rooms Connect Dialog has a few components that facilitate the usage of Rooms.

At the top of the dialog we have an input field for the player's name.

Next is a dropdown for region selection. This dropdown is populated when regions are fetched. The default selection is the first available region.

Due to current limitations the local Server is fetched only if it's started before you enter Play Mode. The Local Development Mode checkbox must also be checked in the project settings (coherence > Settings).

This affects the local region for Rooms and the local worlds for Worlds.

Beneath these elements is a Rooms list of available Rooms in the selected region.

After selecting a Room from the list the Join button can be used to join that Room.

The New Room tab will take us to the Room creation screen.

This screen contains controls for setting a Room's name and maximum player capacity. Pressing the Create button will create a Room with the specified parameters and immediately add it to the Room List in the previous tab. Create and Join will create the room, and join immediately

The Worlds Connect Dialog is much simpler. It simply holds a dropdown for region selection, an input field for the players name, and a Connect button.

You can also build your own interface to connect players to the Server using thePlayResolverAPI. To learn more about the API, see either PlayResolver, Rooms or Worlds according to what your project needs.

Enabling game services

Besides the core Replicator and Simulator, coherence offers additional services to enhance your game's experience and we are constantly working on more.

Currently available services are:

  • Game account

  • Key-value store

  • Matchmaking

Enabling Game Services

In the Project sidebar, you can find links to each service. Each service has an enabled checkbox which you can tick to enable and disable those features:

Disabling a service will immediately remove that functionality from your game. Please disable with caution.

Entity references

Entity references let you set up references between Entities and have those be synchronized, just like other value types (like integers, vectors, etc.)

To use Entity references, simply select any fields of type GameObject, Transform, or CoherenceSync for syncing in the Configuration window:

The synchronization works both when using reflection and in baked sync scripts.

Entity references can also be used as arguments in Commands.

Limitations

It's important to know about the situations when an Entity reference might become null, even though it seems like it should have a value:

  • A client might not have the referenced entity in its LiveQuery. A local reference can only be valid if there's an actual Entity instance to reference. If this becomes a problem, consider switching to using the CoherenceNode component or Parent-Child relationships of prefabs which ensures that another Entity becomes part of the query.

  • The owner of the Entity reference might sync the reference to the Replication Server before syncing the referenced Entity. This will lead to the Replication Server storing a null reference. If possible, try setting the Entity references during gameplay when the referenced Entities have already existed for a while.

In any case, it's important to use a defensive coding style when working with Entity references. Make sure that your code can handle missing Entities and nulls in a graceful way.

Authority transfer

Overview

Authority over state changes to an Entity is transferrable, so it is possible to move the authority over the simulation of an Entity between Clients and Simulation Servers. This is useful for things such as balancing the simulation load, or exchanging items. It is possible for an Entity to have no Client or Simulator as the authority - these Entities are considered orphaned and are not simulated.

Types of authority transfer

In the design phase, CoherenceSync objects can be configured to handle authority transfer in different ways:

  • Request. Authority transfer may be requested, but it may be rejected by the current authority.

  • Steal. Authority will always be given to the requesting party on a FCFS ("first come first serve") basis.

  • Not transferable. Authority cannot be transferred.

Note that you need to set up Auto-adopt Orphan if you want orphans to be adopted automatically when an Entity's authority disconnects, otherwise an orphaned Entity is not simulated.

When using Request, an optional callback OnAuthorityRequestedByConnection can be set on the CoherenceSync behaviour. If the callback is set, then the results of the callback will override the Approve Requests setting in the behaviour.

The request can be approved or rejected in the callback.

Support for requests based on is coming soon.

Requesting authority in code

Requesting authority is very straight-forward.

As the transfer is asynchronous, we have to subscribe to one or more Unity Events in CoherenceSync to learn the result.

The request will first go to the Replication Server and be passed onto the receiving Simulator or Game Client, so it may take a few frames to get a response.

These events are also exposed to the Unity inspector in the Events on authority transfer section of the CoherenceSync behaviour.

3. Persistence

Physics & persistence

This scene will show you how easy it is to set up Networking in your Unity project and sync GameObject transforms and Physics Objects across the network whilst keeping them persistent. As long as the Server is running you can disconnect and re-connect, and your world will persist.

In this example a right click will spawn local Physics-based Objects that all other players will see. These Physics Objects will interact with each other and the physics for every object will be simulated locally on its authority. Clicking one of these objects will either take or relieve authority of the local client over the clicked object. You can disconnect and re-connect and the persistent Entities will all remain.

The controls at the top right of the screen allow the spawning of a unique object that works very similarly to the other Physics-based Objects. This bigger cube is set to No Duplicates in its Uniqueness property which means the Server will only allow one instance of this object to exist at a time. It also has its Lifetime setting set to Session Based this will cause the object to be deleted when its owner disconnects. Just like with the Physics-based Objects, a player can claim authority over this object by clicking it. If that player would then disconnect all clients will have the unique object deleted.

General setup

In the Hierarchy of the scene you can see three core Prefabs:

Core Scene Setup and Coherence Setup are present in all scenes and described in detail on page.

Coherence Entity is not present in this scene.

Input Manager in the Core Scene Setup Prefab is set up to spawn a Sample Entity Physics Persistent where a click is performed.

Coherence Connection Events handles overall scene connectivity. In this scene we use it to clean up objects the client has authority over when disconnecting.

In this scene...

The Physics Entity Spawner component is a simple script to instantiate a Coherence Entity Physics Persistent Prefab with a coherenceSync component that replicates the transform and position. The component also changes the material based on whether it is locally simulated or synced over the network.

The Coherence Entity Physics variants have an Entity Lifetime Type set to Persistent. This means it will remain in the world as long as the Replication Server is running, even if all Clients disconnect. It also has Authority Transfer Style set to Stealing which means the Entity can be "stolen" and simulated on a Client requesting authority.

This is done via the Input Manager in the Core Scene Setup prefab. When the object is left-clicked, it sends the message "Adopt" to the GameObject on the specific Layer Mask "Physics Entities". The component called Coherence Handler on Coherence Entity Physics objects handles the Adopt call and requests authority change via the coherenceSync component.

Coherence Handler is a basic layer for handling Commands and Events, both sending and receiving. You can create your own or reuse and extend this for your project.

Build and try

You can build this scene via the Build Settings. Run the local Replication Server through the Window > Coherence > Settings window and see how it works. You can try running multiple Clients rather than just two and see how replication works for each.

Build and run

Now we can build the project and try out network replication locally.

This example will show you how to launch a local and connect multiple instances.

Running a Replication Server locally

You can run a local Replication Server from the coherence menu:

This will open a new terminal window with the Replication Server and a World created in it.

Building through Coherence Hub

As with most features found in the menu, you can find local replication server functionality in the Coherence Hub as well. Open the Servers tab and run a Room or a World Replication Server.

Build and Connect

Now it's time to make a standalone build and test network replication.

#protip: Go to Project Settings, Player and change the Fullscreen Mode to Windowed and enable Resizable Window. This will make it much easier to observe standalone builds side-by-side when testing networking.

Note that for this sample we are running a World on a server, so make sure that Connect Dialog Selector in your Coherence Sample UI object on scene is set to Worlds as well.

Open the Build Settings window (File > Build Settings). Click on Add Open Scenes to add the current scene to the build. Click Build and Run.

Select a folder (e.g. Builds) and click OK.

When the build is done, start another instance of the executable (or run the project in the Game Window in Unity).

Click Connect on both clients. Now try focusing one and using WSAD keys. You will see the box move on the other side as well.

Congratulations, you've made your first coherence replicated experience. But this is only the beginning. Keep reading to take advantage of more advanced coherence features.

Connect across the local network

If you want to connect to the local Replication Server from another local device (such as another PC, Mac, Mobile or VR device), you can find your IPv4 address and use that as your server address in the Connect dialog. These devices need to be connected to the same network.

You can find your IPv4 address by going to your command line tool and type ipconfig . Remember to include the port number, for example 192.168.1.185:32001.

Make sure your firewall allows remote connections to connect to the Replication Server from other devices on your network.

Field settings

Many of the primitive data types in coherence support configuration to make it possible to optimize the data being sent over the network. These settings can be made individually for each field of a component and will then be used throughout your code base.

Syntax

The field settings uses the meta data syntax in the schema, which looks like this:

The meta data always goes at the end of the line and can be set on both definitions and the fields within a definition, like this:

In this example, a component named Health would be created, but instead of using the default 24 bits when sending its value, it would just use 8. Any updates to it would also be deprioritized compared to other components, so it could potentially be sent a bit late if bandwidth is scarce.

Component updates do not only contain the actual data of the update, but also information about what Entity should be affected, etc. This means that the total saving of data won't be quite as large as you'd think when going from 24 to 8 bits. Still, it's a great improvement!

Priority

All components support a piece of meta data that affects how highly the Replication Server will prioritize sending out updates for that particular component.

This meta data is set on components, like this:

The available priority levels are:

  • "very-low"

  • "low"

  • "mid" (default)

  • "high"

  • "very-high"

Bit optimizations

Some of the primitive types support optimizing the number of bits used to encode them when sending the over the network. It's worthwhile to think through if you can get away with less information than the default, to make room for more frequent updates.

Float, Vector2, Vector3

All of these types support the same two settings:

  • compression - type of compression used

    • None - no compression - full float will be sent

    • FixedPoint - user specified value range is divided equally using a defined precision. Requires range-min, range-max, bits and precision meta data.

    • Truncated - floating point precision bits are truncated, resulting in a precision loss, however with a full float value range available

  • bits – how many bits the type should use to encode its floating point values. Usable only with FixedPoint and Truncated compressions

  • precision – how much precision will be guaranteed. Usable only with FixedPoint compression. Strictly related to bits and shouldn't be set manually

Int32, UInt32

Integers can be configured to only hold a certain range via:

  • range-min – the lowest possible value that the integer can hold

  • range-max – the largets possible value that the integer can hold

Using these settings you can emulate other numeric types like char, short, unsigned int, etc.

Quaternions

Quaternions can have a number of bits per component specified using the bits meta data. Serialization requires writing 3 components and an additional sign bit, thus the total amount of bits used for quaterion is bits * 3 + 1.

Colors

Quaternions can have a number of bits per component specified using the bits meta data. Serialization requires writing 4 components (RGBA), thus the total amount of bits used for color is bits * 4.

Other types

The other types don't have any settings that affect the number of bits they use. If they take up too much bandwidth you'll have to try to send them less often, using priority, update frequency, or LODing.

Matchmaking

The matchmaking provides a powerful service to group players together in teams.

Coherence.Runtime.Matchmaking

The player needs to be to use matchmaking.

The matchmaking module has only one method.

Example

How authority works

Networked entities can be simulated either on a Game Client ("Client authority") or a Simulation Server ("Server authority"). Authority defines which Client or Simulation Server is allowed to make changes to an Entity. An Entity is any networked GameObject.

When an Entity is created, the creator is assigned authority over the Entity and that authority can be between Clients and Simulators, but only one Client or Simulator can be the authority over the Entity at at time.

Client authority

Client authority is the easiest to set up initially, but it has some drawbacks:

  • Higher latency. Because both Clients have a non-zero ping to the Replication Server, the minimum latency for data replication and commands is the combined ping (Client 1 to Replication Server and Replication Server to Client 2).

  • Higher exposure to cheating. Because we trust Game Clients to simulate their own Entities, there is a risk that one such Client is tampered with and sends out unrealistic data.

In many cases, especially when not working on a competitive PvP game, these are not really issues and are a perfectly fine choice for the game developer.

Client authority does have a few advantages:

  • Easier to set up. No Client vs. Server logic separation in the code, no building and uploading of Simulation Servers, everything just works out of the box.

  • Cheaper. Depending on how optimized the Simulator code is, running a Simulator in the cloud will in most cases incur more costs than just running a Replication Server (which is comparatively very lean).

Server authority

Having one or several Simulators taking care of the important World simulation tasks (like AI, player character state, score, health, etc.) is always a good idea for competitive PvP games.

Running a Simulator in the cloud next to the Replication Server (with the ping between them being negligible) will also result in lower latency.

The player character can also be simulated on the Server, with the Client locally predicting its state based on inputs. You can read more about how to achieve that in the section .

Peer-to-peer support (without a Replication Server) is planned in a future release. Please see the for updates.

Remote Communication

Even if an entity is not currently being simulated locally (the client does not have authority), we can still affect its state by sending a or even .

Overview

The schema file has two uses in your project:

  1. As a basis for code generation, creating various structs and methods that can be used in your project to communicate with the Replication Server.

  2. As a description for the Replication Server, telling it how the data in your project looks like – to receive, store, and send this data to its Clients.

When using MonoBehaviours and CoherenceSync you often don't need to interact with the schema directly. Here's an example of a small schema:

To learn more about the types and definitions available in a schema, see the .

If you're unsure where schema files are located, you can easily search through the project using Unity's project search window, witht:Coherence.SchemaAsset

[key1 "value1", key2 "value2", etc...]
component Health [prio "low"]
  value Float [bits "8"]
component House [prio "low"]
// Invoked on success/failure
public delegate void OnMatch(Result result, MatchResponse match);

// The data for every player returned by the matchmaking request
public struct PlayerPayload
{
    public string UserId;
    public string Team;
    public int Score;
    public string Payload;
}

// The response of the matchmaking request
public class MatchResponse
{
    public string MatchId;
    public PlayerPayload[] Players;
    public string Error;
}

public static class Matchmaker
{
    // Sends matchmaking request.
    // region:  matchmaking region, e.g. eu, us, ...
    // team:    team name
    // payload: custom string that will be send to all other players
    public static async Task<MatchResponse> Match(string region, string team, string payload)
    
    // Sends a matchmaking request
    // region:  matchmaking region, e.g. eu, us, ...
    // team:    team name
    // payload: custom string that will be send to all other players
    // tags:    custom tags to segment the player pool, e.g. "level1", "qa", etc
    // friends: group the player with his friends. If any of the friends doesn't show up the request will fail
    public static async Task<MatchResponse> Match(string region, string team, string payload, string[] tags, string[] friends)
}
var res = await Matchmaker.Match("eu", "blue", "car:volvo,rifle:glock");
if (!string.IsNullOrEmpty(res.Error))
{
    // some kind of error, inform the plaeyr
    return;
}

// res.Players contain all the matched players and their data
// including the this player
logged in
transferred
input queues
Peer-to-peer page
network command
requesting a transfer of authority
component Player
  name String64
  score Int
  
component Enemy
  speed Float
  hp Int
  poisonous Bool
specification
// connectionID is the network connection ID of the requesting client.
// sync is the CoherenceSync behaviour of the object the other client is
// requesting authority over.
public bool OnRequest(ushort connectionID, CoherenceSync sync)
{
    return (sync == theRightSync && connectionID == goodConnectionID);
}
var coherenceSync = target.GetComponent<CoherenceSync>();
coherenceSync.RequestAuthority(AuthorityType.Full);
// There Unity Events in CoherenceSync help us understand 
// what happened with authority requests and act accordingly.

// called when the CoherenceSync entity becomes the simulation authority
public UnityEvent OnAuthorityGained;

// called when the CoherenceSync entity loses simulatino authority
public UnityEvent OnAuthorityLost;

// called when a request to assume authority over the CoherenceSync entity
// is rejected
public UnityEvent OnAuthorityTransferRejected;
CoherenceClientConnection.ClientID
Authority events in the CoherenceSync inspector
Start Tutorial
Replication Server
coherence -> CoherenceHub

Game account

Creating a player account is the first step towards using the coherence Cloud API. It is required in order to use the rest of the services.

Initialization

To initialize the Cloud API you need to provide a runtime key that can be obtained from the Settings.

public static class Play
{
    // Initialises the Cloud API
    public static void Init(string RuntimeKey)
}

Callbacks

public static class Auth
{
    public enum Result
    {
        // Successful operation.
        Success,
        
        // Network error, e.g. connection failed.
        NetworkError,
        
        // Server error, e.g. exception on the server.
        ServerError,
        
        // User not found, e.g. when trying to login with non-existing user
        UserNotFound,
        
        // Invalid credentials, e.g. when trying to login with wrong password
        InvalidCredentials,
        
        // Session has expired, e.g. when trying to renew an existing session
        SessionExpired,
        
        // Feature is not enabled, e.g. K/V store
        FeatureDisabled,
    }
       
    // Invoked when completing the login process.
    public delegate void OnSession(Result result);
}

Guest Account

The easiest way to get started is by using a guest account. The only thing that is needed is to call LoginAsGuest. This will create a random username / password combination and will authenticate the player with the coherence Cloud.

Once logged in, the credentials are securely persisted so that if the game is restarted the player will be able to log in automatically.

If the game is uninstalled then the account credentials will be lost and a new guest account will be created next time the game is installed.

public static class Auth
{
    // Authenticates the players as a guest.
    // callback - invoked upon completion.
    public static void LoginAsGuest(OnSession callback)
}

User Account

Another alternative is to login with a username and a password. You have to provide the user interface.

public static class Auth
{
    // Authenticates the player with a username and a password.
    // userName   - any combination of letters, numbers, underscore and dash.
    // password   - any combination of ASCII characters
    // autosignup - whether to create an account automatically if it
    //              doesn't exist and the username is not taken
    // callback   - invoked upon completion
    public static void LoginAsUser(string userName, string password,
        bool autosignup, OnSession callback)
}

Example

This example initializes the Cloud API, checks for an existing session and, if no session was found or if it expired, logs in the player as guest.

using Coherence;
using Coherence.Runtime;
using UnityEngine;

public class ExampleLogin : MonoBehaviour
{
    void OnLogin()
    {
        Debug.Log(string.Format("Logged in. UserName={0}", Auth.UserName));
    }

    async void Start()
    {
        // Initialise the Cloud API
        Play.Init(RuntimeSettings.instance);

        // Check for existing session
        var res = await Auth.IsLoggedIn();
        if (res == Result.Success)
        {
            // Session resumed
            OnLogin();
            return;
        }
        else if (res == Result.SessionExpired || res == Result.UserNotFound)
        {
            // No session found, or session expired. Proceed with normal login.
            res = await Auth.LoginAsGuest();
            if (res == Result.Success)
            {
                OnLogin();
                return;
            }
        }

        UnityEngine.Debug.LogError(string.Format("Could not login. Error={0}", res));
    }
}

PlayResolver

The PlayResolver encapsulates all the internals to fetch and join both Rooms and Worlds.

using Coherence.Runtime;

// ensure connection to the play services backend
async Task<bool> PlayResolver.EnsurePlayConnection();

Rooms API

Detailed usage can be found under chapter Rooms.

// Returns true if there is a valid local server running.
// Returns "local" as the region if local server is running.
async Task<(bool, string)> FetchLocalRegions()

// Returns all the regions where rooms are enabled in the portal.
async Task<IReadOnlyList<string>> FetchRegions()

// Fetch a list of rooms currently active in the given region.
// Optionally, filter by tags provided when creating the room.
async Task<IReadOnlyList<RoomData>> FetchRooms(string region, string[] tags = null)

// Create a new room in the region. 
async Task<RoomData> CreateRoom(string region, RoomCreationOptions options = null)

// Convert RoomData to EndpointData, to call IClient.Connect(endpoint) directly.
// The second return value is false if the room data is invalid. 
(EndpointData, bool) GetRoomEndpointData(RoomData room)

Example usage for Rooms API:

using UnityEngine;
using Coherence.Runtime;
using Coherence.Toolkit;

public class CreatorAndJoiner : MonoBehaviour
{
    private CoherenceMonoBridge bridge;

    private async void Start()
    {
        if (!MonoBridgeStore.TryGetBridge(gameObject.scene, out bridge))
        {
            return; 
        }
        
        // XXX: try-catch everything
        var regions = await PlayResolver.FetchRegions();
        var rooms = await PlayResolver.FetchRooms(regions[0]);

        
        if (rooms.Count == 0)
        {
            var room = await PlayResolver.CreateRoom(regions[0]);
            bridge.JoinRoom(room);
        }
        else
        {
            bridge.JoinRoom(rooms[0]);  
        }
    }
}

Worlds API

Detailed usage can be found under chapter Worlds.

// Returns true if a local worlds replication server is running.
async Task<bool> EnsureLocalServer()

// Fetches the connection data for local replication server.
async Task<WorldData> FetchLocalWorld()

// Fetches a list of worlds from the portal.
async Task<IReadOnlyList<WorldData>> FetchWorlds()

// Convert WorldData into EndpointData, to call IClient.Connect(endpoint) directly.
// The second return value is false if the world data is invalid. 
(EndpointData, bool) GetWorldEndpoint(WorldData world)

Example usage for Worlds API:

using UnityEngine;
using Coherence.Runtime;
using Coherence.Toolkit;

public class WorldJoiner : MonoBehaviour
{
    private async void Start()
    {
        if (!MonoBridgeStore.TryGetBridge(gameObject.scene, out var bridge))
        {
            return; 
        }
        
        // XXX: try-catch everything
        var worlds = await PlayResolver.FetchWorlds();
        if (worlds.Count > 0)
        {
            bridge.JoinWorld(worlds[0]);
        }
    }
}

Features and Roadmap

Current features

General

  • Custom UDP transport layer using bit streams with reliability

  • WebRTC support for WebGL builds

  • Smooth state replication

  • Server-side, Client-side, distributed authority

  • Connected entity support

  • Fast authority transfer

  • Remote messaging (RPC)

  • Persistence

  • Verified support for Windows, macOS, Linux, Android, iOS and WebGL

  • Support for Rooms and Worlds

SDK

  • Unity SDK with an intuitive no-code layer

  • Per-field adjustable interpolation and extrapolation

  • Input queues

  • Easy deployment into the cloud

  • SDK source included, no 3rd-party libraries

  • Multi-room Simulators

  • Multiple code generation strategies (Assets/Baking, automated with C# Source Generators)

  • Multiple object spawning strategies (Resources, Prefab Mapper, Addressables)

Optimization and performance

  • Per-field compression and quantization

  • Per-field sampling frequency adjustable at runtime

  • Unlimited per-field levels of detail

  • Areas of interest

  • Accurate SimulationFrame tracking

Online

  • Developer portal with server and service configurator

  • Automatic server scaling

  • Multiple regions (US East, EU Central)

  • Player accounts

  • Key-value store

  • Matchmaking

Coming soon

General

  • Float64 support

  • Permissions and roles system for clients and simulators

  • World origin shifting

SDK

  • Input queue UX improvements

  • More logging and diagnostics tools

  • Built-in network condition simulation

Online

  • Additional server regions

  • Support for multiple Simulators and Replicators in a single project

  • Dashboard with usage statistics

  • Paid plans with more CCU and credits

Mid-term roadmap

  • Network profiler

  • Support for lean pure C# clients and simulators without Unity

  • Peer-to-peer (without replication server) with NAT punch-through

  • MTU detection

  • Packet replay

  • Ability to deploy multiple Simulation Servers per environment

  • Player analytics

  • Developer portal graphs and analytics

  • Simulator authentication

  • Bare-metal and cloud support

  • Misprediction detection support

  • SDK library that can be used in C++ and other languages

  • More starter/sample projects and helper scripts

  • Ability to embed WebGL games in web portals

  • Global KV-Store

  • Complex data types/entities in the KV store

  • More GGPO support for specific game genres

  • Misprediction detection support

Roadmap

  • Unreal Engine SDK

  • JavaScript SDK

  • TCP fallback support

  • Advanced matchmaking

  • Multiple Replication Servers per game world

  • Customer-specific serialization

  • User-space load-balancing (SDK framework)

  • Game world map with admin interface

  • Advanced anti-cheat functionality

  • Advanced transaction logs (audit trail)

  • Schema versioning (hot updates)

  • Console-specific updates

  • Player analytics

Testing Simulators locally

Before deploying a Simulation Server, testing and debugging locally can significantly improve development and iteration times. There are a few ways of accomplishing this.

Running the editor as a Simulator

Using the Unity Editor as a Simulator allows us to easily debug the Simulator. This way we can see logs, examine the state of scenes and GameObjects and test fixes very rapidly.

To run the Editor as a Simulator, run the Editor from the command line with the proper parameters:

  • --coherence-simulation-server: used to specify the program should run as a coherence Simulator.

  • --coherence-ip: tells the Simulator which IP it should connect to, using 127.0.0.1 will connect the Simulator to a local server, if one is running.

  • --coherence-port: specifies the port the Simulator will use

  • --coherence-world-id: specifies the World ID to connect to, used only when set to Worlds.

  • --coherence-room-id: specifies the Room id to connect to, used only when set to Rooms.

  • --coherence-unique-room-id: specifies the unique Room ID to connect to, used only when set to Rooms.

  • --coherence-auth-token: specifies your local dev authentication token, that you get from pressing the coherence Hub > Simulators > Run local simulator build > Fetch Last Endpoint button. This will change in the near future and no longer be a required parameter.

For example:

"Editor Path"
--coherence-simulation-server 
--coherence-ip 127.0.0.1 
--coherence-port 32001 
--coherence-world-id 0 
--coherence-room-id [room-id]
--coherence-unique-room-id [room-uid]
--coherence-auth-token [auth-token-string]

If you're not sure which values should be used, adding a COHERENCE_LOG_DEBUG define symbol will let you see detailed logs. Among them are logs that describe which IP, port and such the Client is connecting to. This can be done in the Player settings: Project Settings > Player > Other Settings > Script Compilation > Scripting Define Symbols

To learn more about Simulators, see Simulators.

Running a Simulator build locally from command line

Another option is making a Simulator build and running it locally. This option emulates more closely what will happen when the Simulator is running after being uploaded.

You can run a Simulator executable build in the same way you run the Editor.

"simulator build path"
--coherence-simulation-server 
--coherence-ip 127.0.0.1 
--coherence-port 32001 
--coherence-world-id 0 
--coherence-room-id [room-id]
--coherence-unique-room-id [room-uid]

This allows you to test a Simulator build before it is uploaded or if you are having trouble debugging it.

Running a Simulator build locally from the Unity Editor

You can also run existing Simulator build from coherence Hub > Simulators > Run local simulator build.

Use the Fetch Last Endpoint button to autofill the required fields.

Connecting a Simulator to a Room

When using a Rooms-based setup, you first have to create a Room in the local Replication Server (e.g. by using the connect dialog in the Client).

The local Replication Server will print out the Room ID and unique Room ID that you can use when connecting the Simulator.

To learn more about building simulator build see Simulators Build and Deploy.

Start Tutorial

The Network Playground project is a series of scenes with different features of coherence for you to pick through and learn.

They will teach you the fundamentals that should set you on your way to creating a multiplayer game.

Scenes

Each Scene in this Network Playground Project shows you something new:

  • Scene 1. Synchronizing Transforms

  • Scene 2. Physics

  • Scene 3. Persistence

  • Scene 4. Synchronizing Animations and Custom Variables

  • Scene 5. AI Navigation

  • Scene 6. Commands

  • Scene 7. Team based

  • Scene 8. Connected Entities

Each scene also comes with a few helpful core components.

Core scene setup

This Prefab stores all the generic things that make up these simple scenes.

  • Main Camera

  • Global Volume

  • Directional Light

  • Environment (Ground, Walls, etc.)

  • Navigation Interface

    • To move between the scenes without having to enter and exit Play Mode, useful when testing the standalone build.

  • Input Manager

    • Input Manager that uses Raycasting to send a message to the GameObject (with a collider) that it hit.

coherence Setup

This Prefab includes all of the things that make coherence work.

  • Interface

    • Canvas UI that handles Connection/Disconnection dialog and what address to connect.

  • Event System

    • Event system to interact with any UI in the scene.

  • coherence Live Query

    • Game Object/Component with a query radius that coherence uses to ask the server "What is happening in the query radius?" so it does not query unnecessarily big areas of large worlds. You can find more information here.

  • coherence Mono Bridge

    • GameObject/Component to transform the Mono based CoherenceSync component to archetypes so all data can be processed as ECS code.

coherenceSync (on coherence Entity Prefab)

We use this component on anything that we require to be networked, whether this is a playable character, NPC (non playing character), an object (ball, weapon, banana, car, plant, etc.) or any Game/Input Managers that require to sync data between Clients/Simulators/Servers.

It scans the inspector for attached components and allows us to sync those component values (as long as they are public) and communicate them to other Clients. It also allows us to configure individual Prefabs' persistent, authoritative and network connect/disconnect settings. There's much more information on the CoherenceSync page.

Multi-Room Simulators (advanced)

Simulate multiple Rooms at the same time, within one Unity instance

Multi-Room Simulators are Room Simulators which are able to simulate multiple game rooms at the same time - one sim to rule them all!

In order to achieve this, the game code should be defensive on which room it is affecting. Game state should be kept per Room, meaning game managers, singletons (static data), etc. need to account for this.

Each Room is held in a different scene. So for every Room created, the Multi-Room Simulator should open a connection to it, hence loading additively a scene and stablishing a Simulator connection (via MonoBridge).

How do they work?

By using Multi-Room Simulators, the coherence Developer Portal is able to instruct your Simulator which room to join and start simulating.

This communication happens via HTTP. An HTTP server is started by your game build when the MultiRoomSimulator component is active. This component listens to HTTP requests made by the coherence Developer Portal.

For offline local development, you can use a MultiRoomSimulatorLocalForwarder component on your clients, which will create HTTP requests against your local simulator upon client connection, like joining a room.

For local development, enable the Local Development Mode flag in the project settings.

Once the MultiRoomSimulator receives a request to join a room, it spawns a CoherenceSceneLoader that will be in charge of loading additively the scene specified.

MultiRoomSimulator inspector

By default, scenes will have their physics scene. coherence ticks the physics scene on the CoherenceScene component, which the target scene to be loaded should include.

Setting up Multi-Room Simulators

The quickest way to get Multi-Room Simulators set up is by using the provided wizard.

Wizard at coherence > Simulator > Multi-Room Simulator Wizard

It will take you through the GameObjects and Components needed to make it happen.

Some steps are not strictly necessary. For example, you don't need a Sample UI for Multi-Room Simulators to happen. However, if you do use the Sample UI, we help you make sure you have it set up properly.

Manually setting up Multi-Room Simulators

These are the pieces needed for Multi-Room Simulators to work:

  • Simulators

    • In the initialization scene (splash, init, menu, ...)

      • MultiRoomSimulator — listens to join room requests and delegates scene loading (by instantiating CoherenceSceneLoaders)

  • Clients

    • (Only for local development) In the scene where you connect to a Room (where you have the Sample UI or your custom connection logic)

      • MultiRoomSimulatorLocalForwarder — requests the local MultiRoomSimulator to join rooms when the Client connects.

  • Independently

    • In the scene where the networked game logic is (game, Room, main, ...)

      • MonoBridge — handles the connection

      • LiveQuery — filters Entities by distance

      • CoherenceScene — when the scene is loaded via CoherenceSceneLoader, it will try to connect using the data given by it. It attaches to the MonoBridge, creates a connection, and handles auto reconnection. If a scene loaded through CoherenceSceneLoader doesn't have a CoherenceScene on it, one will be created on the fly.

There are two components that can help you fork Client and Simulator logic, for example, by enabling or disabling the MultiRoomSimulator component depending on whether it's a Simulator or a Client build. These are optional but can come in handy.

  • SimulatorEventHandler — events on the build type (Client/Simulator).

SimulatorEventHandler
  • ConnectionEventHandler — events on the connection stablished by the MonoBridge associated with that Scene.

ConnectionEventHandler

In-editor debugging

Hierarchy view with additional coherence controls

It is possible to visualize each individual Room the Multi-Room Simulator is working on. By default, Simulator connections to Rooms are hidden, as shown in the image above. You can toggle the visibility per scene by clicking the Eye icon. You can also change the default visibility of the loaded scene (defaults to hidden) on the CoherenceScene component:

CoherenceSync inspector
Scene visibility options, when this scene is loaded through CoherenceSceneLoader (hence via MultiRoomSimulator)

Limitations

Working with Multi-Room Simulators needs your logic to be constrained to the scene. Methods like FindObjectsOfType will return objects in all scenes — you could affect other game sessions!

Check out Coherence.Toolkit.SceneUtils for alternative APIs to FindObjectsOfType that work per scene.

Also, Coherence.Toolkit.ActiveSceneScope can help make sure instantiation happens where you want it to be.

This is also true for static members, e.g. singletons. When using Multi-Room Simulators, there need to be as many isolated instances of your managers as there are open simulated rooms.

For example, if you were to access your Game Manager through GameManager.instance, now you'll need a per-scene API like GameManager.GetInstance(scene).

There may be third-party or Unity-provided features that can't be accessed per scene, and that affect the whole game.

Loading operations, garbage collections, frame-rate spikes... all these will affect performance on other sessions, since everything is running within the same game instance.

Allowing Multi-Room Simulators in the Developer Portal

Multi-Room Simulators are still Room Simulators. You need to enable Simulators for Rooms and enable Multi-Room Simulators in the coherence Developer Portal, as shown here:

Baking and code generation

Overview

Out of the box, coherence can use C# reflection to sync data at runtime. This is a great way to get started but is very costly performance-wise, and has a number of limitations on what features can be used through this system.

For optimal runtime performance and a complete feature set, we need to create a schema and perform code generation specific to our project. Learn more about this in the section.

coherence calls this mechanism baking.

Baking

Click on coherence > Bake.

This will go through all indexed CoherenceSync GameObjects (Resources folders and Prefab Mapper) in the project and generate a schema file based on the selected variables, commands and other settings. It will also take into account any LODs () that have been added.

For every Prefab with a CoherenceSync component, the baking process will generate a C# script specifically tuned for it.

Two bake modes

coherence offers two mechanisms to generate baked scripts: through Assets or through a Source Generator. By default, coherence baked using Assets, but you can change this setting anytime in the coherence Settings window.

Bake mode: Assets

When you bake using Assets, the generated code will output to Asset/coherence/baked. This is a simple solution, allowing for an easy inspection and debugging of the generated files, but it comes with a few drawbacks:

  • You should version the baked files, which can clutter your VCS workflow.

  • Since baked scripts access your code, changing your code will get you into compilation errors.

Bake mode: Source Generator

When you bake using the Source Generator, the generated code is fed directly to the compilation pipeline, not generating the files in the Assets folder. In this process, coherence can analyze your code syntactically and semantically. This means it can detect cases where changes in your code can affect the baked files, anticipating compiler errors and avoiding them altogether. This mode comes with a few drawbacks too:

  • File Assets/coherence/Footprint.cs is created/updated every bake operation, to trigger a recompile on your code. This file (and its .meta) can be ignored in your VCS.

  • A bake operation is performed on every recompile. For most projects this is not noticeable. But if your project is heavy or your computer is slow, this can take additional time on top of the normal recompilation time.

  • Since baked files are not versioned and have to be generated, the protocol code generator (executable bundled with the SDK package) needs to keep its execute permissions, and should have write permission on <project>/Library/coherence. This is usually not a problem, except in continuous integration scenarios, where there might be strict rules on files having the execute permission.

  • Harder to debug. Since files are not in Assets anymore, you can't click on them and have proper IntelliSense. The last generation made through the Source Generator is available in Library/coherence/LastBake.

As you can see, there are pros and cons to each mechanism, so we recommend you to try both and check what works best for your workflow.

Source generation does not work in Unity version 2021.1. This is a known Unity issue that has no other fix than to upgrade (or downgrade) the version.

Using the Baked Script in your Prefab

Once the baked scripts have been generated, you can make use of it by ticking the checkbox Use Baked Script in the CoherenceSync inspector.

Modifying bound data, safe mode and the watchdog

When you configure your Prefab to network variables, and then bake, coherence generates baked scripts that access your code directly, without using reflection. This means that whenever you change your code, you might break compilation by accident.

For example, if you have a Health.cs script which exposes a public float health; field, and you toggle health in the Configure window and bake, the generated baked script will access your component via its type, and your field via field name.

Like so:

When baking via assets, baked scripts will be located in Assets/coherence/baked.

If you decide you want to change your component name (Health) or any of your bound fields (health), Unity script recompilation can fail. In this example, we will be removing health and adding health2 in its place.

When baking via assets, the watchdog is able to catch compilation problems related with this, and offer you a solution right away.

You can delete the baked folder manually through the coherence Settings window.

It will suggest that you delete the baked folder, and then diagnose the state of your Prefabs. After a few seconds of script recompilation, you will be presented with the Diagnosis window.

In this window, you can easily spot variables in your Prefabs that can't be resolved properly. In our example, health is no longer valid since we've moved it elsewhere (or deleted it).

From here, you can access the Configure window, where you can spot the problem.

Now, we can manually rebind our data: unbind health and bind health2. Once we do, we can now safely bake again.

Remember to bake again after you fix your Prefabs.

Build and deploy

Build a Simulator to be uploaded to the cloud

A Simulator build is a built Unity Player for the Linux 64-bit platform that you can upload to coherence straight from the Unity Editor.

Open Coherence Hub and select the Simulators tab.

From here you can build and upload Simulators.

Click the little info icon in the top right corner to learn more about Simulators and how to build them properly.

Configuring your Simulator build

You can change your Simulator build options by clicking on the coherence Hub > Simulators > Change Simulator Build Options button.

This will select the Simulator build options object in your inspector:

There are several settings you might want to change.

  • Specify the scenes you want to get in the build via the Scenes To Build field.

  • For a local build, you can choose to enable/disable the Headless Mode by ticking the checkbox. For a cloud build, Headless Mode is always enabled by default.

  • Choose your preferred Scripting Implementation from the drop-down list. It can either be or .

  • For more information about the options listed under Build Size Optimizations, see .

Create and upload the build

Make sure you have completed the steps required in .

Make sure you meet the requirements:

  1. You have to have Linux modules (Linux Build Support (IL2CPP), Linux Build Support (Mono), and Linux Dedicated Server Build Support) installed in Unity Editor. See .

  2. You have to be logged into the coherence Developer Portal, through the Unity Editor. See for more information.

Press the coherence Hub > Simulators > Build And Upload Headless Linux Client button.

When the build is finished, it will be uploaded to your currently selected organization and project in the Developer Portal.

Connecting your Simulator to a Room or World

You'll see in the developer dashboard when your Simulator is ready to be associated with a Room or World.

Target frame rate on Simulator builds is forced at 30.

Reducing Simulator build size (experimental ⚠️)

This feature is experimental, please make sure you make a backup of your project beforehand.

You can set the values for the Build Size Optimizations in the drop-down list of the build configuration inspector. It looks like this:

Select the desired optimizations depending on your needs.

Optimization
What it does

Once your Simulator is built and uploaded, you'll be prompted with the option to revert the settings to the ones you had applied before building. This is to avoid these settings from affecting other builds you make.

Specification

Primitive Types

These are the primitive types supported in a coherence schema:

Int

Uses a default range of -2147483648 to 2147483647 (32 bits).

UInt

Uses the default range of 0 to 4294967295 (32 bits).

Int64

Uses the default range of -9223372036854775808 to 9223372036854775807 (64 bits).

UInt64

Uses the default range of 0 to 18446744073709551615 (64 bits).

Float

Encoded using one of the following compression types: None, Truncated, FixedPoint (defaults to None).

Float64

Higher precision floating point using 64 bits. It does not support compression.

Bool

Encoded using a single bit.

Vector2

Encoded using two floats with a specified compression (defaults to None).

Vector3

Encoded using three floats with a specified compression (defaults to None).

Quaternion

Encoded using three components and a sign bit.

Color

Encoded using four components (RGBA).

String

A string with up to 63 bytes encoded using 6 bits for length.

Bytes

An array of bytes with an upper limit of 511 bytes encoded using 9 bits for length.

Packet fragmentation is not supported yet in this version, so packets bigger than the internal MTU (~1200 bytes) may be never sent.

Entity

The Entity type is used to keep references to other Entities. Technically the reference is stored as a local index that has to be resolved to an actual Entity before usage. Also, since a Client might not know about the referenced Entity (due to it being outside of its LiveQuery) an Entity reference might be impossible to resolve in some circumstances. Your Client code will have to take this into account and be programmed in a defensive way that handles missing Entities gracefully.

Field Settings

Several of the primitive types can be configured to take up less space when sent over the network, see .

Components

The most common definition in schemas is components, which correspond to replicated fields for baked MonoBehaviours.

The definition takes a name of the component, and on the following lines an indented list of member variables, each one followed by their primitive type (see above.) The indentation has to be exactly 2 spaces. Here's how it might look:

After code generation, this will give access to a component with the name Portal that has the members locked, connectedTo, and size.

Optionally, each member/type pair can have additional meta data listed on the same line, using the following syntax:

This is how it might look in an actual example:

Built-in components

There are some components that are built into the Protocol Code Generator and that you will always have access to.

Archetypes

Archetypes are used to optimize the sending of data from the Server to each Client, lowering the precision or even turning off whole components based on the distance from the LiveQuery to a particular Entity. Read more about how to define them in the schema on the page .

Commands

Commands are defined very similarly to components, but they use the command keyword instead.

Here's a simple example of a command:

Routing defines to whom the command can be sent. Currently, two values are supported:

  • AuthorityOnly - command will be received only by the owner of the target Entity

  • All - command will be received by every Client that has a copy of this Entity

When using reflection, there are limitations to what types are supported in commands. See the section for more information.

Inputs

Inputs represent a group of values that are snapshotted every frame (or fixed frame). This snapshot is then sent to other clients or a session host, so it can be processed by the same code on both ends, resulting in the same outcome.

Example of an input:

Archetypes in schemas

This document explains how Archetypes work internally. If you're looking for how Level of Detail works in coherence with CoherenceSync, see instead.

Basics

Level of Detail (or LOD-ing, for short) is a technique to optimize the amount of data being sent from the Replication Server to each Client. Often a Client doesn't need to get as much information about an Entity if it's far away. The way this is achieved when working with coherence is by using Archetypes.

Archetypes let you group components together and create distinct "levels of detail". Each such level must have a distance threshold, and a list of components that should be present at that distance. Optionally it can also contain per-field overrides that make the primitive data types in the components take up less space (at the cost of less precision.)

To define an Archetype, use the archetype keyword in your schema, then list the LODs in ascending order. Notice that LOD 0 does not need a distance, since it always starts at 0. Here's an example of a simple Archetype:

In this example, any Enemy Entity that is 200 or more units away from the LiveQuery of a particular Client will only get updates for the WorldPosition. Any client with a distance of 10 – 200 will get WorldPosition and WorldOrientation, and anything closer than that will get the full Entity.

Given one or more Archetype definitions in your schema, you will have access to a few different data types and methods in your project (these will be generated when you run the Protocol Code Generator.)

  • ArchetypeComponent – this component has a field index that keeps track of which one of the Archetypes in your schema that is being used. If you add the ArchetypeComponent yourself you have to use the static constants in the Coherence.Generated.Archetype to set the index. These all have the name "Archetype name" + "Index", e.g. EnemyIndex in the example above.

  • An individual "tag" component (with no fields) called "Archetype name" + "Archetype", e.g. EnemyArchetype in the example above. This component can be used to create optimized ForEach queries for a specific Archetype.

  • LastObservedLod – this component holds the current LOD for the Entity. This can be used to detect when the Entity changes LOD, if that's something you want to react to. Note that this component is not networked, since the exact LOD for an Entity is unique for each Client.

  • Static helper methods on the Archetype class to instantiate the Archetype in a usable state. These are named "Instantiate" + "Archetype name", e.g. InstantiateEnemy in the example above.

Field overrides

If a component isn't present at a certain LOD, no updates will be sent for that component. This is a great optimization, but sometimes a little too extreme. We might actually need the information, but be OK with a slightly less fine-grained version of it.

To achieve this, you can use the same field settings that are available when defining components, but use them as overrides on specific LOD's instead.

Here's an example of the syntax to use:

Notice that the settings are exactly the same as when defining components. To override a field, you must know its name (value in this case.) Any field that is not overridden will use the settings of the LOD above, or the original component if at LOD 0.

Priority

Each component in an Archetype can also override the default priority for that component. Just add the priority meta data after the component, like this:

To read more about priority, see the page about .

CoherenceNode

While the basic case of direct parent-child relationships between Entities is handled automatically by coherence, more complex hierarchies (with multiple levels) need a little extra work.

An example of such a hierarchy would be a synced Player Prefab with a hierarchical bone structure, where you want to place an item (e.g. a flashlight) in the hand:

Player > Shoulder > Arm > Hand

A Prefab can only have a single CoherenceSync script on it (and only on its root node), so you can't add an additional one to the hand. Instead, you need to add the CoherenceNode component to another Prefab so that it can be parented. Please note that this parenting relationship can only be set up in the scene or at runtime; you can't store it in the parent Prefab since that would break the rule of only one CoherenceSync per Prefab.

To prepare the child Prefab that you want to place in the hierarchy, add the CoherenceNode component to it (it also has to have a CoherenceSync). In the example above, that would be the flashlight you want your player to be able to pick up. You don't need to make any changes to the Player Prefab, just make sure it has a CoherenceSync script in the root.

This setup allows you to place instances of the flashlight Prefab anywhere in the hierarchy of the Player (you could even move it from one hand to the other, and it will work).

The one important constraint is that the hierarchies have to be identical on all Clients.

To recap, for CoherenceNode to work you need to things:

  1. One or more Prefabs with CoherenceSync that have some kind of hierarchy of child transforms (the child transforms can't have CoherenceSyncs on them).

  2. Another Prefab with CoherenceSync and CoherenceNode. Instances of this Prefab can now be parented to any transform of the Prefabs with just CoherenceSync (in step 1).

CoherenceNode works using two public fields which are automatically set to sync using the [Sync] attribute.

The path variable describes where in the parent's hierarchy the child object should be located. It is a string consisting of comma-separated indexes. Every one of these indexes designates a specific child index in the hierarchy. The child object which has the CoherenceNode component will be placed in the resulting place in the hierarchy.

The pathDirtyCounter variable is a helper variable used to keep track of the applied hierarchy changes. In case the object's position in the parent's hierarchy changes, this variable will be used to help settle and properly sync those changes.

Note: This is simply an example solution for a particular case which uses other tools coherence provides. Your project's needs might be different and require a different custom solution.

component Portal
  locked Bool
  connectedTo Entity
  size Float
[key1 "value1", key2 "value", etc...]
component Portal
  locked Bool 
  connectedTo Entity [prio "high"]
  size Float [prio "low", bits "16"]
command Damage [routing "AuthorityOnly"]
  amount Int
  explosive Bool
input PlayerInput
  XMov Vector2 [compression "None"]
  YMov Vector2 [compression "None"]
  Jump Bool
  Shoot Bool
  Throttle Float [compression "None"]
  Command String
Field settings
Archetypes and LOD-ing
Supported types in commands
archetype Enemy
  lod 0
    WorldPosition
    WorldOrientation
    Health
    AnimationState
    
  lod 1 [distance "10"]
    WorldPosition
    WorldOrientation
    
  lod 2 [distance "200"]
    WorldPosition
archetype NPC
  lod 0
    WorldPosition
      value [bits "24"]
      
  lod 1 [distance "100"]
    WorldPosition
      value [bits "16"]
      
  lod 2 [distance "1000"]
    WorldPosition
      value [bits "8"]
archetype NPC
  lod 0
    WorldPosition [prio "high"]
this page
Field settings

Replace Textures And Sounds With Dummies

Project's textures and sound files are replaced with tiny and lightweight alternatives (dummies). Original assets are copied over to <project>/Library/coherence/AssetsBackup. They are restored once the build process has finished.

Keep Original Assets Backup

The Assets Backup (found at <project>/Library/coherence/AssetsBackup) is kept after the build process is completed, instead of deleted. This will take extra disk space depending on the size of the project, but is a safety convenience.

Compress Meshes

Sets Mesh Compression on all your models to High.

Disable Static Batching

Static Batching tries to combine meshes at compile-time, potentially increasing build size. Depending on your project, static batching can affect build size drastically. Read more about static batching.

Mono 2x
IL2CPP
this section below
Create an account
Unity - Manual: Adding modules to the Unity Editor
Login through Unity
Recent simulators module on the dashboard
[Sync] public string path;
[Sync] public int pathDirtyCounter;
var healthComponent = GetComponent<Health>();
...
var healthField = healthComponent.health;
//public float health;
public float health2;
How does coherence work
Level of Detail
Bake modes in the coherence settings window
Network Playground Introduction

Interpolation

Overview

For an object to appear to move smoothly on the screen, it must be rendered at a high rate, usually 60 frames per second or more. However, depending on the settings in your project, and the conditions of your internet connection, data may not always arrive at a smooth 60 frames per second across the network. This is completely okay, but in order to make state changes appear smooth on the Client, we use interpolation.

Interpolation is a type of estimation, a method of constructing new data points within the range of a discrete set of known data points.

When you select a variable to replicate in the Configure window, it is automatically assigned a default interpolation setting. The default settings are usually good to get started, but you can modify or create your own interpolation settings that better fit your specific needs.

Binding interpolation settings

In the Configure window, each binding displays its interpolation settings next to it.

Built-in interpolation settings for position and rotation are provided out-of-the-box, but you are free to create your own and use them instead.

Creating a new Interpolation Settings object.

You can also create an interpolation settings asset: Assets > Create > coherence > Interpolation Settings

Interpolation types

Linear interpolation blends values by moving along straight lines from sample to sample. This makes the networked object move in a zig-zag pattern, but this is usually not noticeable when sampled at a sufficient rate and with some additional smoothing applied (see section Other settings > Smoothing below).

Spline interpolation blends between samples using the Catmull-Rom spline method which gives a smoother movement than linear interpolation without any sharp corners, at the cost of increased latency (see: Latency below). Spline interpolation requires at least 4 samples to produce good results.

If interpolation type is set to None, the value will simply snap to the most recent sample without any blending. This is recommended for binding types that have no obvious blending methods, e.g., string, byte array and object references.

You could also implement your own interpolation type (see: Custom Interpolators below).

Latency

Interpolation will add some additional latency to synced bindings. That's because incoming network samples must first be put in a buffer that is then used to calculate the interpolated value.

The amount of latency depends on the binding's sample rate and interpolation type. The lower the sample rate, the higher the latency.

Linear Interpolation requires a headroom of one sample while Spline Interpolation requires two samples. If interpolation type is set to None, there is no additional latency added, and samples will be rendered as soon as they arrive over the network.

Example: A Prefab that uses Spline Interpolation for its position binding with a sample rate of 30 Hz and network latency of 100 ms will appear to be 2*1/30+0.100 = 0.16 s behind the local time.

Since a Prefab can define separate interpolation types and sample rates for its different bindings, it is possible that not all bindings share the same latency. If, for example, position and rotation are interpolated with different latency, the position and rotation of a vehicle might not match on the remote object.

Other Settings

There are a few settings you can tweak:

  • Smoothing

    • Smooth Time: additional smoothing can be applied (using SmoothDamp) to clear out any jerky movement after regular interpolation has been performed.

    • Max Smoothing Speed: the maximum speed at which the value can change, unless teleporting.

  • Latency

    • Network Latency Factor: fudge factor applied to the network latency. A factor of 1 means adapting to network latency with no margin, so the incoming sample must arrive at its exact predicted time to prevent the buffer from becoming stale. In general, a factor of 1.1 is recommended to prevent network fluctuations from causing dead reckoning due to latency peaks.

    • Network Latency Cooldown: when network latency decreases, wait this amount of time (in seconds) before recalculating network latency. This prevents network fluctuations from causing dead reckoning due to latency valleys.

    • Additional Latency: increases latency by a fixed amount (in seconds) to add an additional margin for the sample buffer.

  • Overshooting

    • Max: how far into the dead reckoning to venture when the time fraction exceeds 100%, as a percentage of the sample rate.

    • Retraction: how fast to pull back to 100% when overshooting the allowed dead reckoning maximum (in seconds)

  • Teleport Distance: if two consecutive samples are further apart than this, the value will teleport or snap to the new sample immediately without interpolating or smoothing in between.

Dead reckoning is a form of replicated computing so that everyone participating in a game winds up simulating all the entities (typically vehicles) in the game, albeit at a coarse level of fidelity.

The basic notion of dead reckoning is an agreement in advance on a set of algorithms that can be used by all player nodes to extrapolate the behavior of entities in the game, and an agreement on how far reality should be allowed to get from these extrapolation algorithms before a correction is issued.

Interpolation settings can be tweaked in Play mode where you can see the result on the screen immediately, but the changes you make will be reverted again once you exit Play mode. This is because - in Play mode - a copy of the interpolation settings is created.

Remember that interpolation only happens on remote objects, so you need to select a remote object to experiment with interpolation settings in Play mode.

Interpolation works both in Baked and Reflection modes. You can change these settings at runtime via the Configure window (editor) or by accessing the binding and changing the interpolation settings yourself:

if (coherenceSync.TryGetBinding(typeof(Transform), "position", out IBinding binding))
{
    // change your interpolation settings at runtime
    binding.InterpolationSettings = someSettings;
}

Custom interpolators

The Linear and Spline interpolators that are provided by coherence are sufficient for most common use cases, but you can also implement your own interpolation algorithm by sub-classing Interpolator.

You can choose to override one or more of the base methods depending on which type or types of values you want to support. The method signatures usually take two adjacent samples and a fractional value (from 0 to 1) to blend between them. There are also method signatures that provide four samples, which is useful for the Catmull-Rom spline interpolation.

Here's an example of a custom interpolator that makes the remote object appear at an offset distance from the object's actual position.

using Coherence.Interpolation;
using UnityEngine;

public class InterpolatorWithOffset : Interpolator
{
    public Vector3 offset;
    public override float NumberOfSamplesToStayBehind => 1;

    public override Vector3 InterpolateVector3(Vector3 value1, Vector3 value2, float t)
    {
        var interpolatedValue = Vector3.Lerp(value1, value2, t);
        return interpolatedValue + offset;
    }
}

The NumberOfSamplesToStayBehind property controls the internal latency.

Catmull-Rom splines require four samples to blend between, so its NumberOfSamplesToStayBehind property must be set to 2.

The new Interpolator is available from the Interpolator Type dropdown with the offset field as a configurable parameter.

Extrapolation

Extrapolation uses historical data to predict the future state of a binding. By predicting the state of other players before their network data actually arrives, network lag can be reduced or removed entirely. This will cause mispredictions that need to be corrected when the incoming network data does not match the predicted state.

Extrapolation is not yet supported by coherence.

Simulation Server

coherence uses the concept of ownership to determine who is responsible for simulating each Entity in the Game World. By default, each Client that connects to the Server owns and simulates the Entities they create. There are a lot of situations where this setup is not adequate. For example:

  • The number of Entities in the Game World could be too large to be simulated by the players on their own, especially if there are few players and the World is very large.

  • The game might have an advanced AI that requires a lot of coordination, which makes it hard to split up the work between Clients.

  • It is often desirable to have an authoritative object that ensures a single source of truth for certain data. State replication and "eventual correctness" doesn't give us these guarantees.

  • Perhaps the game should run a persistent simulation, even while no one is playing.

With coherence, all of these situations are can be solved using dedicated Simulation Servers. They behave very much like normal Game Clients, except they run on their own with no player involved. Usually they also have special code that only they run (and not the clients). It is up to the game developer to create and run these programs somewhere in the cloud, based on the demands of their particular game.

Creating a Simulation Server

If you have determined that you need one or more Simulation Servers for your game, there are multiple ways you can go about implementing these. You could create a separate Unity project and write the specific code for the Simulation Server there (while making sure you use the same schema as your original project).

An easier way is to use your existing Unity project and modify it in a way so that it can be started either as a normal Game Client, or as a Simulation Server. This will ensure that you maximize code sharing between Clients and Servers - they both do simulation of Entities in the same Game World after all.

Important: to build a Simulation Server, you have to build for the Linux platform.

To determine whether to start a build as a Client or as a Simulation Server, you can use command line arguments:

Reading command line arguments

using System;
using UnityEngine;

public class Boot : MonoBehaviour
{
    void Start()
    {
        var args = Environment.GetCommandLineArgs();
        var isSimulationServer = false;

        foreach (var arg in args)
        {
            if (arg == "--coherence-simulation-server")
            {
                isSimulationServer = true;
            }
        }

        /* do things based on 'isSimulationServer' here... */
    }
}

To pass the command line argument, start the application like this:

$ ./Game --coherence-simulation-server  

Command line parameters in the cloud

The Simulation Server is started with the following parameters in the cloud:

--coherence-region             // region where the simulator is started
--coherence-world-id           // unique world id (in world mode)
--coherence-http-server-port   // REST access for starting / stopping rooms
--coherence-auth-token         // token to authorize access
--coherence-simulation-server  // identify as simulation server
--coherence-simulator-type     // Room or World

// the following should be used also when starting a local simulation server to 
// successfully connect to the local replication server

--coherence-ip                 // address of the replication server (eg 127.0.0.1)
--coherence-port               // port of the replication server
--coherence-room-id            // room id when connecting to a room
--coherence-unique-room-id     // unique room id to connect to in room mode
--coherence-room-tags          // tags used when creating the room
--coherence-room-kv            // key values supplied when creating the room

SimulatorUtility

The SDK provides a static helper class to access all the above parameters in the C# code called SimulatorUtility.

static class SimulatorUtility
{
    public enum Type
    {
        Undefined = 0,
        World = 1,
        Rooms = 2
    }
    
    public static Type SimulatorType;         // Type of simulator: Room / World.
    public string Region;                     // Region where simulator was spawned.
    public string Ip;                         // Ip of the replication server.
    public int Port;                          // UDP port of the replication server.
    public int RoomId;                        // RoomId for current session. 
    public ulong UniqueRoomId;                // Unique RoomId.
    public ulong WorldId;                     // World Id (in worlds mode).
    public int HttpServerPort;                // Port used for REST commands.
    public string AuthToken;                  // Auth token used for authentication.
    public List<string> RoomTags;             // Tags for the room. 
    public Dictionary<string,string> RoomKV;  // Key Values for the room.
    public bool IsInvokedAsSimulator;         // Whether the instance was invoked as a simulator.
    public bool IsInvokedInCommandLine;       // Whether the instance was invoked on the commandline.
    public bool IsSimulator;                  // Identify whether simulator.  
}

Builds

When building stand-alone builds, Unity also has an option for headless mode. This is great for Simulation Servers since we're not interested in rendering any graphics on these anyway. By using headless mode we get a leaner executable that is easier to deploy in the cloud.

Change your build target to be Linux and tick Headless Mode. Rooms with just Simulators always shut down automatically.

Build and deploy

Refer to the Simulator: Build and deploy section.

CoherenceSync

CoherenceSync is a component that should be attached to every networked GameObject. It may be your player, an NPC or an inanimate object such as a ball, a projectile or a banana. Anything that needs to be synchronized over the network and turned into an Entity. You can select which of the attached components you would like to sync across the network as well as individual public properties.

All networked Entities need to be placed in the Resources folder.

Synced variables and commands

Any scripts attached to the component with CoherenceSync that have public variables will be shown here and can be synced across the network. Enable the script + the variable to sync, it's that easy. Those variables with a lightning bolt next to them signify a public method that can be activated via commands.

Persistence and authority

Ownership transfer

When you create a networked GameObject, you automatically become the owner of that GameObject. That means only you are allowed to update or destroy it. But sometimes it is necessary to pass ownership from one player to another. For example, you could snatch the football in a soccer game or throw a mind control spell in a strategy game. In this case, you will need to transfer ownership from one Client to another.

Entity lifetime

When a player disconnects, all the GameObjects created by that player are usually destroyed. If you want any GameObjects to stay in the Game World after the owner disconnects, you need to set Entity lifetime type of that GameObject to Persistent.

  • Session Based - will be removed when the Client disconnects. GameObjects will stay on the scene of the Client who is an Authority owner for session-based objects until the scene reloads.

  • Persistence - Entities with this option will persist as long as the server is running. For more details, see Configuring persistence.

Uniqueness

  • Allow Duplicates - no restrictions on which objects can be instantiated over the network.

  • No Duplicates - ensure objects are not duplicated by marking them with a UUID.

    • You can set the UUID manually

    • If you leave the field blank a GUID will be assigned at runtime (This is rarely what you want)

    • Use the CoherenceUUID component helper to generate GUIDs for you at editor time. CoherenceUUID is an design time only tool for assigning unique UUIDs to prefab instances.

Uniqueness examples

Manager: If your game has a prefab, of which there can only be 1 in-game instance at any time (Such as a Game Controller), assign a UUID manually on the prefab asset.

Interactable objects: If you have several instances of a given prefab, but each instance must be unique (Such as doors, elevators, pickups etc) you should add the CoherenceUUID script and set it to Auto-generate in scene. This means that i.e. a door will only spawn once, but still replicate its state across the network.

Entity simulation type

  • Client Side - Simulates everything on the local Client and passes the information to the Replication Server to distribute that information to the other Clients.

  • Other forms of simulation (Server; Server with Client Input).

Authority transfer style

  • Not Transferable - The default value is Not Transferable because most often objects are not meant to be transferred.

  • Stealing - Allows the GameObject to be transferred to another Client.

  • Request - This option is intended for conditional transfers, which is not yet supported.

Orphaned entities

By making the GameObject persistent, you ensure that it remains in the game world even after its owner disconnects. But once the GameObject has lost its owner, it will remain frozen in place because no Client is allowed to update or delete it. This is called an orphaned GameObject.

In order to make the orphaned GameObject interactive again, another Client needs to take ownership of it. To do this, enable Auto-adopt orphan.

Transfer request

Once you have set the transfer style to Stealing, any Client can request ownership by calling the RequestAuthority() method on the CoherenceSync component of that GameObject:

someGameObject.GetComponent<CoherenceSync>().RequestAuthority();

A request will be sent to the GameObject's current owner. The current owner will then accept the request and complete the transfer.

You are now the new owner of the GameObject. This means the isSimulated flag has been set to true, indicating that you are now in full control of the GameObject. The previous owner is no longer allowed to update or destroy it.

Helper scripts with a custom implementation of authority transfer can be found here.

The state of the CoherenceSync.isSimulated flag is not guaranteed to have a proper value during the Awake() callback (right after an object is created). All scripts that use this flag should wait at least until the Start() callback.

Connection status

You can set up Custom Events for handling user connection and disconnection. Manual Destroy is useful for session based objects that you want to keep "semi-persistent" which would be removed when all the Clients disconnect.

Baked script

When CoherenceSync variables/components are sent over the network, by default, Reflection Mode is used to sync all the data at runtime. Whilst this is really useful for prototyping quickly and getting things working, it can be quite slow and unperformant. A way to combat this is to bake the CoherenceSync component, creating a compatible schema and then generating code for it.

The schema is a file that defines which data types in your project are synced over the network. It is the source from which coherence SDK generates C# struct types (and helper functions) that are used by the rest of your game. The coherence Replication Server also reads the schema file so that it knows about those types and communicates them with all of its Clients efficiently.

The schema must be baked in the coherence Settings window, before the check box to bake this Prefab can be clicked.

When the CoherenceSync component is baked, it generates a new file in the baked folder called CoherenceSync<NameOfThePrefab>. This component will be instantiated at runtime, and will take care of networked serialization and deserialization, instead of the built-in reflection-based one.

Commands

Refer to the Commands section.

Client vs Simulator logic

When scripting Simulators, we need mechanisms to tell them apart.

Am I a Simulator ?

Ask Coherence.SimulatorUtility.IsSimulator.

using Coherence;
using UnityEngine;

public class SimualatorClass : MonoBehaviour
{
    public void Awake()
    {
        if (SimulatorUtility.IsSimulator)
        {
            // I'm a simulator!
        }
    }
}

There are two ways you can tell coherence if the game build should behave as a Simulator:

  • COHERENCE_SIMULATOR preprocessor define.

  • --coherence-simulation-server command-line argument.

Connect and ConnectionType

The Connect method on Coherence.Network accepts a ConnectionType parameter.

using Coherence;
using Coherence.Common;
using Coherence.Connection;
using Coherence.Toolkit;
using UnityEngine;

public class ConnectAsSimulator : MonoBehaviour
{
    void Start()
    {
        var endpoint = new EndpointData
        {
            region = EndpointData.LocalRegion,
            host = "127.0.0.1",
            port = 32001,
            schemaId = RuntimeSettings.instance.SchemaID,
        };

        var monoBridge = FindObjectOfType<CoherenceMonoBridge>();
        monoBridge.Connect(endpoint, ConnectionType.Simulator);
    }
}

COHERENCE_SIMULATOR

#if COHERENCE_SIMULATOR

// simulator-specific code

#endif

Whenever the project compiles with the COHERENCE_SIMULATOR preprocessor define, coherence understands that the game will act as a Simulator.

The custom build pipeline lets us define preprocessor defines like COHERENCE_SIMULATOR

Command-line argument

Launching the game with --coherence-simulation-server will let coherence know that the loaded instance must act as a Simulator.

You can supply additional parameters to a Simulator that define its area of responsibility, e.g. a sector/quadrant to simulate Entities in and take authority over Entities wandering into it.

You can also build a special Simulator for AI, physics, etc.

Server-side simulation

You can define who simulates the object in the CoherenceSync inspector.

Auto-reconnect

The sample UI provided includes auto-reconnect behaviour out of the box for Room- and World-based simulators. The root GameObject has AutoReconnect components attached to it.

AutoReconnect in the sample UI

Multi-Room Simulators have their own per-scene reconnect logic. The AutoReconnect components should not be enabled when working with Multi-Room Simulators.

If the Simulator is invoked with the --coherence-play-region parameter, AutoReconnect will try to reconnect to the Server located in that region.

Replication Server

The Replication Server replicates the state of the world to all connected Clients and Simulators.

To understand what is happening in the Game World, and to be able to contribute your simulated values, you need to connect to a Replication Server. The Replication Server acts as a central place where data is received from and distributed to interested Clients.

You can connect to a Replication Server in the cloud, but we recommend that you first start one locally on your computer. coherence is designed so you can easily develop everything locally first, before deploying to the cloud.

Replication Servers replicate data defined in schema files. The schema's inspector provides all the tools needed to start a Replication Server.

  1. Run the Replication Server by clicking the Run button or copy the run command to the clipboard via clicking the copy run-command icon located to the right of it.

  2. A terminal/command line will pop up, running your Server locally.

  3. The port the Replication Server will use. Rooms: 42001 Worlds: 32001

  4. The web port used for webGL connections. Rooms: 42001 Worlds: 32002

  5. The Replication Server send frequency. Default: 20 packets / s

  6. The Replication Server receive frequency. Default: 60 packets / s

You can also start the Replication Server from the coherence menu or by pressing Ctrl+Shift+Alt+N.

Replication Server send and receive frequencies

The Replication Server supports different packet frequencies for sending and receiving data.

The send frequency is the frequency that the Replication Server uses to send packets to a given Client. Each Client can be sent packets at different times, but the packet receive frequency for any Client will not exceed the Replication Server's send frequency.

The receive frequency is the maximum frequency at which the Replication Server expects to receive packets from any Client, before throttling. If a Client sends packets to the Replication Server at a higher than expected frequency, that Client will receive a command to slow down sending. If the Client doesn't respect the command to throttle packet sending then the Client is disconnected after a time. All extra packets received by the Replication Server, after a threshold based on the receive frequency, are dropped and not processed. This is to prevent malicious Clients from flooding the Replication Server. The Unity SDK handles throttling automatically.

It is possible for the Replication Server to temporarily request Clients to reduce their packet send rates if the processing load of the Replication Server is too high. This is automatic and send rates from the affected Clients are commanded to resume once the load is reduced.

Low and consistent send rates from the Replication Server allow for optimal bandwidth use and still support a smooth stream of updates to Clients. Try different rates during local replication tests to see what works well for your game.

Currently, there is no support in the project dashboard to set the rates for your project in the cloud, but you can send a request to [email protected] to configure your project's Replication Server packet rates. This feature will be added in the future.

Connecting to a Replication Server

When the Replication Server is running, you connect to it using the Connect method.

Connect to a local Replication Server

using Coherence;
using Coherence.Common;
using Coherence.Connection;
using Coherence.Toolkit;
using UnityEngine;

public class ConnectToLocal : MonoBehaviour
{
    void Start()
    {
        var monoBridge = FindObjectOfType<CoherenceMonoBridge>();
        
        var endpoint = new EndpointData
        {
            region = EndpointData.LocalRegion,
            host = "127.0.0.1",
            port = 32001,
            schemaId = RuntimeSettings.instance.SchemaID,
        };

        monoBridge.Connect(endpoint);
    }
}

After trying to connect you might be interested in knowing whether the connection succeeded. The Connect call will run asynchronously and take around 100 ms to finish, or longer if you connect to a remote Server.

Respond to connection events

using Coherence;
using Coherence.Connection;
using Coherence.Toolkit;
using UnityEngine;

public class ConnectToLocal : MonoBehaviour
{
    private CoherenceMonoBridge monoBridge;

    void Start()
    {
        monoBridge = FindObjectOfType<CoherenceMonoBridge>();
        monoBridge.onConnected.AddListener(HandleConnected);
        monoBridge.OnLiveQuerySynced += HandleLiveQuerySynced;
        monoBridge.onDisconnected.AddListener(HandleDisconnected);

        var endpoint = new EndpointData
        {
            region = EndpointData.LocalRegion,
            host = "127.0.0.1",
            port = 32001,
            schemaId = RuntimeSettings.instance.SchemaID,
        };

        monoBridge.Connect(endpoint);
    }

    private void HandleConnected(CoherenceMonoBridge _) { /* ... */ }

    private void HandleLiveQuerySynced(CoherenceMonoBridge _) { /* ... */ }

    private void HandleDisconnected(CoherenceMonoBridge _, ConnectionCloseReason reason) { /* ... */ }

    private void OnDestroy()
    {
        monoBridge.onConnected.RemoveListener(HandleConnected);
        monoBridge.onDisconnected.RemoveListener(HandleDisconnected);
    }

The OnLiveQuerySynced event is triggered when the initial game state has been synced to the client. More specifically, it is fired when all entities found by the client's first live query have finished replicating. This is the last step of the connection process and a is usually a good place to start the game simulation.

To connect to cloud-hosted Servers, see Rooms API and Worlds API documentation.

Check Run in Background in the Unity settings under Project Settings > Player so that the Clients continue to run even when they're not the active window.

To connect with multiple Clients locally, publish a build for your platform (File > Build and Run, details in Unity docs). Run the Replication Server and launch the build any number of times. You can also enter Play Mode in the Unity Editor.

For Mac Users: You can open new instances of an application from the Terminal:

open -n <path to .app>

Example – a global counter

This document explains how to set up an ever increasing counter that all Clients have access to. This could be used to make sure that everyone can generate unique identifiers, with no chance of ever getting a duplicate.

By being persistent, the counter will also keep its value even if all Clients log off, as long as the Replication Server is running.

The Counter

First, create a script called Counter.cs and add the following code to it:

using UnityEngine;
using Coherence;
using Coherence.Toolkit;

public class Counter : MonoBehaviour
{
    public int counter = 0;
    public NumberRequester requester;

    public void NextNumber(CoherenceSync requester)
    {
        requester.SendCommand<NumberRequester>(
            nameof(NumberRequester.GotNumber),
            MessageTarget.AuthorityOnly,
            counter);
        counter++;
    }
}

This script expects a command sent from a script called NumberRequester, which we will create below.

Next, add this script to a Prefab with CoherenceSync on it, and select the counterand the method NextNumber for syncing in the bindings window. To make the counter behave like we want, mark the Prefab as "Persistent" and give it a unique persistence ID, e.g. "THE_COUNTER". Also change the adoption behaviour to "Auto Adopt":

CoherenceSync inspector

Finally, make sure that a single instance of this Prefab is placed in the scene.

NumberRequester

Now, create a script called NumberRequester.cs. This will be an example MonoBehaviour that requests a unique number by sending the command GetNumber to the Counter Prefab. As a single argument to this command, the NumberRequester will send an entity reference to itself. This makes it possible for the Counter to send back a response command (GotNumber) with the number that was generated. In this simple example we just log the number to the console.

using UnityEngine;
using Coherence;
using Coherence.Toolkit;

public class NumberRequester : MonoBehaviour
{
    CoherenceSync sync;

    private void Awake()
    {
        sync = GetComponent<CoherenceSync>();
    }

    void Update()
    {
        if (sync.HasStateAuthority && Input.GetKeyDown(KeyCode.Return))
        {
            var counter = FindObjectOfType<Counter>();
            var counterSync = counter.GetComponent<CoherenceSync>();

            counterSync.SendCommand<Counter>(
                nameof(Counter.NextNumber),
                MessageTarget.AuthorityOnly,
                sync);
        }
    }

    public void GotNumber(int number)
    {
        Debug.Log($"Got number: {number}");
    }
}

To make this script work, add it to a Prefab that has the CoherenceSync script and mark the GotNumber for syncing in the bindings window.

Level of detail

This feature requires .

Motivation

coherence can support large game worlds with many objects. Since the amount of data that can be transmitted over the network is limited, it's very important to only send the most important things.

You already know a very efficient tool for enabling this – the . It ensures that a client is only sent data when an object in its vicinity has been updated.

Often though, there is a possibility for an even more nuanced and optimized approach. It is based on the fact that we might not need to send as much data for an entity that is far away, compared to a close one. A similar technique is often used in 3D-programming to show a simpler model when something is far away, and a more detailed when close-up.

This idea works really well for networking too. For example, when another player is close to you it's important to know exactly what animation it is playing, what it's carrying around, etc. When the same player is far off in the horizon, it might suffice to only know it's position and orientation, since nothing else will be discernible anyways.

To use this technique we must learn about something called .

Levels of Detail

Any Prefab with the CoherenceSync component can be optimized to use a various levels of details (LODs).

There must always exist a LOD 0, this is the default level and it always has all components enabled (it can have per-field overrides though, see below.)

There can be any number of subsequent LODs (e.g. LOD 1, LOD 2, etc.) and each one must have a distance threshold higher than the previous one. The coherence SDK will try to use the LOD with the highest number, but that is still within the distance threshold.

Example

An object has three LODs, like this:

  • LOD 0 (threshold 0)

  • LOD 1 (threshold 10)

  • LOD 2 (threshold 20)

If this object is 15 units away, it will use LOD 1.

Confusingly, the highest numbered LOD is usually called the lowest one, since it has the least detail.

On each LOD, there are two options for optimizing data being transferred:

  1. Components can be turned off, meaning you won't receive any updates from them.

  2. Its fields can be configured to use fewer bits, usually leading to less fine-grained information. The idea is that this won't be noticeable at the distance of the LOD.

Bits and range

coherence allows us to define the range of numeric fields and how many bits we want to allocate to them.

Here are some terms we will be using:

  • Bits. The number of bits (octets) used for the field. When used for vectors, the number defined the number of bits used for each component (x, y and z). A vector3 set to 24 bits will consume 3 * 24 = 72 bits.

  • Range. For integer values and fixed-point floats, we define a minimum and maximum possible value (e.g. Health can lie between 0 and 100).

More bits mean more precision. Increasing the range while leaving the bit count the same will lower the precision of the field.

The maximum number of bits used for any field/component is currently 32.

coherence allows us to define these values for specific components and fields. Furthermore, we can define levels of detail so that precision and therefore bandwidth consumption falls with the distance of the object to the point of observation.

Levels of detail are calculated from the distance between the entity and the center of the LiveQuery.

Defining levels of detail

On each LOD you can configure the individual fields of any component to use less data. You can only decrease the fidelity, so a field can't use more data on a lower (more far away) LOD. The Archetype editor interface will help you to follow these rules.

In order to define levels of detail, we have to click the Optimize button on a Prefab's CoherenceSync component with defined field bindings.

That opens the Optimization window. We can override the base component settings even without defining further levels of detail.

Clicking on Add new Level Of Detail will add a new LOD. We can now define the distance at which the LOD starts. This is the minimum distance between the entity and the center of the LiveQuery at which the new level of detail becomes active (i.e. the Replicator will start sending data as defined here at this distance).

You can also disable components at later LOD levels if they are not needed. In the example above, you can see that in LOD2 the entire Transform and Animator components are disabled beyond the distance of 20 units. At 100 units (a.k.a. meters), we usually do not see animation details, so we can save a lot of bandwidth and processing power by not replicating this data.

The Data Cost Overview shows us that this takes the original 913 bits down to just 372 bits at LOD level 2.

Field overrides per type

The primitive types that coherence supports can be configured in different ways:

Float, Vector2 & Vector3

These three types can all be configured in the same way, using different compression types:

None

No compression will be used, a full 32-bit float will be transmitted every time.

Truncated

Allows for specifying the number of bits for compression. Less bits means lower bandwidth usage but at the cost of precision loss. The minimum number of bits is 10. Using 22 bits will result in around half of the precision of the full float, while 16 will result in the quarter of the precision.

Fixed point

Allows for specifying the range of values used together with either number of bits or a desired precision.

  • Range affects the maximum and minimum value that the data type can take on. For example, a range of 100 to 200 means only values within that range can be sent - any value outside of this range will be clamped to the nearest correct value.

  • Precision defines the greatest deviation allowed for the data type. For example, a precision of 0.1 means that a float of value 10.0 can be transmitted as anything from 9.9 to 10.1 over the network. The minimum allowed precision is 0.1, while the maximum precision depends on the range. Changing precision automatically recalculates the number of bits required for given range.

  • Bits dictate how many bits to use when calculating the precision for a given range. When set manually, it will trigger recalculation of the precision for a given range. Mind that the number of bits can be rounded down if the calculated precision uses less, e.g. for a range of [0, 1] setting the number of bits to 6 will result in precision of 0.1 and a final bit count of 4, since 4 bits suffice to represent this range with a calculated precision.

When using these range settings for vectors, it affects each axis of the vector separately. Imagine shrinking its bounding box, rather than a sphere.

Integers

Integers can be configured to any span (that fits within a 32-bit integer) by setting its minimum and maximum value.

For example, the member variable age in a game about ancient trolls might use a minimum of 100 and a maximum of 2000. Based on the size of the range (1900 in this case) a bit-count will be calculated for you.

For integers, it usually make sense to not decrease the range on lower LODs since it will overflow (and wrap-around) any member on an entity that switches to a lower LOD. Instead, use this setting on LOD 0 to save data for the whole Archetype.

Quaternions & Colors

Quaternions and Colors can be configured using the number of bits per component. Quaternions require sending 3 components while Colors require 4 components.

Other types

All other types (strings, booleans, entity references) have no settings that can be overridden, so your only option for optimizing those are to turn them off completely at lower LODs.

Using LODs with connected entities

If a LODed game object is parented to another synced object, the child will base its LOD level on the World position of its parent. This means that the (local) position of the LODed child does not have any effect on its LOD, until it is unparented.

Also – to save bandwidth, detection of LOD changes on the client only happens when the entity sends a component update. This means that a child object might appear to be using a nonsensical LOD until it changes in some way, for example by modifying its position.

LODs in the schema

When we bake, information from the CoherenceArchetype component gets written into our schema. Below, you can see the setup presented earlier reflected in the resulting schema file.

If you want to know more about how LODs work inside the schema files, take a look at .

Caveats

The most unintuitive thing about archetypes and LOD-ing is that it doesn't affect the sending of data. This means that a "fat" object with tons of fields will still tax the network and the Replication Server if it is constantly updated, even if it uses a very optimized Archetype.

Also, it's important to realize that the exact LOD used on an entity varies for each other client, depending on the position of their query (or the closest one, if several are used.)

Commands

Overview

Commands are network messages sent from one Entity to another Entity in the networked World. Functionally equivalent to standard RPCs, commands bind to public methods accessible on the GameObject that CoherenceSync sits on.

Design phase

In the design phase, you can expose public methods the same way you select fields for synchronization: through the Configure window on your CoherenceSync component.

The selected public methods will be exposed as network commands in the .

The button on the right of the method lets you choose the routing mode. Commands with aSend to Authority Only mode can be sent only to the authority of the target Entity, while ones with the Send to All Instances can be broadcasted to all clients that have a copy of this Entity. The routing is enforced by the Replication Server as a security measure so that outdated or malicious clients don't break the game.

Sending a command on a networked object

To send a command, we call the SendCommand<> method on the target CoherenceSync object. It takes a number of arguments:

  • The type argument (within the <and>) must be the type of the receiving MonoBehaviour. This ensures that the correct method gets called if the receiving GameObject has components that implement methods that share the same name.

If there are multiple MonoBehaviours of the same type, the command is only sent to the first one in the hierarchy which matches the type.

  • The first argument is the name of the method on the MonoBehaviour that we want to call. It is good practice to use the C# expression when referring to the method name, since it prevents accidentally misspelling it, or forgetting to update the string if the method changes name.

  • The second argument is an enum that specifies the MessageTarget of the command. The possible values are:

    • MessageTarget.All – sends the command to each Client that has an instance of this Entity.

    • MessageTarget.AuthorityOnly – send the command only to the Client that has authority over the Entity.

    • MessageTarget.Other - sends the command to every Entity other than the one SendCommand is called on.

Mind that the target must be compatible with the routing mode set in the bindings, i.e. Send to authority will allow only for the MessageTarget.AuthorityOnly while Send to all instances allows for both values.

Also, it is possible that the message never sends as in the case of a command with MessageTarget.Other sent from the authority with routing of Authority Only.

  • The rest of the arguments (if any) vary depending on the command itself. We must supply as many parameters as are defined in the target method and the schema.

Here's an example of how to send a command:

Receiving a command

We don't have to do anything special to receive the command. The system will simply call the corresponding method on the target network entity.

If the target is a locally simulated entity, SendCommand will recognize that and not send a network command, but instead simply call the method directly.

Sending a command to multiple Entities

Sometimes you want to inform a bunch of different Entities about a change. For example, an explosion impact on a few players. To do so, we have to go through the instances we want to notify, and send commands to each of them.

In this example, a will get sent to each network Entity under the authority of this Client. To make it only affect Entities within certain criteria, you need to filter to which CoherenceSync you send the command to, on your own.

Sending null values in command arguments

Some of the primitive types supported are nullable values, this includes:

  • Byte[]

  • string

  • Entity references: CoherenceSync, Transform, and GameObject

In order to send one of these values as a null (or default) we need to use special syntax to ensure the right method signature is resolved.

Null-value arguments need to be passed as a ValueTuple<Type, object> so that their type can be correctly resolved. In the example above sending a null value for a string is written as: (typeof(string), (string)null)

and the null Byte[] argument is written as: (typeof(Byte[]), (Byte[])null)

Mis-ordered arguments, type mis-match, or unresolvable types will result in errors logged and the command not being sent.

When a null argument is deserialized on a client receiving the command, it is possible that the null value is converted into a non-null default value. For example, sending a null string in a command could result in clients receiving an empty string. As another example, a null Byte[] argument could be deserialized into an empty Byte[0] array. So, receiving code should be ready for either a null value or an equivalent default.

Supported types in commands

When a Prefab is not using a baked script there are some restrictions for what types can be sent in a single command:

  • 4 entity references

  • maximum of 511 bytes total of data in other arguments

  • a single Byte[] argument can be no longer than 509 bytes because of overhead

Some network primitive types send extra data when serialized (like Byte arrays and string types) so gauging how many bits a command will use is difficult. If a single command is bigger than the supported packet size, it won't work even with baked code. For a good and performant game experience, always try to keep the total command argument sizes low.

Install coherence

First, make sure you have Unity 2020.3.36f1 or later installed. The recommended version is Unity 2021.3 LTS. To get Unity, see .

Installation - video tutorial

Unity Package Manager

coherence only supports Unity at the moment. Unreal Engine support is planned. For more specific details and announcements, please check the page. For custom engine integration,.

1. Add a scoped registry

First, open Edit > Project Settings.

Under Package Manager, add a new Scoped Registry with the following fields:

  • Name: coherence

  • URL: https://registry.npmjs.org

  • Scope(s): io.coherence.sdk

  • Enable Preview / Pre-release Packages: checked

  • Show Dependencies: checked

Click Save/Apply.

2. Find and install the coherence package

Now open the Window > Package Manager.

Click Packages and My Registries.

Under coherence, click Install.

Alternative method: edit manifest.json manually

If you want to install coherence manually, go to the folder of your project and open the file /Packages/manifest.json.

Copy paste the lines surrounded by comments that look like this:/* comment */.

The Unity docs have information about .

When you install the coherence Unity Package, all the services for the SDK are installed in the background as well.

You will then see this package in the Package Manager under My Registries.

The coherence SDK has some dependencies on other Unity packages you can see in the image above. If you're already using these in your project, you might have to adjust their version number (Unity will tell you about this).

When you successfully install the coherence SDK you'll get this quick start window pop-up and you'll be good to go.

archetype FemZombie
  lod 0
    WorldPosition 
      value [compression "FixedPoint", range-min "-2400", range-max "2400", bits "19", precision "0.01"]
    WorldOrientation 
      value [bits "24"]
    FemZombie_UnityEngine_Animator 
      InputHorizontal [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      InputVertical [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      InputMagnitude [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      TurnOnSpotDirection [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      ActionState [bits "15", range-min "0", range-max "10"]
  lod 1 [distance "50"]
    WorldPosition 
      value [compression "FixedPoint", range-min "-2400", range-max "2400", bits "16", precision "0.1"]
    WorldOrientation 
      value [bits "20"]
    FemZombie_UnityEngine_Animator 
      InputHorizontal [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      InputVertical [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      InputMagnitude [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      TurnOnSpotDirection [compression "FixedPoint", range-min "-1", range-max "1", bits "15", precision "0.0001"]
      ActionState [bits "15", range-min "0", range-max "10"]
      IdleRandom [bits "15", range-min "-9999", range-max "-9999"]
      RandomAttack [bits "15", range-min "-9999", range-max "-9999"]
      AttackID [bits "15", range-min "-9999", range-max "-9999"]
      DefenseID [bits "15", range-min "-9999", range-max "-9999"]
      RecoilID [bits "15", range-min "-9999", range-max "-9999"]
      ReactionID [bits "15", range-min "-9999", range-max "-9999"]
      HitDirection [bits "15", range-min "-9999", range-max "-9999"]
  lod 2 [distance "100"]
    WorldPosition 
      value [compression "FixedPoint", range-min "-2400", range-max "2400", bits "16", precision "0.1"]
    WorldOrientation 
      value [bits "16"]
baking
LiveQuery
archetypes
Archetypes in schemas
var target = enemy.GetComponent<CoherenceSync>();
                    
target.SendCommand<Character>(nameof(Character.SendDamage), 
                MessageTarget.All,
                damageValue, staminaBlockCost,
                staminaRecoveryDelay, ignoreDefense, 
                hitReaction, hitPosition, 
                ConnectionId, isPlayer);
using Coherence;
using Coherence.Toolkit;
using UnityEngine;

public class MyCommands : MonoBehaviour
{
    public void MulticastCommand()
    {
        var self = GetComponent<CoherenceSync>();
        var listeners = FindObjectsOfType<CoherenceSync>(); 
        
        foreach (var listener in listeners)
        {
            if (!listener || listener == self)
            {
                continue;
            }
            
            listener.SendCommand<MyCommands>(nameof(ReceiveCommand), MessageTarget.AuthorityOnly);
        }
    }

    // bind to this method via the Bindings window
    public void ReceiveCommand()
    {
        Debug.Log("Received!");
    }
}
using Coherence;
using Coherence.Toolkit;
using UnityEngine;
using System;

public class MyCommand : MonoBehaviour
{
    private CoherenceSync sync;

    public void MyNullableCommand(string someString, Byte[] someArray)
    {
        if (!string.IsNullOrEmpty(someString)) //Could be null or string.Empty
        {
            // Do something with the string
        }

        if (someArray != null && someArray.Length > 0) //Could be null or Byte[0]
        {
            // Do something with the array
        }
    }

    public void SendNullableCommand()
    {
        sync.SendCommand<MyCommand>(nameof(MyNullableCommand),
            MessageTarget.All,
            (typeof(string), (string)null),
            (typeof(Byte[]), (Byte[])null));
    }
}
baking process
nameof
command
CoherenceSync
Select window: accessible from CoherenceSync

Custom bindings (advanced)

This is an advanced topic that aims to bring access to coherence's internals to the end user.

CustomBindingProviders are editor classes that tell coherence how a specific component should expose its bindings and how it generates baked scripts.

For example, we could create a custom binding provider for our Health component:

using UnityEngine;

public class Health : MonoBehaviour
{
    public int health;
}
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Health))]
public class HealthBindingProvider : CustomBindingProvider
{
    // check the CustomBindingProvider base class to
    // explore the possibilities!
}

Place CustomBindingProviders inside an editor folder.

We can add additional (custom) bindings:

using Coherence.Toolkit;
using System.Collections.Generic;
using UnityEngine;

[CustomBindingProvider(typeof(Health))]
public class HealthBindingProvider : CustomBindingProvider
{
    public override void FetchDescriptors(List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(descriptors);
        descriptors.Add(new CustomBinding.Descriptor("myCustomBinding", typeof(int))
        {
            bakedSyncScriptGetter = "myCustomBindingGet",
            bakedSyncScriptSetter = "myCustomBindingSet",
            coherenceComponentName = "MyCustomBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
        descriptors.Add(new CustomBinding.Descriptor("debugBinding", typeof(bool))
        {
            bakedSyncScriptGetter = "debugBindingGet",
            bakedSyncScriptSetter = "debugBindingSet",
            coherenceComponentName = "DebugBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
    }
}

In order for these new custom bindings to work on reflected mode, we'll need to implement a runtime serializer that understands how to deal with them.

Check the CustomBinding.Descriptor API for further properties, like interpolation or marking the binding as required.

CustomBindingRuntimeSerializer

For custom bindings to work on reflected mode, we need to implement how their values are serialized and deserialized at runtime:

using UnityEngine;
using Coherence.Toolkit;
using Coherence.Entity;
using Coherence.SimulationFrame;

// this class defines how reflected mode serializes and deserializes Health's custom bindings

public class HealthRuntimeSerializer : CustomBindingRuntimeSerializer
{
    public override void Serialize(CustomBinding customBinding, ICoherenceComponentData componentData)
    {
        var health = customBinding.component as Health;

        if (customBinding.descriptor.name == "debugBinding")
        {
            Debug.Log("[HEALTH] Serializing debugBinding");
        }
        else if (customBinding.descriptor.name == "myCustomBinding")
        {
            // in this example, lets send myCustomBinding as "health" over the network
            var fi = componentData.GetType().GetField("value");
            fi.SetValue(componentData, health.health);
        }
    }

    public override void Deserialize(CustomBinding customBinding, ICoherenceComponentData componentData, long clientFrame)
    {
        var health = customBinding.component as Health;

        if (customBinding.descriptor.name == "debugBinding")
        {
            Debug.Log("[HEALTH] Deserializing debugBinding");
        }
        else if (customBinding.descriptor.name == "myCustomBinding")
        {
            var fi = componentData.GetType().GetField("value");
            var v = (float)fi.GetValue(componentData);
            // we take the value of myCustomBinding, which we know we serialized with the value of health
            // in this example, we just do some custom logic: updating the isAlive bool with it
            health.isAlive = v > 0;
        }
    }

    public override void Interpolate(CustomBinding customBinding, double time)
    {
    }

    public override void SetToLastSample(CustomBinding customBinding)
    {
    }
}

CustomBindingRuntimeSerializers should be placed in a non-editor folder.

Once we have our runtime serializer, we need to make our binding provider use it:

using Coherence.Toolkit;
using System.Collections.Generic;
using UnityEngine;

[CustomBindingProvider(typeof(Health))]
public class HealthBindingProvider : CustomBindingProvider
{
    public override CustomBindingRuntimeSerializer CustomBindingRuntimeSerializer => new HealthRuntimeSerializer();

    public override void FetchDescriptors(List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(descriptors);
        descriptors.Add(new CustomBinding.Descriptor("myCustomBinding", typeof(int))
        {
            bakedSyncScriptGetter = "myCustomBindingGet",
            bakedSyncScriptSetter = "myCustomBindingSet",
            coherenceComponentName = "MyCustomBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
        descriptors.Add(new CustomBinding.Descriptor("debugBinding", typeof(bool))
        {
            bakedSyncScriptGetter = "debugBindingGet",
            bakedSyncScriptSetter = "debugBindingSet",
            coherenceComponentName = "DebugBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
    }
}

Extending and inheriting CustomBindingProviders

You can extend an already existing CustomBindingProvider. For example, coherence ships with a CustomBindingProvider for Transforms:

using System.Collections.Generic;
using Coherence.Toolkit;
using UnityEngine;

[CustomBindingProvider(typeof(Transform))]
public class CustomTransformBindingProvider : CustomBindingProvider
{
    public override void FetchDescriptors(List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(descriptors);

        // your changes
    }
}

This way, you can define additional rules on how you want to treat your Transforms, for example.

Any amount of CustomBindingProviders can be registered over the same component, but only one being used. The one being used, is resolved by a priority integer that you can specify in the CustomBindingProvider attribute. The class with the higher priority defined in the attribute will be the only provider taken into account:

[CustomBindingProvider(typeof(Transform), 1)]

The default priority is set at 0, and coherence's internal CustomBindingProviders have a priority of -1.

To understand how these APIs are used, check out TransformBindingProvider and AnimatorBindingProvider, both shipped with the coherence SDK (<package>/Coherence.Editor/Toolkit/CustomBindingProviders).

{
  "dependencies": {
    "com.unity.collab-proxy": "1.3.9",
    "com.unity.ide.rider": "2.0.7",
    "com.unity.ide.visualstudio": "2.0.7",
    "com.unity.ide.vscode": "1.2.3",
    "com.unity.test-framework": "1.1.24",
    "com.unity.textmeshpro": "3.0.1",
    "com.unity.timeline": "1.4.6",
    "com.unity.ugui": "1.0.0",

    /*** ADD THIS START ***/
    "io.coherence.sdk": "0.9.0",
    /*** ADD THIS END ***/
        
    "com.unity.modules.ai": "1.0.0",
    "com.unity.modules.androidjni": "1.0.0",
    "com.unity.modules.animation": "1.0.0",
    "com.unity.modules.assetbundle": "1.0.0",
    "com.unity.modules.audio": "1.0.0",
    "com.unity.modules.cloth": "1.0.0",
    "com.unity.modules.director": "1.0.0",
    "com.unity.modules.imageconversion": "1.0.0",
    "com.unity.modules.imgui": "1.0.0",
    "com.unity.modules.jsonserialize": "1.0.0",
    "com.unity.modules.particlesystem": "1.0.0",
    "com.unity.modules.physics": "1.0.0",
    "com.unity.modules.physics2d": "1.0.0",
    "com.unity.modules.screencapture": "1.0.0",
    "com.unity.modules.terrain": "1.0.0",
    "com.unity.modules.terrainphysics": "1.0.0",
    "com.unity.modules.tilemap": "1.0.0",
    "com.unity.modules.ui": "1.0.0",
    "com.unity.modules.uielements": "1.0.0",
    "com.unity.modules.umbra": "1.0.0",
    "com.unity.modules.unityanalytics": "1.0.0",
    "com.unity.modules.unitywebrequest": "1.0.0",
    "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    "com.unity.modules.unitywebrequestaudio": "1.0.0",
    "com.unity.modules.unitywebrequesttexture": "1.0.0",
    "com.unity.modules.unitywebrequestwww": "1.0.0",
    "com.unity.modules.vehicles": "1.0.0",
    "com.unity.modules.video": "1.0.0",
    "com.unity.modules.vr": "1.0.0",
    "com.unity.modules.wind": "1.0.0",
    "com.unity.modules.xr": "1.0.0"
  }, /* add this comma if not already present */
  
  /*** ADD THIS SECTION START ***/
  "scopedRegistries": [
    {
      "name": "coherence",
      "url": "https://registry.npmjs.org",
      "scopes": [
        "io.coherence.sdk"
      ]
    }
  ]
  /*** ADD THIS SECTION END ***/
  
}
https://unity.com/download
Unreal Engine Support
please contact our developer relations team
scoped package registries

Release Notes

Read about new features, important changes and fixes for version 0.9

Check out the highlighted features for 0.9 release in our blogpost.

v0.9.2

This is a maintenance patch that includes the following items:

Fixes

  • Spawning of session-based unique Entities that had a UUID in the Prefab and in an instance saved in the Scene.

  • OnInputSimulatorConnected is now raised for Simulator or Client-As-Host only if they have both input and state authority.

  • Selecting Prefab variants no longer updates the internal Archetype name (hence, no unintended schema changes).

  • Configure window: Animator parameters are no longer added if they already exist.

  • coherence now warns you when there are internal inconsistencies within the CoherenceSync Archetype data.

  • coherence Hub: disabled interactions with coherence Cloud when logged out.

Changes

  • The Welcome window is presented when the package is upgraded.

v0.9.1

This is a small patch that includes the following items:

New features and functionality

  • Exposed OnConnectionError event in CoherenceMonoBridge.

Fixes

  • Automatic passing of room secret when using PlayResolver.GetRoomEndpointData.

v0.9

Check out the highlighted features in our blogpost: https://coherence.io/blog/news/release-0.9

New features and functionality

  • coherence Hub window: a singular tool combining all the functionality you need to work with coherence.

  • Per-binding sampling rate configurable from Optimize window.

  • Additional delay parameters added to InterpolationSettings.

  • Replication Server can now send the Client commands to change the connection's send frequency of packets.

  • New floating point compression methods.

  • Quaternion and Color compression control.

  • New CoherenceMonoBridge.OnLiveQuerySynced event for notifying when the initial query finishes syncing.

  • New CoherenceMonoBridge.ClientConnections.OnSynced event for notifying when connections finish syncing.

  • Threaded keep-alive packets to keep connection alive even when the main thread is stalled.

  • Allow manual sending of changes on a CoherenceSync object.

  • SimulatorBuildPipeline public API: a static API to build Simulators, with headless support out of the box.

  • Unity 2021: Headless Unity Players are now built with the Dedicated Server modules.

  • Added option to copy the run local simulator build terminal command to the clipboard.

  • Queries can be added to any synced GameObject.

  • Queries are applied to the input authority. This enables server-authoritative games to restrict Client visibility by placing a query on the player Prefab.

  • Client can connect as a Simulator.

  • Client can connect as a host for server-simulated inputs (Experimental).

  • Local Replication Server can now set both send and receive frequencies.

  • Added options in settings to auto-bake and upload schemas.

  • Added support for Addressables.

  • Log to file in Library/coherence.

  • Icons showing input authority in editor hierarchy.

Changes

  • Entity changes are now based on the simulation frames from their origin.

  • ServerUpdateFrequency is now SendFrequency.

  • By default, new fields marked for syncing do not use compression.

  • Commands do not use any compression for simple types.

  • CoherenceMonoBridge.ClientConnections.OnDestroyed is now called for the local connection.

  • Baking timeout increased to 2 minutes.

  • Baking is now cancellable.

  • CoherenceSync.HasPhysics is now obsolete.

  • CoherenceSync's Rigidbody position and rotation is now updated only in the FixedUpdate. In all other cases the Transform.position/rotation will be updated directly.

  • Predicted toggle is available for Prefabs with no CoherenceInput, but prompts a warning in this case.

  • Exposed IsInterpolationNone on InterpolationSettings so runtime can check if a binding will interpolate.

  • The auto-fill functionality from the Endpoint data to run a local Simulator went from being automatic to working manually through a button in the UI.

  • CoherenceInput fields now only transmit changed field state instead of entire component to save bandwidth.

  • Authority requests and transfers use ClientID instead of ConnectionID.

  • Requesting authority over orphaned entities now works only via CoherenceSync.Adopt() call.

  • Improved layout of settings.

  • Button in Prefab Mapper now automatically places Prefabs in the correct list (Addressable/non-addressable)

Fixes

  • Baking timing out with high number of synced components.

  • CoherenceLiveQuery and CoherenceTagQuery handling on disable and after disconnect.

  • Sending commands from the onConnected callback.

  • OnValueSynced callbacks working for all value types.

  • Byte array sync bugfix.

  • Sampling and send rates now account for time drift across update calls.

  • Error when trying to dispose non-activated InterpolationSettings.

  • Unsigned long handling in reflection mode.

  • Creating connection objects with Global Query turned off.

  • "WebSocket: already connected" error when changing scenes.

  • Bug where reconnecting would cause incoming commands to duplicate.

  • OnEntityUpdated exception caused by samples with frames out-of-order.

  • WebGL connection timeout issues.

  • Double query creation in some circumstances.

  • AutoSimulatorConnection fixed and made the default way to reconnect Simulators. Two obsolete scripts removed.

  • OnValueSynced event will raise after smaller deltas.

  • If baked script is missing, fallback to generic pool will map bindings.

  • OnValueSynced on components shared by multiple Prefabs.

  • Deleting and recreating unique Entities in the same frame no longer results in broken state.

  • Pending Entity changes that are not sent in a given packet have their priority increased so that they don't starve out.

  • Prevent sending component remove updates until the Client knows the Replication Server acknowledged the original create.

  • Checking for a received packet before deciding we haven't received one and disconnecting. Fixes issue when the main thread stalls before we read packets, causing a timeout disconnect.

  • Authority change handling for unique Entities.

  • CoherenceSync spawn issues in bad connection conditions.

  • Include the instantiating bridge as the fist resolver when resolving bridges in a CoherenceSync object.

  • Building Simulators: fixed issue where you had to click the Build button twice, in order for the build to trigger.

  • CustomBinding now respects setToLastSamples parameter during Reset.

  • InputAuthority is displayed correctly in the CoherenceSync inspector.

  • SmoothTime is now 0 when using InterpolationSettings.None.

  • Default Interpolator now implements latest sample instead of nearest neighbor to lower latency.

  • Interpolation Type set to None now doesn't throw exceptions.

  • Prefabs set to load using Resources will no longer be overridden to use PrefabMapper.

  • Prefab instance name no longer breaks LOD's.

Deprecations

  • 'AuthorityMode' has been deprecated in favour of 'AuthorityType'.

  • 'IClient.OnAuthorityTransferRequest' has been deprecated in favour of 'IClient.OnAuthorityRequested'.

  • 'IClient.OnAuthorityTransferRejected' has been deprecated in favour of 'IClient.OnAuthorityRequestRejected'.

  • 'IClient.OnAuthorityChanged' has been deprecated in favour of 'IClient.OnAuthorityChange'.

  • 'IClient.EntityIsOwned' has been deprecated in favour of 'IClient.HasAuthorityOverEntity'.

  • 'IClient.SendAuthorityTransferRequest' has been deprecated in favour of 'IClient.SendAuthorityRequest'.

  • 'IClient.AuthorizeAuthorityTransfer' has been deprecated in favour of 'IClient.SendAuthorityTransfer'.

  • 'SpawnInfo.isLocal' has been deprecated in favour of 'SpawnInfo.authorityType'.

  • 'CoherenceInput.SetInputOwner' has been deprecated in favour of 'CoherenceSync.TransferAuthority'.

  • 'CoherenceSync.isSimulated' has been deprecated in favour of 'CoherenceSync.HasStateAuthority'.

  • 'CoherenceSync.OnAuthorityRequestedByConnection' has been deprecated in favour of 'CoherenceSync.OnAuthorityRequested'.

  • 'CoherenceSync.OnAuthorityGained' has been deprecated in favour of 'CoherenceSync.OnStateAuthorityGained'.

  • 'CoherenceSync.OnAuthorityLost' has been deprecated in favour of 'CoherenceSync.OnStateAuthorityLost'.

  • 'CoherenceSync.OnAuthorityTransferRejected' has been deprecated in favour of 'CoherenceSync.OnAuthorityRequestRejected'.

  • 'CoherenceSync.OnInputOwnerAssigned' has been deprecated in favour of 'CoherenceSync.OnInputAuthorityGained' and 'CoherenceSync.OnInputAuthorityGained'.

  • 'CoherenceSync.RequestAuthority' has been deprecated in favour of 'CoherenceSync.RequestAuthority(AuthorityType)'.

  • 'MonoBridgeStore.GetBridge' has been deprecated in favour of 'MonoBridgeStore.TryGetBridge'.

Removals

  • Manual and auto-latency removed. Latency is now fixed to the binding's sample rate.

  • "World size" setting.

  • Manual disabling of updates to rotation and position of the CoherenceSync object.

  • Removed CoherenceMonoBridge.OnConnectionCreated that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.OnConnectionDestroyed that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.ClientConnectionCount that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.GetMyConnection that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.GetClientConnection that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.GetAllClientConnections that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.GetOtherConnections that was deprecated in v0.8.

  • Removed CoherenceMonoBridge.SendClientMessage that was deprecated in v0.8.

  • Removed dependency to the experimental package Platforms. As a result, your existing BuildConfiguration assets will have its defining script missing. To recreate the Simulator build configuration with the new pipeline, you can do it from the Simulators Module in coherence Hub.

  • Removed dependencies to the following packages: Collections, Properties, Test Framework, Jobs, Burst.

  • No longer show byte arrays, bools or strings as interpolable types in the Configure window.

Known Issues

If you have issues building clients or simulators, refer to troubleshooting.

Older Versions of Unity Editor not Building WebGL on Latest Mac OS and Ubuntu 22

Unity does not build WebGL because Python 2 is missing on latest Mac OS and Ubuntu 22. This is fixed for Unity Editor version 2020.3.40f1 and onwards.

Persistence

There's a known bug preventing the removal of session-based objects from the scene after the Client disconnects. We're working on it.

Level of Detail

  • LOD levels are not properly updated when queries move or resize.

CoherenceMonoBridge: ClientCore

  • When upgrading older projects to 0.9, you may get the following error after trying to connect to a Room/World: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary. If this happens, you should manually select the Prefabs with the CoherenceSync component in the Inspector to trigger an update via the GUI (graphical user interface). If you have any problems after trying this, please let us know on our Discord so we can help you out.

Prefab setup

In this section, we will learn how to prepare a Prefab for network replication.

1. Add CoherenceSync to your GameObject

For a Unity GameObject to be networked through coherence, it needs to have a CoherenceSync component attached. Currently, only Prefabs are supported. If your GameObject is not a Prefab, CoherenceSync can assist you.

First, create a new GameObject. In this example, we're going to create a Cube.

Next, let's add the CoherenceSync component to this Cube.

The CoherenceSync inspector now tells us that we need to make a Prefab out of this GameObject for it to work. We get to choose where to create it.

In this example, I'll be creating it on Assets / Resources by clicking Convert to Prefab in Resources.

1.1 (Optional) Using Prefab Variants

One way to configure your Prefab, instead of just adding CoherenceSync into it, is to fork a Prefab variant and add the component there.

In our Cube example, instead of adding CoherenceSync to Cube, you can create a Cube (Networked) and add CoherenceSync to it:

Learn how to create and use Prefab variants in the Unity Manual.

Creating a variant of Cube
Adding CoherenceSync to the variant

This way, you can retain the original Prefab untouched.

Another way to use Prefab variants to our advantage is to have a base Prefab using CoherenceSync, and create Prefab variants off that one with customizations. For example, Enemy (base Prefab) and Enemy 1, Enemy 2, Enemy 3... (variant Prefabs, using different models, animations, materials, etc.). In this setup, all of the enemies will share the networking settings stored in CoherenceSync, so you don't have to manually update every one of them.

2. Configure CoherenceSync

The CoherenceSync component will help you prepare an object for network synchronization during design time. It also exposes an API that allows us to manipulate the object during runtime.

CoherenceSync will query all public variables and methods on any of the attached components, for example Unity components such as Transform, Animator , etc. This will include any custom scripts such as PlayerInput and even scripts that came with the Asset Store packages that you may have downloaded.

You can find out more about CoherenceSync here.

3. (Optional) Prefabs outside of the Resources folders

In order for coherence to know which Prefab to instantiate through the network, they must be located in Resources folders or added to the Prefab Mapper. The Prefab Mapper is simply a ScriptableObject that holds Prefabs we want to sync.

When a CoherenceSync Prefab outside the Resources folder is not present in the Prefab Mapper, the Add to Prefab Mapper button appears.

The Prefab Mapper asset is located at Assets / coherence.

3.1. Addressables

If you are using the Addressables Package (1.15.0 or newer), the Prefab Mapper also supports that. The Prefab Mapper has a list for direct references and a list for addressables referenced using AddressableAssets.AssetReference. The directly referenced assets will be loaded at game start, while the addressables will be asynchronously loaded when needed.

Remember, in order for coherence to load assets as addressables, you need to follow Unity's default workflow for using Addressables which entails building the Addressables Group Bundles whenever the Prefab changes.

3.2. Prefab Mapper utilities

When adding a new Prefab to the mapper, it will automatically make sure to add it to the correct list (addressable or not), but if the Prefab already exists in the mapper and you have changed how it is loaded, simply press the Validate Mapping Lists button.

If you have a number of valid Prefabs that have not yet been added to the Prefab Mapper you can press the Add all CoherenceSync prefabs button.

If you have empty entries in the Prefab Mapper you can remove them by pressing Remove empty entries. This is optional.

4. Select variables to replicate

Select which variables you would like to sync across the network. Initially, this will probably be the Transform settings: position, rotation, scale.

Under Configure, click Variables.

In the Configuration dialog, select position, rotation and scale.

You can configure variables, methods and components on child objects in the CoherenceSync hierarchy. To do that, simply select the desired object in the Hierarchy window, and the Configuration window will show information for that specific object — similarly to how the inspector works.

Close the Configuration dialog.

5. Add an input script

This simple input script will use WASD or the Arrow keys to move the Prefab around the scene.

Click on Assets > Create > C# Script.

Name it Move.cs. Copy-paste the following content into the file.

Move.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour
{
    public float speed = 1f;

    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
    
        var spf = speed * Time.deltaTime;

        transform.position += transform.forward * (v * spf);
        transform.position += transform.right * (h * spf);
    }
}

Wait for Unity to compile the file, then add it onto the Prefab.

6. Disable input on replicated object

We have added a Move script to the Prefab. This means that if we just run the scene, we will be able to use the keyboard to move the object around.

But what happens on another Client where this object is not authoritative, but replicated? We will want the position to be replicated over the network, but without the keyboard input interfering with it.

Under Configure, click Components.

Here you will see a list of Component Actions that you can apply to non-authoritative GameObjects that have been spawned by the network.

Selecting Disable for your Move script will make sure the Component is disabled for network instances of your Prefab.

7. Implementing your own Component Actions

By extending the ComponentAction abstract class, you can implement your own Component Actions.

ComponentAction.cs
using UnityEngine;

[System.Serializable]
public abstract class ComponentAction
{
    [SerializeField] internal Component component;
    public Component Component => component;

    public virtual void OnAuthority() { }
    public virtual void OnRemote() { }
}

Your custom Component Action must implement the following methods:

  • OnAuthority This method will be called when the object is spawned and you have authority over it.

  • OnRemote This method will be called when a remote object is spawned and you do not have authority over it.

It will also require the ComponentAction class attribute, specifying the type of Component that you want the Action to work with, and the display name.

For example, here is the implementation of the Component Action that we use to disable Components on remote objects:

ComponentAction.cs
using UnityEngine;

[ComponentAction(typeof(Behaviour), "Disable")]
public class DisableBehaviourComponentAction : ComponentAction
{
    public override void OnAuthority()
    {
        var b = component as Behaviour;
        b.enabled = true;
    }

    public override void OnRemote()
    {
        var b = component as Behaviour;
        b.enabled = false;
    }
}

8. Additional CoherenceSync settings

From the CoherenceSync component you can configure settings for Lifetime (Session-based or Persistent, Authority transfer (Request or Steal), Simulation model (Client Side, Server Side or Server Side with Client Input) and Adoption settings for when local persistent entities are orphaned.

There are also some Events that are triggered at different times.

  • On Before Networked Instantiation (before the GameObject is instantiated)

  • On Networked Instantiation (when the GameObject is instantiated)

  • On Networked Destruction (when the GameObject is destroyed)

  • On Authority Gained (when authority over the GameObject is transferred to the local client)

  • On Authority Lost (when authority over the GameObject is transferred to another client)

  • On After Authority Transfer Rejected (when GameObject's Authority transfer was requested and denied).

  • On Input Simulator Connected (when client with simulator is ready for Server-side with Client Input)

  • On Input Owner Assigned (when InputOwner was changed is ready)

Using the Coherence Hub

If you prefer, you can let the Coherence Hub guide you through your Prefab setup process. Simply select a Prefab, open the Synced Object tab in Coherence Hub and follow the instructions.

coherence -> CoherenceHub

Extras

You can find more information in the SDK Fundamentals.

Helper scripts and samples can be found here.

9. Constraints

There are some constraints when setting up a Prefab with CoherenceSync, hereafter referred to as Sync Prefab.

  1. A Sync Prefab has one, and only one CoherenceSync component in its hierarchy

  2. The CoherenceSync component must be at the Sync Prefab root

  3. A Sync Prefab cannot contain instances of other Sync Prefabs

  4. A hierarchy in a scene can contain multiple Sync Prefabs. However, such a hierarchy cannot be saved as a Sync Prefab as that would break rule 1-3.

Breaking rules 1-3 will lead to the build breaking in runtime.

9.1. Examples of valid setup

The ‘Car’ gameobject has the only CoherenceSync component in the hierarchy
The ‘Car’ Prefab has multiple child Prefabs, but none of them are Sync Prefabs
The ‘Wheel’ Sync Prefabs are placed under the ‘Car’ in the scene, but are not part of the ‘Car’ Sync Prefab itself

9.2. Examples of disallowed setup

The ‘Car’ Sync Prefab has multiple wheels, which are Sync Prefabs themselves. This is not allowed

Server-authoritative setup

What is CoherenceInput?

CoherenceInput is a component that enables a Simulator to take control of the simulation of another Client's objects based on the Client's inputs.

When to use CoherenceInput?

  • In situations where you want a centralized simulation of all inputs. Many game genres use client inputs and centralized simulation to guarantee the fairness of actions or the stability of physics simulations.

  • In situations where Clients have low processing power. If the Clients don't have sufficient processing power to simulate the World it makes sense to send inputs and just display the replicated results on the Clients.

  • In situations where determinism is important. RTS and fighting games will use CoherenceInput and rollback to process input events in a shared (not centralized) and deterministic way so that all Clients simulate the same conditions and produce the same results.

coherence currently only supports using CoherenceInput in a centralized way where a single Simulator is setup to process all inputs and replicate the results to all Clients.

Setup with and CoherenceInput

Setting up an object for server-side simulation using CoherenceInput and CoherenceSync is done in three steps:

1. Preparing the CoherenceSync component on the object Prefab

The simulation type of the CoherenceSync component is set to Server Side With Client Input

Setting the simulation type to this mode instructs the Client to automatically transfer State Authority for this object to the that is in charge of simulating inputs on all objects.

2. Declaring Inputs for the simulated object

Each simulated CoherenceSync component is able to define its own, unique set of inputs for simulating that object. An input can be one of:

  • Button. A button input is tracked with just a binary on/off state.

  • Button Range. A button range input is tracked with a float value from 0 to 1.

  • Axis. An axis input is tracked as two floats from -1 to 1 in both the X and Y axis.

  • String. A string value representing custom input state. (max length of 63 characters)

To declare the inputs used by the CoherenceSync component, the CoherenceInput component is added to the object. The input is named and the fields are defined.

In this example, the input block is named Player Movement and the inputs are WASD and mouse for the XY mouse position.

3. Bake the CoherenceSync object

In order for the inputs to be simulated on CoherenceSync objects, they must be optimized through .

If the CoherenceInput fields or name is changed, then the CoherenceSync object must be re-baked to reflect the new fields/values.

Using CoherenceInput

When a Simulator is running it will find objects that are set up using CoherenceInput components and will automatically assume authority and perform simulations. Both the Client and Simulator need to access the inputs of the CoherenceInput of the replicated object. The Client uses the Set* methods and the Simulator uses the Get* methods to access the state of the inputs of the object. In all of these methods, the name parameter is the same as the Name field in the CoherenceInput component.

Client-Side Set* Methods

  • public void SetButtonState(string name, bool value)

  • public void SetButtonRangeState(string name, float value)

  • public void SetAxisState(string name, Vector2 value)

  • public void SetStringState(string name, string value)

Simulator-Side Get* Methods

  • public bool GetButtonState(string name)

  • public float GetButtonRangeState(string name)

  • public Vector2 GetAxisState(string name)

  • public string GetStringState(string name)

For example, the mouse click position can be passed from the Client to the Simulator via the "mouse" field in the setup example.

The Simulator can access the state of the input to perform simulations on the object which are then reflected back to the Client just as any replicated object is.

Input Authority

Each object only accepts inputs from one specific Client, called the object's Input Authority.

When a Client spawns an object it automatically becomes the Input Authority for that object. The object's creator will retain control over the object even after state authority has been transferred to the Simulator.

If an object is spawned directly by the Simulator, you will need to assign the Input Authority manually. Use the TransferAuthority method on the CoherenceSync component to assign or re-assign a Client that will take control of the object:

The ClientId used to specify Input Authority can currently only be accessed from the ClientConnection class. For detailed information about setting up the ClientConnection Prefab, see the page.

Use the OnInputAuthority and OnInputRemote events on the CoherenceSync component to be notified whenever an object changes input authority.

Only the object's current State Authority is allowed to transfer Input Authority.

In order to get notified when the Simulator () takes state authority of the input you can use the OnInputSimulatorConnected event from the CoherenceSync component.

The OnInputSimulatorConnected event can also be raised on the Simulator or host if they have both input and state authority over an entity. This allows the session host to use inputs just like any other client but might be undesirable if input entities are created on the host and then have their input authority transferred to the clients.

To solve this you can check the CoherenceSync.IsSimulatorOrHost flag in the callback:

Server-authoritative network visibility

The CoherenceLiveQuery component can be used to limit the visible portion of the Game World that a player is allowed to see. The Replication Server filters out networked objects that are outside the range of the LiveQuery so that players can't cheat by inspecting the incoming network traffic.

When a query component is placed on a Game Object that is set to Server Side With Client Inputs the query visibility will be applied to the Game Object's Input Authority (i.e., the player) while the component remains in control of the State Authority (i.e. the Simulator). This prevents players from viewing other parts of the map by simply manipulating the radius or position of the query component.

See for more information on how to use queries.

Client-side prediction

Using Server-side simulation takes a significantly longer period of time from the Client providing input until the game state is updated, compared to just using Client-side simulation. That's because of the time required for the input to be sent to the Simulator, processed, and then the updates to the object returned across the network. This round-trip time results in an input lag that can make controls feel awkward and slow to respond.

If you want to use a Server-authoritative setup without sacrificing input responsiveness, you need to use Client-side prediction. With Client-side prediction enabled, incoming network data is ignored for one or more bindings, allowing the Client to predict those values locally. Usually, position and rotation are predicted for the local player, but you can toggle Client-side prediction for any binding in the Configuration window.

By processing inputs both on the Client and on the Server, the Client can make a prediction of where the player is heading without having to wait for the authoritative Server response. This provides immediate input feedback and a more responsive playing experience.

Note that inputs should not be processed for Clients that neither have State Authority nor Input Authority. That's because we can only predict the local player; remote players and other networked objects are synced just as normal.

Misprediction and Server Reconciliation

With Client-side prediction enabled, the predicted Client state will sometimes diverge from the Server state. This is called misprediction. When misprediction occurs, you will need to adjust the Client state to match the Server state in one way or another. This is called Server Reconciliation.

There are many possible approaches to Server Reconciliation and coherence doesn't favor one over another. The simplest method is to snap the Client state to the Server state once a misprediction is detected. Another method is to continuously blend from Client state to Server state.

Misprediction detection and reconciliation can be implemented in a binding's OnNetworkSampleReceived event callback. This event is called every time new network data arrives, so we can test the incoming data to see if it matches with our local Client state.

The misprediction threshold is a measure of how far the prediction is allowed to drift from the Server state. Its value will depend on how fast your player is moving and how much divergence is acceptable in your particular game.

Remember that incoming sample data is delayed by the round-trip time to the Server, so it will trail the currently predicted state by at least a few frames, depending on network latency. The simulationFrame parameter tells you the exact frame at which the sample was produced on the authoritative Server.

For better accuracy, incoming network samples should be compared to the predicted state at the corresponding simulation frame. This requires keeping a history buffer of predicted states in memory.

Client as a host

This feature is in the experimental phase.

A client-hosted session is an alternative way to use CoherenceInput in Server Side With Client Input mode that doesn't require a Simulator.

A Client that created a can join as a Host of this Room. Just like a Simulator, the Host will take over the State Authority of the CoherenceInput objects while leaving the Input Authority in the hands of the Client that created those objects.

The difference between a Host and a Simulator is that the Host is still a standard client connection, which means it counts towards the Room's client limit and will show up as a client connection in the connection

Usage

To connect as a Host all we have to do is call CoherenceMonoBridge.ConnectAsHost:

Client connections

Overview

The Client connection system lets you uniquely identify users connected to the same session, find any user by their ID, spawn objects whenever a new user joins the session, and send messages between those users.

To achieve this a special connection entity is automatically created by the for each connected Client, including a Simulator. Those Entities are subject to a different rule set than standard Entities. Connection entities:

  • Can't be created or destroyed by the Client - they are always Replication Server-driven

  • Are global - they are replicated across Clients regardless of the in-simulation distance or extent

Client connections shine whenever there's a need to communicate something to all the connected players. Usage examples:

  • Global chat

  • Game state changes: game started, game ended, map changed

  • Server announcements

  • Server-wide leaderboard

  • Server-wide events

Enabling client connections

The global nature of Client connection doesn't fit all game types - for example, it rarely makes sense to keep every Client informed about the presence of all players on the server in an MMORPG (think World Of Warcraft). If this is your use case, make sure Client connections are turned off by default.

To enable Client connections, check that Global Query in the is turned on (it should be by default):

Disabling Global Query on one Client doesn't affect other Clients, i.e. the connection Entity of this Client will still be visible to other Clients that have the Global Query turned on.

Connection management

Most of the Client connection functionality is accessible through the CoherenceMonoBridge.ClientConnections object:

Each connection is represented by a plain C# CoherenceClientConnection object. It contains all the important information about a connection - its ClientID, Type, whether it IsMyConnection, and a reference to the GameObject and Coherence Sync associated with it.

The CoherenceClientConnection.ClientID is guaranteed to not change during a connection's lifetime. However, if a Client disconnects and then connects again to the same Room/World, a new ClientID will be assigned (since a new connection was established).

Connection objects

Each Client connection can have a GameObject with CoherenceSync automatically being spawned and associated with it. Those objects, like any other objects with CoherenceSync, can be used for syncing properties or sending messages, with a little twist - they are global and thus not limited by the LiveQuery extent. That makes them perfect candidates for operations like:

  • Syncing global information - name, stats, tags, etc.

  • Sending global messages - chat, server interaction

To enable connection objects:

1. Create a client connection prefab

This step is described in detail in the . In short, a Prefab with a CoherenceSync and a custom component (PlayerConnection in this example) must be created and placed in a Resources folder:

2. Link a connection Prefab to the MonoBridge

For the system to know which object to create for every new Client connection, we have to link our Prefab to the MonoBridge. Simply drag the prefab to the Client field in the MonoBridge inspector:

From now on every new connection will be assigned an instance of this Prefab, which can be accessed through the CoherenceClientConnection.GameObject property.

Prefab selection

Note that there's a separate field for the Simulator Connection Prefab. It can be used to spawn a completely different object for the Simulator connection that may contain Simulator-specific commands and replicated properties. If the field is left empty, no object will be created for the Simulator connection.

The Prefab selection process can be also controlled from code using the CoherenceMonoBridge.ClientConnections.ProvidePrefab callback:

A Prefab provided through the ProvidePrefab callback takes precedence over Prefabs linked in the inspector.

Client messages

Client messages are sent between the . Implementing Client messages is as simple as adding a new method to the component used by our connection Prefab and binding it in the configuration:

Don't forget to bind the new command:

Client messages can be sent using the CoherenceClientConnection.SendClientMessage method:

If the ClientID of the message recipient is known we can use the CoherenceMonoBridge.ClientConnections directly to send a client message:

public void SendMousePosition()
{
    var coherenceInput = GetComponent<CoherenceInput>();
    var mousePos = Input.mousePosition;
    coherenceInput.SetAxisState("mouse", new Vector2(mousePos.x, mousePos.y));
}
public void ProcessMousePosition()
{
    var coherenceInput = GetComponent<CoherenceInput>();
    var mousePos = coherenceInput.GetAxisState("mouse");
    
    //Move object
}
public void AssignNewInputAuthority(CoherenceClientConnection newInputOwner)
{
    var coherenceSync = GetComponent<CoherenceSync>();
    coherenceSync.TransferAuthority(newInputOwner.ClientId, AuthorityType.Input);
}
coherenceSync.OnInputSimulatorConnected.AddListener(() =>
{
    if (coherenceSync.MonoBridge.IsSimulatorOrHost)
    {
        // Ignore for Simulators and hosts.
        return;
    }

    // Insert your game logic here
    Debug.Log("Input ready for use!");
});
public void Update()
{
    if (coherenceSync.HasStateAuthority || coherenceSync.HasInputAuthority)
    {
        ProcessMousePosition();
    }

    if (coherenceSync.HasInputAuthority)
    {
        SendMousePosition();
    }
}
private void Awake()
{
    var positionBinding = GetComponent<CoherenceSync>().CustomBindings.FirstOrDefault(c => c.Name == "position");
    positionBinding.OnNetworkSampleReceived += DetectMisprediction;
}

private void DetectMisprediction(object sampleData, long simulationFrame)
{
    const float MispredictionThreshold = 3;
    
    var networkPosition = (Vector3) sampleData;
    var distance = (networkPosition - transform.position).magnitude;
    
    if (distance > MispredictionThreshold)
    {     
        transform.position = networkPosition;
    }
}
public async Task CreateRoomAndJoinAsHost(string region)
{
    RoomData roomData = await PlayResolver.CreateRoom(region);
    (EndpointData roomEndpoint, bool isEndpointValid) = PlayResolver.GetRoomEndpointData(roomData);

    if (!isEndpointValid)
    {
        throw new Exception($"Invalid room endpoint: {roomEndpoint}");
    }

    CoherenceMonoBridge monoBridge = GetComponent<CoherenceMonoBridge>();
    monoBridge.onConnected.AddListener(OnConnected);
    monoBridge.ConnectAsHost(roomEndpoint);
}

public void OnConnected(CoherenceMonoBridge monoBridge)
{
    Debug.Log($"Connected! Is Host: {monoBridge.IsSimulatorOrHost}");
}
CoherenceSync
Simulator
baking
Client connections
or host
Area of interest
Room
list.
CoherenceSync inspector
The "Server Side With Client Input" option is available in CoherenceSync Inspector.
Client-side prediction is toggled using the P-button to the right of each binding in the Configuration window.
using System.Collections.Generic;
using Coherence.Connection;
using Coherence.Toolkit;
using UnityEngine;

public class ExampleGameManager : MonoBehaviour
{
    public CoherenceMonoBridge MonoBridge;

    void Start()
    {
        // Raised whenever a new connection is made (including the local one).
        MonoBridge.ClientConnections.OnCreated += connection =>
        {
            Debug.Log($"Connection #{connection.ClientId} " +
                      $"of type {connection.Type} created.");
        };

        // Raised whenever a connection is destroyed.
        MonoBridge.ClientConnections.OnDestroyed += connection =>
        {
            Debug.Log($"Connection #{connection.ClientId} " +
                      $"of type {connection.Type} destroyed.");
        };

        // Raised when all initial connections have been synced.
        MonoBridge.ClientConnections.OnSynced += connectionManager =>
        {
            Debug.Log($"ClientConnections are now ready to be used.");
        };
    }
    
    void Update()
    {
        // IMPORTANT: All of the connection retrieving calls may return null 
        // if the connection system was not turned on, not initialized yet,
        // or simply if the connection was not found.
        
        // Specifies how many clients are in this session (room or world).
        int clientCount = MonoBridge.ClientConnections.ClientConnectionCount;
        
        // Returns connection objects for all connections in this session.
        IEnumerable<CoherenceClientConnection> allConnections
            = MonoBridge.ClientConnections.GetAll();

        // Returns all connections except for the local one.
        IEnumerable<CoherenceClientConnection> otherConnections
            = MonoBridge.ClientConnections.GetOther();

        // Returns connection object of the local user.
        CoherenceClientConnection myConnection
            = MonoBridge.ClientConnections.GetMine();

        // Returns connection object of the simulator (if one is connected).
        CoherenceClientConnection simulatorConnection
            = MonoBridge.ClientConnections.GetSimulator();

        // Retrieves a connection by its ClientID.
        CoherenceClientConnection selectedConnection
            = MonoBridge.ClientConnections.Get(myConnection.ClientId);

        // Retrieves a connection by its EntityID (warning: requires
        // connection prefab with a CoherenceSync attached).
        if (myConnection.Sync != null)
        {
            selectedConnection
                = MonoBridge.ClientConnections.Get(myConnection.Sync.EntityID);
        }

        // Specifies if this is a client or a simulator connection.
        ConnectionType connectionType = selectedConnection.Type;

        // Specifies if this is a local connection (belonging to the
        // local user).
        bool isMine = selectedConnection.IsMyConnection;

        // Returns a GameObject associated with this connection.
        // Applicable only if connection prefabs are used.
        GameObject connectionGameObject = selectedConnection.GameObject;
    }
}
using System.Collections.Generic;
using Coherence.Connection;
using Coherence.Toolkit;
using UnityEngine;

public class ExampleGameManager : MonoBehaviour
{
    public CoherenceMonoBridge MonoBridge;
    public CoherenceSync CustomConnectionPrefab;

    void Start()
    {
        MonoBridge = FindObjectOfType<CoherenceMonoBridge>();
        MonoBridge.ClientConnections.ProvidePrefab += (clientId, connectionType) =>
        {
            return CustomConnectionPrefab;
        };
    }
}
public class PlayerConnection : MonoBehaviour
{
    // My chat command
    public void OnChatMessage(string message)
    {
        Debug.Log($"Received chat message: {message}");
    }
}
using UnityEngine;
using Coherence;
using Coherence.Connection;
using Coherence.Toolkit;

public class PlayerConnection : MonoBehaviour
{
    void Start()
    {
        var monoBridge = FindObjectOfType<CoherenceMonoBridge>();
        CoherenceClientConnection myConnection = monoBridge.ClientConnections.GetMine();

        myConnection.SendClientMessage<PlayerConnection>(nameof(OnChatMessage),
            MessageTarget.Other, "Hello!");
    }

    // My chat command
    public void OnChatMessage(string message)
    {
        Debug.Log($"Received chat message: {message}");
    }
}
private ClientID lastClientId;

private void OnConnectionCreated(CoherenceClientConnection clientConnection)
{
    if (!clientConnection.IsMyConnection)
    {
        lastClientId = clientConnection.ClientId;
    }
}

public void SendMessageToLastConnection(string message)
{
    var monoBridge = FindObjectOfType<CoherenceMonoBridge>();
    monoBridge.ClientConnections.SendMessage<PlayerConnection>("OnChatMessage",
        lastClientId, MessageTarget.AuthorityOnly, "Hello!");
}
Replication Server
LiveQuery
MonoBridge
Prefab setup section
commands
Client connection objects
MonoBridge component
PlayerConnection prefab
MonoBridge with linked connection prefab
Binding a client message

GGPO

Overview

coherence Input Queues are backed by a rolling buffer of inputs transmitted between the Clients. This buffer can be used to build a fully deterministic simulation with a client side-prediction, rollback, and input delay. This game networking model is often called the GGPO (Good Game Peace Out).

Input delay allows for a smooth, synchronized netplay with almost no negative effect on the user experience. The way it works is input is scheduled to be processed X frames in the future. Consider a fighting game scenario with two players. At frame 10 Player A presses a kick button that is scheduled to be executed at frame 13. This input is immediately sent to Player B. With a decent internet connection, there's a very good chance that Player B will receive that input even before his frame 13. Thanks to this, the simulation is always in sync and can progress steadily.

Prediction is used to run the simulation forward even in the absence of inputs from other players. Consider the scenario from the previous paragraph - what if Player B doesn't receive the input on time? The answer is very simple - we just assume that the input state hasn't changed and progress with the simulation. As it turns out this assumption is valid most of the time.

Rollback is used to correct the simulation when our predictions turn out wrong. The game keeps historical states of the game for past frames. When an input is received for a past simulation frame the system checks whether it matches the input prediction made at that frame. If it does we don't have to do anything (the simulation is correct up to that point). If it doesn't match, however, we need to restore the simulation state to the last known valid state (last frame which was processed with non-predicted inputs). After restoring the state we re-simulate all frames up to the current one, using the fresh inputs.

Determinism

In a deterministic simulation, given the same set of inputs and a state we are guaranteed to receive the same output. In other words, the simulation is always predictable. Deterministic simulation is a key part of the GGPO model, as well as a lockstep model because it lets us run exactly the same simulation on multiple Clients without a need for synchronizing big and complex states.

Implementing a deterministic simulation is a non-trivial task. Even the smallest divergence in simulation can lead to a completely different game outcome. This is usually called a desync. Here's a list of common determinism pitfalls that have to be avoided:

  • Using Update to run the simulation (every player might run at a different frame rate)

  • Using coroutines, asynchronous code, or system time in a way that affects the simulation (anything time-sensitive is almost guaranteed to be non-deterministic)

  • Using Unity physics (it is non-deterministic)

  • Using random numbers generator without prior seed synchronization

  • Non-symmetrical processing (e.g. processing players by their spawn order which might be different for everyone)

  • Relying on floating point numbers across different platforms, compilations or processor types

Quickstart guide

We'll create a simple, deterministic simulation using provided utility components.

This is the recommended way of using Input Queues since it greatly reduces the implementation complexity and should be sufficient for most projects. If you'd prefer to have full control over the input code feel free to use theCoherenceInput and InputBuffer directly.

Our simulation will synchronize the movement of multiple Clients, using the rollback and prediction in order to cover for the latency.

Player implementation

Start by creating a Player component and a Prefab for it. We'll use the client connection system to make our Player represent a session participant and automatically spawn the selected Prefab for each player that connects to the Server. The Player will also be responsible for handling inputs using the CoherenceInput component.

Create a Prefab from cube, sphere, or capsule, so it will be visible on the scene. That way it will be easier to verify visually if the simulation works, later.

When building an input-based simulation it is important to use the Client connection system, that is not a subject to the LiveQuery. Objects that might disappear or change based on the client-to-client distance are likely to cause simulation divergence leading to a desync.

Our Player code looks as follows:

using Coherence.Toolkit;
using UnityEngine;

[RequireComponent(typeof(CoherenceSync))]
[RequireComponent(typeof(CoherenceInput))]
public class Player : MonoBehaviour
{
    // Movement speed in units/sec
    public float Speed = 5f;
    
    private CoherenceInput input;

    private void Awake()
    {
        input = GetComponent<CoherenceInput>();
    }

    // Retrieves the "movement" input state for a given frame
    public Vector2 GetMovement(long frame)
    {
        return input.GetAxisState("Mov", frame);
    }

    // Sets the "movement" state for the current frame
    public void SetMovement()
    {
        Vector2 movement = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;
        input.SetAxisState("Mov", movement);
    }
}

The GetMovement and SetMovement will be called by our "central" simulation code. Now that we have our Player defined let's prepare a Prefab for it. Create a GameObject and attach the Player component to it, using the CoherenceSync inspector create a Prefab. The inspector view for our Prefab should look as follows:

A couple of things to note:

  • A Mov axis has been added to the CoherenceInput which will let us sync the movement input state

  • Unlike in the Server authoritative setup our simulation uses client-to-client communication, meaning each Client is responsible for its Entity and sending inputs to other Clients. To ensure such behavior set the CoherenceSync > Simulation and Interpolation > Simulation Type to Client Side

  • In the deterministic simulation, it is our code that is responsible for producing deterministic output on all Clients. This means that the automatic transform position syncing is no longer desirable. To turn it off, toggle the Predicted button in the CoherenceSync Bindings window (see the chapter on client-side prediction in Server authoritative setup).

  • In order for inputs to be processed in a deterministic way, we need to use the fixed simulation frames. Tick the CoherenceInput > Use Fixed Simulation Frames checkbox

  • Make sure to use the baked mode (CoherenceInput > Use Baked Script) - inputs do not work in the reflection mode

Since our player is the base of the Client connection we must set it as the connection Prefab in the CoherenceMonoBridge and enable the global query:

State implementation

Before we move on to the simulation, we need to define our simulation state which is a key part of the rollback system. The simulation state should contain all the information required to "rewind" the simulation in time. For example, in a fighting game that would be the position of all players, their health, and perhaps a combo gauge level. In a shooting game, this could be player positions, their health, ammo, and map objective progression.

In the example we're building, player position is the only state. We need to store it for every player:

using UnityEngine;t

public struct SimulationState
{
    public Vector3[] PlayerPositions;
}

The state above assumes the same number and order of players in the simulation. The order is guaranteed by the CoherenceInputSimulation, however, handling a variable number of Clients is up to the developer.

Simulation implementation

Simulation code is where all the logic should happen, including applying inputs and moving our Players:

using Coherence.Toolkit;
using UnityEngine;

public class Simulation : CoherenceInputSimulation<SimulationState>
{
    protected override void SetInputs(CoherenceClientConnection client)
    {
        var player = client.GameObject.GetComponent<Player>();
        player.SetMovement();
    }

    protected override void Simulate(long simulationFrame)
    {
        foreach (CoherenceClientConnection client in AllClients)
        {
            var player = client.GameObject.GetComponent<Player>();
            var movement = (Vector3)player.GetMovement(simulationFrame);
            player.transform.position += movement * player.Speed * FixedTimeStep;
        }
    }

    protected override void Rollback(long toFrame, SimulationState state)
    {
        for (var i = 0; i < AllClients.Count; i++)
        {
            Transform playerTransform = AllClients[i].GameObject.transform;
            playerTransform.position = state.PlayerPositions[i];
        }
    }

    protected override SimulationState CreateState()
    {
        var simulationState = new SimulationState { PlayerPositions = new Vector3[AllClients.Count] };
        for (var i = 0; i < AllClients.Count; i++)
        {
            Transform playerTransform = AllClients[i].GameObject.transform;
            simulationState.PlayerPositions[i] = playerTransform.position;
        }

        return simulationState;
    }

    protected override void OnClientJoined(CoherenceClientConnection client)
    {
        SimulationEnabled = AllClients.Count >= 2;
        if (SimulationEnabled)
        {
            // Lets us rejoin the same simulation without restarting the app.
            StateStore.Clear();
        }
    }

    protected override void OnClientLeft(CoherenceClientConnection client)
    {
        SimulationEnabled = AllClients.Count >= 2;
    }
}
  • SetInputs is called by the system when it's time for our local Player to update its input state using the CoherenceInput

  • Simulate is called when it's time to simulate a given frame. It is also called during frame re-simulation after misprediction - don't worry though, the complex part is handled by the CoherenceInputSimulation internals - all you need to do in this method is apply inputs from the CoherenceInput to run the simulation

  • Rollback is where we need to set the simulation state back to how it was at a given frame. The state is already provided in the state parameter, we just need to apply it

  • CreateState is where we create a snapshot of our simulation so it can be used later in case of rollback

  • OnClientJoined and OnClientLeft are optional callbacks. We use them here to start and stop the simulation depending on the number of clients

The SimulationEnabled is set to "false" by default. That's because in a real-world scenario the simulation should start only after all Clients have agreed for it to start, on a specific frame chosen, for example, by the host.

Starting the simulation on a different frame for each Client is likely to cause a desync (as well as joining in the middle of the session, without prior simulation state synchronization). Simulation start synchronization is however out of the scope of this guide so in our simplified example we just assume that Clients don't start moving immediately after joining.

As a final step, attach the Simulation script to the MonoBridge object on scene and link the MonoBridge back to the Simulation:

That's it! Once you build a client executable you can verify that the simulation works by connecting two Clients to the Replication Server. Move one of the Clients using arrow keys while observing the movement being synced on the other one.

Input in fixed network update

Due to the FixedNetworkUpdate running at different (usually lower) rate than Unity's Update loop, polling inputs using the functions like Input.GetKeyDown is susceptible to a input loss, i.e. keys that were pressed during the Update loop might not show up as pressed in the FixedNetworkUpdate.

To illustrate why this happens consider the following scenario: given that Update is running five times for each network FixedNetworkUpdate, if we polled inputs from the FixedNetworkUpdate there's a chance that an input was fully processed within the five Updates in-between FixedNetworkUpdates, i.e. a key was "down" on the first Update, "pressed" on the second, and "up" on a third one.

To prevent this issue from occurring you can use the FixedUpdateInput class:

using Coherence.Toolkit;
using UnityEngine;

[RequireComponent(typeof(CoherenceSync))]
[RequireComponent(typeof(CoherenceInput))]
public class Player: MonoBehaviour
{
    private FixedUpdateInput fixedUpdateInput;
    private CoherenceInput input;

    private void Awake()
    {
        var coherenceSync = GetComponent<CoherenceSync>();
        fixedUpdateInput = coherenceSync.MonoBridge.FixedUpdateInput;
        input = coherenceSync.Input;
    }

    // Retrieves the "jump" input state for a given frame
    public bool GetJump(long frame)
    {
        return input.GetButtonState("Jump", frame);
    }

    // Sets the "jump" state for the current frame
    public void SetJump()
    {
        bool hasJumped = fixedUpdateInput.GetKey(KeyCode.Space);
        input.SetButtonState("Jump", hasJumped);
    }
}

The FixedUpdateInput works by sampling inputs at Update and prolonging their lifetime to the network FixedNetworkUpdate so they can be processed correctly there. For our last example that would mean "down" & "pressed" registered in the first FixedNetworkUpdate after the initial five updates, followed by an "up" state in the subsequent FixedNetworkUpdate.

The FixedUpdateInput works only with the legacy input system (UnityEngine.Input).

Pause handling

There's a limit to how many frames can be predicted by the Clients. This limit is controlled by the CoherenceInput.InputBufferSize. When Clients try to predict too many frames into the future (more frames than the size of the buffer) the simulation will issue a pause. This pause affects only the local Client. As soon as the Client receives enough inputs to run another frame the simulation will resume.

To get notified about the pause use the OnPauseChange(bool isPaused) method from the CoherenceInputSimulation:

using Coherence.Toolkit;

public class Simulation : CoherenceInputSimulation<SimulationState>
{
    // ... stubbed example simulation implementation ...
    protected override void SetInputs(CoherenceClientConnection client) { /* ... */ }
    protected override void Simulate(long simulationFrame) { /* ... */ }
    protected override void Rollback(long toFrame, SimulationState state) { /* ... */ }
    protected override SimulationState CreateState() { /* ... */ return default; }

    protected override void OnPauseChange(bool isPaused)
    {
        PauseScreen.SetActive(isPaused);
    }
}

This can be used for example to display a pause screen that informs the player about a bad internet connection.

To recover from the time gap created by the pause the Client will automatically speed up the simulation. The time scale change is gradual and in the case of a small frame gap, can be unnoticeable. If a manual control over the timescale is desired set the CoherenceMonoBridge.controlTimeScale flag to "false".

Debugging

The CoherenceInputSimulation has a built-in debugging utility that collects various information about the input simulation on each frame. This data can prove extremely helpful in finding a simulation desync point.

The CoherenceInputDebugger can be used outside the CoherenceInputSimulation. It does however require the CoherenceInputManager which can be retrieved through the CoherenceMonoBridge.InputManager property.

Setup

Since debugging might induce a non-negligible overhead it is turned off by default. To turn it on, add a COHERENCE_INPUT_DEBUG scripting define:

From that point, all the debugging information will be gathered. The debug data is dumped to a JSON file as soon as the Client disconnects. The file can be located under a root directory of the executable (in case of Unity Editor the project root directory) under the following name: inputDbg_<ClientId>.json, where <ClientId> is the CoherenceClientConnection.ClientId of the local client.

Data handling behavior can be overridden by setting the CoherenceInputDebugger.OnDump delegate, where the string parameter is a JSON dump of the data.

The debugger is available as a property in the simulation base class: CoherenceInputSimulation.Debugger. Most of the debugging data is recorded automatically, however, the user is free to append any arbitrary information to a frame debug data, as long as it is JSON serializable. This is done by using the CoherenceInputDebugger.AddEvent method:

using Coherence.Toolkit;

public class Simulation : CoherenceInputSimulation<SimulationState>
{
    // ... stubbed example simulation implementation ...
    protected override void Simulate(long simulationFrame) { /* ... */ }
    protected override void Rollback(long toFrame, SimulationState state) { /* ... */ }
    protected override SimulationState CreateState() { /* ... */ return default; }

    protected override void SetInputs(CoherenceClientConnection client)
    {
        Debugger.AddEvent("myCustomEvent", new
        {
            Data = "my custom data",
            UnityFrameCount = Time.frameCount
        });
        // ... movement code ...
    }
}

Since the simulation can span an indefinite amount of frames it might be wise to limit the number of debug frames kept by the debugging tool (it's unlimited by default). To do this use the CoherenceInputDebugger.FramesToKeep property. For example, setting it to 1000 will instruct the debugger to keep only the latest 1000 frames worth of debugging information in the memory.

Serialization

Since the debugging tool uses JSON as a serialization format, any data that is part of the debug dump must be JSON-serializable. An example of this is the simulation state. The simulation state from the quickstart example is not JSON serializable by default, due to Unity's Vector3 that doesn't serialize well out of the box. To fix this we need to give JSON serializer a hint:

using Coherence.Toolkit;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using UnityEngine;

public struct SimulationState : IHashable
{
    [JsonProperty(ItemConverterType = typeof(UnityVector3Converter))]
    public Vector3[] PlayerPositions { get; set; }

    public Hash128 ComputeHash() { return Hash128.Compute(PlayerPositions); }
}

With the JsonProperty attribute, we can control how a given field/property/class will be serialized. In this case, we've instructed the JSON serializer to use the custom UnityVector3Converter for serializing the vectors.

You can write your own JSON converters using the example found here. For information on the Newtonsoft JSON library that we use for serialization check here.

Comparing debug data

To find a problem in the simulation, we can compare the debug dumps from multiple clients. The easiest way to find a divergence point is to search for a frame where the hash differs for one or more of the clients. From there, one can inspect the inputs and simulation states from previous frames to find the source of the problem.

Here's the debug data dump example for one frame:

"32625635555": {
    "Frame": 32625635555,
    "AckFrame": 32625635556,
    "ReceiveFrame": 32625635556,
    "AckedAt": 32625635555,
    "MispredictionFrame": -1,
    "Hash": "3fa00ad32cee971e43ee4a3206dc79f0",
    "Time": "15:48:36.977",
    "InitialState": {
      "PlayerPositions": [
        "0.2494998,3.992,0",
        "0.2494998,-2.086163E-07,0",
        "-2.086163E-07,0.7484999,0",
        "0.4989998,3.992,0"
      ]
    },
    "InitialInputs": {
      "153470363": "Mov:float2(-0.998f, 0f)",
      "322232370": "Mov: float2(0.998f, 0f)",
    },
    "InputBufferStates": {
      "153470363": {
        "LastFrame": 32625635557,
        "LastSentFrame": 32625635557,
        "LastReceivedFrame": -1,
        "LastAcknowledgedFrame": -1,
        "MispredictionFrame": null,
        "QueueCount": 0,
        "ShouldPause": false
      },
      "322232370": {
        "LastFrame": 32625635556,
        "LastSentFrame": -1,
        "LastReceivedFrame": 32625635556,
        "LastAcknowledgedFrame": 32625635556,
        "MispredictionFrame": null,
        "QueueCount": 0,
        "ShouldPause": false
      }
    },
    "Events": [
      {
        "Event": "InputSent",
        "Time": "15:48:36.977",
        "Data": {
          "ClientId": 153470363,
          "SentFrame": 32625635558,
          "Input": "Mov:float2(0f, -0.998f)"
        }
      },
      {
        "Event": "InputReceived",
        "Time": "15:48:36.978",
        "Data": {
          "ClientId": 322232370,
          "RecvFrame": 32625635557,
          "Input": "Mov: float2(0.998f, 0f)"
        }
      }
    ]
  },

Explanation of the fields:

  • Frame - frame of this debug data

  • AckFrame - the common acknowledged frame, i.e. the lowest frame for which inputs from all clients have been received and are known to be valid (not mispredicted)

  • ReceiveFrame - the common received frame, i.e. the lowest frame for which inputs from all clients have been received

  • AckedAt - a frame at which this frame has been acknowledged, i.e. set as known to be valid (not mispredicted)

  • MispredictionFrame - a frame that is known to be mispredicted, or -1 if there's no misprediction

  • Hash - hash of the simulation state. Available only if the simulation state implements the IHashable interface

  • Initial state - the original simulation state at this frame, i.e. a one before rollback and resimulation

  • Initial inputs - original inputs at this frame, i.e. ones that were used for the first simulation of this frame

  • Updated state - the state of the simulation after rollback and resimulation. Available only in case of rollback and resimulation

  • Updated inputs - inputs after being corrected (post misprediction). Available only in case of rollback and resimulation

  • Input buffer states - dump of the input buffer states for each client. For details on the fields see the InputBuffer code documentation

  • Events - all debug events registered in this frame

Configuration

Input buffer

There are two main variables which affect the behaviour of the InputBuffer:

  • Input buffer size - the size of the buffer determines how far into the future the input system is allowed to predict. The bigger the size, the more frames can be predicted without running into a pause. Note that the further we predict, the more unexpected the rollback can be for the player. The InitialBufferSize value can be set directly in code however it must be done before the Awake of the baked component, which might require a script execution order configuration.

  • Input buffer delay - dictates how many frames must pass before applying an input. In other words, it defines how "laggy" the input is. The higher the value, the less likely Clients are going to run into prediction (because a "future" input is sent to other Clients), but the more unresponsive the game might feel. This value can be changed freely at runtime, even during a simulation (it is however not recommended due to inconsistent input feeling).

The other two options are:

  • Disconnect on time reset - if set to "true" the input system will automatically issue a disconnect on an attempt to resync time with the Server. This happens when the Client's connection was so unstable that frame-wise it drifted too far away from the Server. In order to recover from that situation, the Client performs an immediate "jump" to what it thinks is the actual server frame. There's no easy way to recover from such a "jump" in the deterministic simulation code, so the advised action is to simply disconnect.

  • Use fixed simulation frames - if set to "true" the input system will use the IClient.ClientFixedSimulationFrame frame for simulation - otherwise the IClient.ClientSimulationFrame is used. Setting this to "true" is recommended for a deterministic simulation.

Fixed frame rate

The fixed network update rate is based on the Fixed Timestep configured through the Unity project settings:

To know the exact fixed frame number that is executing at any given moment use the IClient.ClientFixedSimulationFrame or CoherenceInputSimulation.CurrentSimulationFrame property.