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 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:
.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 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);
_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:
By adding the KeyControl component the hero becomes user controlled. Later we could do this:
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
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
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
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:
{
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:
Pingback: Aymeric Lamboley » An entity/component system’s attempt using Box2D