Welcome to minECS, a fully serializable entity component system designed for type safety and efficient state management. Originally forked from bitECS, minECS utilizes schema classes to enhance the structure and reliability of your game's data.
npm install minecsWelcome to minECS, a fully serializable entity component system designed for type safety and efficient state management. Originally forked from bitECS, minECS utilizes schema classes to enhance the structure and reliability of your game's data.
In minECS, components are easily defined using the Schema class and the @Component decorator. These components automatically register when you import them into your project. Here are examples of how to define components for position and velocity:
``ts
@Component()
class Position extends Schema {
@type("number")
@defaultValue(0)
x: number;
@type("number")
@defaultValue(0)
y: number;
}
@Component()
class Velocity extends Schema {
@type("number")
@defaultValue(0)
x: number;
@type("number")
@defaultValue(0)
y: number;
}
`
Systems in minECS can operate manually or within the engine. Use the @System decorator to specify which components a system requires. Systems are dynamically recreated for each world instance to facilitate specific interactions:
`ts
@System(Position, Velocity)
class MovementSystem extends SystemImpl {
run = (world: World, entity: number) => {
const position = world(Position, entity);
const velocity = world(Velocity, entity);
position.x += velocity.x;
position.y += velocity.y;
};
}
`
.`ts
@System(Position)
class RenderSystem extends DrawSystemImpl {
run = (world: ReadOnlyWorld, entity: number) => {
const pos = world(Position, entity);
// Render logic here
};
}
`Managing Worlds
Worlds manage the state of your entities and components.
`ts
const world = createWorld();
const entity = addEntity(world);addComponent(world, Position, entity);
addComponent(world, Velocity, entity, {
x: 30,
y: 30,
});
console.log({ ...world(Position, entity) }); // { x: 0, y: 0, type: "Position" }
stepWorld(world);
console.log({ ...world(Position, entity) }); // { x: 30, y: 30, type: "Position" }
`Queries
Queries provide a way to retrieve and check entities matching a set of components.
`ts
const movementQuery = defineQuery([Position, Velocity]);// Get all matching entities
const entities = movementQuery(world);
// Check if a specific entity matches
const isMatch = movementQuery.has(world, entity);
`Serialization
minECS supports multiple serialization formats: JSON, Binary, and base64. This feature allows you to save and restore game states easily:
`ts
const json = serializeWorld(SerialMode.JSON, world);
const buffer = serializeWorld(SerialMode.BINARY, world);
const base64 = serializeWorld(SerialMode.BASE64, world);const newWorld = deserializeWorld(buffer);
`$3
Delta serialization tracks changes to component properties, allowing you to transmit only what has changed since the last sync.`ts
const delta = createDeltaSerializer(world);// First call creates a full baseline
const fullBuffer = delta.serialize();
// Subsequent calls produce delta buffers
const patchBuffer = delta.serialize();
// Apply the delta to a target world
applyDelta(patchBuffer, remoteWorld);
`Additional Features
$3
The depth property determines the execution order. Systems with lower depth run first.
* Manual Execution: Systems with a depth less than 0 do not run automatically in stepWorld. They must be triggered manually via system.runAll(world) or system.run(world, entity).`ts
@System(...)
class ManualSystem extends SystemImpl {
static depth = -1;
}
`$3
Systems can be throttled to run every N frames or at a specific offset. By using frameModOffset heavy systems can be chained and not overload the engine.`ts
@System(...)
class OptimizedSystem extends SystemImpl {
static frameMod = 10; // Runs every 10 frames
static frameModOffset = 2; // Runs on frames 3, 13, 23...
}
`
$3
You can monitor the performance of your systems using timing functions.`ts
// Runs the world step and captures performance data
stepWorldTiming(world);if (world.timing) {
console.log(
Total time: ${world.timing.totalTime}ms);
world.timing.systems.forEach(s => {
console.log(${s.name}: ${s.totalTime}ms);
});
}// Clears timing data and restores original run methods
clearWorldTiming(world);
`$3
Systems can have an initialization function that runs once when a new entity that matches the system's criteria is added:
`ts
@System(...)
class SomeInitializationSystem extends SystemImpl {
init = (world: World, eid: number) => {
// Initialization code here
};
}
`$3
Systems can define a cleanup function that executes when a component is removed or an entity is deleted. This helps in managing state cleanup reliably:
`ts
@System(...)
class SomeCleanupSystem extends SystemImpl {
cleanup = (world: World, eid: number) => {
// Called when Position is removed or entity is deleted
};
}
`$3
Enhance your components with nested structures, nullable options, and various data types for more complex scenarios:
`ts
@Component()
class Complex extends Schema {
@type(Position)
position: Position; @type("string")
@defaultValue("hello")
message: string;
@type(["number"])
numbers: number[];
@type("boolean")
@defaultValue(true)
flag: boolean;
@type("object")
obj: any;
@type("string")
@nullable()
nullableString: string | null;
}
@Component()
class NestedComplex extends Schema {
@type(Complex)
complex: Complex;
@type([Complex])
complexes: Complex[];
}
`$3
The library provides comprehensive functions to manage entities and components dynamically:
`ts
const world = createWorld();
const entity = addEntity(world);
addComponent(world, SomeComponent, entity);
removeComponent(world, SomeComponent, entity);
removeEntity(world, entity); // Cleans up all components associated with the entity
``These functions give you full control over the entities and components in the system, ensuring that you can dynamically adjust the game world as needed.