Skip to content

API: OxySync

Lyraedan edited this page Sep 24, 2026 · 9 revisions

OxySync is a Unity Mirror-style high-level networking layer built into the API. Derive from Shared.OxySync.NetworkBehaviour and use attributes to replicate fields and call remote methods — no manual packet code required.

Namespaces:

  • Shared.OxySync → NetworkBehaviour
  • Shared.OxySync.Attributes → SyncVarAttribute, CommandAttribute, ClientRpcAttribute, TargetRpcAttribute, ServerAttribute, ClientAttribute, InterestGroupAttribute, FixedInterestGroupAttribute

The Shared assembly is merged into ONI_Together_API.dll, so this works directly from a mod that references the API.

Base class

public abstract class NetworkBehaviour : KMonoBehaviour
{
    public float SyncInterval = 0.5f;              // seconds between SyncVar ticks
    public int InterestGroup { get; set; } = -1;   // -1 = every player
    public int BehaviourId  { get; set; } = -1;    // assigned automatically on spawn
    public virtual int NetId { get; set; }         // backed by the GameObject's NetworkIdentity

    protected bool isServer  { get; }
    protected bool isClient  { get; }
    protected bool inSession { get; }

    protected void CallCommand(string methodName, params object[] args);
    protected void CallClientRpc(string methodName, params object[] args);
    protected void CallTargetRpc(ulong targetPlayer, string methodName, params object[] args);
}
// Other members (send delegates, SyncVarField/CachedMethod collections, timestamp fields,
// static event hooks) are internal plumbing and aren't needed to use the class.
  • NetId resolves through the object's NetworkIdentity. The behaviour must live on an entity that has a NetworkIdentity (e.g. spawned via SpawnUtilsAPI.KNetInstantiate).
  • BehaviourId is derived from the type name and is set automatically in OnSpawn.
  • SyncInterval is the host tick rate for [SyncVar] replication (seconds).
  • isServer / isClient / inSession are the same checks as SessionInfoAPI, exposed for convenience.

Attributes

[SyncVar] — replicated fields

[SyncVar(Hook = nameof(OnCounterChanged), Epsilon = 0.01f, InterestGroup = -1, SendMode = 8)]
private int _counter;
Property Default Meaning
Hook null Method name called when the value changes: void Hook(T oldValue, T newValue)
Epsilon 0.01f Float change threshold before a sync is sent
InterestGroup -1 Override the class default group (-1 = all)
SendMode 0 (Unreliable) PacketSendMode value
  • Maximum 64 [SyncVar] fields per behaviour.
  • The host replicates changed SyncVars to clients on each SyncInterval tick or if the value changes using a dirty bit mask.

[Command] — client → host

[Command(RequiresHost = false, SendMode = (int) PacketSendMode.Reliable)]
public void CmdSetCounter(int value) { ... }

Method name should start with Cmd. RequiresHost (default false) rejects the call if the receiver isn't the host.

[ClientRpc] — host → clients

[ClientRpc(IncludeHost = false, InterestGroup = -1, SendMode = (int) PacketSendMode.Reliable)]
private void RpcOnCounterUpdated(int newValue) { ... }

Method name should start with Rpc. IncludeHost also runs it locally on the host.

[TargetRpc] — host → one client

[TargetRpc(SendMode = (int) PacketSendMode.Reliable]
private void TargetSendDirectMessage(string message) { ... }

Method name should start with Target.

[Server] / [Client]

Markers that are enforced when a method is dispatched through OxySync RPCs. For Unity lifecycle methods (Update, etc.) still guard manually with if (!isServer) return;.

[InterestGroup] / [FixedInterestGroup]

Class-level [InterestGroup(Group = n)] sets the default group for SyncVars and ClientRpcs. -1 means "everyone". [FixedInterestGroup] marks a group as non-dynamic.

Calling remote methods

Call the generated helpers rather than invoking RPC methods directly:

You must be in a session for these to work.

CallCommand(nameof(CmdSetCounter), 5);
CallCommand(() => CmdSetCounter(5));            // expression overload
CallCommand((Action)MyCommandMethod, arg1);     // delegate overload

CallClientRpc(nameof(RpcOnCounterUpdated), 5);
CallClientRpc(interestGroup: 3, nameof(RpcOnCounterUpdated), 5);

CallTargetRpc(targetPlayerId, nameof(TargetSendDirectMessage), "hi");

CallCommand runs locally when you're the host, otherwise sends to the host. CallClientRpc / CallTargetRpc are host-only.

Supported RPC/SyncVar argument types

Primitives (int, float, bool, byte, long, ulong, short, ushort, uint, sbyte, char, double, decimal, string), enums, Vector2, Vector3, Color, Quaternion, byte[], HashedString, KAnimHashedString, Nullable<T>, arrays, List<T>, HashSet<T>, Queue<T>, Stack<T> and Dictionary<K,V> of supported types.

Full example

using Shared.OxySync;
using Shared.OxySync.Attributes;
using UnityEngine;

public class CounterBehaviour : NetworkBehaviour
{
    [SyncVar(Hook = nameof(OnCounterChanged), SendMode = (int) PacketSendMode.Reliable)]
    private int _counter;

    [SyncVar]
    private float _temperature = 25f;

    [Command]
    public void CmdSetCounter(int value)
    {
        _counter = value;
        CallClientRpc(nameof(RpcOnCounterUpdated), value);
    }

    [Command(RequiresHost = true)]
    public void CmdReset() => _counter = 0;

    [ClientRpc]
    private void RpcOnCounterUpdated(int newValue)
    {
        Debug.Log($"Counter is now {newValue}");
    }

    [TargetRpc]
    private void TargetSendDirectMessage(string message)
    {
        Debug.Log($"Direct: {message}");
    }

    private void OnCounterChanged(int oldValue, int newValue)
    {
        Debug.Log($"{oldValue} -> {newValue}");
    }
}

Attach CounterBehaviour to an object that has a NetworkIdentity (or spawn it via SpawnUtilsAPI.KNetInstantiate) so its NetId resolves.