Geotic
Entity Component System library for javascript
Install / Use
/learn @ddmills/GeoticREADME
geotic
adjective physically concerning land or its inhabitants.
Geotic is an ECS library focused on performance, features, and non-intrusive design. View benchmarks.
- entity a unique id and a collection of components
- component a data container
- query a way to gather collections of entities that match some criteria, for use in systems
- world a container for entities and queries
- prefab a template of components to define entities as JSON
- event a message to an entity and it's components
This library is heavily inspired by ECS in Caves of Qud. Watch these talks to get inspired!
- Thomas Biskup - There be dragons: Entity Component Systems for Roguelikes
- Brian Bucklew - AI in Qud and Sproggiwood
- Brian Bucklew - Data-Driven Engines of Qud and Sproggiwood
Python user? Check out the Python port of this library, ecstremity!
usage and examples
npm install geotic
- Sleepy Crawler a full fledged roguelike that makes heavy use of geotic by @ddmills
- snail6 a bloody roguelike by @luetkemj
- Gobs O' Goblins by @luetkemj
- Javascript Roguelike Tutorial by @luetkemj
- basic example using pixijs
Below is a contrived example which shows the basics of geotic:
import { Engine, Component } from 'geotic';
// define some simple components
class Position extends Component {
static properties = {
x: 0,
y: 0,
};
}
class Velocity extends Component {
static properties = {
x: 0,
y: 0,
};
}
class IsFrozen extends Component {}
const engine = new Engine();
// all Components and Prefabs must be `registered` by the engine
engine.registerComponent(Position);
engine.registerComponent(Velocity);
engine.registerComponent(IsFrozen);
...
// create a world to hold and create entities and queries
const world = engine.createWorld();
// Create an empty entity. Call `entity.id` to get the unique ID.
const entity = world.createEntity();
// add some components to the entity
entity.addComponent(Position, { x: 4, y: 10 });
entity.addComponent(Velocity, { x: 1, y: .25 });
// create a query that tracks all components that have both a `Position`
// and `Velocity` component but not a `IsFrozen` component. A query can
// have any combination of `all`, `none` and `any`
const kinematics = world.createQuery({
all: [Position, Velocity],
none: [IsFrozen]
});
...
// geotic does not dictate how your game loop should behave
const loop = (dt) => {
// loop over the result set to update the position for all entities
// in the query. The query will always return an up-to-date array
// containing entities that match
kinematics.get().forEach((entity) => {
entity.position.x += entity.velocity.x * dt;
entity.position.y += entity.velocity.y * dt;
});
};
...
// serialize all world entities into a JS object
const data = world.serialize();
...
// convert the serialized data back into entities and components
world.deserialize(data);
Engine
The Engine class is used to register all components and prefabs, and create new Worlds.
import { Engine } from 'geotic';
const engine = new Engine();
engine.registerComponent(clazz);
engine.registerPrefab({ ... });
engine.destroyWorld(world);
Engine properties and methods:
- registerComponent(clazz): register a Component so it can be used by entities
- regsterPrefab(data): register a Prefab to create pre-defined entities
- destroyWorld(world): destroy a world instance
World
The World class is a container for entities. Usually only one instance is needed,
but it can be useful to spin up more for offscreen work.
import { Engine } from 'geotic';
const engine = new Engine();
const world = engine.createWorld();
// create/destroy entities
world.createEntity();
world.getEntity(entityId);
world.getEntities();
world.destroyEntity(entityId);
world.destroyEntities();
// create queries
world.createQuery({ ... });
// create entity from prefab
world.createPrefab('PrefabName', { ... });
// serialize/deserialize entities
world.serialize();
world.serialize(entities);
world.deserialize(data);
// create an entity with a new ID and identical components & properties
world.cloneEntity(entity);
// generate unique entity id
world.createId();
// destroy all entities and queries
world.destroy();
World properties and methods:
- createEntity(id = null): create an
Entity. optionally provide an ID - getEntity(id): get an
Entityby ID - getEntities(): get all entities in this world
- createPrefab(name, properties = {}): create an entity from the registered prefab
- destroyEntity(entity): destroys an entity. functionally equivilant to
entity.destroy() - destroyEntities(): destroys all entities in this world instance
- serialize(entities = null): serialize and return all entity data into an object. optionally specify a list of entities to serialize
- deserialize(data): deserialize an object
- cloneEntity(entity): clone an entity
- createId(): Generates a unique ID
- destroy(): destroy all entities and queries in the world
Entity
A unique id and a collection of components.
const zombie = world.createEntity();
zombie.add(Name, { value: 'Donnie' });
zombie.add(Position, { x: 2, y: 0, z: 3 });
zombie.add(Velocity, { x: 0, y: 0, z: 1 });
zombie.add(Health, { value: 200 });
zombie.add(Enemy);
zombie.name.value = 'George';
zombie.velocity.x += 12;
zombie.fireEvent('hit', { damage: 12 });
if (zombie.health.value <= 0) {
zombie.destroy();
}
Entity properties and methods:
- id: the entities' unique id
- world: the geotic World instance
- isDestroyed: returns
trueif this entity is destroyed - components: all component instances attached to this entity
- add(ComponentClazz, props={}): create and add the registered component to the entity
- has(ComponentClazz): returns true if the entity has component
- owns(component): returns
trueif the specified component belongs to this entity - remove(component): remove the component from the entity and destroy it
- destroy(): destroy the entity and all of it's components
- serialize(): serialize this entity and it's components
- clone(): returns an new entity with a new unique ID and identical components & properties
- fireEvent(name, data={}): send an event to all components on the entity
Component
Components hold entity data. A component must be defined and then registered with the Engine. This example defines a simple Health component:
import { Component } from 'geotic';
class Health extends Component {
// these props are defaulting to 10
// anything defined here will be serialized
static properties = {
current: 10,
maximum: 10,
};
// arbitrary helper methods and properties can be declared on
// components. Note that these will NOT be serialized
get isAlive() {
return this.current > 0;
}
reduce(amount) {
this.current = Math.max(this.current - amount, 0);
}
heal(amount) {
this.current = Math.min(this.current + amount, this.maximum);
}
// This is automatically invoked when a `damage-taken` event is fired
// on the entity: `entity.fireEvent('damage-taken', { damage: 12 })`
// the `camelcase` library is used to map event names to methods
onDamageTaken(evt) {
// event `data` is an arbitray object passed as the second parameter
// to entity.fireEvent(...)
this.reduce(evt.data.damage);
// handling the event will prevent it from continuing
// to any other components on the entity
evt.handle();
}
}
Component properties and methods:
- static properties = {} object that defines the properties of the component. Properties must be json serializable and de-serializable!
- static allowMultiple = false are multiple of this component type allowed? If true, components will either be stored as an object or array on the entity, depending on
keyProperty. - static keyProperty = null what property should be used as the key for accessing this component. if
allowMultipleis false, this has no effect. If this property is omitted, it will be stored as an array on the component. - entity returns the Entity this component is attached to
- world returns the World this component is in
- isDestroyed returns
trueif this component is destroyed - serialize() serialize the component properties
- destroy() remove this and destroy this component
- onAttached() override this method to add behavior when this component is attached (added) to an entity
- onDestroyed() override this method to add behavior when this component is removed & destroyed
- onEvent(evt) override this method to capture all events coming to this component
- on[EventName](evt) add these methods to capture the specific event
This example shows how allowMultiple and keyProperty work:
class Impulse extends Component {
static properties = {
x: 0,
y: 0,
};
static allowMultiple = true;
}
ecs.registerComponent(Impulse);
...
// add multiple `
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
