test driven cocos2d
DESCRIPTION
Test Driven Game Development with Cocos2d. This is the first part of my 8th Light University talk on how to effectively write unit tests for game development. We're using Cocos2d for the framework, but the principles apply to most game frameworks.TRANSCRIPT
Saturday, October 26, 13
Saturday, October 26, 13
A game is a system in which players engage in an artificial conflict, defined by rules, that results in a quantifiable outcome.
Rules of Play
Saturday, October 26, 13
Saturday, October 26, 13
[Expect(game) toBe:Fun]
TDD in Games with Cocos2D
Saturday, October 26, 13
Sprites and Sprite SheetsScene ManagementActions and AnimationsEffectsMenusTile MapsAnd more....
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
The player starts the game and has three buckets. They can move the buckets right and left.
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
+(CCScene *) scene{!// 'scene' is an autorelease object.!CCScene *scene = [CCScene node];!!// 'layer' is an autorelease object.!HelloWorldLayer *layer = [HelloWorldLayer node];!!// add layer as a child to scene![scene addChild: layer];!!// return the scene!return scene;}
Hello World Layer
Saturday, October 26, 13
Hello World Layer// on "init" you need to initialize your instance-(id) init{!// always call "super" init!// Apple recommends to re-assign "self" with the "super's" return value!if( (self=[super init]) ) {!!!! // create and initialize a Label!! CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World"
fontName:@"Marker Felt" fontSize:64];
!! // ask director for the window size!! CGSize size = [[CCDirector sharedDirector] winSize];!!! // position the label on the center of the screen!! label.position = ccp( size.width /2 , size.height/2 );!!!! // add the label as a child to this Layer!! [self addChild: label];
Saturday, October 26, 13
OCDSpec2Context(BucketsSpec) { Describe(@"moving", ^{ It(@"moves to the right", ^{ Buckets *buckets = [[Buckets alloc] initWithPosition:CGPointMake(10.0, 10.0)];
[buckets move:1];
[ExpectFloat(buckets.position.x) toBe:11.0 withPrecision:0.00001]; });
It(@"moves to the left", ^{ Buckets *buckets = [[Buckets alloc] initWithPosition:CGPointMake(10.0, 10.0)];
[buckets move:-1.0];
[ExpectFloat(buckets.position.x) toBe:9.0 withPrecision:0.00001]; }); }); }
Buckets
Saturday, October 26, 13
@implementation Buckets
-(id) initWithPosition:(CGPoint) position{ if (self = [super init]) { self.position = position; } return self;}
-(void) move:(float) movement{ self.position = CGPointMake(self.position.x + movement, self.position.y);}@end
Buckets
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Bombing Layer
-(id) init{ if( (self=[super init]) ) {!! // ask director for the window size!! CGSize size = [[CCDirector sharedDirector] winSize];
Buckets *buckets = [[Buckets alloc] initWithPosition:CGPointMake(size.width / 2, size.height / 2)]; BucketsSprite *sprite = [BucketsSprite spriteWithBuckets:buckets];
!! // add the sprite as a child to this Layer!! [self addChild: sprite];!}!return self;}
Saturday, October 26, 13
Buckets Sprite@implementation BucketsSprite
+(id) spriteWithBuckets:(Buckets *)buckets{ BucketsSprite *sprite = [BucketsSprite spriteWithFile:@"buckets.png"]; sprite.buckets = buckets;
[sprite scheduleUpdate]; return sprite;}
-(void)update:(ccTime)delta { [self setPosition:self.buckets.position];}@end
Saturday, October 26, 13
Saturday, October 26, 13
Bombing Layer-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{ CGPoint point = [touch locationInView:[touch view]]; CGSize size = [[CCDirector sharedDirector] winSize];
if (point.x > size.width / 2) { self.movement++; } else { self.movement--; } return YES;}
Saturday, October 26, 13
Bombing Layer
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{ CGPoint point = [touch locationInView:[touch view]]; CGSize size = [[CCDirector sharedDirector] winSize];
if (point.x > size.width / 2) { self.movement--; } else { self.movement++; }}
Saturday, October 26, 13
Bombing Layer
-(void)update:(ccTime)delta{ BucketsSprite *sprite = (BucketsSprite *)[self getChildByTag:kBucket]; [sprite move:self.movement];}
Saturday, October 26, 13
Saturday, October 26, 13
?Saturday, October 26, 13
Saturday, October 26, 13
OCDSpec2Context(BomberSpec) { Describe(@"moving back and forth", ^{ It(@"moves towards its next spot", ^{ RiggedLocations *locations = [RiggedLocations newWithValues:@[@0.0]]; Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(10, 40)
speed:1.0 locationChooser:locations];
[bomber start];
[bomber update:1.0];
[ExpectFloat(bomber.position.x) toBe:9.0 withPrecision:0.0001]; });
Bomber Spec
Saturday, October 26, 13
@implementation RiggedLocations
-(float) next { float value = [(NSNumber *) [self.values firstObject] floatValue]; if (self.values.count > 1) { [self.values removeObjectAtIndex:0]; }
return value;}
+(RiggedLocations *) newWithValues:(NSArray *)array { RiggedLocations *locations = [RiggedLocations new]; locations.values = [NSMutableArray arrayWithArray:array]; return locations;}
@end
Rigged Locations
Saturday, October 26, 13
Describe(@"using the random number generator", ^{
It(@"uses the random number generator for its next location", ^{ id rand = [OCMockObject mockForProtocol:@protocol(RandomNumberGenerator)]; NSRange range = NSMakeRange(0, 100);
RandomLocationChooser *chooser = [RandomLocationChooser newChooserWithRange:range generator:rand];
float retVal = 0.0; [[[rand stub] andReturnValue:OCMOCK_VALUE(retVal)] generate];
[ExpectFloat([chooser next]) toBe:0.0 withPrecision:0.0001]; });
Random Locations
Saturday, October 26, 13
Saturday, October 26, 13
It(@"stops at the location when coming from the left", ^{ RiggedLocations *locations = [RiggedLocations newWithValues:@[@19.0, @22.0]]; Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(17, 40) speed:2.0 locationChooser:locations];
[bomber start];
[bomber update:1.0]; [bomber update:1.0];
[ExpectFloat(bomber.position.x) toBe:21.0 withPrecision:0.0001]; });
It(@"doesn't move until it is started", ^{ RiggedLocations *locations = [RiggedLocations newWithValues:@[@19.0, @22.0]]; Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(17, 40) speed:2.0 locationChooser:locations];
[bomber update:1.0];
[ExpectFloat(bomber.position.x) toBe:17.0 withPrecision:0.0001]; });
Bomber Spec
Saturday, October 26, 13
Bomber- (void)update:(float)deltaTime{ if (self.started) { float moveDistance = self.speed * deltaTime; float distanceRemaining = abs(self.location - self.position.x);
if (self.location > self.position.x) { self.position = CGPointMake(self.position.x + moveDistance, self.position.y); } else { self.position = CGPointMake(self.position.x - moveDistance, self.position.y); }
if (moveDistance >= distanceRemaining) { self.location = [self.locations next]; } }
Saturday, October 26, 13
-(id) init{ if (self = [super init]) { srand48(time(0)); } return self;}
-(float) generate{ return drand48();}
“Random”
Saturday, October 26, 13
Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(size.width / 2, speed:60.0 locationChooser:chooser];BomberSprite *bomberSprite = [BomberSprite newSpriteWithBomber:bomber];
[self addChild:bomberSprite z:0 tag:kBomber]; [bomber start];
Bombing Layer
Saturday, October 26, 13
@implementation BomberSprite
+(id) newSpriteWithBomber:(Bomber *)bomber{ BomberSprite *sprite = [BomberSprite spriteWithFile:@"bomber.png"]; sprite.bomber = bomber; [sprite scheduleUpdate]; return sprite;}
-(void)update:(ccTime)delta{ [self.bomber update:delta]; [self setPosition:self.bomber.position];}
Bomber Sprite
Saturday, October 26, 13
Saturday, October 26, 13
It(@"drops a bomb when it changes direction", ^{ RiggedLocations *locations = [RiggedLocations newWithValues:@[@18.0]]; Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(17.0, speed:1.0 locationChooser:locations];
[bomber start]; [bomber update:1.0];
[ExpectInt(bomber.bombCount) toBe:1]; });
Bomber Spec
Saturday, October 26, 13
It(@"moves the bomb on each update", ^{ RiggedLocations *locations = [RiggedLocations newWithValues:@[@18.0]];
Bomber *bomber = [[Bomber alloc] initWithPosition:CGPointMake(17.0 speed:1.0 locationChooser:locations height:10 bombHeight:20];
[bomber start]; [bomber update:1.0]; [bomber update:1.0];
CGPoint bombPosition; [(NSValue *) bomber.bombs[0] getValue:&bombPosition]; [ExpectInt(bombPosition.x) toBe:18]; [ExpectInt(bombPosition.y) toBe:55 - kGravity]; });
Bomber Spec
Saturday, October 26, 13
[self.bomber.bombs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [self.parent removeChildByTag:kBomb + idx]; NSValue *bombValue = (NSValue *)obj; CGPoint location; [bombValue getValue:&location];
CCSprite *bombSprite = [CCSprite spriteWithFile:@"bomb.png"]; bombSprite.position = location; [self.parent addChild:bombSprite z:0 tag:kBomb + idx]; }];
Bomber Sprite
Saturday, October 26, 13
SHOW ME
Saturday, October 26, 13
TDD and Games
• Have a Plan
• You Use the Framework
• Keep Views dumbBullets Suck - Hi Eric
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
Saturday, October 26, 13
5!
Saturday, October 26, 13
@paytonruleswww.paytonrules.comwww.8thlight.comwww.github.com/paytonrules
Saturday, October 26, 13