Last week I delved into Ash – an ActionScript Entity System by Richard Lord:
I built this little shooter:
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 breaks things down into Components, Entities, Nodes and Systems.
Components are simple value objects:
Components do not contain any code, logic or behaviour. They store state, that’s it.
Entities are buckets that hold components:
.add(new Display(new SomeSprite()));
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 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:
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:
_renderNodes = game.getNodeList(RenderNode);
private function addRenderNode(node:RenderNode):void
private function removeRenderNode(node:RenderNode):void
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:
By adding the KeyControl component the hero becomes user controlled. Later we could do this:
Suddenly our main character is no longer under our control and is instead moved about by some AIControlSystem.
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:
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:
Under each component is a list of the properties that make up the component
Hero (marker class)
leftKey, rightKey, upKey, downKey
x, y, rotation
Under each node is a list of the components that define that node
PredatorNode (similar to PreyNode and StalkerNode)
Operates on BulletCollisionNodes
Destroys bullets after a given time threshold
Operates on EnemyCollisionNodes, BulletCollisionNodes and HeroCollisionNodes
Runs through the various collision nodes and damages enemies etc.
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)
Operates on GunControlNodes and EnemyCollisionNodes
Fires bullets from your gun and auto-aims at nearby enemies
Operates on HeroControlNodes
Controls player movement
Operates on LivingNodes
Removes entities when their health reaches zero
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
Operates on RenderNodes
Updates the position and rotation of display objects
Operates on EnemyCollisionNodes and HeroCollisionNodes
Creates waves of enemies
Some Thoughts And Observations
I ended up with some “marker” components. Marker classes generally make me feel uncomfortable, but they seem slightly less evil in an Entity System.
Making an entity and adding a bunch of components to it creates a lot of objects. Consider the bullets in my game:
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));
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.
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?
Check out Richard’s blog posts:
I found the Asteroids example in the Ash source really helpful: