An entity is just a number.
There is a graph of components, indexed by the entity id.
All state is stored in one place.
var components = { Position: {}, Velocity:{}, Spite: {}, Timer: {}, ScreenShake: {}, RemoveComponents: {} }
var player = 1
var enemy = 2
var potplants = [3,4,5]
var game = 6
components.Position[player] = { x:100, y: 100 }
components.Velocity[player] = { x: 0.5, y: 0 }
components.Sprite[player] = sprites.player
components.Position[enemy] = { x: 0, y: 100}
components.Velocity[enemy] = { x: 1, y: 0}
components.Sprite[enemy] = sprites.enemy
potplants.forEach(function(potplant, i){
components.Position[potplant] = { x: i * 100, y: 200 }
components.Sprite[potplant] = sprites.potplant
})
components.Timer[6] = { count: 0, interval: 1000, on: { ScreenShake: { x: 1, y: 0 } } }Systems iterate over a specific component type. If that component type is empty for that game loop, those systems do not run.
This is very different to the traditional iteration over all entities, which is wasteful and grounded in thinking every entity is an object instead of just an identifier.
//gameloop systems
[
[Move, components.Velocity]
[Timer, components.Timer],
[Screenshake, components.Screenshake],
[Render, components.Sprite],
[RemoveComponents, components.RemoveComponents],
].forEach(callSystem)
function callSystem([system, components]){
_.each(components, system)
}
function Move(velocity, entity){
var p = component.Position[entity]
p.x += velocity.x
p.y += velocity.y
}
function Render(sprite, entity){
var p = component.Position[entity]
context.drawImage(p.x, p.y, ..., sprite.img )
}
function Timer(timer, entity){
if(timer.count > timer.timer){
if(timer.repeat){
timer.count = 0
} else {
components.RemoveComponents[entity] = { components: ['Timer'] }
}
_.each(components.on, function(data, name){
components[name] = data
})
}
timer.count ++
}
function ScreenShake(screenshake, entity){
//shake the screen
components.RemoveComponents[entity] = { components: ['ScreenShake']}
}
function RemoveComponents(remove, entity){
remove.components.forEach(function(){
delete components.ScreenShake[entity]
})
}Each system accepts a single component and the entity id for that component. The id allows the system to pull in other components for that entity.
function Move(velocity, entity){
var p = component.Position[entity]
p.x += velocity.x
p.y += velocity.y
}See how the Move system pulls in the position data by accessing the component graph directly. Because all references are local to a function call, deleting a component from the graph will immediately release it into memory unlike class/prototype based approaches.