LogoLogo
HomeOnline DashboardAPIDiscordForums
SDK 1.7 Preview
SDK 1.7 Preview
  • Welcome
  • Overview
    • Features
    • Roadmap
  • Getting started
    • Get the Unity SDK
    • Setup a project
      • 1. Scene setup
      • 2. Prefab setup
      • 3. Test your game locally
        • Local testing using builds
        • Local testing via Unity's Multiplayer Play Mode
        • Local testing via ParrelSync
      • 4. Test in the cloud
        • Deploy a Replication Server
        • Share builds
    • How to... ?
    • Single-player to multiplayer
    • Video tutorials
    • Samples and tutorials
      • Package samples
      • Sample Connection UIs
      • First Steps tutorial
        • 1. Basic syncing
          • 1.1 Animation parameters
          • 1.2 Sending commands
        • 2. Physics / Authority transfer
        • 3. Areas of interest
        • 4. Parenting entities
        • 5. Complex hierarchies
        • 6. Persistence
      • Campfire project
        • Game mechanics
        • Leveraging object pooling
        • Remote interactions: Chairs
        • Remote interactions: Trees
        • A unique object with complex state
        • Custom instantiation and destruction
        • Running a server-side NPC
        • Playing audio and particles
        • A simple text chat
      • Beginner's guide to networking
    • Troubleshooting
  • Manual
    • Unity Components
      • CoherenceSync
      • CoherenceBridge
      • CoherenceLiveQuery
      • CoherenceTagQuery
      • CoherenceGlobalQuery
      • CoherenceInput
      • CoherenceNode
      • PrefabSyncGroup
      • Order of execution
    • Networking state changes
      • Instantiate and Destroy Objects
      • Supported types
      • Messaging with Commands
      • Syncing child GameObjects
      • Animation
      • CoherenceSync references
      • [Sync] and [Command] Attributes
      • [OnValueSynced] Attribute
      • Creating your own syncable member
      • Custom Component Actions
      • Rigid Bodies
      • Interpolation
    • Authority
      • Authority transfer
      • Server-authoritative setup
    • Lifetime
      • Persistence
      • Uniqueness
      • Example: A global counter
    • Parenting network entities
      • Direct children CoherenceSyncs
      • Deeply-nested CoherenceSyncs
      • Nesting Prefabs at Edit time
    • Asset management
      • Instantiating from CoherenceSyncConfig
      • Instantiate via
      • Load via
    • Scene management
    • Multiple Connections within a Game Instance
    • Baking (code generation)
      • Conditional compilation
    • Replication Server
      • Rooms and Worlds
      • Replication Server API
    • Simulators (Servers)
      • Scripting: Client vs Simulator
      • Run local Simulators
      • World Simulators
      • Room Simulators
      • Advanced Simulator Authority
      • Simulator slugs
      • Build and Deploy
      • Command-line arguments
    • Client Connections
    • Optimization
      • Areas of Interest
      • Level of Detail (LOD)
      • Profiling
      • Simulation Frequency
    • Project Settings
    • Advanced topics
      • Big worlds
        • World Origin Shifting
        • Load balancing
      • Competitive games
        • Simulation Frame
        • Determinism, Prediction and Rollback
      • Team workflows
        • Version Control integration
        • Continuous Integration
      • Schema explained
        • Specification
        • Field settings
        • Archetypes
      • Code stripping
      • Replication Server CLI
      • Single-player gameplay
    • Scripting API
  • Hosting
    • Choosing where to host
    • coherence Cloud
      • Online Dashboard
      • Manage Worlds
      • Configure Rooms
      • Player Accounts
      • Game Services
        • Lobbies
        • Cloud Storage
        • Key-Value Store (Legacy)
      • APIs
        • Worlds
        • Rooms
        • Lobbies
        • Cloud Storage
        • Key-Value Store (Legacy)
    • Peer-to-peer
      • Implementing Client hosting
        • Steam Relay
        • Epic Online Services (EOS) Relay
        • Azure PlayFab Relay
  • Support
    • Release notes
    • Glossary
    • Unreal Engine support
    • WebGL support
    • ECS / DOTS support
    • Known issues
    • Upgrade guide
      • Upgrade 1.6 -> 1.7
      • Upgrade 1.5 -> 1.6
      • Upgrade 1.4 -> 1.5
      • Upgrade 1.3 -> 1.4
      • Upgrade 1.2 -> 1.3
      • Upgrade 1.1 -> 1.2
      • Upgrade 1.0 -> 1.1
      • Upgrade 0.10 -> 1.0
      • Upgrade 0.9 -> 0.10
    • Credit cost & pricing
    • Report a bug
Powered by GitBook
On this page
  • Our use case
  • The code
  • Picking up when somebody leaves
  • Running this logic on the Server (Simulator)

Was this helpful?

Export as PDF
  1. Getting started
  2. Samples and tutorials
  3. Campfire project

A unique object with complex state

Was this helpful?

Topics covered

| |

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.

Our use case

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).

The code

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:

public void BurnObjectLocal(CoherenceSync syncToBurn)
{
    //...
    if (_sync.HasStateAuthority)
        BurnObject(syncToBurn.CoherenceSyncConfig.ID);
    else
        _sync.SendCommand<Campfire>(nameof(BurnObject), MessageTarget.AuthorityOnly, syncToBurn.CoherenceSyncConfig.ID);
}

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.

[Command(defaultRouting = MessageTarget.AuthorityOnly)]
public void BurnObject(string syncConfigID)
{
    if (RetrieveBurnableInConfigRegistry(syncConfigID, out Burnable burnable))
    {
        // Did two objects get burned at the same time? Calculate big fire time
        // IsBigFireOn = ...

        ChangeFireState(IsBigFireOn ? burnable.bigFireEffectType : burnable.fireEffectType, syncConfigID);
    }
}

(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.

private void ChangeFireState(FireEffect.EffectType newEffectType, string syncConfigID = "")
{
    // Inform other clients
    _sync.SendCommand<Campfire>(nameof(FireStateChanged), MessageTarget.Other, activeFireEffect, newEffectID, syncConfigID);
    
    // Update synced property
    activeFireEffect = newEffectID;
}

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.

Picking up when somebody leaves

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.

Running this logic on the Server (Simulator)

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:

// ...
if (SimulatorUtility.IsSimulator)
{
    _sync.RequestAuthority(AuthorityType.Full);
}

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.

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

🔥
what happens with the trees
this page
this page
the end
Network Commands
Seamless authority transfer
Optional server-side logic