Sprites using only CoreAnimation

Today I’m going to talk about the development of my likely to be next released game codenamed Mitch Digger. First I want to talk about something I think can be one of the biggest problems for an independent developer: Too many technologies! While it’s a great thing in general I think it can both be overwhelming and potentially waste a lot of time just learning new technologies. Look at developing for iOS alone. In the process of deciding to work on a platformer I looked into cocos2d, openGL ES, corona, unity and more. Deciding on any one resulted in trade offs; Time to learn, capabilities, ease of use. In every case I felt like I was making the wrong choice. In the end I decided for now I’m going to stick with what I know already and see if it was really possible to make a game in just CoreAnimation.

Random aside: I used to build combat robots as a hobby and there was one guy who claimed you could build anything with just a drill, a hack saw and enough ingenuity. He knew this because he did! And these weren’t poorly built either, they were better than most of the robots built by people with poor craftsmanship and better tools. The moral of the story is don’t get too caught up in tools.

With that decision hammered out of the way I started to work on the game. I knew what the game would be already, it’s something I’ve wanted to make for a while and first proposed it for the 360iDev Game Jam with some concept art. I made up some art first because I’m backwards like that. For me the art is easier to get done and it also serves to give me a lot of motivation. If I have some great art done it will really help me envision the game and keep me working on it.

Ok so, 2D platformer huh? Where to start? Well the first question is whether or not I could even make it work in CoreAnimation. What would I need for a platformer? In my mind, two things: Animated Sprites and a Tilemap. So that’s where I started. Today I’m going to talk about the sprite class I made and how it works. Fair warning: I’m by no means the best programmer and this code is by no means optimized. The concept of avoiding premature optimization makes a lot of sense to me so that’s what I’m working with. Once I have a game running I’ll go back, profile and improve what needs to be improved to make it work on older devices. My I have a BS in electrical engineering and a BA in studio art so my programming is mostly self taught. I provide the following code with no claims of its superiority.

- (id) initWithName:(NSString *)sprite {
    self = [super init];
	if (self) {
		//The name of the file .png, @2x.png, and .plist files
		[self setSpriteName:sprite];
	}

	return self;
}

+ (id) spriteWithName:(NSString *)sprite {
	return [[[AFSprite alloc] initWithName:sprite] autorelease];
}

Here we have some basic initialization stuff. My sprite is a subclass of CALayer with my custom methods and overrides to give it some basic “sprite” functionality. I’ve been making my spritesheets in zwoptex so it expects there to be a zwoptex png output (also 2x) and a zwoptex plist file. I just use one plist for the normal and 2x sizes but care must be taken to make sure they are exactly the same. Make sure the padding on the 2x is twice the padding on the 1x and for now I’ve been making sure the images are untrimmed because that has been throwing them off. I’ll probably try to streamline that process a bit in the future.

#pragma mark loadAnimation

- (void) setAnimation:(NSString*)animation fromPlist:(NSString*)file forKey:(NSString*)key {

	//If not the current animation clear the current then add it
	if (![currentAnimation isEqualToString:animation]) {

		//Clear the current animation
		[self removeAllAnimations];

		NSString* animationPath = [[NSBundle mainBundle] pathForResource:file ofType:@"plist"];
		NSDictionary* animationDict = [NSDictionary dictionaryWithContentsOfFile:animationPath];

		NSMutableArray *frames = [[animationDict objectForKey:animation] mutableCopy];
		if (frames){
			float duration = [[frames lastObject] floatValue];
			[frames removeLastObject];

			CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"spriteFrame"];
			[keyframeAnimation setValues:frames];
			[keyframeAnimation setDuration:duration];
			[keyframeAnimation setRepeatCount:HUGE_VAL];
			[keyframeAnimation setCalculationMode:kCAAnimationDiscrete];

			[self addAnimation:keyframeAnimation forKey:key];
			currentAnimation = animation;
		}

        [frames release];
	}
}

This is what I’m using to animate the sprites. I create a plist for each different type of sprite there is. These each contain a dictionary with the keys being names of various animations, “stand”, “walk”, “jump”, etc. The value for each of those keys is a series of frames and the amount of time each one should display for like so:

This method grabs those frames and creates a keyframe Animation to loop between them at the desired frequency. Looking at this function it’s likely I should be caching the dictionary of animations instead of loading it from file each time but this is the kind of thing I’ll go back and profile before I worry about it.

#pragma mark Setters

- (void) setSpriteName:(NSString *)sprite {
	//retain the new release the old
	[spriteName release];
	spriteName = [sprite retain];

	//Load the new sprite image file in
	[self setContents: (id)([UIImage imageNamed:[sprite stringByAppendingString:@".png"]].CGImage) ];

	//Get the sprite attributes from the plist
	NSString* zwoptexPath = [[NSBundle mainBundle] pathForResource:self.spriteName ofType:@"plist"];
	NSDictionary *zwoptexDict = [NSDictionary dictionaryWithContentsOfFile:zwoptexPath];
	NSString* sizeString = [[zwoptexDict objectForKey:@"metadata"] objectForKey:@"size"];

	self.attributesDict = zwoptexDict;

	//Set the texturesize to the full texture
	sizeString = [sizeString stringByReplacingOccurrencesOfString:@" " withString:@""];
	sizeString = [sizeString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"{}"]];

	NSArray *sizeArray = [sizeString componentsSeparatedByString:@","];

	self.textureSize = CGSizeMake([[sizeArray objectAtIndex:0] intValue],
								  [[sizeArray objectAtIndex:1] intValue]);

	//Load all the frames into a better dictionary for reference
	NSDictionary *sourceFramesDict = [self.attributesDict objectForKey:@"frames"];
	self.framesDict = [NSMutableDictionary dictionary];

	for (NSString *singleFrameKey in sourceFramesDict) {
		NSDictionary *singleFrame = [sourceFramesDict objectForKey:singleFrameKey];

		NSString *textureString = [singleFrame objectForKey:@"textureRect"];
		textureString = [textureString stringByReplacingOccurrencesOfString:@"{" withString:@""];
		textureString = [textureString stringByReplacingOccurrencesOfString:@" " withString:@""];
		textureString = [textureString stringByReplacingOccurrencesOfString:@"}" withString:@""];
		NSArray *strings = [textureString componentsSeparatedByString:@","];

		int originX = [[strings objectAtIndex:0] intValue];
		int originY = [[strings objectAtIndex:1] intValue];
		int sizeWidth = [[strings objectAtIndex:2] intValue];
		int sizeHeight = [[strings objectAtIndex:3] intValue];

		[self.framesDict setValue:[NSValue valueWithCGRect:CGRectMake(originX, originY, sizeWidth, sizeHeight)] forKey:singleFrameKey];
	}
}

