quizard-game-core
v1.0.0
Published
Shared game runtime code for editor and client
Maintainers
Readme
@game-core
Shared game runtime code for both the standalone game client and the React editor's Play Mode.
Overview
@game-core provides a unified, framework-agnostic layer for multiplayer game functionality. It enables code sharing between:
- Standalone Game Client (
packages/client) - React Editor Play Mode (
packages/react-editor)
Architecture
Following the project's established patterns:
- Event-Driven: All modules use EventEmitter for loose coupling
- Lifecycle Management: Clear initialize → start → stop → dispose flow
- Type-Safe: Full TypeScript with strict mode
- Zero UI Coupling: Pure game logic, no React or UI dependencies
Modules
GameClient
Colyseus client wrapper with event-driven API.
import { GameClient } from '@game-core';
const client = new GameClient();
// Listen to connection events
client.events.on('connected', (room) => {
console.log('Connected to room:', room.id);
});
client.events.on('status', (status) => {
console.log('Status changed:', status);
});
// Connect to server
const room = await client.connect({
serverUrl: 'ws://localhost:2567',
roomName: 'game_room',
options: { username: 'Player1' }
});
// Send messages
client.send('player_move', { x: 10, y: 0, z: 5 });
// Disconnect
client.disconnect();InputController
Multi-input handler (keyboard, mouse, touch).
import { InputController } from '@game-core';
const input = new InputController({
enableKeyboard: true,
enableMouse: true,
enableTouch: true
});
// Start listening
input.start();
// Listen to input changes
input.onInput((activeKeys) => {
console.log('Active keys:', activeKeys);
});
// Get movement input
const movement = input.getMovementInput(); // { x, z }
// Check specific keys
if (input.isKeyPressed('w')) {
// Move forward
}
// Cleanup
input.stop();StateSync
Synchronizes Colyseus room state with Three.js scene.
import { StateSync } from '@game-core';
import * as THREE from 'three';
const scene = new THREE.Scene();
const sync = new StateSync(room, scene);
// Listen to entity events
sync.events.on('entityAdded', (entity, object) => {
console.log('Entity added:', entity.id);
});
sync.events.on('entityUpdated', (entity, object) => {
// Entity position/rotation updated
});
sync.events.on('entityRemoved', (entityId, object) => {
console.log('Entity removed:', entityId);
});
// Custom object factory
sync.setObjectFactory((entity) => {
// Create custom Three.js objects based on entity type
if (entity.type === 'player') {
return createPlayerMesh();
}
return createDefaultMesh();
});
// Cleanup
sync.dispose();PhysicsWorld
Simple physics simulation.
import { PhysicsWorld } from '@game-core';
import * as THREE from 'three';
const physics = new PhysicsWorld();
// Configure
physics.setGravity(new THREE.Vector3(0, -9.8, 0));
physics.setGroundLevel(0);
// Add physics body
const mesh = new THREE.Mesh(geometry, material);
physics.addBody(mesh, 1.0); // mass = 1.0
// Set velocity
physics.setVelocity(mesh, new THREE.Vector3(5, 0, 0));
// Update in game loop
function gameLoop(deltaTime) {
physics.update(deltaTime);
}
// Cleanup
physics.dispose();Design Patterns
1. Event-Driven Architecture
All modules emit events for loose coupling:
// Subscribe to events
const unsubscribe = client.events.on('connected', (room) => {
// Handle connection
});
// Unsubscribe
unsubscribe();2. Lifecycle Management
Consistent lifecycle across all modules:
// Initialize (constructor)
const module = new Module(config);
// Start
module.start();
// Update (if applicable)
gameLoop.on('update', (dt) => module.update(dt));
// Stop
module.stop();
// Dispose
module.dispose();3. Singleton Pattern
For managers (following GameService pattern):
export class GameManager {
private static instance: GameManager;
static getInstance(): GameManager {
if (!GameManager.instance) {
GameManager.instance = new GameManager();
}
return GameManager.instance;
}
}Integration
In Standalone Client
// packages/client/src/Game.tsx
import { GameClient, InputController, PhysicsWorld } from '@game-core';
const gameClient = new GameClient();
const input = new InputController();
const physics = new PhysicsWorld();
// Use in game loop...In React Editor (Play Mode)
// packages/react-editor/src/core/playmode/PlayModeManager.ts
import { GameClient, InputController, StateSync } from '@game-core';
export class PlayModeManager {
private gameClient: GameClient;
private input: InputController;
private stateSync: StateSync;
async start() {
this.gameClient = new GameClient();
await this.gameClient.connect(config);
this.input = new InputController();
this.input.start();
this.stateSync = new StateSync(
this.gameClient.getRoom()!,
scene
);
}
}Building
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run watch
# Test
npm testStandards
Following project conventions:
- ✅ TypeScript strict mode
- ✅ Event-driven communication
- ✅ Clear lifecycle hooks
- ✅ Comprehensive JSDoc comments
- ✅ No circular dependencies
- ✅ DRY principles
Future Enhancements
- [ ] Advanced physics (Cannon.js/Rapier integration)
- [ ] Client-side prediction
- [ ] Server reconciliation
- [ ] Interpolation/extrapolation
- [ ] Network optimization (delta compression)
- [ ] Entity pooling
- [ ] Animation system
