Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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 and key-value stores.
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.
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") - experimental
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.
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.
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.
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.
A build of the game. To connect to coherence, it will use the coherence SDK.
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.
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).
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.
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.
Read about new features, important changes and fixes for version 1.1
Don't forget to check out our SDK when switching versions!
Published 26.03.2024
Fixed
Commands routing pop-up doesn't block information with scrollbars on a few specific scaling settings anymore.
CoherenceRelayManager no longer causes a stack overflow in rare circumstances if there are errors in the underlying UDP transport.
Published 08.02.2024
Fixed
Labels no longer overflow when UI Scaling is set above 100% (on Windows: Edit > Preferences... > UI Scaling).
Changed
Creating a Command on the Configure Window now defaults to "Send to Everyone, including yourself".
Published 23.01.2024
Added
Sample Scenes were added and are now accessible from the package samples section and coherence's Explore Samples menu.
Fixed
Source Generator no longer skips baked scripts that don't compile.
CoherenceNode: no longer teleports to origin when reparenting.
Published 12.12.2023
Added
coherence > Local Replication Server > Backup World Data
toggle.
Changed
Hub: Persistence renamed to Backup World Data, and disabled by default for local replication servers.
Fixed
Version label in the Hub now copies correct OS information to the clipboard.
CoherenceSync's Configure window no longer shows the Animator Controller on variables that have the same name as animation parameters.
No longer send invalid updates when gaining authority.
Published 28.11.2023
Added
CoherenceSync: added support for nested Prefabs and hierarchies via PrefabSyncGroup
component.
Sample: matchmaking with manual authentication.
CoherenceBridge: added ability to disconnect from within the inspector.
CoherenceSync: added support for byte
, sbyte
, short
, ushort
and char
.
Changed
CoherenceNode: improved stability.
CoherenceSync: reworked Play Mode panel on the inspector.
CoherenceSync: we now allow sending commands while disconnected (method gets called locally).
CoherenceSync: Configure window shows variables and methods tagged with [Sync]
and [Command]
.
CoherenceScene: editor scene visibility settings split for Clients and Simulators.
Reworked interpolation. Removed overshoot settings.
Reworked code generation. Now, it's implemented on the Unity/C#-side.
Updated JWT implementation, which is no longer provided as a pre-build DLL. This also prevents dependency conflicts.
HTTP request timeout changed from 1 second to 10 seconds.
Multi-Room Simulators Wizard: reworked, simplified layout.
Fixed
Game Object getting deselected after adding CoherenceSync.
Reduced the amount of GC allocs made for better performance.
Release WebSocket when leaving Play Mode.
Instantiated InterpolationSettings never getting destroyed.
Time scale related issues, including jitter and low-FPS scenarios.
Restrict and warn about the 31 components per entity limitation.
Resolve inheritance when sending commands.
Client Connection Prefab instances initialization issue when using the CreateScene
API.
Issue where mixing Transform
and RectTransform
networked variables could break.
Console error after baking when Upload Schema on Baking Complete
was on.
Profiler: count messages received.
Sample: wasn't refreshing Worlds properly in some scenarios.
CoherenceSync: deleting a Component or a GameObject on the hierarchy spammed errors on the console.
Uploading WebGL builds now shows the correct information on the progress bar.
Console errors after deleting a Prefab, when the Coherence Objects Window has been opened and closed.
Removed
Unity 2020 support. (Minimum supported version is now Unity 2021.3 LTS)
Published 06.10.2023
Added
Extrapolation: Max Overshoot Allowed range increased to [0, 20].
Extrapolation: Expose stale factor (defaults to 2).
Changed
Highly reduced GC allocs made while serializing.
Fixed
WebSocket request timings.
OnValueSynced not triggering when the update packet came with a parenting change.
Shifting the floating origin now correctly shifts rigidbodies in the same frame without kicking in the physics interpolation.
Published 05.09.2023
Added
Descriptor Providers: two new properties were added to Bindings, called OverrideGetter and OverrideSetter. Setting this to true will allow you to write custom code blocks in your getters and setters, as opposed to being limited to "UnityComponent.{myCustomCode}".
CoherenceBridge API: TranslateFloatingOrigin(Vector3d) overload.
Fixed:
Floating Origin: fixed entities jittering on remote clients when Floating Origin is changed.
Published 11.08.2023
Published 10.08.2023
CoherenceNode: Reset on OnDisable.
Reset entity reference when destroying the entity.
Force re-cache SchemaID after writing Gathered.schema to disk.
Published 25.07.2023
Commands: fixed trying to send a command from an inherited class that is bound in the parent class.
Bindings: fixed rare cases where RectTransform bindings would be reported as missing.
Client Connection Prefabs: fixed Client Connection instances duplication when changing Physics scene.
Coherence Profiler: fixed count of messages received.
ReplicationServerRoomsService: fixed RemoveRoom methods.
Optimize window rendering issue where rows would disappear.
Published 04.07.2023
Still, nothing beats Release Notes for a quick and comprehensive review of the changes, so go ahead!
Minimum supported version is now Unity 2021.3 LTS.
Lobby Rooms
Unity multi-scene support
TCP Fallback
Client-hosting
Profiler: a coherence Profiler module for basic networking information.
Added support for object pooling and a default pooling implementation.
Protocol: added support for ordered components so parenting component changes always arrive with related position changes.
CloudService: non-static public API to communicate with your coherence Cloud project.
Samples: Explore Samples window.
Samples: New Connection Dialog Samples for Rooms and Worlds.
Enter Play Mode Options (No Domain Reload) is now fully supported.
CoherenceSyncConfigRegistry: Added new ScriptableObject to soft reference CoherenceSync assets in runtime and serialize how they are loaded and instantiated.
INetworkObjectProvider: Added runtime interface to be able to customize how CoherenceSync assets are loaded, with three default implementations (Resources, Direct Reference and Addressables)
INetworkObjectInstantiator: Added runtime interface to be able to customize how CoherenceSync prefabs are instantiated, with three default implementations (Default, Pooling and DestroyCoherenceSync)
CoherenceSyncConfigUtils: Editor public API to be able to add, delete and browse CoherenceSync assets, aswell as start or stop syncing variables via scripting.
CoherenceSync Objects Editor Window: New Editor window to be able to browse CoherenceSync assets, found under coherence => CoherenceSync Objects menu item.
UniquenessManager: Encapsulated uniqueness logic under CoherenceBridge.UniquenessManager, where you will be able to register unique IDs at runtime.
AuthorityManager: Encapsulated authority transfer logic under CoherenceBridge.AuthorityManager, where you will be able to send authority transfer requests without accessing the CoherenceSync directly.
ReplicationServerRoomsService: non-static public API to be able to create, delete and fetch rooms from self-hosted Replication Servers.
CoherenceSync: Rigidbody Update Mode.
CoherenceSync: Advanced Uniqueness Options.
CoherenceSync: CoherenceBridge resolver API.
Coherence.Editor.UploadBuildToCoherence API.
CoherenceSync: CoherenceSync instances are now automatically synced and unsynced with the network in the OnEnable/OnDisable methods, instead of Start/OnDestroy.
CoherenceSync: CoherenceSync instances can now be disabled and reused for different network entities, which allows for object pooling to happen.
CoherenceSync Baked Scripts: Baked scripts for CoherenceSync prefabs are no longer MonoBehaviours.
Uniqueness: Improved handling unique IDs for Unique CoherenceSyncs when creating serialized scene Prefab instances.
Updated JWT to 10.0.2.
Reworked AutoSimulatorConnection component.
ParrelSync/symlink support: avoid read exceptions on Gathered.schema.
CoherenceSync: Initialization of disabled objects.
CoherenceSync: adding the component to a Prefab no longer deselects it.
Source Generator: avoid running on IDEs, to avoid IO-related exceptions.
Simulator Slug not being encoded correctly.
CoherenceNode: race condition when setting parent.
PrefabMapper has been removed in favor of CoherenceSyncConfigRegistry.
NetworkTime.Time
PlayResolver API: Play, PlayClient and PlayResolver have been deprecated in favor of CloudService API.
Sample UI: the old Sample UI that depended on the PlayResolver API has been deprecated in favor of the new Unity Package Samples.
CoherenceUUID: This Component is no longer needed and it has been deprecated, the functionality has been baked into the Editor and now works automatically. Prefabs that use this Component can stop using it.
CoherenceMonoBridgeSender: This Component is no longer needed and it no longer has any functionality, it can be safely removed.
Client Connection Prefab References: References to the Prefabs in CoherenceBridge have been deprecated. Please use CoherenceSyncConfig references instead.
Check out our on 1.1!
Tutorial Project: Released the where you will be able to delve into more advanced aspects of how to use coherence.
There's a lot to say about hitting the coherence 1.0 milestone, so we did that in a dedicated . It also provides fuller descriptions of the highlight features that come with 1.0.
If you're updating from version 0.10, please check out our .
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
Floating Origin for extremely large virtual Worlds
TCP Fallback
Support for Client hosting through Steam Datagram Relay
Unity SDK with an intuitive no-code layer
Per-field adjustable interpolation and extrapolation
Input queues
Easy deployment into the cloud
Multi-room Simulators
Multiple code generation strategies (Assets/Baking, automated with C# Source Generators)
Extendable object spawning strategies (Resources, Direct References, Addressables) or implement your own
Per-field compression and quantization
Per-field sampling frequency adjustable at runtime
Unlimited per-field levels of detail
Areas of interest
Accurate SimulationFrame tracking
Network profiler
Brand new developer portal for management and usage statistics
Automatic server deployment and scaling
Multiple regions in the US, Europe and Asia
Player accounts with a persistent key/value store
Matchmaking and lobby rooms
Float64 support
Permissions and roles system for clients and simulators
Input queue UX improvements
More logging and diagnostics tools
Built-in network condition simulation
Additional server regions
Support for multiple Simulators and Replicators in a single project
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
Unreal Engine SDK
JavaScript SDK
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
Games are better when we play together.
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.
If you would like to get started right away, you can check the Installation page 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.
We have an entire section of content to get you up to speed with coherence: Learn.
If you're new to all of this, you can start with the Beginner's guide to networking which is a written intro to high-level networking.
If you prefer to be hands-on, you can explore the package Samples included directly within the Unity SDK. Or, download one of our pre-made Unity projects First Steps or Campfire, which come with great documentation explaining the thinking behind them.
If you enjoy videos as a learning tool, we have a playlist of video tutorials dedicated to getting started with the Unity SDK.
If you are an existing user and looking to update, check out the latest Release Notes. And maybe the SDK update guide as well!
Get help, ask questions and suggest features in our Community
Chat with us on Discord
Contact us at devrel@coherence.io
Tips on how to handle common problems
A fix for this is expected to arrive in version 1.1.5 of coherence very soon. Until that materializes, feel free to check out our Community thread on this topic that includes a workaround.
When binding a byte[]
field, the field will not be synced when changed by someField[x] = y
.
The only way to actually update the field, is through someField = new byte[]...
which assigns a new reference.
We have identified a misprediction bug in coherence 1.1.3 and earlier versions that causes a failed rollback right after a new Client joins a session, throwing relevant error messages. However if the Client manages to connect, this won't affect the rest of the simulation. The fix should ship in the next release.
If you're getting errors such as the ones below, the issue is that your Prefab has multiple AnimationControllers, but coherence only supports a single AnimationController.
Parameter 'Hash 1963678224' does not exist.
CoherenceSync: We can't find any binding for component 'GenericFieldQuaternion0' when receiving a component update.
If you still need multiple AnimationControllers, a workaround is detailed in the Animations article.
If you see missing scripts on freshly imported samples, like this:
Or see an error on saving a prefab from the sample:
Delete the sample itself (mind any modifications you might have done), close Unity and delete the Library
folder. Note that by doing so you're forcing a reimport of all your assets, which might take time depending on your project size.
When using CoherenceNode on your CoherenceSync, if CoherenceNode's networked variables have interpolation applied, you'll experience this issue. Turn off interpolation for such variables.
This can happen if coherence fails to set a Unique ID on your scene instances. Check the CoherenceSync inspector on Prefab instances. Look for Prefab Instance Unique ID
. Click the button to generate an ID. Do this for every Prefab instance that is missing its ID.
Alternatively, you can set a Manual Unique ID of your own on the Prefab itself or per-Prefab instance on your scene.
ExtensionOfNativeClass
attributeReimport assets via menu item Assets / Reimport All
.
There is a bug in some early versions of Unity 2022 LTS that causes Prefabs to be corrupted on reimport, leading to all sorts of issues like scripts losing references, or the CoherenceSync
component showing a button to "Fix Serialized Data" (which cures the problem, but only temporarily).
The real solution is to upgrade to a newer version of Unity 2022. Unity claims they fixed the bug in version 2022.3.5f1
(note the specific patch number, .5
).
The current workaround to avoid this issue is to open your CoherenceSync Prefabs in Prefab mode.
Check that all your Clients are using the same Schema ID.
When working with Prefab Variants, Unity leaks managed references (fields marked with [SerializeReference]
). This can make your Prefab grow big and use more memory than necessary. Until Unity fixes this issue, we provide you with the ability to prune the leaked references. You can prune within the Configure window.
If you are instantiating a Prefab from a coherence event (such as a Command, OnValueSynced, OnStateAuthority, OnLiveQuerySynced...) and then changing its bindings in the same frame, the remote Clients will not instantly get updated values. Instead, if the binding has interpolation enabled, the value of the binding will be interpolated from the original value (from the instantiation step) to the updated value (from the change later in the frame).
The current recommended workaround is to make sure that the Prefab has initial values set before instantiation. Or in the case of position and rotation, those could be directly supplied to Instantiate()
instead of setting them later in the frame.
The source generator relies on an auto-generated configuration file to inject the baked code into Unity's Assembly-CSharp.dll. Such file will not be available on a fresh, non-cached project (i.e., no Library
), such as a Continuous Integration setup.
The SDK doesn't offer yet an API to initialize the source generator from code.
If you experience issues, switch to Assets strategy in your CI setup. You can keep using the source generator to develop locally, since either baking strategy can be used interchangeably, and the generated code is the same.
If you see this message, the CoherenceSync Prefab instance has been modified to point to the correct archetype name. If that's the case, select every CoherenceSync Prefab instance on your scene(s) to make sure this fixup happens. Even if you don't see the message, you're good to go. Make sure you save your project (Ctrl+S) to apply the changes (if any) to disk.
Make sure you allow HTTP connections in Editor to avoid InvalidOperationException: Insecure connection not allowed
errors. Find out how to enable HTTP connections in Unity's InsecureHttpOption article.
Once you have followed the instructions on how to package for Unity, you will be able to explore the package Samples with no additional downloads. You can either:
Go to: Coherence > Explore Samples
Open Unity's Package Manager (Window > Package Manager) and navigate to the package samples
Note that the Samples are meant to be self-explanatory, so they come with no documentation.
If the scenes show up as magenta/pink after importing the samples, it's because the samples are made for the built-in pipeline and your project is using either URP or HDRP.
To fix this in URP, go to: Window > Rendering > Render Pipeline Converter
Click on the checkboxes to choose what to convert (Materials is necessary), then click the Initialize and Convert button. After a brief loading, you should see the sample scenes displayed correctly.
Using the same scene as in the , let's see how to easily sync animation over the network.
| Bindings
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
We haven't mentioned it before, but the character Prefab does a lot more than just syncing its position and rotation.
When you move around, you will notice that animation is also replicated across Clients. This is done via synced Animator parameters (and Network Commands, but we cover these in the ).
Very much like in the example about position and rotation, just sending these across the network allows us to keep animation states in sync, making it look like network-instantiated Prefabs on other Clients are performing the same actions.
Open the player Prefab located in the /Prefabs/Characters
folder. Browse its Hierarchy until you find the child GameObject called Workman. You will notice it has an Animator
component.
Select this GameObject and open the Animator window.
As is usually the case, animation is controlled by a few Animator parameters of different types (int, bool, float, etc.).
Make sure to keep the GameObject with the Animator component selected, and open the coherence Configure window:
You will see that a group of animation parameters are being synced. It's that simple: just checking them will start sending the values across, once the game starts, just like other regular public properties.
There is only one piece missing: animation Triggers. We use one to trigger the transition to the Jump state.
coherence provides two types of online replication services: Rooms and Worlds. Read about the different uses cases for each.
Rooms are best for session-based gameplay where the match between players takes place in a short-lived environment.
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 .
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 devrel@coherence.io
Entities
1000 by default, but can be increased up to 65535 in local development or client-hosted scenarios.
There is no UI button for increasing the supported player count, so you need to work through our Rooms API.
When creating a room viaReplicationServerRoomsService.CreateRoom
you can pass SelfHostedRoomCreationOptions
as creation options.\
To change the Entity limit just set the SelfHostedRoomCreationOptions.MaxEntities
to a desired value.\
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.
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.
Your project does not have to choose one-or-the-other. A project in coherence can contain both World and Rooms.
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.
The basics of coherence
The contains a series of small sample scenes, each one demonstrating one or more features of coherence.
If you're a first time user, we suggest to go through the scenes in the established order. They will guide you through some key coherence and networking concepts:
, ,
Remember that playing the scenes on your own only shows part of the picture. To fully experience the networked aspects, you have to play in one or more built instances alongside the Unity Editor, and even better - with other people.
The Unity project can be downloaded from its . The page contains pre-packaged .zip files.
To quickly try a pre-built version of the game, head to and either play the WebGL build directly in the browser, or download one of the available desktop versions.
Share the link with friends and colleagues, and have them join you!
Once you open the project in the Unity Editor, you can build scenes via File > Build Settings, as per usual.
If you want to try all the scenes in one go, keep them all in the build and place SceneSelector as the first one in the list.
If you're working on an individual scene instead, bring that one to the top and deselect the others. The build will be faster.
To be able to connect, you need to also run a local Replication Server, that can be started via coherence > Local Replication Server > Run for Worlds.
You can try running multiple Clients rather than just two, and see how replication works for each of them. You can also have one Client just be the Unity Editor. This allows you to inspect GameObjects while the game runs.
Since you might be building frequently, we recommend making native builds (macOS or Windows) as they are created much faster than WebGL.
This scene demonstrates the simplest networking scenario possible with coherence. Characters sync their position and rotation, which immediately creates a feeling of presence. Someone else is connected!
| Bindings | Component behaviours |
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
Upon connecting, a script instantiates a character for you. Now you can move and jump around, and you will see other characters move too.
To be able to connect, you need to also run a local Replication Server, that can be started via coherence > Local Replication Server > Run for Worlds.
coherence takes care of keeping network entities in sync on all Clients. When another Client connects, an instance of your character is instantiated in their scene, and an instance of their character is instantiated into yours. We refer to this as network instantiation.
When you click Connect in the sample UI, the CoherenceBridge
opens a connection. The PlayerHandler GameObject on the root of the hierarchy controls character instantiation by responding to that connection event.
Its PlayerHandler
script implements something like this:
On connection, a character is created. On disconnection, the same script destroys the character's instance. Note how instantiating and removing a network entity is done just with regular Unity Instantiate
and Destroy
.
Now let's take a look at the Prefab that is being instantiated. You can find it in the /Prefabs/Characters
folder.
By opening coherence's Configuration window (either by clicking on the Configure button on the CoherenceSync
component, or by going to coherence > GameObject Setup > Configure), you can see what is synced over the network.
When this window opens on the Variables tab you will notice that, at the very top, Transform.position
and Transform.rotation
are checked:
This is the data being transferred over the network for this object. Each Client sends the position and rotation of the character that they have authority over to every other connected Client, every time there is a change to it that is significant enough. We call these bindings.
Each connected Client receives these values and applies them to the Transform
component of their own instance of the remote player character.
To ensure that Clients don't modify the properties of entities they don't have authority over, we need to make sure that they are not running on the character instances that are non-authoritative.
coherence offers a rapid way to make this happen. If you open the Components tab of the Configuration window, you will see that 3 components are configured to do something special:
In particular:
The PlayerInput
and KinematicMove
scripts get disabled.
The Rigidbody
component is made kinematic.
While in Play Mode, try selecting a remote player character. You will notice that some of its script have been disabled by coherence:
One important concept to get familiar with is the fact that every networked entity exists as a GameObject on every Client currently connected. However, only one of them has what we call authority over the network entity, and can control its synced variables.
For instance, if we play this scene with two Clients connected, each one will have 2 player instances in their respective worlds:
This is something to keep in mind as you decide which components have to keep running or be disabled on remote instances, in order to not have the same code running unnecessarily on various Clients. This could create a conflict or put the two GameObjects in a very different state, generating unwanted results.
In the Unity Editor, when connected, the name of a GameObject and the icon next to it informs you about its current authority state (see image above).
For more information, refer to Unity's guides or .
Did you notice that we are able to configure bindings even if this particular GameObject doesn't have a CoherenceSync
component on it? This is done via the one attached to the root of the player Prefab. These parameters on child GameObjects are what we call deep bindings. Learn more in the lesson, or on .
Since Triggers are not a variable holding a value that changes over time, but rather an action that happens instantaneously, we can't just enable in the Config window like with other animator parameters. We will see how to sync them in the , using Network Commands.
See .
See .
You can also upload a build to the cloud and share a link with friends. To do that, or to learn how to host builds on the coherence Cloud.
You can learn more about Component Actions .
There are two types of authority in coherence: State and Input. For the sake of simplicity, in this project we often refer just to a generic "authority", and what we mean is State authority. Go for more info on authority.
If you want to see which entities are currently local and which ones are remote, we included a debug visualisation in the project. Hit the Tab key (or click the Joystick) to switch to a view that shows authority. You can keep playing the game while in this view, and see how things change (try the scene!).
Erik Svedäng, the winner of IGF 2009, explains the high-level concepts behind networking games.
This article will try to explain a handful of fundamental concepts that all are central to how networked games work. It does not contain any code examples and tries to not delve into minor details. Instead, its goal is to prepare someone new to the field for thinking about networking from a high-level perspective; what problems can arise and how they are commonly solved. The information in here is very useful for understanding the coherence SDK, but it should also be general enough to be applicable to any other similar networking library.
When a game runs on your local computer, it contains a lot of data which is used to model the game. This includes things like animation state, the position and orientation of various game objects, AI calculations, physical forces, among with any gameplay-specific variables. Colloquially we refer to all of this data as state. Efficiently updating state is a hard problem, even for a game that is only running locally.
To create the illusion that you're playing together in the same game world, a networked multiplayer game has to transmit enough of its state to the other players. Since computer networks have limited bandwidth it is absolutely necessary to restrict the amount of data being sent.
Generally speaking, there are two main ways to synchronize state; we can either send inputs, or the updated data itself. It is also possible to mix these approaches in various ways. We will now discuss each of the options briefly.
It is usually possible to enumerate a number of predefined inputs that the players of the game are allowed to perform (e.g. "jump", "run", "activate"). When an input is applied to the local game state, we can also make sure it is simultaneously sent to every other player in the session. If we make sure that each player starts the game in exactly the same state, and make sure that everyone applies exactly the same inputs as everyone else, the game state will appear in sync for each player. For certain types of games, this can save a lot of data from having to be transferred.
A good example might be an RTS game with hundreds of units, where it might be enough to send the coordinates of mouse clicks instead of the location of each unit. This of course requires completely deterministic game logic, which is a challenge in itself.
Another problem is that if there's even the slightest mismatch in inputs, the local game states of the players will begin to diverge. To learn more about this approach (and how to work around some of the problems) see our documentation on GGPO.
It is noteworthy that sending inputs doesn't necessarily require a server; thus it is a great model to be used in a peer-to-peer setting.
A second approach is to send the updated data itself. This can often be more costly in terms if data transfer (a single player action can change a lot of local data, which in turn has to be transmitted to the other players). It leads to some nice benefits though; most importantly that game states are allowed to diverge slightly, as long as they have a chance to catch up.
This concept is usually referred to as eventual consistency. Not having a single "initial state" also makes it easier to support features like letting players join late, or backing up the state of the game world.
Since it's the clients that run the simulation locally and then send the updated game state to the server, this setup can be referred to as client-authoritative.
A third option is a combination of the two solutions above, where clients send inputs but receive updated world data. This requires a central Simulator that is be able to run the game logic. The Simulator is a program trusted by the game developer and it knows how the inputs sent by the players are supposed to affect the game state.
This is a server-authoritative setup; players won't be in charge of the simulation and can't affect the game state directly. This has multiple implications, for example it shifts some of the burden of computation from user devices onto the server. To read more about this approach, see Server-authoritative setup.
It is also worth noting that you can combine client-authoritative simulation with inputs in interesting and useful ways. For example, it is possible to let players simulate some less-critical parts of the game state locally, while still sending inputs for their characters to a central server to be processed.
As stated before, a game contains a lot of data and it is not feasible to send all of it over the network in a real-time fashion. While using inputs is often the most lightweight choice in terms of data usage, it is common to have to send updates to the game state - both from the client to the server, and vice versa. In both those cases we have to use some optimizations. Here are the most important ones.
By keeping track of what the other players know about the state of your game, it is often possible to avoid a lot of data transfer. For example, a player might drop some game object on the ground and send the new location of it to each other participant. Unless that object moves, it is unnecessary to keep sending the same position over and over. This simple idea is used pervasively in coherence (and other similar networking solutions) to great effect.
It's important to acknowledge that a game sometimes generates many changes in a short timeframe. In such a situation, it is useful to prioritize changes based on how important they are for the particular game in question, while also factoring in how long it has been on hold. This means that an "old" change that doesn't get sent will build up importance and relative priority compared to other changes, eventually getting sent.
Finally, a major way of limiting data usage is to filter out uninteresting information and only send the most important parts based on the needs of each participant, also known as Area of Interest. Most commonly this takes the form of a position-based query. The query will make sure that a specific player only gets updates from objects in its vicinity. Anything far away will simply be ignored, and no data has to be sent. It is also possible to send some (but less detailed) data depending on distance. To learn more about these techniques, take a look at the coherence documentation for Queries and Level of Detail.
A game can have many users, and to facilitate the optimizations mentioned in the previous section it is necessary to track what each participant knows about the game state (and what they are interested in knowing). Instead of putting this burden on each game client, which entails an additional performance cost and can be hard to coordinate, it is better to make this part of a central server. For coherence, this is named the Replication Server.
In the case of using an input-based setup, there also has to be a central arbiter in charge of handling the received inputs, applying them to the game state, and sending the new game state to each client. In a coherence setup, the simulation of the game (which requires game-specific knowledge) is handled by a Simulator which communicates with the Replication Server.
This modular approach where various tasks are performed by different programs, potentially on different machines or from different physical locations, can help with the scaling of a game if it has many users.
Most people who play computer games versus other people online want it to be fair, with equal conditions for each player.
If your game is client-authoritative, with clients sending updates of the game state to the server, we can't verify the validity of such an update and it becomes a problem. It would be quite feasible for a savvy player to modify their game and remove certain limitations put there by the game developer.
As an example, a game client could send an update that sets the health of each enemy to 0. To prevent such blatant cheating, it is useful to introduce the concept of authority (also often called "ownership"). This means that the Replication Server keeps track of which client has the rights to update each entity in the game. If an unauthorized update is sent to the server, it is rejected and will not get sent to any other participant.
For an input-based game, the cheating problem is slightly different. Since inputs will have to be applied in the right situation to have any effect, it is much harder to simply set the game state to illegal values. The role of authority in this case is to make sure that no player sends inputs for a game object they shouldn't be able to control.
In many cases it is useful to allow for the transfer of authority. For example, there could be a magical potion that you can drink from in the game. If a player has authority over the potion, she can move it around and drink from it, or refill it. If she then gives the potion to another player, they would get authority over it and the original player would no longer be able to update it.
For certain game objects where we don't trust the players with updating them (or don't want potentially expensive logic to run on their devices) it is also possible to have dedicated machines that have authority over those objects and update them (see Simulators).
There are multiple ways of sending data over a network. These are called protocols. When speed is not the single most important factor, TCP is often used. It has mechanisms for checking that the correct information was sent and it will try to resend the information if it was lost along the way to it recipient.
This design does not work well for fast-paced games, since their simulations run at many frames per second. By the time a lost network message has been resent and finally made it to its final address, the information in it will have a high chance of already being outdated.
So instead of TCP, games often use UDP. This protocol is unreliable by design, but coherence adds a reliability layer on top of it. If turns out that an update didn't make it to its recipient, that update will be re-sent, but only after checking if any more recent changes to its data exist. This way, it is more likely that each player gets a consistent and up-to-date view of the shared game state.
Sending data from one computer to another takes time, and there's no way around that. As a programmer of a networked game, it is important to embrace this fact and recognize that it changes how you must think about your game logic. When programming a single-player game (especially if it only runs on a single processor thread) we can assume that any change to the game state is immediate. In a networked game, this is not true.
This means that each player of a networked game is playing in their own "parallel universe", which affect each other at a distance. Updates to data that you don't have authority over will appear in an irregular and unpredictable way. Because of this it is beneficial to use a defensive coding style that tries to correct for out-of-order updates, and other unexpected circumstances.
One example of such a coding technique (which is already built into coherence) is interpolation. It uses a selection of algorithms to predict what a value will be, based on previous values. This "smooths out" the values over time, which often looks better than using the raw versions. The best example of this is probably interpolation of position - if an object is moving in a straight line at a certain speed and then the update with its new position is somehow lost, it is better to assume that the object will keep moving instead of stopping it.
If the concepts in this article were new to you, we hope that you now feel more confident thinking about the challenges of networked game. While networking surely can be tricky at times, it's also immensely cool and fun when it works - hopefully coherence will make you reach that point in no time! Our docs contain lots of information on how to proceed from here. Perhaps you should start by following a tutorial?
Using the same scene as in the previous lesson, we now take a look at another way to make Clients communicate: Network Commands. Network Commands are commonly referred to as "RPCs" (Remote Procedure Calls) in other networking frameworks. You can think of them as sending messages to objects, instead of syncing the value of a variable.
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
Q or D-pad up: Wave
Building on top of previous examples, let's now focus on two key player actions. Press Space to jump, or Q to greet other players. For both of these actions to play their animation, we need to send a command over the network to invoke Animator.SetTrigger()
on the other Client.
Like before, select the player Prefab located in the /Prefabs/Characters
folder, and browse its Hierarchy until you find the child GameObject called Workman.
Open the coherence Configure window on the third tab, Methods:
You can see how the method Animator.SetTrigger(string)
has been marked as a Network Command. With this done, it is now possible to invoke it over the network using code.
You can find the code doing so in the Wave
class (located in /Scripts/Player/Wave.cs
):
Analysing this line of code, we can recognize 5 key parts:
First, notice how the command is invoked on a specific CoherenceSync
(that sync
property).
We want to invoke this command on a component that is an Animator
.
We invoke a method called "Animator.SetTrigger".
With MessageTarget.Other
, we are asking to send this message only to network entities other than the one that has the CoherenceSync
we chose to use.
We pass the string "Wave"
as the first parameter of the method to invoke.
Because we don't invoke this on the one with authority, you will notice that just before invoking the Network Command, we also call SetTrigger
locally in the usual way:
An alternative to this would have been to call CoherenceSync.SendCommand()
with MessageTarget.All
.
In this example we used Network Commands to trigger a transition in an animation state machine, but they can be used to call any instantaneous behavior that has to be replicated over the network. As an example of this, it is also used in the Persistence lesson to change a number in a UI element across all Clients.
We have seen a lot of examples where objects belonging to a Client would disappear with them when they disconnect. We call these objects session-based entities.
But coherence also has a built-in system to make objects survive the disconnection of a Client, and be ready to be adopted by another Client or a Simulator. We call these objects persistent. Persistent objects stay on the Replication Server even if no Client is connected, creating the feeling that the game world is alive beyond an individual player session.
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
P or Right shoulder button: Plant a flower (hold to preview placement)
Players can plant flowers in this little valley. Each flower has 3 phases: starts as a bud, blooms into a full flower, and then withers after some time.
Creating a flower generates a new, persistent network entity. Even if the Client disconnects, the flower will persist on the server. When they reconnect, they will see the flower at their correct stage of growth (this is a little trick we explain later).
Planting too many flowers starts erasing older flowers. A button in the UI allows clearing all flowers (belonging to any player) at any time.
When using the plant action, any connected player instantiates a copy of the Flower Prefab (located in the /Prefabs/Nature
folder).
By selecting the Prefab asset, we can see its CoherenceSync
component is set up like this:
In particular, notice how the Lifetime property is set to Persistent. This means that when the Client who plants a flower disconnects, the network entity won't be automatically destroyed. Auto-adopt Orphan set to on makes it so the next player who sees the flower instantly adopts it, and keeps simulating its growth.
Opening coherence's Configuration window, you will see that we sync position, rotation, and a variable called timePlanted
:
When it gets instantiated, the flower writes the current UNIX timestamp into the timePlanted
variable. This variable never changes after this, and is used to reconstruct the phase in which the flower is in (see below). Similarly, as the flower is not moving, position and rotation are only synced at the time of planting.
Once a flower has spawned, all of its logic runs locally (no coherence involved). An internal timer calculates what phase it should be in by looking at the timePlanted
property and doing the math, and playing the appropriate animations and particles as a result.
coherence supports the ability to have an instance of the game active in the cloud, running some logic all the time (we call this a Simulator). However, this might be an expensive setup, and it's good advice to think things through differently to keep the cost of running your game lower.
To achieve this, the flowers of this scene store the Flower.timePlanted
value on the Replication Server. A Replication Server with no connected Clients is dormant, and has a very low cost to run. So when nobody's connected the flowers are not actually simulating, they are just waiting.
When a new Client comes online and this value is synced to them, they immediately fast-forward the phase of the flower to the correct value, and then they start simulating locally as normal.
This gives the players the perception that things are still running even when they are not connected.
This setup is not bulletproof, and could be easily cheated if a player comes online with a modified Client, changing the algorithm calculating the flowers' phase.
But for a game in which this calculation is not critical, especially if it doesn't affect other player's experience of the game, this can be a nice setup to cut some costs.
Every Client can, at any time, remove all flowers from the scene by clicking a button in the UI.
It's important to remember that you shouldn't call Destroy()
on a network entity on which the Client doesn't have authority on. To achieve this, we first request authority on remote flowers and listen for a reply. Once obtained it, we destroy them.
Check the code at the end of the Flower
script:
As we discussed in the Physics lesson, switching authority is a network operation that is asynchronous, so we need to wait for the reply from the player who currently has authority.
Every now and then it makes sense to parent network entities to each other, for instance when creating vehicles or an elevator. In this sample scene we'll see what are the implications of that, and how coherence uses this to optimize network traffic.
Moving platforms | Local positions | Parenting at runtime | Optimization
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
This wintery setting contains 2 moving platforms running along splines. Players can jump on them and they will receive the platform's movement and rotation, while still being able to move relative to the platform itself.
One important note: this sample describes parenting at runtime. For more information on edit-time parenting, see the page about Nesting Prefabs at Edit time.
This scene doesn't require anything special in terms of network setup to work.
Direct parenting of network entities in coherence happens exactly like usual, with a simple transform.SetParent()
. The player's Move
script is set to recognize the moving platforms when it lands on them, and it just parents itself to it.
As for the platforms, they are just moving themselves as kinematic rigid bodies, following the path of their spline (see the FloatingPlatform
script). Their position and rotation is synced on the network, and the first Client to connect assumes authority over them.
Once directly parented, coherence automatically switches to sync the child's position and rotation as local, rather than in world space. This means that when child entities don't move within their parent, no data about them is being sent across the network.
Imagine for instance a situation where 3 players are riding one of the platforms and not moving, only the coordinates of the platform are being synced every frame.
You might have noticed we always mentioned "direct" parenting. One limitation of this simple setup is that the parented network entity has to be a first-level child of the parent one. This doesn't exclude that the parent can have other child GameObjects (and other networked entities!), but networked entities have to be a direct child.
A hierarchy could look like this:
Platform
Player
Character graphics
Bones
...
Platform's graphics
...
(In bold is the root of each Prefab, which has a CoherenceSync
component)
You can even parent multiple network entities to each other. For example, a networked character holding a networked crate, riding a networked elevator, on a networked spaceship. In that case:
Spaceship
Elevator1
Elevator graphics
Elevator2
Player
Crate
Character graphics
Elevator graphics
Spaceship graphics
...
For cases like these, coherence takes care of them automatically. More complex hierarchies require a different handling, and we cover them in another lesson.
Getting updates about every entity in the whole scene is unfeasible for big-world games, like MMOs. For this, coherence has a flexible system for creating areas of interest, and getting updates only about the entities that each Client cares about, using a tool called Live Query.
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
This scene contains two cubes that represent areas of interest. Every connected Client can only see other players if they are standing inside one of these cubes.
Select one of the two GameObjects named LiveQuery. You will see they have a Coherence Live Query component:
This component defines an area of interest, in this case a 10x10x10 cube (5 is the Radius). This is telling the Replication Server that this Clients is only interested in network entities that are physically present within this volume.
If a Client has to know about the whole world, it's just enough to set the Live Query Radius to 0, to make it capture all updates.
Now it's clear why Transform.position
cannot be excluded from synchronization, as we saw in the first lesson. coherence needs to know where network entities are in space at all times, to detect if they fall within a Live Query or not.
In addition, Live Queries can be moved in space. They can be parented to the camera, to the player, or to other moving elements that denote an area of interest - depending on the type of game.
It is also possible, like in this scene, to have more than one Live Query. They will act as additive, requesting updates from entities that are within at least one of the volumes.
Notice that at least one Live Query is needed: a Client with no Live Query in the scene will receive no updates at all.
If you explored previous scenes you might have noticed that GameObjects with a Live Query component were actually there, but in this scene we gave them a special visual representation, just for demo purposes.
Try moving in and out of volumes. You will notice that network-instantiation takes care of destroying the GameObject representing a remote entity that exits a Live Query, and reinstantiates it when it enters one again.
Also, notice that the player belonging to the local Client doesn't disappear. coherence will stop sending updates about this instance to other Clients, but the instance is not destroyed locally, as long as the Client retains authority on it.
If a GameObject can be in a state that needs to be computed somehow, it might not appear correctly in the instant it gets recreated.
For instance, an animation state machine might not be in the correct animation state if it had previously reached that state via a trigger parameter. You would have to ensure that the trigger is called again when the instance gets network-instantiated (via a Network Command) or switch your state machine to use other type of animation parameters, which would be automatically synced as soon as the entity gets reinstantiated.
In this sample we look at how to network simple physics simulated directly on the Clients, and the implications of this setup.
If we were making a game that relied on precise physics at play between the players (like a sports match, for instance), we would probably go with a setup where the Clients connect to a Simulator that runs the physics and prevents cheating.
However, that makes running the game much more expensive for the developer, since a Simulator has to be always on.
Physics | Authority transfer | Uniqueness | Persistence
WASD or Left stick: Move character
Hold Shift or Shoulder button left: Run
Spacebar or Joypad button down: Jump
E or Joypad button left: Pick up / throw objects
This scene features a few crates that the players can pick up and throw around. Who runs the physics simulation here? You could say that everyone runs their part.
Let's take a closer look at the setup.
Select one of the crates in the scene. You can see that they have normal Box Collider
and Rigidbody
components. Up until a player is connected, they are being simulated locally. In fact if you press Play, they will fall down and settle.
The crates also have a CoherenceSync
component. The first player to connect gets authority over them, and begins simulating the physics for them.
That Client now syncs 5 values over the network, including the most important ones that will drive the crate's motion: Transform.position
and Transform.rotation
.
On other Clients however (the ones that connect after the first one) these crates will become "remote". Their Rigidbody will become kinematic, so that now their movement is controlled by the authority (i.e. the first Client).
At this point, the first Client to connect is simulating all the crates. However, if we were to leave things like this, interacting with physical objects that are simulated by another Client would be quite unpleasant due to the lag.
To make it better, other Clients steal authority over crates, whenever they either:
Touch/collide with a crate directly
Pick a crate up
In code, this authority switch is a trivial operation, done in a single line. You can find the code in the Grabbable
class. Essentially, it boils down to this:
As you can see, it's good practice to ask first if the requesting script already has authority over an object, to avoid wasted work.
If the request succeeds, the instance of the crate on the requesting Client becomes authoritative, and the Client starts simulating its physics. On the other Client (the previous owner) the object becomes remote (and its Rigidbody kinematic), and is now just receiving position and rotation over the network.
Careful! Since authority request is a network operation, you can't run follow-up code right away after having requested it. It's good practice to set a listener to the events that are available on the Coherence Sync component, like this:
This way, as soon as the reply comes back, we can perform the rest of the code.
Also note that while it's totally possible to configure an object so that Clients can just steal authority from each other, we configured the crates here to require an authority request.
When they want authority, Clients have to request it and most importantly, wait for an answer.
We implemented this request / answer mechanism to avoid problems of concurrency, where two players are requesting authority on a crate at the same time, and end up with a broken state because the game code assumes that they both got it.
So who is running the physics, after all? We can now say that it's everyone at the same time, as roles change all the time.
As we mentioned in the intro - in a simple game where precise physics are non-crucial this might be enough, and it will definitely keep the costs of running the game down, since no Simulator has to run in order to make the game playable.
As mentioned before, pressing Tab (or clicking the Joystick) switches to an authority view. It's very interesting to see how crates switch sides when a player interacts with them.
For more on authority, take a look inside the Grabbable
class. It has more code regarding authority events, all commented.
There is one important thing to note in this setup. Since the objects are already in the scene at the start, by default every time a Client connects it would try to sync those instances to the network. This is very similar to what we have seen with character instantiation so far: each Clients brings their own copy.
However, in this case this would effectively duplicate the crates, once online. One extra copy for each connected player! We don't want that.
For this reason, the CoherenceSync
is configured so that these crates have No Duplicates. This is generally the correct way of configuring networked Prefab instances that have been manually placed in the scene.
In addition to a unique identifier (the Manual Unique ID), coherence will auto-assign an additional identifier (the Prefab Instance Unique ID) whenever the crate is instantiated in the scene at edit time.
With these parameters in mind, the way the crates behave is as follows:
At the start, none of the entities exist on the Replication Server (yet).
Client A connects. They sync the crates onto the network. Being unique, the Replication Server takes note of their ID.
Client B connects. They try to bring the same crates onto the network, but because it is set to be No Duplicates and coherence finds there is already a network entity with the same ID, it doesn't create a new network entity but recognises that crate as the one on the server, and just makes it non-authoritative for Client B.
If Client A disconnects, the crates are not destroyed because their Lifetime is set to Persistent. They briefly become orphaned (no one has authority on them) but immediately the authority is passed to Client B due to the option Auto-adopt Orphan being on.
For more information on persistence, there's a whole lesson about it.
If everyone disconnects, the crates remain on the Replication Server as network entities that are orphaned. They keep whatever position/rotation they had, since nobody is simulating them anymore.
At this point, nobody is connected. The Replication Server is not doing any work.
When a new Client reconnects and tries to bring the crates online again, the same thing happens again: the crates in the scene are associated with the orphaned entities and are adopted by the new client, who assumes authority on them.
They will also most probably see the crates snap to the last seen position/translation that was stored on the Replication Server, which is synced just before they assume full control over the crates.
At this point, they start simulating their physics locally, like normal.
Advanced networking concepts
Once you have learned the basics using the First Steps tutorial project, Campfire is the natural follow-up to get acquainted with more advanced and practical topics.
As with First Steps, you can download the whole Campfire Unity project and explore it at your own pace. Instead of being a series of independent scenes, Campfire is one big scene that presents multiple concepts working together at the same time. We recommend using the pages on this section as guidance on the individual topics, starting with getting acquainted with the game structure.
The Unity project can be downloaded from its Github repo. The Readme will tell you the minimum Unity version to use.
To quickly try out the game, we shared a WebGL build on the coherence Cloud. You can play it directly in the browser, or download one of the available desktop versions. Share the link with friends and colleagues, and try it together!
To play as a regular Client, make sure that the GameObject called Simulator is disabled in the scene Main:
Without it, the game will behave as a pure Client and spawn a player character on connection.
If you want to make a game build, simply having that object off will produce a Client build. You can run many Client builds to experience multiplayer gameplay.
In this project, there is an NPC that is supposed to be controlled by the Simulator (the Keeper Robot). Though this is intended to be a server-side behavior, you can actually make it run locally and play as a player at the same time without modifications to the code.
First, enable the Simulator GameObject in the scene.
Now press Play and connect.
The robot will start acting, exactly like it would do if it were running on a Simulator (minus, of course, the network delay). This allows you to see what would be happening on the server, with the full debugging power of the Unity Editor.
You can even use this Editor instance running alongside one or more Client builds.
To create a Simulator build, you have two ways to go about it, as usual:
building a Simulator to launch locally on your machine
building one to upload on the coherence Cloud
In both cases, make sure that the Simulator GameObject is enabled in the scene.
Don't change the Keeper Robot's Simulate In property like described in the previous section, since to run this behavior on the Simulator we want it to stay Server Side.
For more information, refer to the Simulators: Build and Deploy page.
Before we dive into the networking-specific topics, in this introductory page we'll quickly go over how the whole gameplay is structured and set up. We'll cover it both from a point of view of Prefabs and of code so you know where to look for what.
WASD: Move | Shift: Sprint | Spacebar: Jump | E: Pick up/throw, Chop trees, Sit/stand | C: Random appearance | 1: Wave emote | 2: Dance | 3: Yes emote | 4: No emote | Enter: Show chat/send message | Esc: Cancel chat
Left stick: Move | Left trigger: Sprint | Button south: Jump | Button west: Pick up/throw, Chop trees, Sit/stand | Button east: Random appearance | D-pad up: Wave emote | D-pad down: Dance | D-pad left: Yes emote | D-pad right: No emote | Select button: Show/hide chat | Start button: Send chat
You'll find the Player Prefab in Prefabs/Characters
.
When connecting, an instance of the Player is instantiated in the scene by the PlayerHandler
script, which listens to the corresponding event fired by CoherenceBridge
.
The player character is a Rigidbody-driven kinematic capsule that is hovering above the ground slightly, and detecting the ground via a raycast. Movement values are provided by the Move
script on its root, which is in turn informed by the PlayerInput
component. When instantiated over the network both these components are disabled, and the Rigidbody is set to be kinematic.
Besides movement, other actions are controlled by scripts on three child GameObjects: Interactions, Emotes, and Chat.
When pressing the interaction key, the right action will be carried on by one of the scripts ChopAction
, SitAction
, and GrabAction
, depending on the type of the object highlighted (a ChoppableTree
, a Chair
, or a Grabbable
).
The chat system is described here. Other actions are described below.
The Player Prefab builds on the structure and functionality of the one used in the First Steps tutorial project, adding more actions. If you find it complex to dive into, try exploring that version first.
The trees have an Interactable
script that indicates which mesh gets highlighted.
They have an amount of energy that determines the number of times they need to be chopped to be cut down. When they run out, they transition to a chopped state and spawn a tree log. A coroutine makes them spring out again after a certain amount of time.
Read more about how characters interact with remote trees in this page about dealing with a non-authority object.
The campfire is at the center of this demo. Players can burn anything they can pick up by simply throwing the object into it. The campfire exists only in one instance and is pre-placed in the scene, and marked as unique on the network by setting the Uniqueness property of its CoherenceSync
to No Duplicates.
Most of the logic of the campfire is in the Campfire
component. This handles a lot of the networking flow, and can be run by a Client but, if a Simulator connects, they will take over.
In addition to calculating which fire effect to display, it's also in charge of replicating the sound of burning an object on all Clients (read more about effects here).
Learn more about the campfire's logic on this page.
They are all Prefab Variants of a base Prefab called Base_BurnableObject, which you can inspect to get a sense of the common functionality.
The objects have several scripts: Grabbable
provides the ability for them to be picked up, carried and thrown, while Burnable
grants the ability to be burnt on the campfire.
They have a collider at the root which determines collisions, but a child GameObject named Interaction (and its Interactable
script) has the trigger collider that makes it interactive, and allows to pick the object up. The Interactable
script also holds a reference to the objects to highlight when the player's interaction trigger intersects the object.
The logs that are spawned when chopping down trees are not unique, and they are set to Allow Duplicates. Check this page for more info on the logs and how they are recycled using an object pool.
Instead, the other burnable objects are pre-placed in the scene, and set to be unique (No Duplicates): the banjo, the cooler, the bins, the mushrooms, and more. More details on the lifetime of these pre-placed objects in the section below.
The Keeper Robot is an NPC designed to be run by a Simulator (aka, the "server"), to restore the campsite to its initial state even when no-one is connected.
Its script will cycle through all unique campfire objects every X seconds. If an object has been destroyed, it will recreate it and put it in its place. If it has been moved, it will just chase it down and put it back into its place.
The way the robot knows about destroyed objects is because the objects, when created the first time, spawn an invisible marker (that we call an "object anchor") which the robot can inspect to know which object has disappeared, and where it was originally placed. The page about custom instantiation has more info on these objects and their anchors.
Read more about this server-side NPC works on its dedicated page.
Sitting is one of the three actions that can be performed by interacting with objects. It doesn't have networking effects, so it's not covered in this tutorial pages.
Object pooling | CoherenceSyncConfigRegistry | CoherenceSyncConfig
Network entities need to be created and removed all the time. This can be due to entities getting in and out of a LiveQuery, or simply because gameplay requires so. If that is the case, we can leverage coherence's object pooling system in order to avoid costly calls to Instantiate
and Destroy
, which are famously expensive operations in Unity.
In this project we use pooling for one very clear use case: the tree logs that get spawned when chopping down a tree.
This was a natural choice as players will be chopping trees all the time, but we can also assume that they will burn the logs on the fire almost as often. So by pre-allocating a pool of around 10 logs, we should be covered in most cases.
To set up the log to behave like this, all we did was to set that option on the log's own CoherenceSync
inspector.
A pool configured like this means that coherence will pre-spawn 10 instances of the Prefab at the beginning of the game.
However if we were to need more, we could request more instances and they would be created and added to the pool. The game can even go above 20. If that were to happen, any instance released beyond 20 wouldn't just be returned to the pool, but would be destroyed.
In other words, 10 and 20 represent the lower and upper limit for the amount of memory we are reserving for the logs alone in our game. We are considering anything above 20 as a temporary exception.
When we press Play, coherence instantiates these 10 logs, deactivate them, and put the pool in the DontDestroyOnLoad scene:
Because they are inactive, their CoherenceSync
components are not syncing any value.
To spawn a new log we only need to call one line of code. However, we don't provide a reference to a regular Prefab like we would with Instantiate
. We instead leverage the CoherenceSyncConfig
object that represents the log.
This CoherenceSyncConfig
contains all the info that coherence needs to handle this particular Prefab over the network. If we inspect it, we will notice that it contains in fact how the object is loaded (Load via) and how it's instantiated (Instantiate via).
You can notice how this is the same info we saw while configuring the CoherenceSync
before.
Now that we have a reference to it, we can spawn the log with one line of code. In the ChoppableTree
script, we do something like:
This line looks remarkably similar to Unity's own Instantiate
in its syntax. The difference is that it gives us back a reference to the CoherenceSync
attached to the log instance that will be enabled. From this, we can do all sorts of setup operations by just fetching other components with GetComponent
, to prepare the instance.
When we are done with it (in this case, when it's thrown into the campfire), we can dispose of it:
(this line is in the Burnable.cs
class, inside the GetBurned()
method)
The instance is then automatically returned into the pool, and disabled.
When taking an instance out of the pool or when returning it, coherence doesn't automatically do any particular clean up to its state.
As such, when we reuse a pool instance, it is good practice to think of what values should be reset that might have been messed up by previous usage. We should think about what happens during gameplay, and use OnEnable
/ OnDisable
as needed to ensure that disabled instances are put in a state that makes them ready to be used again.
For this project, since an object can be burned while being carried, we do some cleaning in the OnDisable
of the Grabbable.cs
class to prepare the wood logs for another round, like so:
| |
In a networked game, an object's logic is always run by one node on the network, whether it's a Client or a Server (which we call a in coherence). We say that the node "has authority" on the network entity.
There are cases where it makes sense to transfer authority, like it happens in this project with objects that can be picked up. When the player grabs an object, the Client performing it requests authority over the network entity. Once it gets authority it starts running its scripts and has full control over it. This is a very good way to go when only one player can interact with a certain object at a given time.
For more info, check in the First Steps project.
However, there are cases when we don't want to change who has authority on an entity. In the case of an object that many players can interact with at the same time, it wouldn't make sense to continuously move authority between nodes.
The interaction with such remote entities then needs to happen entirely through .
In this project, it is the case of the chairs placed in the scene. The first Client or Simulator to connect will take authority over them, and it will keep it until they disconnect.
When a player wants to sit down on a chair, they inform the Authority that they are doing so. The client holding authority will then set the chair as busy, which prevents other players from sitting on it next time they try.
However, for the sake of simplicity and to illustrate the point, we intentionally left this interaction a bit flaky. Can you guess why? What could go wrong with this setup?
The action originates in SitAction.cs
:
SitAction
checks if the isBusy
property of the chair is set to true
(by the authority, of course). If so, it means someone else is already sat on the chair. If false
, we can sit. So it invokes Chair.Occupy()
.
And further down, the essence of the interaction:
So both when occupying a chair (Occupy()
) or standing up (Free()
), the player executing the action invokes the ChangeState
method, either directly or as a Network Command - depending if they are the one with authority.
So one way or the other, ChangeState
gets executed on the authority, who sets the isBusy
property to its new value. On the next coherence update, the property will be sent to the other Clients.
The answer: Clients are using the isBusy
property as a check for whether they can sit or not. It is possible that two players will approach a chair at the same time, check if isBusy
is false (and yes, it will be false), at which point they will inform the authority that want to sit down on it.
The authority performs no additional checks, so you will see both players successfully sitting on the chair, overlapping on each other.
Thankfully we also coded the rest of the interaction so that this doesn't break the game. So while this incidence and the consequences for this interaction are low-risk, if you're looking to create a more robust system it could make sense to implement a check on the authority, and have the Client wait for an answer before they sit down.
Game characters and other networked entities are often made of very deep hierarchies of nested GameObjects, needing to sync specific properties along these chains. In addition, a common use case is to parent a networked object to the tip of a chain of GameObjects.
Let's see how to handle these cases.
|
A/D or Left/right joypad triggers: Rotate crane base
W/S or Left joystick up/down: Raise/lower crane head
Q/E or Left joystick left/right: Move crane head forward/back
P/Space/Enter or Joypad button left: Pick up and release crate
This scene features a robotic arm that can be controlled by one player at a time. In the scene, a small crate can be picked up and released.
The first player to connect takes control of the arm, and other players can request it via a UI button.
To demonstrate complex hierarchies we choose to sync the movement of a robot arm, made of several GameObjects. In addition to syncing several positions and rotations, we also sync animation variables and other script parameters, present on child objects.
To sync the whole arm we use a coherence feature called deep bindings, that is bindings that are located not on the root object, but deeper in the transform hierarchy.
Select the RobotArm Prefab asset located in /Prefabs/Characters
, and open it for editing. You will immediately notice a host of little coherence icons to the right of several GameObjects in the Hierarchy window:
These icons are telling us that these GameObjects have one or more binding currently configured (a variable, a method, or a component action).
Now open the coherence Configuration window, and click through those objects to discover what's being synced:
In addition to position and rotation, we also choose to sync the animation parameter ClawsOpen, and enable Animator.SetTrigger()
as a Network Command. Finally we disable the Robot Arm script when losing authority (to disallow input).
This is the base of the robot arm, for which we only sync rotation:
We don't sync the rotation of every object in the chain, since the arm is equipped with an IK solver, which allows us to just sync the target (Two-Bone IK_target) and work out the rotation of the limb (robotarm_bottomarm and robotarm_toparm) on each Client:
By syncing all of these properties, we can have the robotic arm move in sync on all Clients, simply by translating the tip of the IK, and rotating the base of the crane. All of the bindings in this hierarchy are synced through the Coherence Sync component present on the Prefab's root object RobotArm.
As you can see, using deep bindings doesn't require any special setup: they are enabled in exactly the same way as a binding, a Network Command, or a Component action is enabled on the root GameObject.
The Path property displays the location in the hierarchy where this object will be inserted. It gets automatically updated by coherence every time the object is parented. Each number represents a child in the root object (and it's 0-based).
Once we have this component set up, parenting the object only requires calling Transform.SetParent()
like any usual parenting operation, and setting its Rigidbody
component to be kinematic.
When we do this, coherence takes care of propagating the parenting to other Clients, so that the crate becomes a child GameObject on every connected Client.
This code is in the RobotArmHand
class, a component attached to the tip of our hierarchy chain: GrabPoint. In OnTriggerEnter
we detect when the crate is in range, storing a reference to it in a variable of type Transform
named grabbableObject
.
This reference is set to sync:
When the player presses the key P (or the Left Gamepad face button), the referenced crate is parented to the GrabPoint GameObject.
Note that coherence natively supports syncing references to CoherenceSync
and Transform
components, and to GameObjects.
Even if the Robot Arm Hand script is disabled on non-authoritative Clients, it references the correct grabbed crate in the grabbableObject
variable due to it being synced over the network. So when its authority disconnects, other Clients will already have the correct reference to the crate network entity.
This allows us to gracefully handle a case where, for instance, a Client picks up the crate and disconnects. Because both the crate and the robot arm have Auto-adopt Orphan set to "on", authority is passed onto another Client and they immediately have all the data needed to keep handling the crate.
To move authority between Clients, we can use the UI in the bottom left corner. The button is connected to the Robot Arm Authority script on the ArmAuthoritySwapper GameObject, and it transfers authority on both the robot arm and the crate. This script takes care also of what happens as a result of the transfer, including setting the crate to be kinematic or not.
Is Kinematic is set as follows:
The code is in the RobotArmAuthority
class. To detect whether it's currently being held, it's as simple as checking whether its Transform.parent
is null
:
Remember you can use Tab/click the Gamepad stick to use the authority visualization mode. Try requesting authority from another Client while in this mode.
Chat |
Communication is an inherent part of online games and a chat, however simple, is a great way to enhance the range of expression for the players.
We wanted to implement a very simple chat system. By pressing Enter, a small screen-space UI opens up and allows the player to compose a message. When they press Enter again, a balloon on top of their character displays the message to them, and to all connected Clients.
This is done in three parts.
The Chat
script on the player reads the input, requests ChatComposerUI
to display the chat composer that is part of the screen-space scene UI.
When the player sends a chat message, Chat
is informed by an event sent by ChatComposerUI
, and sends a Network Command SendChatMessage
to all other clients.
Finally, the received message is displayed in world-space over the player's head the script ChatVisualiserUI
present in the Player Prefab.
By default, coherence's Network Commands have a limit in the length that can be sent in one command. This is limited by the length of a UDP packet. While this limitation might be removed in the future, for now it means that chat messages can't be longer than a certain amount.
This amount, however, is quite different depending if you use a parameter of type string
or of type byte[]
(byte array). If you send a string
, you will be able to pass on around 50 characters. This is really not much for a chat system.
If you use byte[]
though, the number of characters goes up to (around) 500. Now we're talking!
So what we do in this demo is that first we convert the string
that the player has typed in the UI into a byte array, and we send that via Network Command:
Then, on the receiving side, we reconvert it back into a string
:
This simple trick allows us to send longer messages, or to send the same message generating less traffic.
Because we are sending the chat messages on the CoherenceSync
that is on the Player Prefab, it means that if that particular player instance is not visible to a Client because it's outside of their LiveQuery, they won't receive the Network Command and thus the chat message. This is maybe desirable in this demo, where the chat is visualised on top of the player.
This page talked about a simple chat system to use during gameplay, but keep in mind that coherence also has a solution for long-form chats as part of Lobby rooms. Players can be in a lobby before but also during gameplay.
| |
It's often the case that in addition to objects being fully owned by players, like their characters, there is often the need to have objects that exist only in one copy in the world and that need to store a complex state that needs to be reflected in the same way on each Client. And the state might not be a simple int
or bool
that can be just automatically synced over the network whenever it changes, but something more complex that requires to be elaborated.
This is often the case for more invisible objects like a leaderboard, a spawn point, a score counter or a match timer; but can also be the case for objects that have graphics.
An example of such an object, that also happens to be very central to this demo, is the campfire. As the players pick up objects and throw them on the fire, the campfire needs to perform a calculation based on a timer and the type of the object burned to decide which fire effect to play.
Timing is key here! If two players throw in two objects, one right after the other, they activate a special effect that makes the campfire burn bigger and brighter. But the two objects need to get on the fire within 2.5 seconds from each other (it's the teamEffortLength
variable in the Campfire.cs
script).
Because this calculation depends on the timer value that is managed by the Authority, we can't just independently calculate a result on each Client, as they would almost certainly end up with different results. We need to inform the Authority that the action is taking place, let it figure out the final state, and only then propagate the resulting state and actions to all Clients.
(1) Action happens on a Client -> (2) Authority campfire is notified, processes result -> (3) Authority campfire sends result to all others -> (4) Non-authority campfire objects execute local effects
We do have an extra challenge here though. Ultimately we want the Authority to inform everyone to play specific visual and sound effects depending on the object burned. But we can't send Network Commands with a reference to audio assets or particle systems. So we need to change this information to something we can send, and then on the receiving end, "unpack it" and transform it into the info we actually need (i.e., which sound).
If you look into the Campfire.cs
script, you will find this sequence of actions as exemplified by the flow below:
(1) The player throws an object on the fire. BurnObjectLocal()
is invoked by the Burnable
that collided with the Campfire
. The script checks if Authority is already on this Client:
The method invoked in both cases is BurnObject()
, but it's invoked differently depending on whether it is local (direct invocation) or remote (using SendCommand
via the CoherenceSync
).
We use the ID of the CoherenceSyncConfig
of the object that burned as a parameter. The ID is a string, so it's something we can send over the network.
(2) The logic for which fire effect to play is then calculated in BurnObject()
.
The campfire uses the CoherenceSyncConfig
ID as a key to look into the CoherenceSyncConfigRegistry
, and find the right object archetype to play the right effect.
(3) ChangeFireState()
is invoked locally on the Authority. Here the Authority updates its own property activeFireEffect
which, being a synced property, gets sent to the other Clients.
But updating that int wouldn't be enough to tell which sound to play, so we send a command to invoke the FireStateChanged()
method, passing the CoherenceSyncConfig
ID which the non-authoritative campfire instances can use to trace down the object that burned in the CoherenceSyncConfigRegistry
.
(4) The non-authoritative clients execute FireStateChanged()
, which turn on/off the appropriate fire particles, and play a specific sound.
If the Client (or a Simulator) detaining the authority on the campfire disconnects, we need to make sure that whoever gets assigned authority next can pick up the job exactly where it was left off, and continue simulating the campfire logic without interruption.
That's why in the Campfire.cs
class we make sure to sync three values:
activeFireEffect
is an index (expressed as an integer) of which fire effect should be playing right now.
fireTimer
and bigFireTimer
are two countdowns that indicate how much time the fire will still burn normally or, when in "big fire mode", brighter.
However, there's an opportunity to be smart here. fireTimer
and bigFireTimer
are variables that are updated every Update on the Authority, but they are only useful in case the Authority gets transferred. So what we can do using the Optimization panel is to reduce the frequency they are sent to other Clients to a much more manageable value of once every second.
This might not be very precise and would have been unacceptable in the case of a visible timer, but here it doesn't matter. To the players this is going to be invisible, but we avoid a lot of network traffic.
As mentioned before, this mini state-machine behavior can run perfectly on one of the connected Clients. There is one catch though: this way, if no one is connected, the fire will stop updating because no one is simulating it, and thus it will never burn out.
Try this: connect, throw an object on the fire, disconnect, and reconnect after some time. The value of fireTimer
will still be the same and so the fire will still be burning no matter how much time has passed.
Using an Authority transfer, it is trivial to let this behaviour run on a Simulator if there is one connected. Look into the Campfire
class, within OnLiveQuerySynced
:
With this simple code, whenever a Simulator connects and sees the persistent campfire network entity, it will take Authority over it. If it were ever to go offline and a client is connected, that Client would take back Authority. If the Simulator comes back online, it would steal it again. And so on.
While this is not a cheat-proof solution, it can be useful for various scenarios.
Having a behavior set up this way allows the Prefab and its logic to be used in an offline mode without modification (because the offline player would act as the owner Client). This can be useful to create a free demo version; a tutorial mode; or even to showcase the game in conditions of limited connectivity.
You could launch the game with no Simulators to run a game preview while keeping costs down, like during an Early Access or a Steam festival. Later on when it goes live, the game could be switched to use a Simulator, and no change to the code would be required.
Object lifecycle | | Runtime Unique IDs
In many cases, creating and destroying GameObjects like usual will be enough. Just call Instantiate()
or Destroy()
, and coherence takes care of instantiating and destroying the appropriate Prefab instance on each connected Client.
However, there are moments when it makes sense to customize how exactly coherence does this. To take full control over the lifetime of the object, or to attach custom behavior to these events.
coherence provides by default (and we use one too), but for ultimate control we also have the ability to create new, completely custom ones.
The campsite in this demo has a few pre-placed unique objects in the scene, that can be picked up, moved, and burned on the campfire.
Until the comes in and recreates them, they will not be replaced.
When we burn them, we could in theory just destroy the instance. However the burn code is deeply nested in the Burnable.cs
class which is used not only by these unique objects, but also by the pooled and non-unique wood logs.
In this method we do this:
However, by default unique network entities also get disabled, not destroyed. This doesn't work for our special objects!
We could potentially add an if
statement in the GetBurned()
above, detect if the object being destroyed is a log or not, and act differently based on that. Or subclass the Burnable
and implement overrides for GetBurned
...
... or we can just create a custom instantiator, and take full control of the object's lifecycle. Let's see the code.
Creating a custom instantiator is trivial. We just need a class to implement the interface INetworkObjectInstantiator
, like so:
The key parts of this script being that on network entity creation a simple Object.Instantiate()
is performed, and on release Object.Destroy()
. The other methods (omitted here) are actually empty.
We also want to prepend the class with the DisplayName
attribute so it shows up in the dropdown when we configure a CoherenceSync
. Now the UniqueBurnableObjects instantiator appears alongside the others in the Instantiate via dropdown:
That's it, the instantiator is ready to use.
When we call ReleaseInstance()
now, it will act differently depending on which instantiator the Prefab is configured to use: the wood logs get disabled, but the unique campfire objects get destroyed.
This was a very simple use case for customization, but it illustrates how easy it can be to get in control of the lifetime of Prefab instances associated to network entities.
One interesting thing we do with anchors is that they are themselves unique objects, but because they are spawned at runtime, they need to get their unique ID dynamically at runtime.
The code is in the PersistentObject
class:
We take the ManualUniqueId
from the object spawning it (i.e., "Boombox"), and we combine with the string "-anchor" to create a new unique ID, "Boombox-anchor". We register this ID to the UniquenessManager
of the CoherenceBridge
to inform it that the next spawned network entity will have that ID. And then we simply call Instantiate()
.
Because they are set to be Persistent, even though a player has burned something and disconnected, the anchors stay on the Replication Server. When a Simulator connects it will find these placeholders and, thanks to the synced properties, will know exactly what to recreate and where to put it.
The check code is in KeeperRobot.cs
, under CheckAnchors()
and ActOnAnchor()
.
First, each anchor's isObjectPresent
property is used for a quick scan. This property is synced.
If the object is still present, the robot needs to get a reference to it. It calls GetLinkedObject()
on the anchor, which does this:
Once again using the UUID of the object this anchor is a placeholder for (holdingForUUID
) as a key, we can now ask the UniquenessManager
to retrieve an object that has that UUID.
With a reference to this, the robot can now put it back into place using the anchor's position and rotation as a reference.
And if the object has been destroyed (isObjectPresent
is false), the robot proceeds to recreate it.
After that, like we saw before, the robot registers the newly recreated object with the UniquenessManager
so that it has the same UUID that it had before being burned.
The object is reinstated, and to a new Client connecting, it will look exactly the same as if it never got removed.
| Flexible authority
Even when creating a game that is mainly client-driven, we can still run some of the code on a Simulator. This is very useful to create, for instance, an NPC that operates even when all Clients (players) are disconnected, to give a semblance of a living world.
In this project we used this pattern for the little yellow robot that sits beside the camp. If players move one of the camp's key objects out of place, the robot will tidy up after them. It can even recreate burned objects out of thin air!
Because this behavior is run by a Simulator, even if no-one is connected, given enough time all objects will be back in their place.
To setup the robot Prefab to be run by a Simulator couldn't be simpler. The only thing we need to do is to set the Simulate In property of the CoherenceSync
to Server Side.
We also set both the KeeperRobot
script and the NavMeshAgent
to disable on remote instances from the coherence Configuration panel, so they automatically turns themselves off on Client machines.
Note that the GameObject named "Simulator" is disabled by default in the demo scene. When creating a Simulator build, you need to enable it before building, or the robot won't appear in the Simulator (and hence, on Clients).
Besides the simple state machine code that runs it, only one thing is worth noting here.
The exact moment when the robot starts acting is not in Start
like usual. We imagined this behavior for an always-on world, so that it could start acting even long after other Clients disconnected. To ensure this, we hook into the onLiveQuerySynced
event of the CoherenceBridge
:
This way, the Simulator has the time to sync up with whatever happened to the campfire objects on the Replication Server, before even beginning to act.
This means that while gameplay can benefit from the presence of this NPC, it's not dependent on it. The Simulator can be always online, or connect and disconnect at times, or to be online only at certain times of the day, and so on.
Coding behaviors like this can open up many creative possibilities in the game's design.
One typical pattern here is to wrap any server-side logic in the conditional compilation directive #if COHERENCE_SIMULATOR
. This is a great idea especially if the code needs to be obfuscated to normal Client builds, because by doing so, it won't be compiled in the Client at all.
We did it, but we were careful to leave some things out:
As you can see, we left out the 4 Network Commands used to play sounds, and the properties they need to do it. The idea here is that the authoritative instance of the robot, which is running the logic on the Simulator, instructs the non-authoritative instances to play sounds when needed.
Remember that disabling a script only prevents Unity functions to be called (Awake
, Start
, Update
...), but it doesn't prevent invoking its methods.
Besides the above, wrapping synced variables or Network Commands inside a pre-compiler directive would hide them from coherence schema baking, effectively creating a different schema for the Simulator, which would then not be able to connect to the RS.
Make sure you keep all data of this type out of the #if
, so that both Client and Simulator bake the same schema.
Finally, you might have noticed how we not only compile this code for Simulator builds, but also when in the Unity editor:
This allows us to quickly test the behavior of this robot without adding and removing compilation directives. By simply changing the Simulate In property of the CoherenceSync
to Client Side, we can hit the Play button and see the robot move, as if a Simulator was connected.
This is a great way to speed up development and one of the advantages of coherence's flexible authority model: you don't need to code a behavior in a special way to change it from Client to Server side and vice versa.
It is good practice though to switch the robot to Server Side again at regular intervals, and test the game by making an actual Simulator build, in order to create the whole network scenario with all its actors.
This will help locate bugs that have to do with timing, connection speed, authority transfers, etc.
Networked audio | Networked particles | Animation Events
Usually, visual feedback can be expressed via syncing variables like Animator parameters, positions, and rotations. But sometimes we have the need to play sounds and particles, which are not types that can be automatically set to sync, or that we can send as arguments of . So how to do it?
This project has a lot of moments where particles and sounds need to play, and we used different strategies for different cases, depending on how fast, repeated, or slow the action is.
The most straightforward solution to play a sound is to use a Network Command. Using Commands, you can remotely invoke methods on AudioSource
or ParticleSystem
components.
To do that, you could simply open the coherence Configuration panel (from the CoherenceSync
), and check the methods you're interested in.
While this is a perfectly fine way of doing things, it requires you to call multiple Network Commands in case you wanted to play a sound and particles at the same time. This could lead to desynchronisation between sound and visuals.
As such, in this project we preferred compacting these calls into methods on their own that are invoked as one Network Command, often without parameters to minimize the data being sent across.
Connected to the above, let's see how to create our own Network Commands to play sounds (or particles) as a result of an event that happened remotely.
For these sounds, we isolated the sound-playing behavior into Commands of their own. At the end of the KeeperRobot.cs
class, we have:
(soundHandler
is a script attached to the same gameObject)
Each of these methods is invoked as a Network Command, like so:
You can see how we don't play the sound over the network, that would be bandwidth-consuming for no reason, but we just communicate the intention to play it.
Because we only have 4 sounds, we sort of "brute-forced" this, and created an individual Network Command for each sound. This is not a bad idea from the point of view of network traffic: sending a Network Command with no parameter produces less traffic than sending one with.
But it could be unwieldy if we had - say - 100 different sounds to play.
This solution also requires us to bake and produce a new schema if we add or remove one of these Commands. So for a more flexible solution, it could be nice to index the sounds and maybe create a generic Command like:
In this case though, it was ok to go for individual Commands.
There are actions that are really quick or short, and asking to play a sound via a command might result in a mismatch between the visuals (an animation) and the sound, due to network delay.
For instance, it wouldn't make sense to send a Command to inform other Clients to play the sound of a footstep. Chances are, by the time they receive the Command, another two-three footsteps have happened.
A script called PlayAnimationEvents.cs
(remember to add it to the same object as the Animator
!) listens to these events. An example from it:
This ensures an immediate playback, in sync with the animation. Plus, it produces zero network traffic.
So yes, fun fact: to "network" sounds and particles often you can do without networking anything at all!
One more trick! If you have a state machine blending several clips, you might hear multiple overlapping sounds when a transition happens. One less known trick is to measure the weight of each clip while executing Animation Events, like we do below:
Secondly, open the KeeperRobot prefab contained in Prefabs/Characters
. On the CoherenceSync
component, change its Simulate In property to Client Side.
When approaching an object that can be interacted with, the InteractionInput
script does the work of detecting objects that have an Interactable
script, and highlights them by changing their layer. This makes them render with an additional outline, as per one of the passes in the URP Renderer Renderer_WorldUI, contained in Settings
.
The prefab for the interactive tree is in Prefabs/Interactive
. The log that is spawned by it is in Prefabs/Interactive/Burnables
.
The campfire Prefab is in Prefabs/Interactive
.
All non-static interactive objects are in Prefabs/Interactive/Burnables
.
You'll find the robot Prefab in Prefabs/Characters
.
You will find chairs in Prefabs/Interactive/Chairs
.
Check the Log prefab in Prefabs/Interactive/Burnable/
:
This Sync Config can be found in the coherence/
folder, and is a sub-object of another ScriptableObject: the CoherenceSyncConfigRegistry
.
You will find the code of chairs in Chair.cs
, located in Scripts/Objects
. Looking into it, we find the property used as a gate:
We do this in other parts of the demo, like when chopping a tree or when picking up an object. Check the following to explore this similar but more complex use case.
One important note: this sample describes deep parenting at runtime. For more information on edit-time deep parenting, see the page about .
As mentioned in the lesson about , parenting a network entity to a GameObject that belongs to a chain requires some setup. To be able to pick up the crate with the crane, we equip it with a CoherenceNode
component:
Similarly to the crates in the , we don't just want the crate to automatically become non-kinematic when we have authority on it. We want the crate to stay kinematic when authority changes while it's being held by the arm.
On the authority Client | On non-authoritative Clients |
---|
But if chat messages are shown in a UI panel and players should receive them all regardless, then it might make more sense to rely on a special type of CoherenceSync
: . By sending the Network Command on that, it would ensure that the Command is sent and received regardless of LiveQuery ranges.
Read the for more info.
For more information about Lobbies, read .
This is in a way similar to . The event flow is very similar:
Right now, we are looking at things in the context of a setup where Authority on the campfire is on one of the Clients. It is totally possible to give the Authority to a Server (and in fact we do in this project, see of this page), but the actual logical process doesn't change at all.
For more info on CoherenceSyncConfig
check out .
For more info on CoherenceSyncConfigRegistry
check out .
So, as long as a Simulator is connected, the campfire will keep burning
A simple ReleaseInstance()
does the trick for the logs which are non-unique objects. They just go back into the .
If you're curious about this code, you can check out the file in the coherence package folder in io.coherence.sdk/Coherence.Toolkit/CoherenceSyncConfigs/ObjectInstantiators
and open DefaultInstantiator.cs
API Reference for INetworkObjectInstantiator
can be found .
The first time these special unique objects come online, they spawn a persistent invisible object we call "object anchor". This object holds the original position and rotation of the object, so that the can come in at a later time and put the recreated object back into its place. You could think of these objects as placeholders.
Using the anchor's syncConfigId
as a key, it looks in the CoherenceSyncConfigRegistry
and finds the archetype to recreate. This is similar to how we used the registry as a catalogue .
By just doing so, when you start the game as a Client, the robot GameObject will be deactivated. But if starting as a Simulator (instructions are ), it will run.
The code for the robot is all contained in the KeeperRobot.cs
class, in Scripts/Robot
.
For instance, the has a series of voices that play whenever it is performing an action. The robots is always controlled by the Simulator, so we need to play sounds on the Clients' devices.
So for footsteps, jump, landing, and more; we used a slightly different strategy. Audio and particles are all played locally as part of the animation, using Unity's own .
Is being held | true | true |
Has been released | false | true |
Fighting games come in many shapes and forms. Usually they involve 2 or more players fighting each other. The players can kick, punch, block, grab and trigger intricate combos for extra damage. Often this type of game relies on quick reactions to the opponent's movement.
For a fighting game, we need a reliable game state regardless of user ping. It needs to be deterministic. For example, if two players press the kick button a few milliseconds apart, the Simulator needs to be able to figure out which player is the one doing the kicking, and which is the one getting kicked. Our solution is called input queues and the setup is described in great detail here. The key idea is that only the input is being processed by the Client, and the Simulator is responsible for deciding the outcome. The Simulator stores a queue of inputs, which is then used to decide on the correct order of actions.
If you want to synchronize more than just the root of the Game Object, e.g. if you want to have precise replication of ragdoll on all Clients, you need to create bindings to more that just the root transform. We support deep bindings which allow you to select any object in the hierarchy and synchronize whatever is needed.
In most MMOs you control a character, interact with other players and group up with other players to clear dungeons. Here's a few networking considerations for anyone creating something similar.
In a MMO the world needs to be persistent. Any given user can join and leave a world at any given time and we want their changes to be persistent. Which NPCs was killed, which treasures were looted, which items are available on the action house, etc. In order to achieve this, you need to run a World Replication Server in the coherence Cloud which will make sure the world state is saved even if no players are logged in.
Given that there can be a large amount of players distributed over a very large area, we don't really care about the ones in all the different areas. By not sending information about players far from the player itself, we can significantly limit the amount of data sent over the network. When using coherence you can use the LiveQuery Component to set a bounding box in which we replicate data from networked entities - anything outside of it is ignored.
Even within a LiveQuery bounding box there might be further room to optimize. When having a large amount of networked entities, you might want to prioritize those that are closer. Our solution to this is called Level Of Detail (LOD). Using this you are able to control values such compression, value range and sample rate in order to hit the optimization sweet spot.
To make sure that all users experience an identical game world at any given time, we need a Simulator to be responsible for taking decisions for the AI, triggering events, etc. In coherence we support launching your game with any number of Simulators taking responsibility for the various parts of your game.
A common part of all MMO's are instanced areas where a smaller group of players enters together and completes a set of tasks. It does not make much sense to run these instances as a part of the World Replication Server. The idea is that it can be possible to spin up a separate server for each group who enters such an instance. In coherence we offer this through the Room Replication Server. This allows you to have a shared world, as well as any number of instances for a specific area for a subset of players.
Racing games involve multiple vehicles racing a number of laps. They can be realistic or arcade-y, but the end goal is always the same: crossing the finish line first.
In multiplayer racing games it is vital to have as precise information about the position of other clients as possible. The server and the players both need to know details like if its possible to overtake in a curve, if players bumped into each other, and even more importantly - who crossed the finish line first. We need to have server authority, but each Client also needs reliable predictions about where the other players are.
First-Person Shooters (FPS) are games where multiple players join opposing teams and shoot each other. You will often win by either eliminating the opposite team, exploding a bomb, or running out the timer.
Good communication between players is often essential in winning. Serious players will have voice communication, but its also good to have in-game comms to easily communicate tactics. In coherence there is the concept of Client Connections which you can use to easily send messages between players. It can also be used to communicate game state changes e.g., "The bomb has been planted".
When building your level there may be certain objects that should be duplicated across Clients. You want to have a duplicate of each player on every Client, but for something like doors which can be opened or closed, you only want one "shared" door across all Clients. For this to happen you need to understand Uniqueness and Lifetime. Using those concepts, you can make sure that a given objects is persistent on the scene, and that only one exists.
Similar to doors, the bomb is also unique. The difference is that the bomb is spawned and only 1 bomb can exist in the game at any time. Doors are also unique, but multiple instances of a door asset can exist, just not at the same place. To understand more, read about Setting up a global counter. This is the same principle of having a Prefab that is uniquely identified.
Online competitive multiplayer games are tricky to get exactly right, and there is no "right" solution. It's a constant tradeoff between cheat protection, latency, client prediction, etc. You need to do further research to decide on the solution that best suits you. One thing you definitely need to learn about is client vs server authority.
For turn-based games, the requirements for networking can be quite different from other, more fast-paced games. You are only interested in changes to the game state, and don't really need more granularity than that. Let's take chess as an example.
You don't really need a player character so in order to process input you don't really need a CoherenceSync object. You could use a Client Connection Prefab if you want to have an easy way to implement chat.
You might want to have a Simulator to process everything if you want cheat protection, but for a game such as this it could also be viable to simply opt for client-side simulation and then have one of the Clients have authority over the game controller. That is the default setting when adding a CoherenceSync Component. Each client can then talk to the game controller using Commands.
Authority | Authority transfer | Network Commands
We saw in the previous section about sitting on chairs how sometimes it makes sense not to move authority around between Clients. At this point, Network Commands are the way to interact with a remote object.
Now let's take a look at another case of remote object, where the interactions with it need to be validated by the one holding authority, to avoid nasty cases of concurrency.
In this project, it is the case of the trees that are placed in the scene. The first Client or Simulator to connect will take authority over them, and it will keep it until they disconnect.
When a player wants to chop a tree, they request the Authority to subtract 1 unit of energy. When the energy runs out, it's the Authority that spawns a new Log instance.
This centralization, as opposed to passing authority around, allows multiple players to chop the same tree at the same time and prevents many race conditions, because the important action (destroying the tree and spawning the log) is all resolved on the Client with Authority.
Conceptually, we can imagine the event flow to go like this:
(1) Chop action happens on a Client -> (2) Authority is notified, elaborates new state -> (3) Authority sends result to all others -> (4) All other Clients play out animation and effects
You can find this flow in practice in the ChoppableTree.cs
script. In this script, only one variable is synchronized, the energy of the tree:
The flow goes like this:
(1) A player presses the button to chop down the tree.
It locally invokes the method TryChop()
, which checks if the tree hasn't been already chopped down, subtracts energy locally, and also invokes the Chop()
method, locally or remotely depending if authority on this tree is here or not.
(2) On the Authority, the Chop()
method is called, and checks if the tree needs to be effectively cut down based on its energy:
(3) If so, CutDown()
is invoked locally, spawning the log and informing all other clients to play the animation of the tree disappearing:
(4) Finally, other Clients play animation, particles and sound locally in ChangeState()
. They will also see the log spawn thanks to the automatic network-instantiation.
Why do we subtract energy from a synced variable in TryChop()
when we are not the Authority?
Ultimately, the final word on whether the tree has been chopped down completely is always on the Authority's side, of course. But by subtracting energy locally and immediately, we can deal with cases where the player manages to produce two or more chop inputs before the Network Command has travelled to the Authority (and back) with a result.
Imagine: the tree has 1 energy. If we didn't subtract energy locally, the player would be able to chop several times because until the Authority tells them that the tree is down, they still think it has 1 energy.
In fact, it would send several Chop()
Network Commands for no reason, which the Authority would have do discard on arrival.
Instead, if we immediately change the value on the variable and we use it as an indication of whether we can chop or not this will stop the chopping after one hit, as it should be.
Soon, the Authority will have elaborated on its side that the tree has gone down, and will inform our Client (with ChangeState()
). Because energy
is a synced variable, it will be overwritten again with the value computed on the Authority - which of course will be 0 at this point, so it will match.
So nothing is lost and no state is compromised, but with this little trick we get immediate feedback and we avoid some unneeded network traffic.
Now we can build the project and try out network replication locally. This example will show you how to launch a local Replication Server and connect multiple instances of the game to it.
You can run a local Replication Server from:
coherence > Local Replication Server > Run for Rooms / Run for Worlds
As with most features found in the menu, you can find local Replication Server functionality in the coherence Hub as well.
Open the Replication Servers tab and run a Room or a World Replication Server:
Regardless of how you launch it, a new terminal window will open and display the running Replication Server:
Whether you run one for Rooms or for Worlds depends on which setup you plan to use, which in turn requires the correct corresponding Sample UI.
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.
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 Unity by just hitting Play).
Click Connect in the connection UIs on both clients. Now, try focusing on one and using WASD 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 exploring the documentation to take advantage of more advanced coherence features.
In this section, we will learn how to prepare a Prefab for network replication.
Setting up basic syncing is explained in this video, from 1:00 and onwards:
You can let the coherence Hub guide you through your Prefab setup process. Simply select a Prefab, open the GameObject tab in the coherence Hub (coherence > coherence Hub) and follow the instructions.
You can also follow the detailed step-by-step text guide below.
CoherenceSync
to your GameObjectFor 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, we add the CoherenceSync
component to Cube.
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:
If you wish to start networking a Prefab that already exists in your project, you have several options to do it. Remember that they all lead to the same result.
You can:
Click on the Sync with coherence checkbox at the top of the Prefab inspector.
Manually add the CoherenceSync
component.
Drag the Prefab to the CoherenceSync Objects window. You can find it in coherence > CoherenceSync Objects.
One way to configure your Prefab instead of just adding CoherenceSync
to 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) Prefab and add CoherenceSync
to it:
Learn how to create and use Prefab variants in the Unity Manual.
This way, you can retain the original Prefab untouched, and build all the network functionality separately, in its own Prefab.
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.
The Prefab variant inherits the network settings from its parent, and you change those with overrides in the Configuration window. When a synced variable, method or component action is present in the variant and not in the parent, it will be bolded and it will have the blue line next to it, just like any other override in Unity:
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.
In its Inspector you can configure settings for Lifetime (Session-based or Persistent), Authority transfer (Not Transferable, Request or Steal), Simulate In (Client Side, Server Side or Server Side with Client Input) and Adoption settings for when persistent entities become orphaned, and more.
There are also a host of Events that are triggered at different times.
Its Inspector has quite a number of settings. For more information on them, refer to the CoherenceSync
page.
For now, we can leave these settings to their defaults.
CoherenceSync
allows you to automatically network all public variables and methods on any of the attached components, from Unity components such as Transform
, Animator
, etc. to any custom script, including scripts that came with the Asset Store packages that you may have downloaded.
To set it up, click on the Configure button in the CoherenceSync
's Inspector. This brings up the Configuration window. Here you can select which variables you would like to sync across the network:
You will notice that position is already selected, and can't be removed. For our use case, let's add rotation and scale.
Note that you can configure variables, methods and components not only on the root, but also on child GameObjects.
Close the Configuration dialog.
This simple movement 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:
Wait for Unity to compile the file, then add it to the Prefab.
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 other Clients, where this object is not authoritative, but replicated? We want the position to be replicated over the network without keyboard input interfering with it.
In the Configuration window, click the Components tab:
Here you will see a list of Component Actions that you can apply to non-authoritative entities 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:
This ensures that, if a copy of this Prefab is instantiated on a Client with no authority, this script will be disabled and won't interfere with the position that is being synced on the network.
Once everything is setup, you should ensure to run the process of Baking: coherence will produce the necessary netcode (i.e. a bunch of C# scripts) to ensure that when the game is running and the Client connects, all of the properties and network messages you might have configured will correctly sync.
This process is very quick, and can be done in different ways:
From the menu item coherence > Bake
Within the coherence Hub, in the Baking tab, using Bake Now:
When a Prefab contains changes that need baking, its Inspector will warn you. Pressing Bake here will actually bake all code for all Prefabs:
In short, setting up a Prefab for networking requires:
A Prefab with a CoherenceSync
added to it
Optionally, setting some of its properties
Configuring what to sync in the Configure window
Disabling components on remote entities, in the Configure > Components tab
Baking the netcode
This is it! Setting up a Prefab to be networked doesn't require additional steps.
Now you can run this setup locally or using the coherence Cloud.
Helper scripts and samples can be found here. These cover things such as:
Spawning a player
Basic inputs to get Game Objects moving
Score keeper
Displaying player names
Camera facing UI
Off-screen spawning of enemies or other Game Objects
Indicator (arrow) for guiding the player towards off-screen Game Objects
Implementing Network Commands and Authority Transfer
Connection Events
It's quick and easy to set up a networked scene from scratch using the coherence SDK.
The topics of this page are covered in the first minute of this video:
Preparing a scene for network synchronization requires to add three fundamental objects:
coherence > Scene Setup > Create CoherenceBridge
This object manages the connection with coherence's relay, the Replication Server, and is the centre of many connection-related events.
coherence > Scene Setup > Create LiveQuery
Creates a LiveQuery, which allows to define what are of the world the application is interested in when requesting data 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.
You don't have to define a range for the LiveQuery. Leaving the range to 0 means that the range is infinite, so nothing is filtered out.
coherence > Explore Samples
A Connect dialog UI provides an interface to the player to connect to the Replication Server, once the game is played. You can create your own connection dialog, but we provide a few samples as a quick way to get started.
If you're unsure about which dialog to add, choose Rooms for now. You can read more about Rooms and Worlds.
If you're unsure about which dialog to add, choose Rooms for now. You can read more about Rooms and Worlds.
The connection dialog samples we provide are here for you to completely customize, you can read more in the section dedicated to Samples.
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.
To open it, go to coherence > coherence Hub
coherence currently supports Unity. For custom engine integration,. For updates regarding Unreal Engine support, please check the page.
Latest are officially supported.
The minimum supported Unity LTS versions are:
2021.3.18+
2022.2.5+
Name: coherence
URL: https://registry.npmjs.org
Scope(s): io.coherence.sdk
Now open Window > Package Manager. Select My Registries in the Packages dropdown.
Highlight the coherence package, and click Install.
Edit <project-path>/Packages/manifest.json
.
Add an entry for the coherence sdk on the dependencies
object, and for the scoped registry in the scopedRegistries
array:
You will then see the package in the Package Manager under My Registries.
When you successfully install the coherence SDK the Welcome Window will show.
The coherence package comes with several samples you can choose to add to your project. Each provides Prefabs and scripts that you can add to your Scene and edit however you want.
There are currently 3 samples available: a , a , and a Lobby connection dialog.
The difference between Rooms and Worlds is explained on this page: , while have somewhat of a different role in that they are usually used in addition to Rooms in a game flow.
Each sample comes with a Prefab that can be added to your Scene. You can add them via coherence > Explore Samples.
Effectively these do two things for you :
Import the sample in the Samples directory of your project, if it isn't already.
Add the Prefab from the sample to your Scene.
Int the example above, that would be Room Connection Dialog.prefab
.
The Rooms Connect Dialog has a few helpful components that are explained below.
At the top of the dialog we have an input field for the player's name.
Next is a toggle between Cloud and Local. You can switch to Local if you want to connect to a Rooms Server that is running on your computer.
Next is a dropdown for region selection. This dropdown is populated when regions are fetched from the coherence cloud. The default selection is the first available region. This is not enabled when you switch from Cloud to Local. This is also only relevant if you deploy your game to several different regions.
Next is a dropdown of available Rooms in the selected region (or in your local server if using the Local mode).
After selecting a Room from the list the Join button can be used to join that Room.
If you know someone has created a room but you don't see it, you can manually refresh the rooms list using the Refresh button.
The Create a room section adds a Room to the selected region.
This section 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 Dropdown above. Create and Join will create the Room, and also join it 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.
If you start a local World server, it will appear as LocalWorld
.
Samples are copied to your assets folder, this means you can change and customize the scripts and Prefabs however you like.
Future versions of coherence won't override your changes. If you upgrade to a newer version of coherence and import a new sample, they will be imported in a separate folder named after the coherence version.
If you want the new sample to overwrite the old one, first rename the folder in which the samples are, then import the new version.
If you notice that the samples are non-responsive to input, make sure you have an EventSystem component in the scene.
Defines a network entity and what data to sync from the GameObject. Anything that needs to be synchronized over the network can use a CoherenceSync component. You can select data from your GameObject hierarchy that you'd like to sync across the network.
Queries an area of interest, so that you can read/write across the network on the desired location. 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 Simulators, the LiveQuery could be attached to the playable character or camera.
Handles the connection between the coherence transport layer and the Unity scene.
Enables a Simulator to take control of the state authority of a Client's CoherenceSync, while retaining input authority.
This component is added by CoherenceSync on .
Quicker iteration during development
When developing multiplayer experiences you will need to run multiple instances of your game in order to test properly. You also need to make sure these instances can be restarted quickly, so you can iterate quickly.
coherence does not have a built-in solution for multi-client testing, but there are several options available to you, each with their own benefits and drawbacks.
The easiest method is to simply create a new build each time you want to test anything. You can launch any number of instances of that build, and have an instance running in the Editor as well.
Benefits
Easy to distribute amongst team members.
Well-understood workflow.
Drawbacks
Long iteration time as you need to continuously make builds.
Harder to debug the executables.
is an open-source project which allows you to open multiple Unity Editor instances, all pointing to the same Unity project (using Symbolic links).
Benefits
Short iteration times.
Easy debug because it's possible to inspect GameObjects in multiple instances.
Drawbacks
Requires you to have multiple Unity Editors open.
Caveats
EditorPrefs are not consistently shared between Editors.
Open ParrelSync -> Clones Manager. Create a new clone, and open it.
Continue development in the original Unity Editor. Never edit files in the clone Editors.
Unity has an experimental package called Multiplayer Play Mode (MPPM) available for 2023.1. As this is currently experimental, we do not officially recommend it - but it does show some promise and should be mentioned. This package allows a single Editor to run several instances of a game.
Benefits
Short iteration times.
No issues with schema incompatibility.
Drawbacks
Experimental.
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.
At this point, you can create a free account, which will grant you a number of credits that are more than sufficient to go through developing and testing your game in the cloud.
Open the coherence Hub window. Then open the coherence Cloud tab.
After pressing Login you will be taken to the login page. Simply login as usual, and return to Unity.
You are now logged into the Portal through Unity. Select the correct Organization and Project, and you are ready to start creating.
Now we can finally deploy our schema and Replication Server to the coherence Cloud.
In this example we're working with Worlds. Make sure you have created a World before trying to deploy the Replication Server. To create a World, follow the steps described in .
The topics on this page start from around 1:00 in the video below:
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.
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.
You can now build the project and send the build to friends or colleagues for testing.
If you used one of the Connect Dialog samples, once you play the game it will 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 Dashboard).
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.
For quick and easy testing, we suggest trying out the publish to WebGL option. Anyone with the link can then try the build in a browser.
Keep in mind to add the description of game controls though!
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.
If you want an example of WebGL builds, try out our sample projects or (make sure to use Chrome!)
First, you need to build your game to a local folder on your computer as you normally would. Ensure to bake before doing so!
In the coherence Hub window, select the coherence Cloud tab.
You can upload your build from the Share Build section of the tab. Select the platform, browse for the previously-created build, and click on the Begin Upload button.
Now that build has been uploaded, you can share it by enabling and sharing the public URL on the coherence Cloud Dashboard:
Here you can customise the page to a degree. Don't forget to include instructions in the description, if your game doesn't have any!
By unchecking the Enabled option, you can obscure the page altogether, without having to remove builds.
Click on the Game Builds tab to manage builds for different platforms.
If you uploaded a WebGL build, the public link now allows for instant play directly in the browser:
If you uploaded builds for other platforms, they will be downloadable by clicking on the icons right below the WebGL build.
First, go to Edit > Project Settings. Under , add a new Scoped Registry with the following information:
Refer to Unity's instructions on .
The folder to rename is the one that is named after the version number (normally its path would be something like Samples/coherence/1.1.0/
for coherence 1.1.0).
All instances of the game must have identical schemas, so you might want to ensure you are using Assets rather than Source Generator so the netcode is synced across editors automatically. If you do intend to use Source Generator, you need to remember to bake in each open Unity Editor. In that case, it might make sense to enable Bake Automatically On Enter Play Mode in coherence settings. You can change both of these from the coherence Hub > Baking, or in Project Settings > coherence.
First, install the ParrelSync package as described in the .
When you need to test, ensure the netcode is for all editors.
In your web browser, navigate to . Create an account or log into an existing one.
If the status does not say "In Sync", or if you encounter any other issues with the server interface, refer to the section.
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.
For a guide on how to use LiveQuery, see Areas of Interest.
More complex areas of interest types are coming in future versions of coherence.
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.
For a guide on how to use TagQuery, see Areas of Interest.
This page describes the order of various coherence events and scripts in relation to Unity's main loop.
Check out ScriptExecutionOrder.
Additionally, take a look at your project's Script Execution Order settings by opening Edit > Project Settings and selecting the Script Execution Order category. See this Unity manual article for more details.
Depending on the reason for a disconnection the onDisconnected
event can be raised from different places in the code, including LateUpdate
.
When a Prefab instance with CoherenceSync is created at runtime, it will be fully synchronized with the network in the OnEnable method of CoherenceSync. This means that you can expect your custom Components to have fully resolved synchronized values and authority state in your Awake method. It occurs in the following order:
Awake() is called
Internal initialization.
OnEnable() is called
Synchronize with a new or existing Network Entity.
OnBeforeNetworkedInstantiation event is invoked.
Initial component updates are applied (for entities you have no authority over).
OnNetworkedInstantiation event is invoked.
OnStateAuthority or OnStateRemote (for authority or non-authority instances respectively) event is invoked.
Awake() is called
At this point, if you get the CoherenceSync component, you can expect networked variables and authority state to be fully resolved.
Instead of hard referencing Prefabs in your scripts to instantiate them, you can reference a CoherenceSyncConfig and instantiate your local Prefab instances through our API. This will utilize the internal INetworkObjectProvider and INetworkObjectInstantiator interfaces to load and instantiate the Prefab in a given networked scene (a scene with a CoherenceBridge component in it).
You can also hard reference the Prefab in your script, and use our extensions to instantiate the Prefab easily using the internal INetworkObjectInstantiator interface implementation. The main difference is that the previous approach doesn't need a Prefab hard reference, and you won't have to change the code if the way that the Prefab is loaded into memory changes (for example, if you go from Resources to load it via Addressables).
The CoherenceSyncConfigRegistry
is a ScriptableObject that will hold a list of references to your existing CoherenceSyncConfig
objects.
CoherenceSyncConfig
is a separate ScriptableObject that holds the configuration for each of your networked objects.
Since the Registry needs to keep a list of references, in coherence we offer three different ways you can handle your CoherenceSyncConfigRegistry
asset:
Sub Assets: The CoherenceSyncConfig
assets will be saved as sub-assets of the CoherenceSyncConfigRegistry
asset.
This option generates the least amount of assets on the disk but it is highly susceptible to version control conflicts.
Standalone Assets: The CoherenceSyncConfig
assets will be saved separately in the Assets/coherence/baked folder, the CoherenceSyncConfigRegistry
will simply keep a list of references to them.
This options creates more assets on the disk but it is less susceptible to version control conflicts.
Dynamic Standalone Assets: The CoherenceSyncConfig
assets will be saved separately in the Assets/coherence/baked folder. The CoherenceSyncConfigRegistry
will not be saved to disk but it will be generated on-demand in memory.
This option is recommended for large development teams, to avoid version control conflicts.
coherence can sync the following types out of the box:
bool
int
uint
byte
char
short
ushort
float
string
Vector2
Vector3
Quaternion
GameObject
Transform
RectTransform
CoherenceSync
SerializeEntityID
byte[]
long
ulong
Int64
UInt64
Color
double
RectTransform
is still in experimental phase - use at your own discretion!
How to parent CoherenceSync objects to each other
Out of the box, coherence offers several options to handle parenting of networked entities. While some workflows are automatic, others require a specific component to be added.
Generally there is a distinction if the parenting happens at runtime vs. edit time, and whether the two entities are direct parent-child, or have a complex hierarchy. See below for each case.
At runtime:
CoherenceSyncs as a direct child: when you create a parent-child relationship of CoherenceSync
objects at runtime.
Deeply-nested CoherenceSyncs: when you create a complex parent-child relationship of CoherenceSync
objects at runtime.
At edit time:
Nesting connected Prefabs assets: the developer prepares several connected Prefabs and nests them one to another before entering Play Mode. This covers both Prefabs in the scene and in the assets.
The CoherenceSync
component will help you prepare an object for network synchronization. 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, including third-party Asset Store packages that you may have downloaded.
Refer to the Prefab setup page to learn how to configure your Prefab to network state changes.
The Bridge establishes a connection between your scene and the coherence Replication Server. It makes sure all networked entities stay in sync.
When you place a GameObject in your scene, the Bridge detects it and makes sure all the synchronization can be done via the CoherenceSync
component.
At runtime, you can inspect which Entites the Bridge is currently tracking.
A Bridge 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.
The CoherenceBridge offers a couple of Unity Events in its inspector where you can hook your custom game logic:
This event is invoked when the Replication Server state has been fully synchronized, it is fired after OnConnected.
For example, if you connect to a ongoing game that has five players connected, when this event is fired all the entities and information of all the other players will already be synchronized and available to be polled.
This event is invoked the moment you stablish a connection with the Replication Server, but before any synchronization has happened.
Following the previous example, if you connect to an ongoing game that has five players connected, when this event is fired, you won't have any entities or information available about those five players.
This event is invoked when you disconnect from a Replication Server. In the parameters of the event you will be given a ConnectionCloseReason value that will explain why the disconnection happened.
This event is invoked when you attempt to connect to a Replication Server, but the connection fails, you will be returned a ConnectionException with information about the error.
The Client Connections system allows you to keep track of how many users are connected and uniquely identify them, as well as easily send server-wide messages.
You can read more about the Client Connections system here.
If you have a Developer Portal account, you can connect to Worlds or Rooms hosted in coherence Cloud. You can use the CloudService instance from CoherenceBridge to fetch existing Worlds or create or fetch existing Rooms, after you fetch a valid World or Room, you can use the JoinWorld or JoinRoom methods to easily connect your client.
You can read more about the Unity Cloud Service here.
Currently, the maximum number of persistent Entities supported by the Replication Server is 32 000. This limit will be increased in the near future.
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.
Once a CoherenceSync
is added to a Prefab, you can select which individual public properties you would like to sync across the network, expose methods as Network Commands, and configure other network-related properties.
To start syncing variables, open the Configure window that you can access from the CoherenceSync
's Inspector.
Any components attached to the GameObject with CoherenceSync
that have public variables will be shown here and can be synced across the network.
To start syncing a property, just use the checkbox. Optionally, choose how it is interpolated on the right.
Network Commands are public methods that can be invoked remotely. In other networking frameworks they are often referred to as RPCs (Remote Procedure Calls).
To mark a method as a Command, you can do it from the Configure window in the same way described above when syncing properties by going to the second tab labelled "Commands".
For more info, refer to the page about messaging with Commands.
When an entity is instantiated in the network, other Clients will see it but they won't have authority on it. It is then important to ensure that some components behave differently when an entity is non-authoritative.
To quickly achieve this, you can leverage Component Actions, which are located in the Components tab of the Configure window:
The sections above describe UI-based workflows to sync variables and commands. We also offer a code-based workflow, which leverages [Sync] and [Command] C# attributes directly from within code.
(You can notice in the screenshot above how the isBeingCarried
property is synced in code, and displays the [Sync]
tag in front of its name.)
The two workflows can be used together, even on the same Prefab!
You can also create your own, custom Component Actions.
When you create a networked GameObject, you automatically become the owner of that GameObject. That means only you are allowed to update its values, or destroy it. But sometimes it is necessary to pass ownership from one Client to another. For example, you could snatch the football in a soccer game or throw a mind control spell in a strategy game. In these cases, you will need to transfer ownership over these Entities from one Client to another.
When an authority transfer request is performed, an Entity can be set up to respond in different ways to account for different gameplay cases:
Not Transferable - Authority requests will always fail. This is a typical choice for player characters.
Steal - Authority requests always succeed.
Request - This option is intended for conditional transfers. The owner of an Entity can reply to an authority request by either accepting or denying it.
Approve Requests - The requests will succeed even if no event listener is present.
Note that for Request, a listener to the event OnAuthorityRequested
needs to be provided in code. If not present, the optional parameter Approve Requests can be used as a fallback. This is only useful in corner cases where the listener is added and removed at runtime. In general, you can simply set the transfer style to Steal and all requests will automatically succeed.
Any Client or Simulator can request ownership by invoking the RequestAuthority()
method on the CoherenceSync
component of a Network Entity:
A request will be sent to the Entity's current owner. They will then accept or deny the request, and complete the transfer. If the transfer succeeds, the previous owner is no longer allowed to update or destroy the Entity.
The Helper scripts page has a custom implementation of authority transfer.
When a Client disconnects, all the Network Entities created by that Client are usually destroyed. If you want any Entity to stay after the owner disconnects, you need to set Entity lifetime type of that Prefab to Persistent.
Session Based - the Entity will be removed on all other Clients, when the owner Client disconnects.
Persistence - Entities with this option will persist as long as the Replication Server is running. For more details, see Configuring persistence.
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, one can use APIs (specifically, Adopt()
) or – more conveniently – enable Auto-adopt orphan on the Prefab.
Allow Duplicates - multiple copies of this object can be instantiated over the network. This is typical for bullets, spell effects, RTS units, and similar repeated Entities.
No Duplicates - ensures objects are not duplicated by assigning them a Unique ID.
Manual Unique ID - You can set the Unique ID manually in the Prefab, only one Prefab instance will be allowed at runtime, any other instance created with the same UUID will be destroyed.
Prefab Instance Unique ID - When creating a Prefab instance in the Scene at Editor time, a special Prefab Instance Unique ID is assigned, if the manual UUID is blank, the UUID assigned at runtime will be the Prefab Instance ID.
Manual ID vs. Prefab Instance ID
To understand the difference between these two IDs, consider the following use cases:
Manager: If your game has a Prefab of which there can only be 1 in-game instance at any time (such as a Game Manager), assign an ID manually on the Prefab asset.
Multiple interactable scene objects: If you have several instances of a given Prefab, but each instance must be unique (such as doors, elevators, pickups, traps, etc.), each instance created in Editor time will have a auto-generated Prefab Instance Unique ID. This will ensure that when 2 players come online, they only bring one copy of any given door/trap/pickup, but each of them still replicates its state across the network to all Clients currently in the same scene.
Defines which type of network node (Client or Simulator) can have authority over this Entity.
Client Side - The Entity is by default owned by the Client that spawns it. It can be also owned by a Simulator.
Server Side - The Entity can't be owned by a normal Client, but only by a "server" (in coherence called Simulator).
Server Side with Client Input - This automatically adds a CoherenceInput
component. Ownership is split: a Simulator holds State Authority, while a Client has Input Authority. See Server Authoritative setup for more info.
You can hook into the events fired by the CoherenceSync
to conveniently structure gameplay in response to key moment of the component's lifecycle. Events are initially hidden, but you can reveal them using the button at the bottom of the Inspector called "Subscribe to...".
Once revealed, you can use them just like regular UnityEvents
:
You can also subscribe to these events in code.
You might also want to check out the CoherenceSync instance lifecycle section at the bottom of the Order of execution article.
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<AssetIdOfThePrefab>
. This class will be instantiated at runtime, and will take care of networked serialization and deserialization, instead of the built-in reflection-based one.
You can find more information on the page about Baking.
Binding to variables and methods within the hierarchy
If a synced Prefab has a hierarchy, you can synchronize variables, methods and component actions for any of the child GameObjects within its hierarchy.
Note: on this page we cover children GameObjects or nested Prefabs that don't have their ownCoherenceSync
.
If a child object has a CoherenceSync
of their own, they become an independent network entity. For that, see the Parenting section.
When the Configure window is open it will show the variables, methods and component actions available for synchronization for your currently selected GameObject.
First, make sure to be editing the Prefab in Prefab Mode:
Once in Prefab Mode and with the Configure window open, shift the selection to any of the GameObjects that belong to the hierarchy.
The Configure window will be updated automatically, showing you everything that is available to be synchronized on the child GameObject:
That's it!
Syncing properties, methods and component actions on child GameObjects doesn't require any different flow than what you usually do for the root object. They all get collected and networked as part of one single network entity.
After your changes to GameObject, don't forget to Bake again to rebuild the netcode for the entity.
Make sure to not destroy child GameObjects that have synced properties, or you will receive a warning in the Console. To destroy a synced object, always remove the root.
(you can totally destroy children that don't have any synced property)
Commands are network messages sent from one CoherenceSync to another CoherenceSync. Functionally equivalent to RPCs, commands bind to public methods accessible on the GameObject hierarchy that CoherenceSync sits on.
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.
By clicking on the method, you bind to it, defining a command. The grid icon on its right lets you configure the routing mode. Commands with a Send to Authority Only
mode can be sent only to the authority of the target CoherenceSync, while ones with the Send to All Instances
can be broadcasted to all clients that see it. The routing is enforced by the Replication Server as a security measure, so that outdated or malicious clients don't break the game.
To send a command, we call the SendCommand
method on the target CoherenceSync
object. It takes a number of arguments:
The generic type parameter must be the type of the receiving Component. This ensures that the correct method gets called if the receiving GameObject has components that implement methods that share the same name.
Example: sync.SendCommand<Transform>(...)
If there are multiple commands bound to different components of the same type (for example, your CoherenceSync hierarchy has five Transforms, and you create a command for Transform.SetParent on all of them), the command is only sent to the first one found in the hierarchy which matches the type.
The first argument is the name of the method on the component that we want to call. It is good practice to use the C# nameof
expression when referring to the method name, since it prevents accidentally misspelling it, or forgetting to update the string if the method changes name.
Alternatively, if you want to know which Client sent the command, you can add CoherenceSync sender
as the first argument of the command, and the correct value will be automatically filled in by the SDK.
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:
If you have the same command bound more than once in the same Prefab hierarchy, you can target a specific MonoBehaviour when sending a message, you can do so via the SendCommand(Action action) method in CoherenceSync.
Additionally, if you want to target every bound MonoBehaviour, you can do so via the SendCommandToChildren method in CoherenceSync.
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.
While commands by default carry no information on who sent them in order to optimise traffic, you can create commands that include a Client ID as one of the parameters. Then, on the receiving end, compare that value with a list of connected Clients.
You can create your own implementation for these IDs or, more simply, use coherence's built-in Client Connections feature.
Sometimes you want to inform a bunch of different CoherenceSyncs 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 command will get sent to each CoherenceSync under the state authority of this Client. To make it only affect CoherenceSyncs within certain criteria, you need to filter to which CoherenceSync you send the command to, on your own.
Some of the primitive types supported are nullable values, this includes:
Byte[]
string
Entity references: CoherenceSync, Transform, and GameObject
Refer to the supported types page.
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.
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.
When a Client receives a command targeted at AuthorityOnly
but it has already transferred an authority of that entity, the command is simply discarded.
In this page we will learn about how coherence handles loading CoherenceSync Prefabs into memory and instantiating them when a new remote entity appears in the network. You will also learn how you can hook your own asset loading and instantiation systems seamlessly.
Whenever you start synchronizing one of your prefabs, either by adding the CoherenceSync component manually or clicking the Sync with coherence toggle in the prefab inspector, coherence will automatically create a CoherenceSyncConfig object that will be added to the CoherenceSyncConfigRegistry asset found in the Assets/coherence folder.
This CoherenceSyncConfig object allows us to do the following:
Hard reference the prefab in Editor, this means that whenever we have to do postprocessing in synced prefabs, we don't have to do a lookup or load them from Resources.
Serialize the method of loading and instantiating this prefab in runtime.
Soft reference the prefab in Runtime with a GUID, this means we can access the loading and instantiating implementations without having to load the prefab itself into memory.
All your CoherenceSync prefabs will have a related CoherenceSyncConfig object, you can inspect all your prefabs in the CoherenceSync Objects window, found under the coherence => CoherenceSync Objects menu item:
You can also manually inspect your CoherenceSyncConfig objects by selecting the CoherenceSyncConfigRegistry asset in Assets/coherence/CoherenceSyncConfigRegistry.asset:
You can also find your related CoherenceSyncConfig in the inspector of the CoherenceSync component, you can directly edit your Config from here:
This option allows you to specify how this prefab will be loaded into memory in runtime, we support three default implementations, or you can create your own. The three default implementations are Resources, Direct Reference or Addressables, these three will be automatically managed by coherence and you won't have to worry much about them.
Resources loader will be used if your prefab is inside a Resources folder, if you wish to use any other type of loading method, you will be prompted to move the prefab outside of the Resources folder.
This loader will be used if your prefab is outside of a Resources folder, and the prefab is not marked as Addressable. This means that we will need to hard reference your prefab in the CoherenceSyncConfig, which means it will always be loaded into memory from the moment you start your game.
Addressables
This option is only available if you have the Addressables Unity Package installed.
This loader will be used if your prefab is marked as an Addressable asset, and it will be soft referenced using Addressables AssetReference class.
You can implement the INetworkObjectProvider interface to create your custom implementations that will be used by coherence when we need to load the prefab into memory.
Custom implementations can be Serializable and have your own custom serialized data.
Implementations of this interface will be automatically selectable via the Load via option in the CoherenceSyncObject asset.
This option allows you to specify how this prefab will be instantiated in runtime, we support three default implementations, or you can create your own. The three default implementations are Default, Pooling or DestroyCoherenceSync.
This instantiator will create a new instance of your prefab, and when the related network entity is destroyed, this prefab instance will also be destroyed.
This instantiator supports object pooling, instead of always creating and destroying instances, the pool instantiator will attempt to reuse existing instances. It has two options:
Max Size: maximum size of the pool for this prefab, instances that exceed the limit of the pool will be destroyed when returned.
Initial Size: coherence will create this amount of instances on app startup.
This instantiator will create a new instance for your prefab, but instead of completely destroying the object when the related network entity is destroyed, it will destroy or disable the CoherenceSync component instead.
You can implement the INetworkObjectInstantiator interface to create your custom implementations that will be used by coherence when we need to instantiate a pefab in the scene.
Custom implementations can be Serializable and have your own custom serialized data.
Implementations of this interface will be automatically selectable via the Instantiate via option in the CoherenceSyncObject asset.
CoherenceSync direct parent-child relationships at runtime
Objects with the CoherenceSync
component can be connected at runtime to other objects with a CoherenceSync
component to form a direct parent-child relationship.
For example, an item of cargo can be parented to a vehicle, so that they move together when the vehicle is in motion.
Keep in mind that on this page we deal with direct parenting of two CoherenceSync
GameObjects. If it's not practical to parent a network entity directly to the root of another, see instead how to deeply nest CoherenceSyncs.
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.
Parenting network entities directly doesn't require any extra work. Any parenting code (i.e. Unity's own transform.SetParent()
will work out of the box, without any need for additional action.
You can add and remove parent-child relationships at runtime – even from the Unity editor, by drag-and-drop.
If the child object is using LODs, it will base its distance calculations on the world position of its parent. For more info, see the Level of detail documentation.
When the parent CoherenceSync
is destroyed, by default its CoherenceSync
children get destroyed together with it. This can be changed via the Preserve Children option on the parent, under Advanced Settings:
When Preserve Children is enabled, if the authority destroys or disables the parent entity, child entities get unparented instead of being destroyed together with the parent. Those children will now reside at the root of the Scene hierarchy.
For an example of direct child CoherenceSync
components parenting and unparenting at runtime, check out the First Steps sample project, specifically lesson 4.
Creating complex hierarchies of CoherenceSyncs at runtime
While the basic case of direct parent-child relationships between CoherenceSync entities is handled automatically by coherence, more complex hierarchies (with multiple levels) need a specific component.
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
To prepare the child Prefab that you want to parent at runtime, add the CoherenceNode
component to it (in addition to its CoherenceSync
). In the example above, that would be the flashlight you want your player to be able to pick up. No additional changes are required.
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 would work).
You don't need to input any value in the fields of the CoherenceNode
. They are used at runtime, by coherence, automatically.
To recap, for deep-nesting network entities to work, you need two things:
The parent: a Prefab with CoherenceSync
that has some hierarchy of child transforms (these child transforms are not networked entities themselves).
The child: another connected Prefab with CoherenceSync
and CoherenceNode
.
One important constraint for using CoherenceNode
is that the hierarchies have to be identical on all Clients.
Example: if on Client A an object is parented to Player > Shoulder > Arm > Hand, the hierarchy on Client B needs to be exactly: Player > Shoulder > Arm > Hand.
Removing or moving an intermediate child (such as Shoulder or Arm) would lead to undesirable results, and desynchronization.
Position and rotation
Similarly to the above, intermediate child objects need to have the same position and rotation on all Clients. If not, that would lead to desync because the parented entity doesn't track the position of its parent object(s).
If you plan to move these intermediate children, then we suggest to sync the position and/or rotation of those objects as part of the containing Prefab.
Following the previous example, if an object is parented to Player > Shoulder > Arm > Hand, you might want to mark the position and rotation of Shoulder, Arm and Hand as synced, as part of the prefab Player.
This way if any of them moves, the movement will be replicated correctly on all clients, and the object parented to Hand will also look correct.
Keep in mind that there is no penalty for syncing positions of objects that never or rarely move, because the position is not synced every frame if it hasn't changed.
This section is only useful to you if you want to understand deeply how CoherenceNode
works under the hood.
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.
For an example of a CoherenceSync
parenting and unparenting at runtime in a deep hierarchy, check out the First Steps sample project, lesson 5.
Extending what can be synced from the Configure window
This is an advanced topic that aims to bring access to coherence's internals to the end user.
The Configure window lists all variables and methods that can be synced for the selected Prefab. Each selected element in the list is stored in the Prefab as a Binding
with an associated Descriptor
, which holds information about how to access that data.
By default, coherence uses reflection to gather public fields, properties and methods from each of the Prefab's components. You can specify exactly what to list in the Configure window for a given component by implementing a custom DescriptorProvider
. This allows you to sync custom component data over the network.
Take this player inventory for example:
Since the inventory items are not immediately accessible as fields or properties, they are not listed in the Configure window. In order to expose the inventory items so they can be synced across the network, we need to implement a custom DescriptorProvider
.
DescriptorProvider
The main job of the DescriptorProvider
is to provide the list of Descriptors
that you want to show up in the Configure window. You can instantiate new Descriptors
using this constructor:
name: identifying name for this Descriptor
.
ownerType: type of the MonoBehaviour that this Descriptor
is for.
bindingType: type of the ValueBinding class that will be instantiated and serialized in CoherenceSync, when selecting this Descriptor
in the Configure window.
required: if true, every network Prefab that uses a MonoBehaviour of ownerType will always have this Binding active.
If you need to serialize additional data with your Descriptor
, you can inherit from the Descriptor
class or assign a Serializable
object to Descriptor.CustomData
.
Here is an example InventoryDescriptorProvider
that returns a Descriptor for each of the inventory items:
To specify how to read and write data to the Inventory component, we also need a custom binding implementation.
Binding
A Descriptor
must specify through the bindingType which type of ValueBinding
it is going to instantiate when synced in a CoherenceSync
. In our example, we need an InventoryBinding
to specify how to set and get the values from the Inventory
. To sync the durability property of the inventory item, we should extend the IntBinding
class which provides functionality for syncing int values.
We are now ready to sync the inventory items on the Prefabs.
Even though coherence provides Component Actions out of the box for various component, you can implement your own Component Actions in order to give designers on the team full authoring power on network entities, directly from within the Configure window UI.
Creating a new one is simply done by extending the ComponentAction
abstract class:
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:
Notifying State Changes
It is often useful to know when a synchronized variable 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.
Let's start with a simple example:
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.
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.
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.
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:
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.
Cyclic references are undefined behavior for now. Therefore multiple entities created on the same Client that reference each other might never get synced properly. This is also holds true for references that exist through intermediate entities (A has reference to B has reference to C has reference A - cyclic).
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.
Aside from configuring your CoherenceSync bindings from within the Configure window, it's possible to use the [Sync]
and [Command]
C# attributes directly on your scripts. Your prefabs will get updated to require such bindings.
Mark public fields and properties to be synchronized over the network.
It's possible to migrate the variable automatically, if you decide to change its definition:
Mark public methods to be invoked over the network. Method return type must be void
.
It's possible to migrate the command automatically, if you decide to change the method signature:
Note that marking a command attribute only marks it as programmatically usable. It does not mean it will be automatically called over the network when executed.
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.
While coherence doesn't officially support working with multiple AnimatorControllers, there's a way to work around it. As long as the parameters you want to network are shared among the AnimatorControllers you want to use, they will get networked. Parameters need to have the same type and name. Using the example above, any AnimatorController featuring a Boolean Walk parameter is compatible, and can be switched.
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. coherence calls this mechanism Baking.
Learn more about schemas in the section.
Click on the coherence > Bake menu item.
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 that have been added.
For every Prefab with a CoherenceSync
component attached, the baking process will generate a C# baked script specifically tuned for it.
Check .
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.
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.
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 code completion. 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 try both and check what works best for your workflow.
Source generators do not work on Unity version 2021.1. This is a known Unity issue that has no other fix than to upgrade (or downgrade) the version.
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:
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.
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.
Once the baked code has been generated, Prefabs will automatically make use of it. If you want to switch a particular Prefab to reflection code, you can do so in the Inspector of its CoherenceSync
, by unchecking the Baked checkbox:
Preparing nested connected Prefabs at edit time
coherence supports all Prefab-related Unity workflows, and nesting is one of them. It can make a lot of sense to prepare multiple networked Prefabs, parent them to each other, and either place them in the scene, or save them as a complex Prefab, ready to be instantiated. This page covers these cases.
When preparing a networked Prefab that contains another networked Prefab, one extra component is needed to allow coherence to sync the whole hierarchy: PrefabSyncGroup
.
For instance, let's suppose we have a vehicle in an RTS that can carry cargo, and it comes with cargo pre-loaded when it's instantiated:
In this example Spacetruck is a synced Prefab, with 4 instances of the synced Prefab Cargo nested within. To make this work, we add a PrefabSyncedGroup
to the root:
The component keeps track of child Prefabs that are also synced Prefabs. Now, whenever Spacetruck is instantiated, PrefabSyncGroup
makes sure to take 4 instances of Cargo and link the Prefab instances to the correct network entities.
So to recap:
The outermost Prefab needs CoherenceSync
and PrefabSyncGroup
.
The child Prefabs need CoherenceSync
and, optionally, CoherenceNode
.
When preparing such a Prefab, you need to set the Uniqueness property to No Duplicates. This ensures that, once multiple Clients connect and open the same scene, the synced Prefabs contained within are not spawned on the network multiple times.
Let's suppose we have a networked Prefab that represents a structure in an RTS (a LandingPad) that can be pre-placed in the scene. This structure also contains a networked vehicle Prefab (a Lander). This Prefab is synced as an independent network entity because at runtime it can detach, change ownership, be destroyed, etc.
To achieve this, all we need to do is ensure that both Prefabs are set to be unique. When we drag-and-drop the LandingPad Prefab into the scene, coherence automatically assigns a randomly-generated Prefab Instance Unique ID as an override. This number identifies these particular instances of these two Prefabs in the scene.
With this setting, we don't need to do anything else for these compound Prefabs to work.
To recap:
The outermost Prefab needs its Uniqueness set to No Duplicates. Optionally, you can add PrefabSyncGroup
to enable runtime-instantiation.
Any child Prefab also needs its Uniqueness set to No Duplicates. It also needs a CoherenceNode
if it's parented deep in the hierarchy.
An important thing to keep in mind when working with compound Prefabs in the scene: when you add a new nested synced Prefab to an existing one that has already been placed in the scene a few times, the Prefab Instance Unique ID for these instances will initially be the same.
For this reason, once you play the game, you might see all children disappear (except one). That is normal: coherence thinks that all these network entities are the same, because they have the same uniqueness ID.
You need to ensure that these new children have an overriden and unique ID on each instance in the scene. To do so, click on the button next to the Prefab Instance Unique ID for each child that needs it:
Networked entities can be simulated either on a Game Client ("Client authority") or a Simulator ("Server authority"). Authority defines which Client or Simulator is allowed to make changes to the synced properties of an Entity, and in general defines who "runs the gameplay code" for that Entity. An Entity is any networked GameObject.
To learn more about authority, check out this short video:
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).
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. In this scenario, the Simulator has authority over key game elements, like a "game manager", a score-keeping object, and so on.
Running a Simulator in the cloud next to the Replication Server (with the ping between them being negligible) will also result in lower latency.
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.
First, create a script called Counter.cs and add the following code to it:
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 counter
and 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":
Finally, make sure that a single instance of this Prefab is placed in the scene.
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.
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.
For the full list of supported binding types, see .
This comes in handy in projects that use authoritative . 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 .
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 for this.
Entity references can also be used as arguments in .
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 component or of prefabs, which ensures that that Entity stays part of the query.
You still need to follow the guidelines in the article to make it work.
Triggers can be invoked over the network using . Here's an example where we inform networked Clients that we have played a jump animation:
Now, bind the PlayJumpAnimator
method as a .
When baking via assets, baked scripts will be located in Assets/coherence/baked
.
Please note that if the nested Prefabs are more than one level under the root object, you still need to add a CoherenceNode
component to the child ones (in the example above, Cargo), to enable .
When dealing with synced Prefabs that are hand-placed in the scene before connecting, such as level design elements like interactive doors, you need to ensure that they are seen as "unique". This is also covered in the , but it's worth talking about it in the context of nested synced Prefabs.
Like for runtime-instantiated Prefabs, keep in mind that if the Lander is nested in the hierarchy, it will also need a CoherenceNode
component.
If you plan to also instantiate this Prefab at runtime, you can add a PrefabSyncGroup
to the root as described in the . This makes the Prefab work when instantiated at runtime, while the uniqueness takes care of copies in the scene.
When an Entity is created, the creator is assigned authority over the Entity. Authority can be then at a later point between Clients – or even between Clients and Simulators, or . Regardless, only one Client or Simulator can be the authority over the Entity at any given time.
In addition to key gameplay objects, 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 about .
Peer-to-peer support (without a Replication Server) is planned in a future release. Please see the for updates.
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 .
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.
coherence uses the concept of ownership to determine who is responsible for simulating each Entity. By default, each Client that connects to the Replication 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 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 can be solved using dedicated Simulators. They behave very much like normal 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.
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 contact our developer relations team.
To use Simulators, you need to enter your credit card details. You can do it by logging into our Dashboard, selecting the Billing tab, finding the Payment Methods section and clicking the Manage button.
If you're on the Free plan, you won't be charged anything - our payment provider will temporarily reserve a small amount to verify that the credit card is in working order.
Only Paid and Enterprise plans offer Simulators external network connectivity. When switching from Free plan to a Paid or Enterprise plan, it may take up to 10 minutes for the Simulators to have their external connectivity enabled.
If you have determined that you need one or more Simulator 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 Simulator 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 Client, or as a Simulator. 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.
To force a build to start as a Simulator, you can use the following command line argument:
The Simulator is started with the following parameters in coherence Cloud:
Important: if you want to deploy Simulators on the coherence Cloud, they have to be built for Linux 64-bit.
The SDK provides a static helper class to access all the above parameters in the C# code called SimulatorUtility
.
To build Simulators, it's best to use the Linux Dedicated Server Build Target.
This is great for Simulators since we're not interested in rendering any graphics on these outside of local development. You will also get a leaner executable that is smaller and faster to be published in coherence Cloud.
When a room has only Simulators (no Clients) it shuts down automatically after a short period of time.
Refer to the Simulator: Build and deploy section.
The simulation frame represents an internal clock that every Client syncs with a Replication Server. 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:
Accessible via CoherenceBridge.NetworkTime
.
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
Unity's Time.timeScale
is automatically set to the value of NetworkTime.NetworkTimeScale
if the CoherenceBridge.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 interpolation of the synced values.
Local simulation frame that progresses in user-controlled fixed steps. Accessible via CoherenceBridge.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 CoherenceBridge.OnFixedNetworkUpdate
. This loop is used internally to power the CoherenceInput
and the GGPO code.
Unlike ClientSimulationFrame
, the CoherenceBridge.OnFixedNetworkUpdate
loop never skips frames - it is guaranteed to run for every single frame increment.
Before deploying a Simulation Server, testing and debugging locally can significantly improve development and iteration times. There are a few ways of accomplishing this.
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 that the program should run as a coherence Simulator.
--coherence-simulator-type
: tells the Simulator what kind of connection to make with the Replication Server, can be Rooms or World.
--coherence-region
: tells the Simulator which region the Replication Server is running in: EU, US or local.
--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.
For example:
Keep in mind that all regular Unity arguments are supported. You can see the full list here: Unity Editor command line arguments.
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.
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.
This allows you to test a Simulator build before it is uploaded or if you are having trouble debugging it.
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.
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 creating a Simulator build, see SIMULATORS: Build and Deploy.
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.
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.
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.
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.
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.
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.
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.
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.
Setting up an object for server-side simulation using CoherenceInput and CoherenceSync is done in three steps:
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 Simulator that is in charge of simulating inputs on all objects.
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.
In order for the inputs to be simulated on CoherenceSync objects, they must be optimized through baking.
If the CoherenceInput fields or name is changed, then the CoherenceSync object must be re-baked to reflect the new fields/values.
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.
Check the CoherenceInput API for a complete list of the available methods.
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.
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 Client connections 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 (or host) 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:
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 Area of interest for more information on how to use queries.
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.
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.
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 Room 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 list.
To connect as a Host all we have to do is call CoherenceBridge.ConnectAsHost:
Authority over an Entity is transferrable, so it is possible to move the authority between different Clients or even to a Simulator. This is useful for things such as balancing the simulation load, or for 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.
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.
Disabled. 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. Auto-adopt is only allowed for persistent entities.
When using Request, an optional callback OnAuthorityRequested
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 CoherenceClientConnection.ClientID
is coming soon.
Requesting authority is very straight-forward.
RequestAuthority
returns false
if the request was not sent. This can be because of the following reasons:
The sync is not ready yet.
The entity is not allowed to be transferred becauseauthorityTransferType
is set to NonTransferable
.
There is already a request underway.
The entity is orphaned, in which case you must call Adopt
instead to request authority.
The request itself might fail depending on the response of the current authority.
As the transfer is asynchronous, we have to subscribe to one or more Unity Events in CoherenceSync to learn the result.
Also because of their asynchronous nature, clients can receive commands for entities that they have already transferred. Such commands are dropped.
These events are also exposed in the Custom Events section of the CoherenceSync inspector.
Scenes or levels are a common feature of Unity games. They can be loaded from Unity scenes, custom level formats, or even be procedurally generated. In networked games, players should not be able to see entities that are in other scenes. To address this, coherence's scene feature gives you a simple way of controlling what scene you're acting in.
Each Coherence scene is represented by an integer index. You can map this index to your scenes or levels in any way you find appropriate. Projects that don't use scenes will implicitly put all their entities into scene 0.
Since the connection to the Replication Server is done through the CoherenceBridge component, it means that if you switch Scenes, the current CoherenceBridge that holds the connection to the Replication Server will be destroyed.
In order to keep a CoherenceBridge with its connection alive between Scene changes, you will have to set it as Main Bridge in the Component inspector:
If this approach to keeping the connection alive is not a good fit for your game, see Custom Setup in the second part of this document.
In the CoherenceBridge inspector you will find all the options related to handling Scene transitions. First thing to know is that Client Connections must be enabled for this feature to work.
These are the options related to Scene transitions:
Main Bridge: This CoherenceBridge instance will be saved as DontDestroyOnLoad and its connection to the Replication Server will be kept alive between Scene changes. All other CoherenceBridge components that are instantiated from this point forward will update the target Scene of the Main Bridge, and destroy themselves afterwards.
Use Build Index as Scene Id: Every Scene needs a unique identifier over the network. This option will automate the creation of this ID by using the Scene Build Index (from the Build Settings window).
Scene Identifier: If the previous option is unchecked, then you will be able to manually set a Scene Identifier of your own (restricted to unsigned integers).
Using these options will automate Scene transitions.
The only requirement is having a single CoherenceBridge set as Main (the first one that your game will load). The rest of the Scenes you want to network should also have a CoherenceBridge component, but not set as main.
These options require no extra code on your part.
A client connection and all the entities it has authority over are always kept in the same coherence scene. Clients cannot have authority over entities in other scenes. This implies two things:
When a client changes scene, it will bring along any entities it has authority over.
If an entity changes ownership via authority transfer, it will be moved to the new owner's scene.
An entity that does not have an owner is an orphan. Orphaned entities will stay in the scene where their previous owner left them.
Note that Unity will destroy all game objects not marked as DontDestroyOnLoad
whenever a new Unity scene is loaded (non-additively). If the client has authority over any of those entities at that point, coherence will replicate that destruction to all other clients. If that is undesirable and you need to leave entities behind, make sure that authority has been lost or transferred before loading the new Unity scene. You can of course also mark them as DontDestroyOnLoad
, which will bring them along to then new scene.
Since this process involves a bit of logic that has to be executed over several frames, coherence provides a LoadScene
helper method (co-routine) on CoherenceSceneManager
. Here's an example of how to use it:
It is not possible to move entities to other scenes without the client connection also moving there. Additionally, you can't currently query for entities in other scenes.
Both of these limitations are planned to be addressed in future versions of coherence.
If your project isn't a good fit for the automatic scene transitioning support described above, it is possible to use a more manual approach. There are a few important things to take care of in such a setup:
If you ever load another Unity scene, the CoherenceBridge
that connects to the server needs to be kept alive, or else the client will be disconnected. A straightforward way of doing this is to call Unity's DontDestroyOnLoad
method on it. This creates two problems when replicating entities from other Clients:
The bridge instantiates remote entities into the scene where it is currently located. To override this behaviour, set the InstantiationScene
property on your CoherenceBridge
to the desired scene.
Any new CoherenceSync instances will look for the bridge in the same scene that they are located. If the bridge is moved to the DontDestroyOnLoad
scene, this lookup will fail. You can use the static CoherenceSync.BridgeResolve
event to solve this problem (see the code sample in the next section). Alternatively, if you have a reference to a Scene, you can register the appropriate bridge for entities in that scene with CoherenceBridgeStore.RegisterBridge
before it is loaded.
Additionally, coherence queries (e.g. CoherenceLiveQuery
) also look for their bridge in their own scene, so you might have to set its bridgeResolve
event too.
If you load levels via your own level format, or by loading Unity scenes additively, it is quite possible that you can skip some of the steps above.
The only thing strictly necessary for coherence scene support is to call
CoherenceBridge.SceneManager.SetClientScene(uint sceneIndex);
so that the Replication Server knows in which scene each Client is located.
Here's a complete code sample of how to use all the above things together:
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.
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.
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.
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.
Currently, the maximum number of persistent objects supported by the Replication Server is 32 000. This limit will be increased in the near future.
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.
On a Simulator, we can limit the framerate globally using Unity's built-in static variable targetFrameRate.
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.
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.
Since the default packet send frequency of the Replication Server is 20Hz, sample rates above that value won't have any benefits unless you increase the Replication Server send frequency, too. See here how to adjust the Replication Server send frequency.
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.
An integration with the Unity Profiler provides basic statistics on networking events and bandwidth.
The module is only available in Unity 2021.2 and newer.
To view the module, open the Unity Profiler by selecting Window > Analysis > Profiler. Open the Profiler Modules dropdown menu in the top left, and select the coherence module.
To hide unneeded graph lines, select the colored square next to the item you do not wish to see.
This feature requires baking.
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 LiveQuery. 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 archetypes.
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:
Components can be turned off, meaning you won't receive any updates from them.
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.
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.
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.
The primitive types that coherence supports can be configured in different ways:
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 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 and Colors can be configured using the number of bits per component. Quaternions require sending 3 components while Colors require 4 components.
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.
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.
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 Archetypes.
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.)
The coherence Settings window is located in coherence > Settings.
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 coherence 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.
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.
A terminal/command line will pop up, running your Server locally.
The port the Replication Server will use. Rooms: 42001
Worlds: 32001
The web port used for webGL connections. Rooms: 42001
Worlds: 32002
The Replication Server send frequency. Default: 20
packets / s
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.
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.
For a locally hosted Replication Server, you can edit the send and receive frequencies by using the CLI arguments --send-frequency
and --recv-frequency
. Or by changing it in the coherence Settings -> Local Replication Server -> Send Frequency / Recv Frequency.
On the dashboard, the packet frequencies for sending and receiving data can be adjusted per project too. It is part of the Advanced Config section of Worlds create/edit and Rooms pages of the dashboard.
Adjusting the send and receive frequencies on the dashboard is available for paid plans.
When the Replication Server is running, you connect to it using the Connect
method.
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.
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 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:
By default, the number of players that can connect to a locally hosted Replication Server is limited to 100.
By definition, a locally hosted Replication Server is one that is not managed by coherence, for example if it has been started from a Unity editor or by a game client in the self-hosting scenario. Replication Servers running in the coherence Cloud have no player limit.
This restriction can be lifted by supplying the SDK with an unlock token. The token can be generated in the Settings section of your project dashboard at coherence.io.
Once you have the token, it needs to be added to the coherence RuntimeSettings
(Assets/coherence/RuntimeSettings.asset):
The unlock token will now be automatically passed to all the Replication Server instances started via Unity editor or the Coherence.Toolkit.ReplicationServer
API.
If you plan to execute the Replication Server manually the token can be supplied via the --token <token>
command line argument.
Simulators per room can be enabled in the dashboard for the project. The Simulator used is matched according to the Simulator slug 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 Simulators section. The Simulator is shutdown automatically when the Room is closed.
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.
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 Bridge).
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.
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.
The quickest way to get Multi-Room Simulators set up is by using the provided wizard.
It will take you through the GameObjects and Components needed to make it happen.
Here's a quick overview video of the setup:
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, ...)
Bridge — 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 Bridge, 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).
ConnectionEventHandler — events on the connection stablished by the Bridge associated with that Scene.
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:
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.
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:
Supporting Unity physics in a network environment requires managing the state of rigid bodies on replicated Prefabs. Generally, if a Prefab using CoherenceSync has a Rigidbody or Rigidbody2D component, the replicated instances of the Prefab should have the body set to kinematic so that they do not simulate in the physics step on non-authoritative clients. There is a convenient configuration for this in the CoherenceSync configuration components tab.
For most purposes, this is all that is required to have physically simulated entities correctly replicated on Clients. However, only the transform of the rigid body is actually replicated. For additional physical state replication a more advanced setup is required.
The CoherenceSync component supports three modes for replication of Unity rigid bodies:
Direct - the default mode used for basic replication of the transform of the Unity GameObject with a rigid body component. When a rigid body is detected, the position and rotation of the GameObject are provided by and assigned to the rigid body's position and rotation directly and Unity updates the GameObject transform after the physics step.
Interpolated - similar to Direct mode, except the update to the rigid body position and rotation are applied using MovePosition and MoveRotation which allows the Unity physics system to calculate rigid body state such as linear and angular velocity on Clients with replicated Entities.
For best behavior, it is recommended that the interpolation timing use only FixedUpdate. See the article on Interpolation.
Manual - disables automatic update of position and rotation of CoherenceSync Prefabs with rigid bodies and enables the use of callbacks, allowing custom implementation of how position and rotation updates are applied. The callbacks are OnRigidbody2DPositionUpdate, OnRigidbody3DPositionUpdate, OnRigidbody2DRotationUpdate, and OnRigidbody3DRotationUpdate.
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.
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.
You can also create an interpolation settings asset: Assets > Create > coherence > Interpolation Settings
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).
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.
There are a few settings you can tweak:
Smoothing
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.
Stale Factor: defines when to insert a virtual sample in case of a longer time gap between the samples. High stale factor puts the virtual sample close to first sample leading to a smooth transition between two distant samples. This is suitable for parameters that do not change rapidly - the position of a big ship for example. Low stale factor places the virtual sample near the second sample resulting in initial lack of change in value during interpolation followed by a quick transition to the second sample. This is best suited for parameters that can change rapidly, e.g. position of a player.
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:
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.
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.
Combination - you can combine any of the above, so that bindings are updated in more than one Unity callback
Nothing - bindings will completely stop receiving new values because interpolation is fully disabled
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.
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.
You can change your Simulator build options by editing the SimulatorBuildOptions object, or in the coherence Hub Simulators tab.
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.
Make sure you meet the requirements:
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.
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.
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.
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.
When scripting Simulators, we need mechanisms to tell them apart.
Ask Coherence.SimulatorUtility.IsSimulator
.
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.
Whenever the project compiles with the COHERENCE_SIMULATOR
preprocessor define, coherence understands that the game will act as a Simulator.
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.
You can define who simulates the object in the CoherenceSync inspector.
coherence includes an auto-connect MonoBehaviour out of the box for Room- and World-based Simulators. The Component its called AutoSimulatorConnection.
Multi-Room Simulators have their own per-scene reconnect logic. The AutoSimulatorConnection components should not be enabled when working with Multi-Room Simulators.
If the Simulator is invoked with the --coherence-play-region
parameter, AutoSimulatorConnection will try to reconnect to the Server located in that region.
Flag | Description |
---|
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.
You can see load balancing in action in this video:
While load balancing is supported for standalone projects, 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.
This is a collection of features that makes it easy for the players of your coherence-enabled game to host Replication Servers themselves, without using our cloud services. It involves three main parts:
A mechanism for bundling the coherence Replication Server with the game.
SDK methods that start and stop the Replication Server on the player's personal device.
A relay for communication between the Replication Server and some 3rd party networking service, such as .
Players running their own local Replication Server will still be bound by the legal terms of the coherence end user agreement. For questions regarding this, please reach out to us at the devrel@coherence.io email address.
If you decide to release your game with support for Client-hosting, it is important to first consider the tradeoffs of this approach:
Server costs will be paid by those who provide the networking service, i.e. Valve in the case of relying on Steam Networking.
Players will be running the Replication Server on their personal devices, so their specs and network conditions will have a big impact on performance and reliability for all players.
You will not have access to the full range of features included when you're using the coherence Cloud services.
It lets your players keep playing the game over the Internet, even if your company or coherence goes out of business.
To bundle the coherence Replication Server with the build of your game, go to coherence > Settings > Bundle stand-alone Replication Server and check what platforms should have this feature enabled.
Currently, coherence supports bundling on Windows, MacOS, and Linux. We are working on adding support for more platforms in the future.
If your game uses a custom build process where the automatic bundling doesn't work well, you can also use a manual approach.
Here's a code example:
Note that the pathToBuild
is assumed to point to the same place as the Unity BuildSummary.outputPath
in a normal build. It behaves slightly differently depending on the target platform.
The BundleDefault
method will copy the Replication Server and a combined schema (which contains all active schemas) for the target platform into a designated location inside the folder of the build.
To start the bundled Replication Server from within your game, you can use the Launcher
and ReplicationServer
classes provided by the coherence Unity SDK. It will make sure that the correct parameters are passed to the Replication Server at startup, and it will also help you manage the child process.
Launching a child process is not supported in Unity IL2CPP builds. If your game is using IL2CPP, you must use another method (see the next section below).
Here's a simple code example of how to start and stop the Replication Server using the coherence API:
It is very important to keep track of your child process (via the ReplicationServer
class) and close it down properly, or else you will leave the Replication Server running on the user's machine. Note that it's only the person hosting a game that needs to start an instance of the Replication Server, players joining a game should connect normally.
There are third-party packages in the Unity asset store that let you launch subprocesses from IL2CPP builds.
You can create launcher scripts, similar to the ones coherence generates in your Unity project at ./Library/coherence
, and ship them with your game. Depending on if the game is using Rooms or Worlds, the script is called run-replication-server-rooms
or run-replication-server-worlds
. The player can then run the script to start the Replication Server in an easy fashion from outside the game.
For players to communicate with one another over the Internet, a networking service is required. The networking service provides features such as setting up games, establishing connections, and sending data.
By default, coherence provides all of these networking services out-of-the-box. In this scenario, players all communicate with one another via a Replication Server that is hosted in the coherence Cloud, so you don't have to worry about anything.
In a Client-hosted scenario however, the Replication Server runs on the hosting player's machine. Therefore, the connectivity between clients and host must be provided via an external networking service. In the context of Client-hosting, we call such networking service a relay, since it is used to relay traffic between the Clients and the Replication Server running on the host's machine. You can also think of a relay as tunneling traffic between clients and host.
The sample code also demonstrates how to register a lobby with the Steam Matchmaking API to make it easy for players to find and join an ongoing session.
The Steam relay is available here: https://github.com/coherence/steam-integration-sample.
The host (Client A) starts a Replication Server on its local machine.
The host connects to the local Replication Server.
The host initializes a SteamRelay that listens for incoming Steam connections.
Another player (Client B) connects to the host via Steam using the SteamTransport.
The SteamRelay accepts the incoming connection, creating a SteamRelayConnection.
The SteamRelayConnection immediately starts passing data between the Steam servers and the Replication Server.
The relayed connection is now fully established. All data between Client B and the Replication Server is relayed through Steam.
For each new Client that connects, steps 4-7 are repeated.
Although the diagram above shows that traffic is routed via Steam servers, it is often the case that traffic can flow directly between player and host machines without actually making the extra hop via the Steam servers. This technique is commonly referred to as "hole punching" or "NAT Punch-through" and greatly reduces latency, however, it is not supported on all networks due to firewall restrictions.
Steam's networking service will first attempt a NAT punch-through and then automatically fall back to relayed communication if the punch-through failed.
To be able to test your game with the SteamRelay you'll need at least two Steam accounts - even for local development. Since only a single Steam account can be logged in to one machine at a time, you will need at least two machines or a sandbox solution to be able to connect. Trying to connect two instances of the game on the same machine will result in "invalid connection" or "failed to create lobby" errors.
Similar to the Steam relay above, you can create your own custom relay implementation and route traffic via any networking service. The relay implementation consists of three parts, each class implementing one of three interfaces.
ITransport (Client) - Outgoing connection. Passes messages between the client and the networking service.
IRelay (Host) - Listens for incoming connections and instantiates IRelayConnections.
IRelayConnection (Host) - Incoming connection. Passes messages between the Replication Server and the networking service.
Let's say we want to implement a custom relay that uses an API called FoobarNetworkingService. The code here outlines the main points to implement for routing network traffic.
First, we'll create a CustomTransport class to manage the outgoing connection from the client to the host. CustomTransport implements the ITransport interface that provides a few important methods. The Open and Close methods are used to connect and disconnect to/from the networking service. The Send and Receive methods are used to send and receive messages to/from the networking service.
The CustomTransport will be instantiated when the client attempts to connect to the host, usually as a result of calling CoherenceBridge.Connect. You can control how the transport is instantiated by implementing an ITransportFactory.
Finally, to configure the client to actually use the CustomTransport, just set the transport factory on CoherenceBridge.
This is everything needed on the client-side.
You can call _SetTransportFactory(null)_ to disable the custom transport and connect as normal.
On the host-side, we need a CustomRelayConnection class to manage the incoming connection. This class implements IRelayConnection and is a mirror image of the CustomTransport. The OnConnectionOpened and OnConnectionClosed methods are called in response to CustomTransport.Open and CustomTransport.Close. The SendMessageToClient and ReceiveMessagesFromClient methods are responsible for sending and receiving messages over the networking services, similar to CustomTransport.Send and CustomTransport.Receive.
Now we just need a CustomRelay class that listens for incoming FoobarConnections and maps them to a corresponding CustomRelayConnection.
Finally, to configure the host to actually use the CustomRelay, simply set the relay on the CoherenceBridge:
You can call _SetRelay(null)_ to disable relaying.
These are all the necessary steps required to configure a custom relay.
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 > Smoothing below).
Smooth Time: additional smoothing can be applied (using ) to clear out any jerky movement after regular interpolation has been performed.
By default, each binding is interpolated on every call. This can be changed using the Interpolate On property on the under Advanced Settings. Possible values are:
Update / LateUpdate / FixedUpdate - bindings will be updated with interpolated values on every / / call
If you are using Rigidbody for movement of a GameObject, it is recommended to set Interpolate On to FixedUpdate. Also, to achieve completely smooth movement, should be enabled and you should avoid setting the position of a GameObject directly using or .
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 .
Make sure you have completed the steps required in .
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 .
You have to be logged into the coherence Developer Portal, through the Unity Editor. See for more information.
Optimization | What it does |
---|
When you add the Component, it will parse the connection data passed with to connect to the given Replication Server automatically. This will also work for Simulators you upload to the coherence Cloud.
Builds using IL2CPP (instead of Mono) do starting child process with Process.Start()
, which is used internally in the coherence Launcher
class. If you are depending on IL2CPP and want to support Client-hosting, you must use one of the available workarounds instead:
Steam offers a free networking service for games available on its platform. In order to use you'll need a registered with a valid Steam App ID. Once you have a Steam App ID, you'll be able to pass messages between clients via Steam's servers.
To make things easy, coherence provides a complete Steam relay implementation that provides out-of-the-box networking over Steam. The Steam relay utilizes the to access the Steam API.
For a complete relay code example, please review the .
|
|
| Specific IP to point to. |
| Specific port to point to. |
| Specific Room to point to. |
| Room tags (space-separated). |
| Key-value pairs (space-separated). Example:
|
| Specific World ID to point to. |
| Connect and behave as a Simulator. |
| Same as |
Quick exploration and recommendations for different game genres
This section introduces you to coherence features and terminology by using well-known genres and game types as examples. Each example will come with a list of considerations and how we propose to use coherence to achieve a similar result. As you well know, game creation is a complex process, so the list is far from exhaustive, but aims to highlight pitfalls, suggest solutions and generally just provide you with a starting point when trying to create a multiplayer game with coherence in the context of a game type you are working on.
This section is a beginner-friendly exploration into familiarizing with coherence's terminology and networking mindset, and by no means is representative of a production-ready architecture proposal.
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 |
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.
A list of uploaded Simulators and their corresponding slugs can be found in the Developer Portal:
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 .