March 7, 2014

High Level Design: Systems

The final pieces of the Entity Component model is systems. Entities are separated out into components, and components are plain old data (POD) classes; systems are where all the game logic happens. Each system has a list of entities in which it is interested, some event methods (such as initialize and update), and the logic in charge of that system.

There are a number of benefits to using systems rather than having the logic contained inside each component:

  • Data-driven programming; data and functionality are completely separated.
  • No tightly coupled components; no one component will depend on another outside the scope of a system.
  • A single update loop is needed for any one system, which can cut down the cost of each update in some cases.

First, what exactly is a system? A system is the delegate responsible for all logic regarding a specific type of entity. For example, the SpriteRenderer is a system responsible for every entity with a Sprite component. The PhysicsSystem is responsible for every entity with Transform (position) and Physics (velocity) components. As you can see, more than one system may be responsible for an entity.

Figure: How systems work with scenes and entities.

Figure: How systems work with scenes and entities.

When the active scene is initialized, it registers systems to itself. As entities are added, either by the scene or by an external source, the scene distributes the entities to the appropriate systems based on what components the new entity has. Each system overrides a boolean method that determines what components are required on entities it controls. In this way, each system has no need to loop through entities it doesn’t care about, and the entities it does loop through are guaranteed to have the required components.

Consider the PhysicsSystem. In order to be affected by physics, an entity must have a Physics component and a position (which will be part of the Transform component). The basic physics we need are gravity and terminal velocity: gravity will affect the entity’s velocity, and terminal velocity will limit that velocity. At the end of each update, the entity’s position will be updated based on its velocity.

#import "LGPhysicalSystem.h"
#import "LGEntity.h"
#import "LGPhysics.h"
#import "LGTransform.h"

@implementation LGPhysicalSystem

@synthesize gravity, terminalVelocity;

- (BOOL)acceptsEntity:(LGEntity *)entity
{
	return [entity hasComponentsOfType:[LGPhysics class], [LGTransform class], nil];
}

- (void)update
{
	for(LGEntity *entity in self.entities)
	{
		LGPhysics *physics = [entity componentOfType:[LGPhysics class]];
		LGTransform *transform = [entity componentOfType:[LGTransform class]];

		[physics addToVelocity:gravity];
		[physics limitVelocity:terminalVelocity];

		[transform addToPosition:[physics velocity]];
	}
}

The engine core’s run loop is passed to the scene. The scene then passes it to the update method of each of the registered systems. Rather than have an update loop for each entity (or worse, for each component), a single loop is needed per system. Granted, in this case, each tick still goes through every entity as if each entity had its own loop. However, other cases can significantly improve by having only a single update loop.

Consider another system: the collision system. A basic approach to entities colliding with entities is to have a collision check section of the update loop in each entity. For a scene with N entities, then, a total of N * N collision checks must be performed. This can be optimized (and should be) to not waste time on entities that are obviously not colliding. However, in the ECS model, a collision system is responsible for all collisions. Rather than have a nested for loop going through each entity N * N times, it can only check the upper half of the matrix of entity pairs, cutting the collision comparisons in half by eliminating duplicates.

- (void)update
{
	// Use nested loops to make sure each entity pair is only run once per loop
	for(int i = 0; i < [self.entities count]; i++)
	{
		for(int j = i + 1; j < [self.entities count]; j++)
		{
			LGEntity *a = [self.entities objectAtIndex:i];
			LGEntity *b = [self.entities objectAtIndex:j];			
			[self resolveCollisionsBetween:a and:b];
		}
	}
}

By separating the logic into a system, entities don't have to know about other entities and components don't have to know about other components. It's this third party -- the system -- that controls everything.

Posted by Luke Godfrey

Luke Godfrey is a graduate student at the University of Arkansas, pursuing a PhD in Computer Science. He has created applications on a number of platforms, and is especially interested in mobile and web development.

View more posts from this author

One thought on “High Level Design: Systems

  1. Pingback: Rectangle Collisions | Dev Blog

Leave a Reply

Your email address will not be published. Required fields are marked *