Skip to main content

The Client Entity

On the client side, entities are Three.js objects that represent server-side ECS entities. They receive data updates from the server and render accordingly.

The Entity Base Class​

Every client entity extends VOXELIZE.Entity<T>, where T is the type of data received from the server:

Basic Entity Structure
import * as VOXELIZE from "@voxelize/core";
import * as THREE from "three";

type MyEntityData = {
position: VOXELIZE.Coords3;
health: number;
};

class MyEntity extends VOXELIZE.Entity<MyEntityData> {
constructor(id: string) {
super(id);

this.add(
new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
)
);
}

onCreate = (data: MyEntityData) => {
this.position.set(...data.position);
};

onUpdate = (data: MyEntityData) => {
this.position.set(...data.position);
};

onDelete = () => {
// Cleanup resources if needed
};

update = () => {
// Called each frame for animations
};
}

Lifecycle Hooks​

Entities have four lifecycle hooks:

HookWhen CalledPurpose
onCreateEntity first appearsInitial setup, position, spawn effects
onUpdateServer sends new dataUpdate position, state, animations
onDeleteEntity removedCleanup, despawn effects
updateEvery animation frameContinuous animations, interpolation

Registering Entity Classes​

Register your entity class with the Entities manager:

Entity Registration
const entities = new VOXELIZE.Entities();

entities.setClass("my-entity", MyEntity);

network.register(entities);

The type string must match the server's ETypeComp (case-insensitive).

Position Interpolation​

For smooth movement, interpolate positions in onUpdate and update:

Smooth Position Updates
class SmoothEntity extends VOXELIZE.Entity<{ position: VOXELIZE.Coords3 }> {
private targetPosition = new THREE.Vector3();

onUpdate = (data: { position: VOXELIZE.Coords3 }) => {
this.targetPosition.set(...data.position);
};

update = () => {
this.position.lerp(this.targetPosition, 0.1);
};
}

Using Characters for Entities​

For humanoid entities, use VOXELIZE.Character:

Character-Based Entity
import * as VOXELIZE from "@voxelize/core";

type BotData = {
position: VOXELIZE.Coords3;
direction: number[];
};

class Bot extends VOXELIZE.Entity<BotData> {
public character: VOXELIZE.Character;

constructor(id: string) {
super(id);

this.character = new VOXELIZE.Character({
nameTagOptions: {
fontFace: "monospace",
yOffset: 0.2,
},
});
this.add(this.character);
}

onCreate = (data: BotData) => {
this.character.set(data.position, data.direction);
};

onUpdate = (data: BotData) => {
this.character.set(data.position, data.direction);
};

update = () => {
this.character.update();
};
}

Extending with Mixins​

Use mixins to add reusable functionality:

Mixin Pattern
export const HoldingMixin = <
T extends new (...args: any[]) => VOXELIZE.Character
>(
Base: T
) => {
return class extends Base {
public holdingObjectId = 0;

setHoldingObjectId = (id: number, world?: VOXELIZE.World) => {
if (id === this.holdingObjectId) return;

const mesh = world?.makeBlockMesh(id, { material: "basic" });
this.setArmHoldingObject(mesh);
this.holdingObjectId = id;
};
};
};

class HoldingCharacter extends HoldingMixin(VOXELIZE.Character) {}

Loading 3D Models​

For entities with custom 3D models:

Model-Based Entity
import * as VOXELIZE from "@voxelize/core";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

type ModelData = {
position: VOXELIZE.Coords3;
rotation: number[];
modelUrl: string;
};

class ModelEntity extends VOXELIZE.Entity<ModelData> {
private loader = new GLTFLoader();
private model: THREE.Object3D | null = null;
private targetPosition = new THREE.Vector3();
private targetRotation = new THREE.Euler();

onCreate = (data: ModelData) => {
this.targetPosition.set(...data.position);
this.position.copy(this.targetPosition);

this.loader.load(data.modelUrl, (gltf) => {
this.model = gltf.scene;
this.add(this.model);
});
};

onUpdate = (data: ModelData) => {
this.targetPosition.set(...data.position);
this.targetRotation.set(
data.rotation[0],
data.rotation[1],
data.rotation[2]
);
};

update = () => {
this.position.lerp(this.targetPosition, 0.1);
this.rotation.x += (this.targetRotation.x - this.rotation.x) * 0.1;
this.rotation.y += (this.targetRotation.y - this.rotation.y) * 0.1;
this.rotation.z += (this.targetRotation.z - this.rotation.z) * 0.1;
};
}

Debug Visualization​

Add debug helpers to visualize entity state:

Debug Helpers
class DebugBot extends VOXELIZE.Entity<BotData> {
private pathLine: THREE.Line | null = null;

onUpdate = (data: BotData & { path?: VOXELIZE.Coords3[] }) => {
if (data.path && data.path.length > 0) {
this.updatePathVisualization(data.path);
}
};

private updatePathVisualization(path: VOXELIZE.Coords3[]) {
if (this.pathLine) {
this.remove(this.pathLine);
}

const points = path.map(
(p) => new THREE.Vector3(p[0] + 0.5, p[1] + 0.5, p[2] + 0.5)
);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });
this.pathLine = new THREE.Line(geometry, material);
this.add(this.pathLine);
}
}

Custom Entities Manager​

Extend the base Entities class for additional functionality:

Custom Entities Manager
import * as VOXELIZE from "@voxelize/core";
import { EntityProtocol } from "@voxelize/protocol";

class GameEntities extends VOXELIZE.Entities {
public lastEntity: EntityProtocol<any> | null = null;

onEntity = (entity: EntityProtocol<any>) => {
this.lastEntity = entity;

if (entity.type === "scoreboard") {
this.handleScoreboard(entity);
}
};

private handleScoreboard(entity: EntityProtocol<any>) {
// Handle scoreboard updates
}
}

Adding Entities to the Scene​

The Entities manager is a Three.js Group. Add it to your world:

Adding to Scene
const entities = new VOXELIZE.Entities();

entities.setClass("bot", Bot);
entities.setClass("drop", Drop);

network.register(entities);

world.add(entities);

Call update() in your render loop:

Render Loop
function animate() {
requestAnimationFrame(animate);

entities.update();

renderer.render(scene, camera);
}

Read on to learn about creating entities on the server.