Sync Component State
Automatically network your component fields with a single decorator.
Fields in your custom components can be networked very easily. Changes to the field will automatically be detected and sent to all users in the room. The changes are also persisted as part of the Room State, so users that join later will receive the current state as well.
Using @syncField
To automatically network a field in a component, decorate it with the @syncField() decorator:
:::: code-group ::: code-group-item Sync a number
import { Behaviour, syncField } from "@needle-tools/engine"
export class SyncedNumber extends Behaviour {
// Use `@syncField` to automatically network a field.
// You can optionally assign a method or method name to be called when the value changes.
@syncField("myValueChanged")
mySyncedValue?: number = 1;
private myValueChanged() {
console.log("My value changed", this.mySyncedValue);
}
onPointerClick() {
this.mySyncedValue = Math.random();
}
}::: ::: code-group-item Sync an object's color
import { Behaviour, IPointerClickHandler, PointerEventData, Renderer, RoomEvents, delay, serializable, showBalloonMessage, syncField } from "@needle-tools/engine";
import { Color } from "three"
export class Networking_ClickToChangeColor extends Behaviour implements IPointerClickHandler {
// START MARKER network color change syncField
/** syncField does automatically send a property value when it changes */
@syncField(Networking_ClickToChangeColor.prototype.onColorChanged)
@serializable(Color)
color!: Color;
private onColorChanged() {
// syncField will network the color as a number, so we need to convert it back to a Color when we receive it
if (typeof this.color === "number")
this.color = new Color(this.color);
this.setColorToMaterials();
}
// END MARKER network color change syncField
/** called when the object is clicked and does generate a random color */
onPointerClick(_: PointerEventData) {
const randomColor = new Color(Math.random(), Math.random(), Math.random());
this.color = randomColor;
}
onEnable() {
this.setColorToMaterials();
}
private setColorToMaterials() {
const renderer = this.gameObject.getComponent(Renderer);
if (renderer) {
for (let i = 0; i < renderer.sharedMaterials.length; i++) {
// we clone the material so that we don't change the original material
// just for demonstration purposes, you can also change the original material
const mat = renderer.sharedMaterials[i]?.clone();
renderer.sharedMaterials[i] = mat;
if (mat && "color" in mat)
mat.color = this.color;
}
}
else console.warn("No renderer found", this.gameObject)
}
}::: ::::
Change Callbacks
The @syncField decorator has an optional parameter to specify a method that should be called when the value changes:
@syncField("onColorChanged")
color?: Color;
private onColorChanged() {
// This runs when the color changes
// Update your object's material, etc.
}The callback method:
- Should be defined in the same class
- Can be a method name (string) or a method reference
- Runs on all clients when the value changes
- Useful for triggering visual updates or side effects
Project Setup
Custom Project Setup
If you're using a custom project setup, you need to have experimentalDecorators: true in your tsconfig.json file for syncField decorators to work. Projects created with Needle Starters have this enabled by default.
Creating and Destroying Objects
Often, you want to create and destroy objects at runtime, and these changes should be synchronized across the network.
PlayerSync Component
The PlayerSync component simplifies this by automatically instantiating a specific object for each connected player. When a player leaves the room, the object is destroyed for all users.
In Unity/Blender:
- Add a
PlayerSynccomponent to your scene - Assign a prefab to the "Player" field
- The prefab will be instantiated for each connected player
Manual Instantiation
Needle Engine provides two high-level methods:
syncInstantiate()
Duplicate objects across the network:
import { syncInstantiate } from "@needle-tools/engine";
// Create a new instance for all users
const newObject = syncInstantiate(originalObject, {
parent: parentObject,
position: new Vector3(0, 1, 0),
visible: true
});syncDestroy()
Destroy objects across the network:
import { syncDestroy } from "@needle-tools/engine";
// Destroy the object for all users
syncDestroy(objectToDestroy);Best Practices
Keep Synced Fields Simple
@syncField works best with simple data types:
- Numbers
- Strings
- Booleans
- Simple objects with JSON-serializable properties
For complex objects or high-frequency updates, consider Manual Networking.
Use Callbacks for Side Effects
Always use the callback parameter when you need to react to changes:
@syncField("onHealthChanged")
health: number = 100;
private onHealthChanged() {
// Update UI, trigger effects, etc.
this.updateHealthBar();
}Consider Performance
Each synced field creates network traffic when it changes. For objects that update frequently (like transforms), use built-in components like SyncedTransform which are optimized for high-frequency updates.
Supported Field Types
The following types can be automatically synced:
- Primitives:
number,string,boolean - three.js types:
Vector2,Vector3,Vector4,Quaternion,Color - Arrays: Arrays of supported types
- Objects: Plain objects with JSON-serializable properties
Next Steps
Learn more:
- Manual Networking - Send custom messages for more control
- Understanding Networking - Learn how state sync works
Reference:
- Networking Events API - Complete event reference
- Built-in Components - Networking component catalog