Skip to main content

Netblocks (Addon)

The netblocks addon enables real-time co-located multiplayer for your XR Blocks applications. It provides peer presence tracking, synchronized entity states, RPC events, and spatial voice channels.


Setup & Room Connection​

To establish a multiplayer session, enable netblocks and join a room:

import * as xb from 'xrblocks';
import {
enableNet,
BroadcastChannelTransport,
} from 'xrblocks/addons/netblocks/src/index.js';

class MultiplayerScript extends xb.Script {
async init() {
// 1. Initialize the netblocks singleton (hooks into the frame loop)
const net = enableNet();

// 2. Join a room using a specified Transport
this.session = await net.joinRoom('main-lounge', {
displayName: 'PlayerOne',
// BroadcastChannelTransport allows testing multiplayer locally across tabs
transport: new BroadcastChannelTransport(),
});

console.log('Connected as peer:', this.session.localPeerId);
}
}

xb.add(new MultiplayerScript());
await xb.init();

Keep the session returned by joinRoom(). The snippets below use this.session inside MultiplayerScript, after joining has completed. enableNet() also exposes the active session as xb.core.net.session; it does not register a string-keyed 'net' service in the registry.


Transport Channels​

Transports abstract network routing, allowing you to swap backends without rewriting application logic:

  • BroadcastChannelTransport: Same-origin communication between browser tabs on the same device, without a server.
  • WebRTCTransport: Peer-to-peer connectivity using the public PeerJS signaling broker by default (up to 12 peers). Default STUN servers are supplied; pass iceServers to add TURN when needed.
  • WebSocketTransport: Relayed connection via a lightweight WebSocket server (scalable to larger rooms).

State Replication (NetObject)​

A NetObject synchronizes a target THREE.Object3D's position, rotation, and scale across all active peers.

Setting Up a Replicated Object​

To replicate a model, wrap it in a NetObject and register it with the session. Add the imports at module scope and the remaining statements after joining in init(). Use the same object ID and equivalent parent coordinate systems on every peer:

import * as THREE from 'three';
import {NetObject} from 'xrblocks/addons/netblocks/src/index.js';

// 1. Create your local mesh
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(0.3, 0.3, 0.3),
new THREE.MeshStandardMaterial({color: 0x0000ff})
);
this.add(mesh);

// 2. Create the NetObject wrapper with a unique ID
this.netObj = new NetObject({
id: 'shared-box-1',
object: mesh,
});

// 3. Register to session
this.session.netObjects.add(this.netObj);

Grabbing and Ownership​

Ownership determines which peer broadcasts the object's transform. Add these direct-hand grab callbacks to MultiplayerScript to claim and release the shared object. They manage network ownership, not movement; configure manipulation separately as described in Interaction and Manipulation.

// Claim ownership when grabbed
onObjectGrabStart(event) {
if (event.target !== this.netObj.object) return;
this.session.claim(this.netObj);
}

// Release ownership when dropped
onObjectGrabEnd(event) {
if (event.target !== this.netObj.object) return;
this.session.release(this.netObj);
}

RPC & Custom Events​

After joining, send and listen for custom events across all connected peers in the room:

const session = this.session;

// Listen for a custom event
session.events.on('ping', (payload, senderId) => {
console.log(`Received ping from peer ${senderId}:`, payload);
});

// Emit event to all other peers
session.events.emit('ping', {time: Date.now()});

Spatial Voice Chat​

Enable spatial WebRTC voice channels so users hear peers panning and attenuating based on their relative distance:

import {
enableNet,
WebRTCTransport,
} from 'xrblocks/addons/netblocks/src/index.js';

const net = enableNet();
const session = await net.joinRoom('lobby', {
transport: new WebRTCTransport(),
voice: true, // Enable voice
});

// Manually mute/unmute
session.voice.setMuted(true);
session.voice.setMuted(false);

Run this after XR Blocks initialization, or use these join options in your script's init(). Muting stops outgoing microphone audio without closing peer connections; it does not mute incoming playback. Call net.leaveRoom() when leaving the room.

For a complete example, see demos/netblocks/index.html and the server relays inside src/addons/netblocks/README.md.