Games And Entity Systems

Last week I delved into Ash – an ActionScript Entity System by Richard Lord:

https://github.com/richardlord/Ash

I built this little shooter:

http://www.boyblack.net/proto/hunted/TopDown.html

What’s That Then?

Entity Systems offer an approach to object design that fits well with games, where requirements and behaviours need to be tweaked or swapped out constantly. Traditional object oriented design falls over a little bit in that environment.

In an application (as opposed to a game), objects and collaborations are usually clearly defined and do not change after startup. Objects play specific roles, and for the most part, those roles don’t change much.

In a game, however, the behaviours of “actors” within the system can change significantly during gameplay. An enemy may be stunned, for a period, losing the ability to attack. The hero may pick up a weapon, become invincible, or learn to fly. Time may slow down, speed up, or change direction.

Class inheritance is obviously not a good choice here (it rarely is anyway). But actually, many object oriented design approaches are at odds with these requirements.

Ash

Ash breaks things down into Components, Entities, Nodes and Systems.

Components

Components are simple value objects:

public class Position
{
  public var x:Number;
  public var y:Number;
}

public class Display
{
  public var displayObject:DisplayObject;
}

Components do not contain any code, logic or behaviour. They store state, that’s it.

Entities

Entities are buckets that hold components:

const entity:Entity = new Entity()
  .add(new Position(5,5))
  .add(new Display(new SomeSprite()));

game.addEntity(entity);

An entity does not need a name or unique identifier – it is merely the sum of its components. Components can be added to it and removed from it at runtime, changing what the entity does and what role it plays.

Nodes And Systems

So, if components and entities do not contain any actual code, where does the logic go?

This is perhaps the biggest difference between the Entity System approach and typical object oriented programming. Normally, objects encapsulate and hide their data. They expose well defined APIs, but they keep their inner workings, and the data they operate on, private.

There are very good reasons for this, but what it means is that all the emphasis is placed on methods and message passing and not on the data. Sharing mutable data between objects in an object oriented system is almost always a bad idea. Somebody else may change your data “behind your back” and leave you in an invalid state.

In an Entity System data is kept separate from the systems that operate on that data. The data is not encapsulated. Any system may operate on any data that it wants to mutate.

In Ash, specifically, Systems operate on lists of Nodes. A Node is a combination of one or more Components:

public class RenderNode extends Node
{
  public var display:Display;
  public var position:Position;
}

When an entity is given both a Display and a Position component a Render node will be created for that entity. If either of those components is removed from the entity the Render node for that entity will be destroyed.

A System that processes such Render nodes might do this in its update loop:

var renderNode:RenderNode;
for (renderNode = _renderNodes.head; renderNode; renderNode = renderNode.next)
{
  var displayObject:DisplayObject = renderNode.display.displayObject;
  var position:Position = renderNode.position;
  displayObject.x = position.x;
  displayObject.y = position.y;
}

We loop through a linked list of nodes, access the components attached to those nodes, and do whatever it is we want to do with those components. We don’t care about the entities that those components are attached to.

A NodeList has signals for the addition and removal of nodes from the list which is handy when you need to prepare nodes before processing them. For example, in the RenderSystem we add DisplayObjects to the stage and remove them again when Render nodes are created and destroyed:

override public function addToGame(game:Game):void
{
  _renderNodes = game.getNodeList(RenderNode);
  _renderNodes.nodeAdded.add(addRenderNode);
  _renderNodes.nodeRemoved.add(removeRenderNode);
}

private function addRenderNode(node:RenderNode):void
{
  container.addChild(node.display.displayObject);
}

private function removeRenderNode(node:RenderNode):void
{
  container.removeChild(node.display.displayObject);
}

What’s It All Good For?

Adding and removing components from entities at runtime changes the nodes associated with those entities, in turn changing which systems operate on those entities. This effectively allows us to change the roles and behaviours of entities on the fly.

Imagine the hero – the character that you control in the game:

const entity:Entity = new Entity()
  .add(new Position())
  .add(new Motion())
  .add(new KeyControl(
    Keyboard.LEFT, Keyboard.RIGHT,
    Keyboard.UP, Keyboard.DOWN,
    Keyboard.Z, Keyboard.A))
  .add(new Gun())
  .add(new Display(new HeroView()));

By adding the KeyControl component the hero becomes user controlled. Later we could do this:

  entity.remove(KeyControl);
  entity.add(new AIControl());

Suddenly our main character is no longer under our control and is instead moved about by some AIControlSystem.

Hands On

To get to grips with all of this I built a simple game using Ash and Starling. It’s a top-down shooter with baddies that circle and attack you. You can check it out here:

http://www.boyblack.net/proto/hunted/TopDown.html

It takes a while to start feeling comfortable with the Entity System approach, and I’m certainly not there yet. But I have to say, it’s really fun! It forced me to approach problems from different angles, and helped me to build something pretty flexible.

Here’s a quick breakdown of the some of the core Components, Nodes and Systems I ended up with:

Components

Under each component is a list of the properties that make up the component

