March 11, 2014

The Sprite System

Now that the ECS framework is in place, I will begin implementing some basic systems to use in a game. These systems will include everything necessary to create a simple game, handling input, physics, collisions, and rendering. A developer may choose to implement his own systems if greater complexity or different behavior is required. The first system I will implement is the sprite system.

The game engine is responsible for rendering game objects as (animated) graphics to the device screen. The sprite system is designed to do the following:

  • Cache textures.
  • Animate sprites.
  • Render sprites to the appropriate location on the screen.

The location on the screen is already stored in the Transform component, but textures and animations are not yet defined, so a Sprite component will be created to hold this information.

Textures

Textures are just images that can be cached and rendered as one or more game objects. Each texture will be a sprite sheet (also known as a texture atlas), which is an image composed of a number of smaller images. For simplicity, the sprite sheets accepted by this system must have sub-images of equal size, although texture atlases in general often allow arbitrary frame sizes and positions. By only allowing sprite sheets with frames aligned to a grid, computations are much easier.

The Sprite component will have a method to set the texture by name. If a cached texture matching that name exists, then use that; otherwise, create a texture from a file matching that name and cache it for later use. The component will also have a frame property, a CGSize that keeps track of the width and height of each sub-image of the sprite sheet.

Animations

With the texture and the frame defined, specific sub-images can be accessed using an integer value position, such that the row of the sub-image is (position - 1) / (texture.width / frame.width) and the column of the sub-image is (position - 1) % (texture.width / frame.width). By subtracting 1, the sprite sheet is indexed starting with 1 instead of 0, allowing 0 to be a dedicated “empty” frame.

To define animations, then, we need a list of positions that make up an animation and a flag that signals whether the animation loops. This implementation will require that animations are continuous in the sprite sheet (rather than allowing arbitrary positions), but it would be easy to modify to allow NSArrays of positions for an animation. To keep track of the positions and the loop flag, I created a SpriteState class.

@implementation LGSpriteState

@synthesize start, end, isAnimated, loops;

- (id)initWithPosition:(int)p
{
	return [self initWithStart:p andEnd:p];
}

- (id)initWithStart:(int)s andEnd:(int)e
{
	return [self initWithStart:s andEnd:e loops:YES];
}

- (id)initWithStart:(int)s andEnd:(int)e loops:(BOOL)l
{
	self = [super init];
	
	if(self)
	{
		start = s;
		end = e;
		isAnimated = e > s;
		loops = l;
	}
	
	return self;
}

@end

The Sprite component keeps a NSMutableDictionary of states, so that animations can be stored and accessed by a string (i.e. “run” or “fall”). In a system designed for player input, input can determine which animation state the sprite should have.

Finally, here’s the sprite system’s update method that uses all of this information to render the sprite (using a UIView) and animate it. Note that I’m using rounded values of [transform position] so that the textures aren’t blurred on the screen (which happens when a UIView is placed at non-integral coordinates).

- (void)update
{
	for(LGEntity *entity in self.entities)
	{
		LGSprite *sprite = [entity componentOfType:[LGSprite class]];
		LGTransform *transform = [entity componentOfType:[LGTransform class]];
		
		if([sprite visible])
		{
			[[sprite view] setFrame:CGRectMake(round([transform position].x - [sprite offset].x), round([transform position].y - [sprite offset].y), [sprite size].width, [sprite size].height)];
			
			if([[sprite state] isAnimated])
			{
				[sprite incrementAnimationCounter];
				if([sprite animationCounter] == 0)
					[sprite nextPosition];
			}
		}
	}
}

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

Leave a Reply

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