Custom Peers
Peers represent other players in the world. You can customize peer rendering by extending the Peers class and using mixins.
The Peer Class
The default peer uses VOXELIZE.Character:
Basic Peer
import * as VOXELIZE from "@voxelize/core";
class Peer extends VOXELIZE.Character {
constructor() {
super({
nameTagOptions: {
fontFace: "monospace",
yOffset: 0.2,
},
});
}
}
The Peers Manager
Extend VOXELIZE.Peers to customize peer creation and updates:
Custom Peers Manager
import * as VOXELIZE from "@voxelize/core";
type PeersMeta = {
position: VOXELIZE.Coords3;
direction: number[];
role?: string;
holdingObjectId?: number;
};
class Peers extends VOXELIZE.Peers<Peer, PeersMeta> {
createPeer = (id: string): Peer => {
const peer = new Peer();
peer.userData.id = id;
return peer;
};
onPeerUpdate = (
peer: Peer,
metadata: PeersMeta,
info: { username: string }
) => {
peer.set(metadata.position, metadata.direction);
peer.username = info.username;
};
}
Mixins for Extra Features
Holding Objects
Add the ability for peers to hold items:
Holding Mixin
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;
if (id === 0) {
this.setArmHoldingObject(undefined);
} else {
const mesh = world?.makeBlockMesh(id, { material: "basic" });
this.setArmHoldingObject(mesh);
}
this.holdingObjectId = id;
};
};
};
class Peer extends HoldingMixin(VOXELIZE.Character) {}
Hit Effects
Add visual feedback when a peer is hit:
Hittable Mixin
export const HittableMixin = <
T extends new (...args: any[]) => VOXELIZE.Character
>(
Base: T
) => {
return class extends Base {
private hitTimeout: number | null = null;
hit = () => {
this.head.children.forEach((child) => {
if (child instanceof THREE.Mesh) {
(child.material as THREE.MeshBasicMaterial).color.setHex(0xff0000);
}
});
if (this.hitTimeout) clearTimeout(this.hitTimeout);
this.hitTimeout = window.setTimeout(() => {
this.head.children.forEach((child) => {
if (child instanceof THREE.Mesh) {
(child.material as THREE.MeshBasicMaterial).color.setHex(0xffffff);
}
});
}, 300);
};
};
};
Combined Mixins
Combined Peer
class Peer extends HittableMixin(HoldingMixin(VOXELIZE.Character)) {
public role = "player";
}
Peer Groups
Organize peers into groups:
Peer Groups
class Peers extends VOXELIZE.Peers<Peer, PeersMeta> {
public players = new THREE.Group();
public spectators = new THREE.Group();
createPeer = (id: string): Peer => {
const peer = new Peer();
this.players.add(peer);
return peer;
};
onPeerUpdate = (peer: Peer, metadata: PeersMeta) => {
if (metadata.isSpectator) {
this.players.remove(peer);
this.spectators.add(peer);
peer.visible = false;
} else {
this.spectators.remove(peer);
this.players.add(peer);
peer.visible = true;
}
};
}
Server-Side Peer Data
Client Parser
Parse incoming peer data on the server:
Client Parser
world.set_client_parser(|world, metadata_str, client_ent| {
#[derive(Deserialize)]
struct PeerUpdate {
position: Option<Vec3<f32>>,
direction: Option<Vec3<f32>>,
}
let metadata: PeerUpdate = serde_json::from_str(metadata_str).unwrap();
if let Some(pos) = metadata.position {
let mut positions = world.write_component::<PositionComp>();
if let Some(p) = positions.get_mut(client_ent) {
p.0.set(pos.0, pos.1, pos.2);
}
}
});
Client Modifier
Add components to new clients:
Client Modifier
world.set_client_modifier(|world, entity| {
world.add(entity, RoleComp::new("player"));
world.add(entity, InventoryComp::new(36));
});
Sending Custom Peer Data
Client Side
Client Peer Data
class Peers extends VOXELIZE.Peers<Peer, PeersMeta> {
packInfo = () => {
return {
id: this.ownID,
username: this.ownUsername,
metadata: {
position: this.getPosition(),
direction: this.getDirection(),
role: this.ownPeer?.role,
holdingObjectId: this.ownPeer?.holdingObjectId,
},
};
};
}
Server Side
Custom metadata is synced via PeersMetaSystem or custom systems.
Role-Based Styling
Role Colors
const roleColors: Record<string, number> = {
admin: 0xff0000,
mod: 0x00ff00,
player: 0xffffff,
};
onPeerUpdate = (peer: Peer, metadata: PeersMeta) => {
const color = roleColors[metadata.role || "player"];
peer.head.children.forEach((child) => {
if (child instanceof THREE.Mesh) {
(child.material as THREE.MeshBasicMaterial).color.setHex(color);
}
});
};