Core Animation is not only capable of creating advanced, modern animations, but with a little tweaking, it’s possible to create old-school sprite animations akin to the ones you would see on an NES or SNES game console. This post will explore creating a very simple
CALayer subclass that is capable of displaying a sprite animation sequence.
For this post, we’ll recreate a video game sprite animation loop like this:
There are many ways to accomplish such an animation on iOS, the easiest of which is to use
animationImages property by providing an array of images containing each frame of the animation. But for the purposes of this post, we want to explore a method that makes use of a sprite sheet, which is just a single image that contains multiple sprites packed together.1 The simplest and most naive type of sprite sheet just concatenates every frame of an animation sequence:
We’ll use the above image for this example.
To start, let’s create a new
CALayer subclass for our sprite animation:
This layer takes two parameters:
spriteSheetImage, which is a film strip-like image similar to the above sprite sheet, and
spriteFrameSize, which is the size of a single frame. For this example, we assume every frame of the animation has the same size and the sprite sheet image has been padded appropriately.2
After we know the sprite sheet image and frame size, we need to configure the layer and create the actual animation:
There’s a lot going on here, so let’s break it down.
magnificationFilter control the resampling behavior of layer contents. In effect, these properties let you control how smooth a layer appears when its resized.
kCAFilterNearest means to use the “nearest neighbor” technique, which ensures the layer will remain sharp and pixellated no matter the size.3
We mask the layer to its bounds; otherwise, the entire sprite sheet would be visible. Additionally, we use
contentsGravity to effectively left align the bitmap so it appears to start at the first frame of the sprite animation.
Here we actually provide the layer with the sprite sheet bitmap data and set its size to the size of a single frame of animation.
In order to advance the animation for each frame, we need to know the relative offset of where the frames start as a fraction of the total width of the image.
contentsRect is the first key to this technique. Basically, it defines what part of the layer’s contents will actually be used when rendering it.
"contentsRect.origin.x" means this animation is going to affect only the x value of its origin since all we want to do is advance the contents’ origin to the left for each frame of the animation.
Since this is a keyframe animation, we need to provide it key values. Because
contentsRect is a unit rectangle,4 we need to use origin values that are fractions of the total width of the sprite sheet image, which is what we calculated above.
For such a simple sprite animation, we want the animation to pace itself linearly so each frame of the animation is displayed for the same amount of time.
calculationMode is the second key to this technique. Normally, keyframe animations are calculated linearly; that is, Core Animation computes in-between values between each key frame using simple linear interpolation. However, what we actually want in this case is for Core Animation to just jump to each offset where a new frame is displayed.
Here’s what it would look like if we didn’t use a discrete calculation mode:
And here’s what we get when we do, which is what we actually want:
The last part of this adds the actual animation and sets the layer’s speed to 0. This effectively pauses the animation. Given this, we can now add two simple functions to control playback:
Now all we have to do is create an instance of
That’s pretty much all there is to it! Certainly, there are much better options available for actually working with sprites, most notably the eponimous SpriteKit. Still, hopefully this post was useful to demonstrate some interesting features of Core Animation that aren’t as widely known.
Here’s a Kite composition that reproduces the animation in this post.
Here’s an Xcode Playground that has the code used in this post.
Here’s a gist of all the code as well.
Sprite sheets (or texture atlases) are widely used in video games to more efficiently draw individual—often related—sprites. Instead of constantly incurring the cost of uploading multiple tiny sprite bitmaps to the GPU, a single, larger bitmap that packs a bunch of sprites together is significantly faster. ↩
More efficient sprite sheet algorithms trim out most of the transparent space of individual frames to make the image smaller and rely on associated metadata for each frame to tell the renderer how to actually pad out each frame. ↩
To avoid distortions, pixel art should always be scaled by an integer multiplier or divisor. Many video games, especially modern ports of older games, may opt to use linear or trilinear filtering instead of nearest neighbor if they have to arbitrarily scale sprites to match modern display resolutions. This allows screen contents to fit at a variety of display resolutions, but the result is blurry-looking pixel art. ↩
contentsRectis defined as a unit rectangle, which defaults to ((0, 0), (1, 1)). What this means is that the origin of the contents should be 0 times the width and 0 times the height, and the size of the contents should be 1 times the width and 1 times the height. ↩