Bullet
  damage
  range

Display
  object
  layer

Enemy
  prey

Gun
  timeSinceLastShot
  bulletLifetime

Hero (marker class)

HeroControl
  leftKey, rightKey, upKey, downKey
  attackKey, runKey
  rotationSpeed

Life
  health, maxHealth
  stamina, maxStamina

Motion
  velocityX, velocityY
  friction

Position
  x, y, rotation

Predator
  prey

Prey
  predator

Stalker
  prey

Nodes

Under each node is a list of the components that define that node

BulletCollisionNode
  Bullet
  Position
  Motion

EnemyCollisionNode
  Enemy
  Life
  Position
  Motion

GunControlNode
  Gun
  HeroControl

HeroCollisionNode
  Hero
  Position

HeroControlNode
  HeroControl
  Position
  Motion

LivingNode
  Life

MovementNode
  Motion
  Position

PredatorNode (similar to PreyNode and StalkerNode)
  Predator
  Position
  Motion
  Life

RenderNode
  Display
 Position

Systems

BulletRangeSystem
  Operates on BulletCollisionNodes
  Destroys bullets after a given time threshold

CollisionSystem
  Operates on EnemyCollisionNodes, BulletCollisionNodes and HeroCollisionNodes
  Runs through the various collision nodes and damages enemies etc.

EnemyRadarSystem
  Operates on EnemyCollisionNodes
  Places markers around the edge of the screen showing you where enemies are (but only when there are very few enemies left)

GunControlSystem
  Operates on GunControlNodes and EnemyCollisionNodes
  Fires bullets from your gun and auto-aims at nearby enemies

HeroControlSystem
  Operates on HeroControlNodes
  Controls player movement

LifeSystem
  Operates on LivingNodes
  Removes entities when their health reaches zero

MovementSystem
  Operates on MovementNodes
  Applies velocity to position

PredatorSystem, PreySystem and StalkerSystem
  Operates on PredatorNodes, PreyNodes and StalkerNodes
  Updates the motion and position components of these nodes to control enemies in various ways
  Adding more of these kinds of systems will make the game more fun

RenderSystem
  Operates on RenderNodes
  Updates the position and rotation of display objects

WaveSystem
  Operates on EnemyCollisionNodes and HeroCollisionNodes
  Creates waves of enemies

Some Thoughts And Observations

Marker Classes

I ended up with some “marker” components. Marker classes generally make me feel uncomfortable, but they seem slightly less evil in an Entity System.

Garbage

Making an entity and adding a bunch of components to it creates a lot of objects. Consider the bullets in my game:

public function createUserBullet(gun:Gun, x:Number, y:Number, rotation:Number):Entity
{
  const speed:Number = 700;
  const velocityX:Number = speed * Math.cos(rotation);
  const velocityY:Number = speed * Math.sin(rotation);
  const entity:Entity = new Entity()
    .add(new Bullet(50, gun.bulletLifetime))
    .add(new Position(x, y, rotation, 6))
    .add(new Motion(velocityX, velocityY))
    .add(new Display(new BulletView(), Display.BULLET));
  _game.addEntity(entity);
  return entity;
}

We end up creating 6 throw-away objects for every bullet. The solution here is to implement object pooling, but that adds a lot of boilerplate. I’ll probably add pooling for the bullets, but avoid it for the other actors unless it becomes an issue.

Design

It’s challenging trying to decide how to slice up behaviours. Do I need a new system for this? Should I just add some extra code to an existing system? Do I need a new Node definition?

Where To?

Check out Richard’s blog posts:

1. http://www.richardlord.net/blog/introducing-ash
2. http://www.richardlord.net/blog/what-is-an-entity-framework
3. http://www.richardlord.net/blog/why-use-an-entity-framework

I found the Asteroids example in the Ash source really helpful:

https://github.com/richardlord/Ash/tree/master/examples/no-dependencies/asteroids/net/richardlord/asteroids

