Skip to content

Instantly share code, notes, and snippets.

@ooflorent
Created January 8, 2014 10:39
Show Gist options
  • Save ooflorent/8314853 to your computer and use it in GitHub Desktop.
Save ooflorent/8314853 to your computer and use it in GitHub Desktop.
Basic ECS
// Components
// ----------
function KeyboardController() {}
function AIController() {}
function Position(x, y) {
this.x = x || 0;
this.y = y || 0;
}
function Motion(dx, dy) {
this.dx = dx || 0;
this.dy = dy || 0;
}
function Display(sprite) {
this.sprite = sprite;
}
// Systems
// -------
function InputSystem() {
this.keyboard = function(entity) {
var motion = entity.get(Motion);
// Read keyboard inputs and update motion component.
// ...
};
this.ai = function(entity) {
var motion = entity.get(Motion);
// Make AI decisions and update motion component.
// ...
};
}
function PhysicsSystem() {
this.move = function(delta, entity) {
var position = entity.get(Position);
var motion = entity.get(Motion);
// Update position.
position.x += motion.dx * delta;
position.y += motion.dy * delta;
};
}
function RenderingSystem() {
this.sprite = function() {
// Render entity.
// ...
};
}
// Game
// ----
function Game() {
// Instantiates the ECS engine
this.world = Object.create(World);
// Creates systems
this.input = new InputSystem();
this.movement = new MovementSystem();
this.rendering = new RenderingSystem();
}
Game.prototype = {
/**
* Game initialization
*/
create: function() {
// Create player
this.world.createEntity([
new KeyboardController(),
new Position(400, 300),
new Motion(),
new Display('hero.png')
]);
// Create enemies
for (var i = 20, i--;) {
this.world.createEntity([
new KeyboardController(),
new Position(rand(0, 800), rand(0, 600)),
new Motion(),
new Display('enemy.png')
]);
}
},
/**
* Update loop
*/
update: function(delta) {
// Process inputs and AI
this.world.match(KeyboardController).map(this.input.keyboard);
this.world.match(AIController).map(this.input.ai);
// Apply physic
this.world.match(Motion).map(this.physics.move.bind(this.physics, delta));
// Render entities
this.world.match(Display).map(this.rendering.sprite);
}
};
// Create game
var game = new Game();
// Then do whatever you want!
// - Call create() to start the game
// - Call update(delta) to update and render the game
/**
* Basic ECS engine.
*
* WARNING: This engine is not optimized.
*/
var World = (function() {
var nextID = 0;
var entities = [];
var entitiesToComponents = [];
var componentsToEntities = {};
/**
* Internal entity definition.
*/
var Entity = {
/**
* Retrieves the specified component.
*/
get: function(comp) {
return entitiesToComponents[this.id][comp.name];
},
/**
* Adds the specified component.
*/
add: function(component) {
var name = component.constructor.name;
if (!componentsToEntities[name]) {
componentsToEntities[name] = [];
}
entitiesToComponents[this.id][name] = component;
componentsToEntities[name][this.id] = true;
},
/**
* Removes the specified component.
*/
remove: function(component) {
delete entitiesToComponents[this.id][component.name];
delete componentsToEntities[component.name][this.id];
},
/**
* Kills the entity.
*/
kill: function() {
delete entities[this.id];
delete entitiesToComponents[this.id];
for (var comp in componentsToEntities) {
delete componentsToEntities[comp][this.id];
}
}
};
return {
/**
* Creates a new entity.
*/
createEntity: function(components) {
var entityID = nextID++;
var entity = Object.create(Entity, {
id: {
value: entityID,
writable: false
}
});
entities[entityID] = entity;
entitiesToComponents[entityID] = {};
if (components) {
components.forEach(function(component) {
entity.add(component);
});
}
return entity;
},
/**
* Returns all entities having the specified component.
*/
match: function(comp) {
return Object.keys(componentsToEntities[comp.name] || []).map(function(entityID) {
return entities[entityID];
});
}
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment