Getting Started
This guide will walk you through the fundamental concepts of the Entity Component System (ECS) framework and get you up and running with EliCS in your TypeScript or JavaScript projects.
ECS Principles
EliCS is an Entity Component System (ECS) framework tailored for TypeScript and JavaScript applications. This design pattern shifts from traditional class hierarchy to composition within a Data-Oriented Programming paradigm, leading to more efficient and extendable code.
Key ECS Terms
- Entities: Unique objects that can have multiple components attached.
- Components: Varied facets of an entity (e.g., geometry, physics, health). They typically store data.
- Systems: Code pieces that process entities and modify their components.
- Queries: Used by systems to select entities based on component composition.
- World: A container for entities, components, systems, and queries.
Typical ECS Workflow
- Create Component Types: Define your application's data structures.
- Create Entities: Instantiate entities and attach components.
- Create Systems: Implement logic that processes entities based on queries.
- Execute Systems: Continuously run all systems.
Adding EliCS to Your Project
Install EliCS via npm:
npm install elics
Creating a World
The "world" is where all ECS elements coexist.
import { World } from 'elics';
const world = new World();
Defining Components
Components are data containers that define entity properties.
import { createComponent, Types } from 'elics';
// Define enums for type-safe state management
enum UnitType {
Infantry = 1,
Cavalry = 2,
Archer = 3,
}
enum CombatState {
Idle = 10,
Moving = 20,
Attacking = 30,
Defending = 40,
}
const Position = createComponent({
value: { type: Types.Vec3, default: [0, 0, 0] },
});
const Velocity = createComponent({
value: { type: Types.Vec3, default: [0, 0, 0] },
});
const Health = createComponent({
value: { type: Types.Float32, default: 100 },
});
const Unit = createComponent({
type: { type: Types.Enum, enum: UnitType, default: UnitType.Infantry },
state: { type: Types.Enum, enum: CombatState, default: CombatState.Idle },
damage: { type: Types.Float32, default: 10, min: 1, max: 100 },
armor: { type: Types.Int16, default: 0, min: 0, max: 50 },
morale: { type: Types.Float32, default: 1.0, min: 0.0, max: 1.0 },
});
Register these components:
world
.registerComponent(Position)
.registerComponent(Velocity)
.registerComponent(Health)
.registerComponent(Unit);
Creating Entities
Instantiate entities and attach components:
// Create different types of units with enum values
const infantryUnit = world.createEntity();
infantryUnit.addComponent(Position, { value: [10, 20, 30] });
infantryUnit.addComponent(Velocity);
infantryUnit.addComponent(Health);
infantryUnit.addComponent(Unit, {
type: UnitType.Infantry,
state: CombatState.Moving,
damage: 15, // Valid: within 1-100 range
armor: 20, // Valid: within 0-50 range
morale: 0.8, // Valid: within 0.0-1.0 range
});
const archerUnit = world.createEntity();
archerUnit.addComponent(Position, { value: [50, 0, 100] });
archerUnit.addComponent(Health, { value: 80 });
archerUnit.addComponent(Unit, {
type: UnitType.Archer,
state: CombatState.Defending,
damage: 25, // Valid: within 1-100 range
armor: 5, // Valid: within 0-50 range
morale: 0.9, // Valid: within 0.0-1.0 range
});
Creating Systems
Systems contain the core logic.
import { createSystem } from 'elics';
const queryConfig = {
movables: { required: [Position, Velocity] },
combatUnits: { required: [Unit, Health] },
};
class MovementSystem extends createSystem(queryConfig) {
update(delta, time) {
this.queries.movables.entities.forEach((entity) => {
const position = entity.getVectorView(Position, 'value');
const velocity = entity.getVectorView(Velocity, 'value');
position[0] += velocity[0] * delta;
position[1] += velocity[1] * delta;
position[2] += velocity[2] * delta;
});
}
}
class CombatSystem extends createSystem(queryConfig) {
update(delta, time) {
this.queries.combatUnits.entities.forEach((entity) => {
const unitState = entity.getValue(Unit, 'state');
const unitType = entity.getValue(Unit, 'type');
// Process units based on their state
switch (unitState) {
case CombatState.Attacking:
this.handleAttack(entity, unitType);
break;
case CombatState.Defending:
this.handleDefense(entity, unitType);
break;
case CombatState.Moving:
// Units continue moving
break;
}
});
}
handleAttack(entity, unitType) {
const damage = entity.getValue(Unit, 'damage');
// Different unit types have different attack behaviors
if (unitType === UnitType.Archer) {
// Ranged attack logic
console.log(`Archer attacks with ${damage} damage`);
} else if (unitType === UnitType.Infantry) {
// Melee attack logic
console.log(`Infantry attacks with ${damage} damage`);
}
// Change state back to idle after attacking
entity.setValue(Unit, 'state', CombatState.Idle);
}
handleDefense(entity, unitType) {
// Defense logic based on unit type
console.log(
`${unitType === UnitType.Archer ? 'Archer' : 'Infantry'} is defending`,
);
}
}
world.registerSystem(MovementSystem);
world.registerSystem(CombatSystem);
Updating the World
Update the world with delta
and time
.
function render() {
...
world.update(delta, time);
...
}
What's Next?
- Explore the Architecture: Delve into the architecture of EliCS and access the detailed API documentation for an in-depth understanding of EliCS's capabilities.
- Discover the EliCS Story: Learn about its origins, the inspirations that shaped it, and the design philosophy that drives its development.
EliCS is ready to power your next application with its flexible and efficient ECS framework. Happy coding!