Skip to content

Instantly share code, notes, and snippets.

@JoeOsborn
Created June 28, 2011 21:53
Show Gist options
  • Save JoeOsborn/1052332 to your computer and use it in GitHub Desktop.
Save JoeOsborn/1052332 to your computer and use it in GitHub Desktop.
SpriteLayer, a CALayer subclass for spritesheet-based animation
//
// SpriteLayer.h
//
// Created by Joseph C Osborn on 2011.01.12.
// Copyright 2011 Universal Happy-Maker. All rights reserved.
// This code is made available under the 3-clause BSD license:
// http://www.opensource.org/licenses/BSD-3-Clause
//
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
extern const int SpriteAnimEnd;
@interface SpriteLayer : CALayer {
CGSize frameSize;
NSString *currentAnimation;
int currentFrame;
NSMutableDictionary *animations;
}
@property (readwrite, assign, nonatomic) CGSize frameSize;
@property (readonly, copy, nonatomic) NSString *currentAnimation;
@property (readonly, assign, nonatomic) float framesWide, framesHigh;
- (void)addSpriteAnimationNamed:(NSString *)n atFramerate:(float)fps looping:(BOOL)loop withFrames:(int)frame1, ...;
- (void)play:(NSString *)anim;
- (void)play:(NSString *)anim restart:(BOOL)restart;
- (CAAnimation *)CAAnimationNamed:(NSString *)n;
@end
//
// SpriteLayer.m
//
// Created by Joseph C Osborn on 2011.01.12.
// Copyright 2011 Universal Happy-Maker. All rights reserved.
// This code is made available under the 3-clause BSD license:
// http://www.opensource.org/licenses/BSD-3-Clause
//
#import "SpriteLayer.h"
const int SpriteAnimEnd = -1;
@interface SpriteAnim : NSObject {
BOOL loop;
float framerate;
NSArray *frames;
NSMutableArray *times;
}
@property (readonly, assign, nonatomic) BOOL loop;
@property (readonly, assign, nonatomic) float framerate;
@property (readonly, copy, nonatomic) NSArray *frames;
- (id)initWithFramerate:(float)fps looping:(BOOL)loop frames:(NSArray *)frames;
- (CAAnimation *)animation;
@end
@implementation SpriteAnim
@synthesize loop, framerate, frames;
- (id)initWithFramerate:(float)fps looping:(BOOL)looping frames:(NSArray *)frameArray {
if((self = [super init])) {
framerate = fps;
loop = looping;
frames = [frameArray copy];
times = [[NSMutableArray alloc] initWithCapacity:frames.count];
for(int i = 0; i < frames.count; i++) {
[times addObject:[NSNumber numberWithFloat:i / (float)frames.count]];
}
[times addObject:[NSNumber numberWithFloat:1.0]];
}
return self;
}
- (void)dealloc {
[frames release];
[times release];
[super dealloc];
}
- (CAAnimation *)animation {
CAKeyframeAnimation *a = [CAKeyframeAnimation animationWithKeyPath:@"contentsRect"];
a.values = frames;
a.keyTimes = times;
if(loop) {
a.repeatCount = HUGE_VALF;
}
a.fillMode = kCAFillModeForwards;
a.removedOnCompletion = NO;
a.duration = frames.count / framerate;
a.calculationMode = kCAAnimationDiscrete;
return a;
}
@end
@interface SpriteLayer()
@property (readwrite, nonatomic, copy) NSString *currentAnimation;
@end
@implementation SpriteLayer
@synthesize frameSize, currentAnimation;
@dynamic framesWide, framesHigh;
- (id)init {
if((self = [super init])) {
animations = [[NSMutableDictionary alloc] initWithCapacity:8];
currentAnimation = nil;
frameSize = CGSizeZero;
NSMutableDictionary *actions = [NSMutableDictionary dictionaryWithDictionary:self.actions];
[actions setObject:[NSNull null] forKey:@"position"];
[actions setObject:[NSNull null] forKey:@"contentsRect"];
self.actions = actions;
self.magnificationFilter = kCAFilterNearest;
self.minificationFilter = kCAFilterNearest;
}
return self;
}
- (void)dealloc {
[currentAnimation release];
[animations release];
[super dealloc];
}
- (void)addSpriteAnimationNamed:(NSString *)n atFramerate:(float)fps looping:(BOOL)loop withFrames:(int)frame1, ... {
NSMutableArray *frameArray = [NSMutableArray arrayWithCapacity:8];
int framesWide = (int)ceilf(self.framesWide);
//int framesHigh = (int)ceilf(self.framesHigh);
double fw = self.frameSize.width/CGImageGetWidth((CGImageRef)self.contents);
double fh = self.frameSize.height/CGImageGetHeight((CGImageRef)self.contents);
va_list args;
va_start(args, frame1);
int f = frame1;
while(f != SpriteAnimEnd) {
int col = f % framesWide;
int row = f / framesWide;
CGRect cBounds = CGRectMake(fw*col, fh*row, fw, fh);
[frameArray addObject:[NSValue valueWithCGRect:cBounds]];
f = va_arg(args, int);
}
va_end(args);
SpriteAnim *anim = [[[SpriteAnim alloc] initWithFramerate:fps looping:loop frames:frameArray] autorelease];
[animations setObject:anim forKey:n];
}
- (void)play:(NSString *)anim {
[self play:anim restart:NO];
}
- (void)play:(NSString *)anim restart:(BOOL)restart {
if(!restart && [self.currentAnimation isEqualToString:anim]) { return; }
CAAnimation *caAnim = [self CAAnimationNamed:anim];
if(!caAnim) { return; }
if([self animationForKey:@"sprite-animation"]) {
[self removeAnimationForKey:@"sprite-animation"];
}
self.currentAnimation = anim;
caAnim.delegate = self;
[self addAnimation:caAnim forKey:@"sprite-animation"];
}
- (CAAnimation *)CAAnimationNamed:(NSString *)n {
return [[animations objectForKey:n] animation];
}
//- (void)animationDidStart:(CAAnimation *)anim {
//
//}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if([self animationForKey:@"sprite-animation"] == anim) {
self.currentAnimation = nil;
}
}
- (float)framesWide {
return CGImageGetWidth((CGImageRef)self.contents)/frameSize.width;
}
- (float)framesHigh {
return CGImageGetHeight((CGImageRef)self.contents)/frameSize.height;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment