Skip to content

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

  1. Create Component Types: Define your application's data structures.
  2. Create Entities: Instantiate entities and attach components.
  3. Create Systems: Implement logic that processes entities based on queries.
  4. Execute Systems: Continuously run all systems.

Adding EliCS to Your Project

Install EliCS via npm:

bash
npm install elics

Creating a World

The "world" is where all ECS elements coexist.

typescript
import { World } from 'elics';

const world = new World();

Defining Components

Components are data containers that define entity properties.

typescript
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:

typescript
world
	.registerComponent(Position)
	.registerComponent(Velocity)
	.registerComponent(Health)
	.registerComponent(Unit);

Creating Entities

Instantiate entities and attach components:

typescript
// 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.

typescript
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.

typescript
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!

MIT License | Made with ❤️ by Felix Z