Skip to content

Instantly share code, notes, and snippets.

@ReiiSky
Forked from JAForbes/readme.md
Created June 11, 2019 11:01
Show Gist options
  • Select an option

  • Save ReiiSky/d05d20475782774a0b38ba287c0a61b9 to your computer and use it in GitHub Desktop.

Select an option

Save ReiiSky/d05d20475782774a0b38ba287c0a61b9 to your computer and use it in GitHub Desktop.
Data Driven Entity Component System

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment