LogoLogo
⚠️ Outdated documentationGo to latestHomeAPI
SDK 1.3
SDK 1.3
  • Welcome
  • Overview
    • Features
    • Roadmap
  • Getting started
    • Get the Unity SDK
    • Setup a project
      • Scene setup
      • Prefab setup
      • Sample connection UIs
      • Local development
        • Local testing using Builds
        • Local testing via Unity's Multiplayer Play Mode (MPPM)
        • Local testing via ParrelSync
      • Test in the cloud
        • Deploy a Replication Server
        • Share builds
    • Video tutorials
    • Samples and tutorials
      • Package samples
      • First Steps tutorial
        • 1. Basic syncing
          • 1.2. Animation parameters
          • 1.3. 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
  • Manual
    • Unity Components
      • CoherenceSync
      • CoherenceBridge
      • CoherenceNode
      • CoherenceLiveQuery
      • CoherenceTagQuery
      • PrefabSyncGroup
      • CoherenceInput
      • Order of execution
    • Networking state changes
      • 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
      • Instantiate via
      • Load via
      • Instantiating from CoherenceSyncConfig
    • Scene management
    • Baking (code generation)
    • Replication Server
      • Rooms and Worlds
      • Replication Server API
    • Simulators (Servers)
      • Scripting: Client vs Simulator
      • Run local Simulators
      • World Simulators
      • Room Simulators
      • Simulator slugs
      • Multi-Room Simulators
      • 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
      • Command-line interface tools
      • Single-player gameplay
    • Scripting API
  • Hosting
    • Choosing where to host
    • coherence Cloud
      • Online Dashboard
      • Manage Worlds
      • Configure Rooms
      • Lobbies
      • Game Services
        • Account
        • Key-Value Store
      • coherence Cloud APIs
        • Worlds API
        • Rooms API
        • Lobbies API
        • Game Services
          • Authentication Service (Player Accounts)
          • Key-value store
    • Peer-to-peer
      • Implementing Client hosting
  • Support
    • Release notes
    • Glossary
    • Unreal Engine support
    • WebGL support
    • ECS / DOTS support
    • Known issues and troubleshooting
    • Upgrade guide
      • 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 custom instantiator
  • Object anchors

Was this helpful?

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

Custom instantiation and destruction

Was this helpful?

Topics covered

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.

Our use case

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:

private IEnumerator GetBurned()
{
    // ...
    _sync.ReleaseInstance();
}

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.

The custom instantiator

Creating a custom instantiator is trivial. We just need a class to implement the interface INetworkObjectInstantiator, like so:

[Serializable, DisplayName("UniqueBurnableObjects", "Custom instantiator [...]")]
public class UniqueBurnableInstantiator : INetworkObjectInstantiator
{
    // OnUniqueObjectReplaced() does nothing
    
    public ICoherenceSync Instantiate(CoherenceBridge bridge,
                        ICoherenceSync prefab, Vector3 position, Quaternion rotation)
    {
        return Object.Instantiate(prefab as MonoBehaviour,
                                position, rotation) as ICoherenceSync;
    }
    
    public void Destroy(ICoherenceSync obj)
    {
        var monoBehaviour = obj as MonoBehaviour;
        Object.Destroy(monoBehaviour.gameObject);
    }
    
    // WarmUpInstantiator() does nothing
    // OnApplicationQuit() does nothing
}

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.

Object anchors

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:

private void SpawnAnchor()
{
    // Code simplified for clarity ...

    string uniqueID = _sync.ManualUniqueId;
    _bridge.UniquenessManager.RegisterUniqueId(uniqueID + "-anchor");
    objectAnchorSync = Instantiate(anchorPrefab,
                            transform.position, transform.rotation);
}

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:

public GameObject GetLinkedObject()
{
    UniqueObjectReplacement uor =
        _sync.CoherenceBridge.UniquenessManager.TryGetUniqueObject(holdingForUUID);
    bool found = uor.localObject != null;
    return found ? ((CoherenceSync)uor.localObject).gameObject : null;
}

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.

private Transform RecreateObject(ObjectAnchor objAnchor)
{
    foreach (CoherenceSyncConfig config in configRegistry)
    {
        if (config.ID == objAnchor.syncConfigId)
        {
            _sync.CoherenceBridge.UniquenessManager.RegisterUniqueId(objAnchor.holdingForUUID);
            CoherenceSync newSync = config.GetInstance(holdSocket.position, holdSocket.rotation);
            // ...
        }
    }
    // ...
}

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.

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 .

📁
object pool
here
Keeper Robot
when dealing with the campfire object
the Pool Instantiator
Keeper Robot
Custom instantiators
different object instantiators
Some pre-placed objects: the boombox, the banjo, a bin
The anchors, created in Play mode
The Inspector of an anchor holds information about the object it relates to