This one is a little messy, but it’s for changing the sprite. This gets called upon initilization and if you wanted to change the sprite file (though I doubt that will happen often). The first thing it does is set the contents of the CALayer, as you’ll see later we change the bounds to limit it to only show one of the sprite frames at a time instead of the whole sheet. Next it loads the dictionary of the frame information from zwoptex’s plist. This is what it uses to figure out where each frame lies on the sheet. I store the full size of the texture because later I have to reference things in a [0,1] proportional way instead of by points or pixels. The last part is just soring things more directly in a dictionary instead of as strings. zwoptex stores the information in formats like “{ X, Y }” and I’ll be needing to access those values constantly so this results in some processing savings by not doing it over and over again. (This is a case where I did profile it already and sped things up).

#pragma mark SpriteEnabling

+ (BOOL) needsDisplayForKey:(NSString*)key {
	//If the spriteFrame changes redisplay the sprite
	if ([key isEqualToString:@"spriteFrame"])
		return YES;
	return [super needsDisplayForKey:key];
}

This makes the CALayer set the needDisplay property whenever the spriteFrame changes. This will cause it to actually call the display method where we can actually change which frame is being displayed.

//Used to change the frame when updating the sprite
- (void)display {
	//Get the current frame that's being displayed
	NSString* currentSpriteFrame = ((AFSprite*)[self presentationLayer]).spriteFrame;

	if (!currentSpriteFrame)
		return;

	[self changeSpriteFrame:currentSpriteFrame];
}

Display is the meat and potatos. This is what gets called whenever the image being displayed needs to be updated. The reason we need to grab the current frame from the presentation layer is because of how animations work. When you create an animation for some property on a layer that property is going to have either the initial value or the final value depending on how the animation got created. The presentation layer is where the animation actually “happens” this is where the value gets changed each time the animation ticks, thus if we grab the frame from the presentation layer it contains whichever frame should be showing [i]right now[/i]. Once we have that we call the next method…

- (void)changeSpriteFrame:(NSString *)frameString {

	CGRect frameBounds = [[self.framesDict valueForKey:[frameString stringByAppendingString:@".png"]] CGRectValue];

	//Set the bounds and contents rec to the frame on the texture
	self.bounds = CGRectMake(0,0,frameBounds.size.width,frameBounds.size.height);
	self.contentsRect = CGRectMake(frameBounds.origin.x   /textureSize.width,
								   frameBounds.origin.y   /textureSize.height,
								   frameBounds.size.width /textureSize.width,
								   frameBounds.size.height/textureSize.height);

}

This method changes the actual portion of the spritesheet being displayed. The frame bounds is the rectangle on the spritesheet we want to display. We set the width and the height on the bounds equal to the point value width and height of the sprite. Then the contentsRect is a rectangle defined in “unit space” which is the portion of the full image we want to draw. Thus for x and y: [0,0] would be the top left and [1,1] would be the bottom right. And for width and height: [0.5,0.5] one quarter of the total image (half width * half height) and [1,1] would be the full image.

//Used to disable the animating of the change of contentsRect on the "texture"
+ (id )defaultActionForKey:(NSString *)aKey;
{
	//If contentsRect is being changed, do nothing instead
    //if ([aKey isEqualToString:@"contentsRect"] || [aKey isEqualToString:@"bounds"])
        return (id )[NSNull null];

   // return [super defaultActionForKey:aKey];
}

Finally this just disables all the implicit animations that CALayers normally have. They’re a great feature, but when animating at 30-60FPS you don’t want things animating, you just want them to instantly change.

And that’s it! And here it is in action:

You can see in that video that it seems entirely possible to make a simple platformer in CoreAnimation. The red box is the collision rect for Mitch and the blue box is the last tile collsion that was checked. Also ignore the fact that I appear to suck at playing my own game, it was hard playing and recording at the same time! That being said I think the 2 buttons + tilt controls works very well for a platformer, it feels really intuitive when playing and like I said isn’t anything like I make it look in this video.

In two weeks I’ll be talking about how I did the tilemap and hopefully have some more gameplay progress to show. My homework until then: Think of a name… why is that always the hardest part?

One Response to “Sprites using only CoreAnimation”

  1. Abdo says:

    I find this post to be very useful as I’m relatively new to game development and I’m using Core Animation as well on my 2d game. I believe that Core Animation is underestimated when it comes to 2d games.

    Great job on the mitch digger game, the prototype looks awesome !

    I gotta say the frame rate is really smooth. Did you notice any frame rate issues while coding ? I may have up to 5 CALayers moving on the screen in my game so I’m a bit curious if that would affect the frame rate (the calayers are basically images not bigger than 80×80, and I just change their positions in each frame) ..

    Thanks and good luck in choosing a name for your game ;)