Posted in Code, Tutorials | Tagged , , | 26 Comments
  • Jorul

    Demo doesn’t work:( Just black screen after pressing P.

  • http://shaun.boyblack.co.za/blog/ shaun

    What browser, OS and Flash Player are you using?

  • http://shaun.boyblack.co.za/blog/ shaun

    What browser, OS and Flash Player are you using?

  • Jorul

    Oh, yeah, sorry. Now I see the note. So everything is alright! On Safari it works.

    I am also Ash’s fan, but have never used it in real project:) Hope to have a chance soon.

    Good work with your game demo!

  • http://shaun.boyblack.co.za/blog/ shaun

    As a matter of interest, were you viewing the demo in Chrome on Mac OS X (Lion)? With a MacBook Air?

  • Jorul

    No, Mac Book Pro, Snow Leopard 10.6.8. Chrome is correct.

  • http://twitter.com/hayes_maker Andy Hayes

    The game is cool.  The difficulty level is similar to some very hard 8-bit games which I love.  Nice intro to ASH too

  • wrobel221

    We completely moved our development to Ash based. Gives us complete freedom and expressiveness. Here’s a video of development version http://www.youtube.com/watch?v=uR-rFUA0vV4 of game that is based on Ash (rendering based on ND2D). Currently the game has 23 systems and it is fully manageable, turning off some systems removes certain features which is also cool.

    In addition to that we currently have an early version of rendering system that allows us to change a small amount of code and switch rendering between Starling and Display List.

    So Ash is our way to go :)

  • http://shaun.boyblack.co.za/blog/ shaun

    Awesome! Best of luck finishing the game. Yeh, I love the ease with which features and behaviours can be turned on and off by enabling and disabling systems.

  • http://shaun.boyblack.co.za/blog/ shaun

    Thanks! It’s a little repetitive at the moment, but when I get some time I’ll spice it up a bit.

  • Wouter Schreuders

    This seems very similar to how a decorator pattern is supposed to work(I say supposed to since I’ve never implemented a decorator pattern myself) Is that correct?

  • http://shaun.boyblack.co.za/blog/ shaun

    Nah, similar, but not quite the same. A decorator “wraps” an object. Components in an entity system do not wrap entities. Decorators contain the behaviour that they add, whereas components do not contain any behaviour at all. The result is similar (adding behaviour at run-time) but the approach is quite different.

  • cubicme

    great example, i’ve used ash before for a few game for a local game jam. it’s really fun to use and SwiftSuspenders integration is awesome. the biggest problem i had with entity system in general, is collision system, using an engine like box2d to handle all collision tends to get messy.

  • http://shaun.boyblack.co.za/blog/ shaun

    Ah yes, the collision detection in my demo is very rudimentary. I guess I’ll get a feel for the messiness in a project that needs proper detection.

  • tom davies

    Good stuff Shaun. Cubime identifies one of the major issues with the ES approach being that “conventional” frameworks dont integrate well. I found collions tricky but have a solution that works quite well you can play arround with here http://www.tomseysdavies.com/2012/02/07/samphire/.  Also I use the command patten for my “maker classes”

  • http://shaun.boyblack.co.za/blog/ shaun

    I can imagine that a major problem is libraries that trigger events., as those events will always be out of sync with the carefully crafted priorities of your systems. I nearly ran into that when dealing with Keyboard events, but decided to stick with the “isDown” approach.

    Ah, I wasn’t talking about “maker” classes, but rather “marker” (empty) classes. Although I do have an overburdened “creator” class that should be broken down into commands.

  • http://shaun.boyblack.co.za/blog/ shaun

    Also, Samphire is a great idea!

  • tom davies

    I need glasses. Yes I ended up with a few “marker” classes myself. It certainly feels wrong but im not totaly sure if it is an issue. Enitiy Systems break a lot of the normal rules anyway but I would try and keep them to a minimum.

  • http://shaun.boyblack.co.za/blog/ shaun

    It should be fixed now. Chrome on OS X seems to explode if you set the stage scaleMode and alignment properties *after* creating a 3D context:

    http://forum.starling-framework.org/topic/on-some-mobile-devices-starling-crashes

  • Pingback: Aymeric Lamboley » An entity/component system’s attempt using Box2D

  • Mark

    The problem with rocks is the interpretation and some gems is hard to delve so hard that the gradient is so deep how could one explain such properties accumulated in such matter but to simplistically set out it in array as a whole this make me fuzzy inside and concentrated to that which I have not attained jet, then I also agree that libraries are a good source to reference which (keys) are done and what else needs more attention, but yes I am not returning back to which (keys) are done instead marker toward that in the direction I am going if I left something behind this is which make me happy that when the end keys joins to the first key it all start over again so why not this time around delve into that keyboard by creating my own node structures hell YE!!!! I am bored with this strange black strings? aksdjhfslkjhfanclksjehhfssdkjsjdksdjkhlakjhldskjhfkjhdfkljhdalkjsdfhrqeoyueqouywruywiouryruwyuweriouwyemvxmnbcvmvxmncvbmnxcbmnbaljfdalhfdslweieyruwoiuye 3 levels 2o something is enough well I use 22 16 for whatever index you use for library. But I am putting my stones in your dice gangster!

  • wrobel221

    I’m currently handling event-driven or things that are out of my game loop with having models with pending requests to be handled in corresponding system. Like UI calls requestReload in model and ReloadSystem checks requestedReload in that model. Then it works quite fine for me. The other way would be to add those requests as new Entities to the game and handle it in same system.

  • Delneet

    Fun shooter, very addictive!

    Could you please upload your code to github? (If it’s not to much to ask…)

  • graham wood

    Thanks, great game and nice intro! do you plan to release the source code ?

  • Hussein Tammam

    Wow, this is amazing. I’ve only just started with the ash framework, but I’m really enjoying it. I’ve also noticed that there aren’t many resources on the topic. I’d really appreciate it if I could get a look at the source code.

  • Sargis Sargsyan

    Hi guys. I have some strange question. Can i use ASH for 3 match (puzzle) games like Diamond Dash?