Run State Designer on the server or whichever peer has authority, then send the resulting gameplay changes to the other clients. Use this pattern when the state machine should make decisions in one authoritative place while the networking solution synchronizes outcomes such as transforms, animations, or events.

The state machine itself does not need to know which networking package the project uses, and clients do not need a synchronized copy of its execution state.

Prerequisites and authority

Choose how the networking implementation identifies the authoritative instance. In a server-authoritative game this is the server. In a client-authoritative setup it is the client that owns the decision.

Confirm that the networking layer already sends the results produced by the state machine to the other clients. For example, when a Play Animation state runs on the authority, the clients receive the animation result rather than running the same decision graph independently.

GameObject setup

Enable the State Machine component only on the authoritative instance. Replace IsServer() with the authority check provided by the project’s networking implementation:

public class StateMachineEnabler : MonoBehaviour
{
    [Tooltip("A reference to the state machine.")]
    public StateMachine m_StateMachine;

    private void Awake()
    {
        m_StateMachine.enabled = IsServer();
    }
}

This keeps the State Designer graph disabled on non-authoritative clients while allowing the networking layer to deliver the results they need to display.

Entity setup

For a baked Entity State Machine, disable Start When Enabled on the authoring State Machine before baking. Leave the component enabled so its data is baked. With automatic startup disabled, each instance remains stopped until the authority explicitly starts it.

The runtime initialization system creates the required systems and adds DeferredBakedStateMachineStart. Calling StateMachine.StartBakedStateMachine immediately after Instantiate is too early: it returns false until that component exists. Request startup after initialization and retain the request if startup has not succeeded.

The following example extends Entity Baking. Add a bool IsServer field to its SpawnData struct, and have the networking layer set that field in the runtime singleton once authority is known, before enabling this spawner. The name represents the example’s server authority policy; use the corresponding ownership decision for a client-authoritative game. Replace the original spawner system with these systems:

using Opsive.StateDesigner.Runtime;
using Opsive.StateDesigner.Runtime.Components;
using Unity.Collections;
using Unity.Entities;

public struct PendingAuthorityStart : IComponentData { }

public partial struct EntitySpawnerSystem : ISystem
{
    private void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<SpawnData>();
        // The networking layer enables this system after setting authority.
        state.Enabled = false;
    }

    private void OnUpdate(ref SystemState state)
    {
        var spawner = SystemAPI.GetSingleton<SpawnData>();
        using var entities = state.EntityManager.Instantiate(spawner.Prefab, 100, Allocator.Temp);

        // Apply any per-agent setup here. Only the authority requests startup.
        if (spawner.IsServer) {
            for (int i = 0; i < entities.Length; ++i) {
                state.EntityManager.AddComponent<PendingAuthorityStart>(entities[i]);
            }
        }
        state.Enabled = false;
    }
}

public partial struct AuthorityStateMachineStartSystem : ISystem
{
    private EntityQuery m_ReadyToStart;

    private void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<PendingAuthorityStart>();
        m_ReadyToStart = SystemAPI.QueryBuilder()
            .WithAll<PendingAuthorityStart, DeferredBakedStateMachineStart>().Build();
    }

    private void OnUpdate(ref SystemState state)
    {
        // Snapshot the query because successful startup removes a component.
        using var entities = m_ReadyToStart.ToEntityArray(Allocator.Temp);
        for (int i = 0; i < entities.Length; ++i) {
            if (StateMachine.StartBakedStateMachine(state.World, entities[i])) {
                state.EntityManager.RemoveComponent<PendingAuthorityStart>(entities[i]);
            }
        }
    }
}

The spawner starts disabled. After the networking layer sets SpawnData.IsServer, it must enable the spawner, which runs once. The separate startup system keeps the pending request until initialization is complete and startup succeeds, regardless of which system runs first that frame. It uses the entity’s current world rather than assuming the default world. Non-authoritative instances receive no PendingAuthorityStart tag and remain stopped because their prefab was baked with Start When Enabled disabled.

This example assumes authority is established before spawning and remains unchanged. If ownership can transfer, the networking layer must stop the previous authority’s graph and manage startup on the new owner as part of that transfer.

Network behavior

Only the authoritative State Machine evaluates states and transitions. The networking implementation then synchronizes the visible or gameplay result. This keeps State Designer independent of the networking package and avoids running the same decision flow separately on every client.

The same division applies to server-authoritative and client-authoritative projects: one instance owns the decisions, and the other peers receive their results.

Verify the result

Run a server and client together and confirm that:

  • The State Machine component is enabled on the authoritative GameObject and disabled on the non-authoritative copy.
  • A state change produces the expected synchronized result on the clients.
  • A baked Entity State Machine starts only where the authority check passes.

If the graph runs on every peer, verify the implementation of IsServer() or the equivalent authority check. For baked entities, also confirm Start When Enabled was disabled before baking; skipping the manual start call does not prevent automatic startup. If manual startup returns false, retain the pending request until the runtime initialization system has added DeferredBakedStateMachineStart. If clients do not show the result, verify the networking code that synchronizes the transform, animation, event, or other output; State Designer does not perform that synchronization itself.