Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Entity Systems

Avatar for Jason Frame Jason Frame
February 12, 2013

Entity Systems

Avatar for Jason Frame

Jason Frame

February 12, 2013
Tweet

More Decks by Jason Frame

Other Decks in Programming

Transcript

  1. Entity Systems • Origins in MMORPGs • Collection of architecture

    patterns used to organise a game “world” • No fixed definition Saturday, 1 June 13
  2. Entity Systems • Decrease Coupling • Increase (runtime) flexibility •

    Separate data from behaviour Saturday, 1 June 13
  3. Traditional Inheritance • Elaborate object hierarchies are inflexible • Functionality

    gradually bubbles to the top • Wasted memory • Increased complexity Saturday, 1 June 13
  4. Composition class GameObject { public Health health = null; public

    Physics physics = null; public Position position = null; public Inventory inventory = null; } player = new GameObject(); player.health = new PlayerHealth(100); player.physics = new DefaultPhysics(); player.position = new Position(mkvec2(0,0)); Implement each facet of an entity in its own class: Saturday, 1 June 13
  5. Mixins function GameObject() { } GameObject.prototype = { addPhysics: function(props)

    { props = props || {}; this.x = props.x || 0.0; this.y = props.y || 0.0; this.vx = props.vx || 0.0; this.vy = props.vy || 0.0; this.inverseMass = 1 / (props.mass || 1); this.distanceFrom = function(rhs) { // calculate distance from rhs and return } } } Great for dynamic languages! Saturday, 1 June 13
  6. Mixins var obj = new GameObject(); obj.addPhysics({x: 0, y: 0,

    }); Great for dynamic languages! Saturday, 1 June 13
  7. Mixins - Pros • No longer carrying around a bunch

    of extra cruft on objects that don't need it • Can craft any type of object on the fly • Don't need concrete classes for each combination Saturday, 1 June 13
  8. Mixins - Cons • In the absence of a powerful

    type system still need to check what we're dealing with: function gameUpdate(delta) { allGameObjects.forEach(function(obj) { if (object.hasPhysics) { // do stuff } if (object.hasAI) { // do stuff } }); } Saturday, 1 June 13
  9. The Problem • Fundamental mismatch • Object bigotry? • Not

    every object needs a bit of everything • Is there a better way of thinking about this? Saturday, 1 June 13
  10. Autopoietic (Social) Systems • Infinitely chaotic environment • Systems operate

    on environment • Systems extract information from environment • Systems are operationally closed Saturday, 1 June 13
  11. Systems • Implement one single game task • Maintains a

    collection of components • Acceptance check • update(delta) method • e.g. Physics, rendering, AI etc. Saturday, 1 June 13
  12. Entities • Represents any game object • e.g. player, door,

    trigger, monster, gun-turret • Just a (unique) integer ID Saturday, 1 June 13
  13. Components • Describes a single facet of a single entity

    • Usually a map of key/value pairs • Max of one component of each type per entity • Data only - no behaviour/logic Saturday, 1 June 13
  14. Entities & Components positionComponent {x:0, y:0} physicsComponent {vx:0, vy:0, inverseMass:1}

    healthComponent {health:100} •Entity is bag of components •Entity uses ID as primary key to look up components EID 1 => Still just data! Saturday, 1 June 13
  15. Entity System Benefits • Flexible component composition • Tooling •

    Serialisation • Parallelism • Runtime modification Saturday, 1 June 13
  16. Runtime Modification • Remove Health component • Remove Inventory component

    • Change Animation component to Death animation • Create new entity with existing Inventory/Position components Entity Death Saturday, 1 June 13
  17. Example - Bootstrapping world = new World(); world.addSystem(new PlayerInputSystem(world)); world.addSystem(new

    PhysicsSystem(world)); world.addSystem(new RenderingSystem(world)); Saturday, 1 June 13
  18. Example - Entity Creation entityId = world.createEntity(); world.addComponent(entityId, { type:'physics',

    x:0, y:0, vx: 0, vy: 0, mass: 1 }); world.addComponent(entityId, { type: 'playerInput' }); world.addComponent(entityId, { type: 'render', sprite: 'paddle' }); Saturday, 1 June 13
  19. Example - World Lifecycle Events • Adding entity to world

    triggers lifecycle event • Inform all systems of new entity • Other lifecycle events: delete entity, change entity, enable/disable entity Saturday, 1 June 13
  20. Example - Physics System function update(delta) { this._entities.forEach(function(entity) { var

    physics = this._manager.getComponent( entity, 'physics'); physics.x += physics.vx * delta / 1000; physics.y += physics.vy * delta / 1000; physics.vx *= physics.damping; physics.vy *= physics.damping; }); // resolve collisions } Saturday, 1 June 13
  21. Inter-System Communication • `PhysicsSystem` detects car hits wall at speed;

    `DamageSystem` must react • `DamageSystem` detect character is dead. `InventorySystem` should drop treasure and `AnimationSystem` should play death animation Saturday, 1 June 13
  22. Inter-System Communication • Systems neither know about nor understand other

    systems • Decouple systems • Message passing Saturday, 1 June 13
  23. Example - Inter-system Communication function PhysicsSystem() { var self =

    this; this.update = function(delta) { var collisions = []; // do physics mojo... // ... // ... collisions.forEach(function(c) { // `c` is collision event object self.emit('collision', c); }); } } Saturday, 1 June 13
  24. Example - Inter-system Communication function DamageSystem(world) { var self =

    this; world.on('collision', function(collisionEvent) { if (collisionEvent.speed > DAMAGE_THRESHOLD) { var cmp = self._manager.getComponent( collisionEvent.entity, 'health'); cmp.health -= 10; if (cmp <= 0) { self.emit('entityDead', { ... }); } } }); } Saturday, 1 June 13
  25. Revisiting Entities • Entities as IDs is a throwback to

    statically- typed languages • In Javascript, we can make entity an object •Do not allow logic to seep in Saturday, 1 June 13
  26. Entities as Objects function Entity(id, manager) { this.id = id;

    this._manager = manager; } Entity.prototype.addComponent = function(name, cmp) { this[name] = cmp; // notify observers } entity.addComponent('physics', ...); // check for component if ('physics' in entity) { Saturday, 1 June 13
  27. Attribute/Behaviour Systems • Again, entity is just an ID •

    No systems or components • Instead, attributes and behaviours Saturday, 1 June 13
  28. Attribute function makeAttribute(name, type, value) { var observers = [];

    function notify(ov, nv) { for (var i = observers.length - 1; i >= 0; --i) { observers[i].onMessage({ type: ‘attributeChange’, attribute: attr, oldValue: ov, newValue: nv }); } } var attr = { name: name, type: type, get: function() { return value; }, set: function(v) { var oldValue = value; value = v; notify(oldValue, value); }, addObserver: function(b) { observers.push(b); }, removeObserver: function(b) { observers.splice(observers.indexOf(b), 1); }, }; return attr; } Saturday, 1 June 13
  29. Behaviour • Object that responds to a 2 methods •

    onUpdate(delta) • onMessage(message) • May have private internal state Saturday, 1 June 13
  30. Game Update Loop function update(delta) { var behaviour = rootBehaviour;

    while (behaviour) { behaviour.update(delta); behaviour = behaviour.next; } } Saturday, 1 June 13
  31. Optimisation - Caching function PhysicsSystem() { this._entities = {}; this.update

    = function() { for (var entityId in this._entities) { var physicsComponent = this._entities[entityId].getComponent('physics'); // integrate position } // resolve collisions } this.entityAdded = function(entity) { this._entities[entity.id] = entity; } // entityRemoved // entityChanged } Saturday, 1 June 13
  32. Optimisation - Caching function PhysicsSystem() { this._entities = {}; this.update

    = function() { for (var entityId in this._entities) { var physicsComponent = this._entities[entityId]; // integrate position } // resolve collisions } this.entityAdded = function(entity) { // store the physics component directly this._entities[entity.id] = entity.getComponent('physics'); } // entityRemoved // entityChanged } Saturday, 1 June 13
  33. Optimisation - Component Querying function systemAcceptsEntity(entity) { return entity.hasComponent('physics') &&

    entity.hasComponent('render') && entity.hasComponent('damage'); } Method call + hash lookup for each test Saturday, 1 June 13
  34. Optimisation - Component Querying Components = { PHYSICS: 1, DAMAGE:

    2, RENDER: 4, ... }; entity = world.createEntity(); entity.addComponent(Components.PHYSICS,{ ... }); entity.addComponent(Components.RENDER, { ... }); function Entity() { this._components = {}; this._componentMask = 0; } Entity.prototype.addComponent = function(type, props) { this._components[type] = props; this._componentMask |= type; } Idea: represent each type of component with a single bit. Saturday, 1 June 13
  35. Optimisation - Component Querying Entity.prototype.hasComponent = function(mask) { return (this._componentMask

    & mask) == mask; } // check for a single component entity.hasComponent(Components.PHYSICS); // check for multiple components entity.hasComponent(Components.PHYSICS | Components.DAMAGE | Components.RENDER); Idea: represent each type of component with a single bit. Saturday, 1 June 13
  36. Optimisation - Bit mask limitations • Limited by the number

    of bits in the underlying data type • In Javascript, bitwise operations operate on 32 bits • 32 bits => 32 components max • Array/object abstraction? Saturday, 1 June 13
  37. Optimisation - Event Handling • Introduce event categories • Each

    event belongs to single category • Split up the bitfield • Each category gets unique bit • Each system specifies its category mask • Use remaining bits for Event ID Saturday, 1 June 13
  38. Optimisation - Event Handling Role <-- Category ---> <--------- Event

    -----------> Bit 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 In Javascript, 16 bits for category + 16 bits for event number == 16 * (2^16) == 1M event types Saturday, 1 June 13
  39. Entity Systems - Drawbacks • Overhead • Hierarchical data problem

    • “Framework” required Saturday, 1 June 13
  40. Beyond Games • Simulations/data visualisations • Chris Granger’s Light Table

    project uses a component-entity system behind the scenes • Useful anywhere custom objects must be composed dynamically at runtime Saturday, 1 June 13