Auto-Magical Sync
Tag a field or property with a single attribute, and GONet takes care of everything else. No manual serialization, no send/receive boilerplate, no headaches.
What is Auto-Magical Sync?
Auto-Magical Sync is the core synchronization system in GONet. You mark any field or property with the [GONetAutoMagicalSync] attribute and GONet handles detecting changes, serializing data, transmitting it across the network, and applying it on remote machines. There is no manual serialization code to write and no send/receive methods to implement.
Behind the scenes, GONet uses code generation to produce highly optimized sync logic for every marked field. When a value changes on the authority, GONet detects the difference, encodes it efficiently, and delivers it to all other participants in the session. On the receiving end, values are applied automatically, with optional interpolation for smooth visual results.
This design means you can focus entirely on gameplay logic. Movement, health, ammunition, scores, animation state -- anything you want synchronized simply gets an attribute, and GONet does the rest.
Basic Usage
Here is a minimal example. A PlayerCharacter class syncs health and position with just two attributes. The authority moves the character normally, and GONet automatically replicates the position to every other machine.
public class PlayerCharacter : GONetParticipantCompanionBehaviour
{
[GONetAutoMagicalSync]
public float Health { get; set; } = 100f;
[GONetAutoMagicalSync]
public Vector3 Position
{
get => transform.position;
set => transform.position = value;
}
internal override void UpdateAfterGONetReady()
{
if (IsMine)
{
// Just move — GONet syncs Position automatically
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
transform.Translate(new Vector3(h, 0, v) * Time.deltaTime * 5f);
}
}
}That is all it takes. The IsMine check ensures only the owning client writes to the property. Every other client receives the updated position automatically and applies it to their local copy of the object.
Supported Types
The [GONetAutoMagicalSync] attribute supports the following value types for automatic network synchronization:
Primitives
bool, byte, sbyte, short, ushort, int, uint, long, ulong, float
Unity Math
Vector2, Vector3, Vector4, Quaternion
Note on double: double is explicitly rejected by the code generator because the value blending system operates on float precision. Use float instead, or use a custom serializer if you need double-precision data on the wire.
Other types (enums, strings, collections, custom types): These are not supported by [GONetAutoMagicalSync] directly. To send enums, strings, arrays, lists, dictionaries, or custom structs/classes across the network, use RPCs or Events, which support a much broader set of types via MemoryPack serialization. You can also bridge unsupported types into auto-sync using a custom serializer.
Hard limit: Each GONetParticipant supports a maximum of 256 synced fields. If you need more, split your data across multiple GONetParticipant components on child objects.
Sync Settings
Every synced field can be fine-tuned directly in the attribute. These inline settings let you control update rate, reliability, interpolation behavior, and processing order on a per-field basis.
[GONetAutoMagicalSync(
SyncChangesEverySeconds = 0.05f, // 20 updates/sec
Reliability = AutoMagicalSyncReliability.Reliable,
ShouldBlendBetweenValuesReceived = true, // Interpolate on receivers
ProcessingPriority = 0 // Higher = processed first
)]
public float Health { get; set; }- SyncChangesEverySeconds -- Controls how often changes are checked and sent. Lower values mean lower latency but higher bandwidth. The default depends on your GONet configuration.
- Reliability -- Choose between
Reliable(guaranteed delivery, ordered) andUnreliable(faster, may drop). Use Reliable for critical state like health; Unreliable for high-frequency data like position. - ShouldBlendBetweenValuesReceived -- When true, receivers interpolate between updates rather than snapping. Essential for smooth visual movement.
- ProcessingPriority -- Higher priority values are processed first within a frame. Useful when one field depends on another being up to date.
Sync Profiles
When you find yourself applying the same sync settings across many fields, Sync Profiles save you the repetition. These are ScriptableObjects that bundle a complete set of sync parameters into a reusable template.
GONet ships with several pre-built profiles, located at Resources/GONet/SyncSettingsProfiles/:
__GONet_DEFAULT-- Baseline settings for general-purpose sync (24 Hz, unreliable, blending on)_GONet_Transform_Position-- Optimized for position data (velocity-augmented, Hermite spline blending)_GONet_Transform_Rotation-- Optimized for rotation data (velocity-augmented, low-pass filter blending)_GONet_Animator_Controller_Parameters-- For animator parameters (auto-selects between float and discrete profiles based on parameter type)
[GONetAutoMagicalSync(SettingsProfileTemplateName = "MyGame_SmoothFloat")]
public float Speed;To create your own profile, duplicate an existing one in the Unity Project window, rename it, and adjust the settings in the Inspector. Profile settings override any inline attribute settings when a profile is specified. For a complete reference of every profile setting -- including custom value blending and custom serializer overrides -- see the Sync Profiles Deep Dive section below.
Quantization
Quantization compresses continuous floating-point values into fewer bits, reducing bandwidth at the cost of a small precision trade-off. For many gameplay values, the precision loss is imperceptible -- and as you will see below, GONet's sub-quantization step detection can fill in the gaps for you automatically.
Here is a basic example that quantizes a position component:
[GONetAutoMagicalSync(
QuantizeDownToBitCount = 14,
QuantizeLowerBound = -100f,
QuantizeUpperBound = 100f
)]
public float PositionX { get; set; }Two settings work together to determine your bandwidth and precision: QuantizeDownToBitCount controls how many bits each value consumes on the wire (directly controlling bandwidth), while the bounds (QuantizeLowerBound and QuantizeUpperBound) control how those bits are distributed across a range (determining precision). You can tune both independently to get exactly the bandwidth and precision you want.
Quantization: Choosing Bits and Bounds
The formula is simple: precision = range / 2^bits. A smaller range or more bits means better precision. Fewer bits means less bandwidth. Understanding this trade-off lets you make informed decisions for every synced value in your game. And keep in mind: with sub-quantization step detection, you can often get away with fewer bits than the math suggests, because velocity sync fills in the precision gaps automatically.
How bit count affects bandwidth
Every synced float defaults to 32 bits (a standard IEEE float). Quantization lets you choose exactly how many bits to spend. Here is what different bit counts look like for a single position axis with a [-100, 100] range (200 unit span):
| Bits | Precision | In mm | Bytes per Vector3 | Bandwidth savings |
|---|---|---|---|---|
| 32 (unquantized) | full float | -- | 12 bytes | baseline |
| 16 | ~0.003 units | ~3 mm | 6 bytes | 50% |
| 14 | ~0.012 units | ~12 mm | 5.25 bytes | 56% |
| 12 | ~0.049 units | ~49 mm | 4.5 bytes | 63% |
| 10 | ~0.195 units | ~195 mm | 3.75 bytes | 69% |
| 8 | ~0.784 units | ~784 mm | 3 bytes | 75% |
How range affects precision
With the same 14-bit quantization, a narrower range gives better precision. You do not need to make the range match your world size -- auto re-baselining (explained below) handles that. Choose the range that gives you the precision you want.
| Range | Span | Precision (14 bits) | In mm |
|---|---|---|---|
| [-25, 25] | 50 units | ~0.003 units | ~3 mm |
| [-50, 50] | 100 units | ~0.006 units | ~6 mm |
| [-100, 100] | 200 units | ~0.012 units | ~12 mm |
| [-250, 250] | 500 units | ~0.031 units | ~31 mm |
| [-500, 500] | 1000 units | ~0.061 units | ~61 mm |
Notice: at [-100, 100] with 14 bits, you get ~12 mm precision. Most games will never need precision finer than that for position. And if you do, just use a narrower range or add a couple of bits.
Real-world examples
Here is how to think about quantization for different kinds of games:
FPS / Third-person
Player moves at ~5 m/s. Camera is close.
14 bits, [-100, 100] = ~12 mm precision. Plenty for characters, projectiles, and items. Most players will never notice.
Racing / flight sim
Vehicles at 50+ m/s. High speed, less close-up scrutiny.
12 bits, [-100, 100] = ~49 mm precision. At speed, ~5 cm of precision is invisible. Or use 14 bits for a tighter feel. Either way: huge bandwidth savings.
RTS / top-down
Units are tiny on screen. Camera is far away.
10 bits, [-100, 100] = ~195 mm precision. With dozens or hundreds of units, aggressively low bit counts save enormous bandwidth. 20 cm of drift is invisible at RTS zoom levels.
Slow rotation (turrets, doors)
Rotates at 10-30 deg/s. Players see the angle closely.
12 bits, [-180, 180] = ~0.088 degree precision. Smooth enough for any turret or door. And with velocity sync, even sub-step motion is captured (see below).
Putting it all together: full bandwidth calculation
Here is a concrete example showing total bandwidth for one player's position sync:
Setup: Vector3 position, 14 bits per component, [-100, 100] range, 24 Hz sync
Per update: 3 axes x 14 bits = 42 bits = 5.25 bytes
Per second: 5.25 bytes x 24 Hz = 126 bytes/sec
vs. unquantized: 12 bytes x 24 Hz = 288 bytes/sec
Savings: 56% bandwidth reduction per synced position
With velocity-augmented sync: ~50% of those 24 updates are VELOCITY packets (often 10 bits per axis = 30 bits = 3.75 bytes), so effective per-second drops to roughly 108 bytes/sec -- a combined ~63% reduction.
Multiply these savings across every synced value on every networked object, and quantization is often the single biggest bandwidth optimization you can make.
Auto Re-Baselining
One of the most counterintuitive aspects of GONet's quantization is that the bounds do not limit where objects can go. You can set [-100, 100] and have objects at position 50,000 without any issues. This is not a special mode or an edge case -- it is how quantization works for every GONet project, whether your map is 200 meters or 200 kilometers.
The reason: GONet does not quantize absolute values. It quantizes the delta from a dynamically adjusted baseline. The bounds define the size of a sliding window around that baseline, and the window moves automatically as values change.
How it works
- 1.Initial baseline -- When sync starts, the baseline is set to the object's current value. All quantized values are encoded as deltas from this baseline.
- 2.80% threshold check -- Every frame, GONet checks whether the delta from the current baseline is approaching the edge of the quantization range. The threshold fires at 80% of the bounds -- well before the value would saturate.
- 3.Re-baseline -- When the threshold is hit, GONet re-centers the baseline at the current value and sends two reliable events to all clients: a baseline-expired notification and the new baseline value.
- 4.All clients update -- Every client updates its local baseline. Future quantized deltas are relative to the new baseline, keeping them small and efficient.
Example walkthrough
[GONetAutoMagicalSync(
QuantizeDownToBitCount = 14,
QuantizeLowerBound = -100f,
QuantizeUpperBound = 100f
)]
public float PositionX { get; set; }
// Object spawns at x = 0
// baseline = 0
// delta = 0 - 0 = 0 → fits in [-100, 100] ✓
// Object moves to x = 85
// delta = 85 - 0 = 85 → exceeds 80% of 100 (80) → REBASELINE!
// GONet sends a reliable baseline-update event
// new baseline = 85, delta resets to 0
// Object moves to x = 170
// delta = 170 - 85 = 85 → exceeds 80% again → REBASELINE
// new baseline = 170, delta resets to 0
// Object at x = 50,000
// dozens of rebaselines have occurred along the way
// each one costs a single reliable event (a few bytes)
// but every regular sync tick still uses just 14 bits
// quantization precision stays at ~12 mm the entire timeWhat does a re-baseline cost?
Each re-baseline sends one reliable event containing the new baseline value -- a few bytes, delivered once. Compare that to the continuous savings: every sync tick after that re-baseline uses your chosen bit count (e.g., 14 bits instead of 32 bits). At 24 Hz, that is 24 savings per second for the cost of one small event every time the object travels 80% of the window. For most games, re-baselines are infrequent compared to the thousands of quantized updates sent between them.
The key takeaway
Choose your bounds based on the precision you want, not the size of your world. A range of [-100, 100] with 14 bits gives ~12 mm precision. A range of [-50, 50] with the same 14 bits gives ~6 mm. A range of [-25, 25] gives ~3 mm. The narrower the range, the better the precision for the same bandwidth.
Auto re-baselining ensures that no matter how far your objects travel, quantization stays efficient and precise. You do not need to worry about clamping, overflow, or out-of-range values. GONet handles it.
Sub-Quantization Step Detection
This is where quantization and velocity-augmented sync combine into something genuinely unique. Normally, when you aggressively quantize (fewer bits, wider range), there is a visible trade-off: objects that move slowly can appear to "step" or "stutter" because their per-tick motion is smaller than one quantization step. GONet eliminates this problem entirely.
The problem
Consider a quantization setup with 12 bits over [-100, 100]:
Step size: 200 / 4096 = ~0.049 units (~49 mm)
Object speed: 0.5 units/sec
Sync rate: 24 Hz
Motion per sync tick: 0.5 / 24 = ~0.021 units
Problem: 0.021 < 0.049 -- each tick's motion is smaller than one quantization step!
Without velocity sync, the quantized position would not change from one tick to the next. The value rounds to the same quantized output. The object appears frozen, then snaps forward when it finally crosses a step boundary (every ~2.3 ticks in this example). This creates a staircase-like stuttering motion.
GONet's solution
When velocity-augmented sync is enabled, GONet uses raw (unquantized) values for change detection. Even if the quantized position has not changed, GONet detects that the raw position did change, calculates the actual velocity, and sends a VELOCITY packet. The receiving client uses that velocity to extrapolate smooth motion between the quantized anchor points.
Tick 1: Position = 10.000, quantized = 10.010 (step boundary), velocity = 0.5 u/s
Tick 2: Position = 10.021, quantized = 10.010 (same step!), velocity = 0.5 u/s
Tick 3: Position = 10.042, quantized = 10.010 (still same step), velocity = 0.5 u/s
Without velocity sync: receiver sees no change for 3 ticks, then snaps
With velocity sync: receiver extrapolates 0.5 u/s continuously -- smooth motion
What this means in practice
Sub-quantization step detection means you can quantize much more aggressively than you otherwise could. The precision gap that normally limits how few bits you can use is largely filled by velocity-based extrapolation:
| Setup | Step size | Without velocity sync | With velocity sync |
|---|---|---|---|
| 14 bits, [-100, 100] | ~12 mm | Smooth above ~0.3 m/s | Smooth at any speed |
| 12 bits, [-100, 100] | ~49 mm | Stutters below ~1.2 m/s | Smooth at any speed |
| 10 bits, [-100, 100] | ~195 mm | Stutters below ~4.7 m/s | Smooth at any speed |
| 8 bits, [-100, 100] | ~784 mm | Stutters below ~18.8 m/s | Smooth at any speed |
The "stutters below" column shows the minimum speed at which quantized position updates change every tick (at 24 Hz). Below that speed, only velocity sync keeps the motion smooth. This is why velocity-augmented sync is not just a bandwidth optimization -- it is also a quality optimization that lets you use fewer bits without sacrificing smoothness.
Blending and Interpolation
When ShouldBlendBetweenValuesReceived is set to true, GONet interpolates between received values on the client side. Instead of snapping to each new value as it arrives, the object smoothly transitions from its current state to the target state.
This dramatically reduces the visual jitter that comes from receiving discrete network updates at a lower rate than the local frame rate. A server sending 20 updates per second still looks smooth at 60+ FPS on the client thanks to interpolation.
GONet uses an adaptive buffer system that automatically adjusts to current network conditions:
- Normal conditions (e.g., 24Hz updates): The interpolation buffer stays near its minimum, around 100ms. Latency is low and movement looks responsive.
- Under congestion (e.g., 2Hz updates): The buffer automatically expands to approximately 750ms, giving the system enough data points to interpolate smoothly even when packets arrive infrequently.
You can configure the buffer bounds and blending behavior in the GONetGlobal inspector on your GONetGlobal prefab in the scene.
Velocity-Augmented Sync
For objects that move at relatively steady velocities -- players running, vehicles cruising, projectiles in flight -- velocity-augmented sync can reduce bandwidth by over 90%.
Instead of sending the position every sync tick, GONet alternates between VALUE packets (the actual position) and VELOCITY packets (the current velocity). Receiving clients extrapolate position between value updates using the velocity, which means far fewer full-precision position packets need to cross the wire.
[GONetAutoMagicalSync(IsVelocityEligible = true)]
public Vector3 Position { get; set; }This works best for objects with predictable trajectories: player characters, vehicles, and projectiles. Objects that teleport or change direction instantly will not benefit as much, because the velocity prediction will be wrong more often.
Additional velocity sync settings are available through Sync Profiles:
VelocityQuantizeDownToBitCount-- Bit count for velocity quantizationVelocityQuantizeLowerBound-- Lower bound of the velocity rangeVelocityQuantizeUpperBound-- Upper bound of the velocity rangeVelocityAnchorIntervalSeconds-- How often to send a full VALUE packet as an anchor
Intrinsic Sync: Position, Rotation, and Animator
GONet automatically generates sync code for three things without you adding any attributes: position, rotation, and Animator Controller parameters. These are called intrinsics -- they use dedicated, pre-built sync profiles that are optimized for their specific data types.
Position and Rotation
Every GONetParticipant has built-in position and rotation sync. You enable it at design time by checking IsPositionSyncd and IsRotationSyncd on the GONetParticipant component in the Inspector. Once set, these cannot be changed at runtime -- the code generator uses them to determine what sync code to produce. They use the _GONet_Transform_Position and _GONet_Transform_Rotation profiles respectively, which are velocity-augmented by default for bandwidth savings and smooth motion.
Animator Controller Parameters
GONet's code generator scans every Animator on your GONetParticipant GameObjects and generates sync code for each parameter. You configure which parameters to sync in the GONetParticipant inspector, and GONet automatically selects the right sync profile based on the parameter type:
- Float parameters (Speed, BlendAmount, etc.) -- Use the float profile: unreliable delivery, blending enabled for smooth transitions, synced at 20 Hz.
- Int and Bool parameters (State, IsGrounded, etc.) -- Use the discrete profile: reliable delivery (state transitions must not be missed), no blending (you are either in state 1 or state 2, never 1.5), synced immediately at end-of-frame.
This selection is automatic. You do not choose between the float or discrete profiles -- GONet inspects the Animator Controller parameter type and assigns the correct one during code generation. You can toggle individual parameters on or off at runtime:
// Toggle a specific animator parameter's sync at runtime
gonetParticipant.animatorSyncSupport["Speed"].isSyncd = false;
// IMPORTANT: Refresh the cache after runtime changes
gonetParticipant.RefreshAnimatorSyncCache();Animator Triggers
Triggers are a special case. Unlike float, int, and bool parameters, Unity's Animator does not provide a way to read trigger state (there is no GetTrigger() method). Since auto-sync requires reading values to detect changes, triggers cannot be automatically synchronized.
Instead, GONet provides a dedicated method on GONetParticipant that fires the trigger locally and sends it over the network as an event:
// Fire a networked trigger (sends to all clients)
gonetParticipant.SetAnimatorTrigger("Attack");
// For better performance, use the pre-computed hash constant:
gonetParticipant.SetAnimatorTrigger(AnimatorTriggerHashes.Attack);Under the hood, SetAnimatorTrigger applies the trigger on the local Animator, then publishes an AnimatorTriggerFiredEvent that GONet routes to all other clients via the Event Bus. Remote clients receive the event and apply the trigger on their local copy. The trigger resets at the end of the frame, just like a normal Unity trigger.
// Full example: triggering a networked attack animation
public class AttackBehaviour : GONetParticipantCompanionBehaviour
{
internal override void UpdateAfterGONetReady()
{
if (IsMine && Input.GetButtonDown("Fire1"))
{
// Fires locally + sends to all other clients
GONetParticipant.SetAnimatorTrigger("Attack");
}
}
}Intrinsic profiles summary
Position, rotation, and animator parameters all use fixed, pre-built profiles optimized for their data type. These are configured at design time and baked in during code generation -- they cannot be toggled or swapped at runtime. You can edit the profile ScriptableObjects in Resources/GONet/SyncSettingsProfiles/ to globally adjust settings like sync frequency or quantization, but the profile assignments themselves are fixed.
Advanced: The sections below cover the complete Sync Profile settings reference, custom value blending implementations, and custom serializer overrides. If you are just getting started, you can skip ahead to the Next Steps.
Sync Profiles: Complete Settings Reference
Every Sync Profile exposes the following settings in the Unity Inspector. The Inspector also shows a live readout of the resulting quantization precision (in source units and millimeters) so you can see exactly how much precision you are trading for bandwidth.
Sync Frequency
| Setting | Default | Description |
|---|---|---|
SyncChangesASAP | false | If true, sync changes immediately (end of frame). Overrides frequency settings below. |
SyncChangesFrequencyOccurrences | 24 | How many times per unit of time to check for and send changes. Range: 0-1000. |
SyncChangesFrequencyUnitOfTime | TimesPerSecond | Unit of time for the frequency. Options: TimesPerSecond, TimesPerMinute, TimesPerHour, TimesPerDay. |
Reliability & Blending
| Setting | Default | Description |
|---|---|---|
SendViaReliability | Unreliable | Reliable (guaranteed, ordered) or Unreliable (faster, may drop). Use Reliable for critical state (health, ammo); Unreliable for high-frequency data (position). |
ShouldBlendBetweenValuesReceived | false | Enable interpolation/extrapolation on receivers. When true, values are smoothed between network updates instead of snapping. |
Quantization (Value)
| Setting | Default | Description |
|---|---|---|
QuantizeDownToBitCount | 0 | Number of bits for quantization (0 = no quantization, max 31). Lower bits = less bandwidth but less precision. |
QuantizeLowerBound | float.MinValue / 2 | Lower bound of the quantization window. Sets precision, not world limits (see auto re-baselining). |
QuantizeUpperBound | float.MaxValue / 2 | Upper bound of the quantization window. Sets precision, not world limits. |
The Inspector also displays a read-only QuantizationResultingPrecision field showing the calculated precision in source units and millimeters.
Velocity-Augmented Sync
| Setting | Default | Description |
|---|---|---|
IsVelocityEligible | false | Enable velocity-augmented sync. Only for smooth/predictable values (positions, rotations) -- not for discrete values like health. |
AutoCalculateVelocityQuantization | true | If true, velocity bounds are calculated automatically from the VALUE precision and sync interval. If false, uses the manual settings below. |
VelocityQuantizeLowerBound | -20 | Lower bound for velocity quantization (units/sec). Only used when AutoCalculateVelocityQuantization is false. |
VelocityQuantizeUpperBound | 20 | Upper bound for velocity quantization (units/sec). Only used when AutoCalculateVelocityQuantization is false. |
VelocityQuantizeDownToBitCount | 10 | Bit count for velocity quantization. Can be lower than value bit count since velocity is less precision-sensitive. Range: 1-31. |
VelocityAnchorIntervalSeconds | 0 | Max seconds between full VALUE anchors during velocity sync (0 = use global default). Prevents drift from accumulating. Range: 0-5. |
Processing & Physics
| Setting | Default | Description |
|---|---|---|
ProcessingPriority | 0 | Order within a frame. Higher = processed first. Useful when one field depends on another. Range: -255 to 255. |
MustRunOnUnityMainThread | false | Force processing on the Unity main thread. Required for values that interact with Unity APIs that are not thread-safe. |
PhysicsUpdateInterval | 1 | For Rigidbody sync: how often to sync physics state. 1 = every FixedUpdate (60 Hz), 2 = every 2nd (30 Hz), up to 4. Range: 1-4. |
Custom Serializer & Value Blending Overrides
Sync Profiles expose two override arrays that let you plug in your own implementations for serialization and value blending. These are only available through profiles, not through inline attribute settings.
| Setting | Description |
|---|---|
SyncValueTypeSerializerOverrides | Array of (ValueType, CustomSerializerType) pairs. Each entry maps a GONet sync type to your custom serializer class. |
SyncValueTypeValueBlendingOverrides | Array of (ValueType, CustomValueBlendingType) pairs. Each entry maps a GONet sync type to your custom blending strategy class. |
Custom Value Blending
GONet ships with built-in blending strategies (Hermite spline for position, low-pass filter for rotation), but you can implement your own. Create a class that implements IGONetAutoMagicalSync_CustomValueBlending and assign it in a Sync Profile.
public class MyCustomBlending : IGONetAutoMagicalSync_CustomValueBlending
{
public GONetSyncableValueTypes AppliesOnlyToGONetType
=> GONetSyncableValueTypes.System_Single;
public string Description => "My custom float blending";
public bool TryGetBlendedValue(
NumericValueChangeSnapshot[] valueBuffer,
int valueCount,
long atElapsedTicks,
out GONetSyncableValue blendedValue,
out bool didExtrapolatePastMostRecentChanges)
{
// valueBuffer is a ring buffer sorted most-recent-first
// valueCount is how many entries are valid
// atElapsedTicks is the target time (now - buffer lead)
// Return true if you produced a valid blended value
// Example: simple linear interpolation between two nearest snapshots
// ... your implementation here ...
blendedValue = default;
didExtrapolatePastMostRecentChanges = false;
return false;
}
}The valueBuffer contains a ring buffer of recent value snapshots with timestamps. Each snapshot includes the value and, for velocity-augmented fields, the velocity at that point. Your implementation can use any interpolation or extrapolation strategy -- linear, cubic, spring-damper, predictive, etc.
The didExtrapolatePastMostRecentChanges output tells GONet whether you had to extrapolate beyond available data, which affects at-rest detection and stale value handling.
Custom Serializers
For complete control over how a value is encoded on the wire, implement the IGONetAutoMagicalSync_CustomSerializer interface. This lets you define custom bit packing, compression, or encoding schemes.
public class MyCustomSerializer : IGONetAutoMagicalSync_CustomSerializer
{
public void InitQuantizationSettings(
byte quantizeDownToBitCount,
float quantizeLowerBound,
float quantizeUpperBound)
{
// Store quantization config for use in Serialize/Deserialize
}
public bool AreEqualConsideringQuantization(
GONetSyncableValue valueA,
GONetSyncableValue valueB)
{
// Return true if the two values would produce
// identical quantized output (avoids unnecessary sends)
}
public void Serialize(
BitByBitByteArrayBuilder bitStream_appendTo,
GONetParticipant gonetParticipant,
GONetSyncableValue value)
{
// Write the value to the bit stream
}
public GONetSyncableValue Deserialize(
BitByBitByteArrayBuilder bitStream_readFrom)
{
// Read and return the value from the bit stream
}
}GONet creates three serializer instances per synced value when velocity-augmented sync is active: one for VALUE packets (using position quantization bounds), one for VELOCITY packets (using tighter velocity-specific bounds), and a combined instance for backward compatibility. Your custom serializer receives the appropriate bounds via InitQuantizationSettings().
Assigning Custom Overrides to a Profile
To use your custom blending or serializer, create a Sync Profile (or duplicate an existing one) and assign your class in the Inspector:
- 1.Duplicate an existing profile in
Resources/GONet/SyncSettingsProfiles/and rename it (e.g.,MyGame_SmoothPosition). - 2.In the Inspector, expand Sync Value Type Value Blending Overrides or Sync Value Type Serializer Overrides. Add an entry, select the value type, and assign your class.
- 3.Reference the profile by name in your attribute:
[GONetAutoMagicalSync(SettingsProfileTemplateName = "MyGame_SmoothPosition")]
public Vector3 Position
{
get => transform.position;
set => transform.position = value;
}Next Steps
Now that you understand how to synchronize state, learn how to invoke logic across the network with Remote Procedure Calls, or jump straight to performance tuning.