Skip to main content

The Events System

Events are location-based messages that broadcast to nearby clients. Unlike methods which are world-wide RPC calls, events only reach clients who have the originating chunk loaded.

How Events Work

  1. Client emits an event
  2. Server receives and determines the client's current chunk
  3. Event broadcasts to all clients with that chunk loaded
  4. Or, server can handle the event with a custom handler

Client-Side Events

Setting Up

Events Setup
import * as VOXELIZE from "@voxelize/core";

const events = new VOXELIZE.Events();

network.register(events);

Emitting Events

Emitting an Event
events.emit("attack", {
targetId: "enemy-123",
damage: 10,
});

Listening for Events

Listening for Events
events.on("explosion", (payload) => {
const { position, radius } = payload;
createExplosionEffect(position, radius);
});

events.on("player-hit", (payload) => {
const { playerId, damage } = payload;
showDamageIndicator(playerId, damage);
});

Removing Listeners

Removing Listeners
const handler = (payload: any) => {
console.log(payload);
};

events.on("my-event", handler);
events.off("my-event", handler);

Server-Side Events

Handling Events

Handle events on the server instead of broadcasting:

Server Event Handler
#[derive(Serialize, Deserialize)]
struct AttackPayload {
target_id: String,
damage: i32,
}

world.set_event_handle("attack", |world, client_id, payload| {
let data: AttackPayload = serde_json::from_str(payload)
.expect("Invalid attack payload");

// Process the attack
// Find target entity and apply damage
});

Dispatching Events

Dispatch events from the server:

Dispatching Events
use voxelize::{Event, Vec2};

world.events_mut().dispatch(
Event::new("explosion")
.payload(serde_json::json!({
"position": [10.0, 64.0, 10.0],
"radius": 5.0,
}))
.location(Vec2(0, 0)) // Chunk coordinates
.build()
);

Broadcast to All

To broadcast to all clients regardless of location:

Broadcasting to All
world.events_mut().dispatch(
Event::new("server-announcement")
.payload(serde_json::json!({
"message": "Server restarting in 5 minutes",
}))
.build()
);

Event vs Method

FeatureEventMethod
ScopeLocation-basedWorld-wide
Default behaviorBroadcast to nearbyRequires handler
Use caseVisual effects, local actionsState changes, RPCs

Example: Hit Effect

Client emits hit event:

Client Hit Event
function onPlayerHit(targetId: string) {
events.emit("player-hit", {
targetId,
attackerId: network.clientId,
});
}

Server processes and broadcasts:

Server Hit Handler
world.set_event_handle("player-hit", |world, client_id, payload| {
let data: HitPayload = serde_json::from_str(payload).unwrap();

// Apply damage to target
// ...

// Get attacker position for effect location
let client = world.clients().get(client_id).unwrap();
let pos = world.read_component::<PositionComp>()
.get(client.entity)
.unwrap();

// Broadcast hit effect to nearby players
let chunk = world.read_component::<CurrentChunkComp>()
.get(client.entity)
.unwrap()
.coords
.clone();

world.events_mut().dispatch(
Event::new("hit-effect")
.payload(serde_json::json!({
"position": [pos.0.0, pos.0.1, pos.0.2],
"targetId": data.target_id,
}))
.location(chunk)
.build()
);
});

Client renders effect:

Client Hit Effect
events.on("hit-effect", (payload) => {
const { position, targetId } = payload;
playHitSound(position);
showHitParticles(position);
});

Example: Block Break Effect

Block Break Event
events.on("block-break", (payload) => {
const { voxel, blockId } = payload;
const [vx, vy, vz] = voxel;

const particles = createBlockParticles(blockId);
particles.position.set(vx + 0.5, vy + 0.5, vz + 0.5);
world.add(particles);

setTimeout(() => {
world.remove(particles);
}, 1000);
});

Read on to learn about method handling.