Test Driven Cocos2d

54
Saturday, October 26, 13

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 of Test Driven Cocos2d

Page 1: Test Driven Cocos2d

Saturday, October 26, 13

Page 2: Test Driven Cocos2d

Saturday, October 26, 13

Page 3: Test Driven Cocos2d

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

Page 4: Test Driven Cocos2d

Saturday, October 26, 13

Page 5: Test Driven Cocos2d

[Expect(game) toBe:Fun]

TDD in Games with Cocos2D

Saturday, October 26, 13

Page 6: Test Driven Cocos2d

Sprites and Sprite SheetsScene ManagementActions and AnimationsEffectsMenusTile MapsAnd more....

Saturday, October 26, 13

Page 7: Test Driven Cocos2d

Saturday, October 26, 13

Page 8: Test Driven Cocos2d

Saturday, October 26, 13

Page 9: Test Driven Cocos2d

Saturday, October 26, 13

Page 10: Test Driven Cocos2d

Saturday, October 26, 13

Page 11: Test Driven Cocos2d

Saturday, October 26, 13

Page 12: Test Driven Cocos2d

Saturday, October 26, 13

Page 13: Test Driven Cocos2d

Saturday, October 26, 13

Page 14: Test Driven Cocos2d

Saturday, October 26, 13

Page 15: Test Driven Cocos2d

The player starts the game and has three buckets.  They can move the buckets right and left.

Saturday, October 26, 13

Page 16: Test Driven Cocos2d

Saturday, October 26, 13

Page 17: Test Driven Cocos2d

Saturday, October 26, 13

Page 18: Test Driven Cocos2d

+(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

Page 19: Test Driven Cocos2d

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

Page 20: Test Driven Cocos2d

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

Page 21: Test Driven Cocos2d

@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

Page 22: Test Driven Cocos2d

Saturday, October 26, 13

Page 23: Test Driven Cocos2d

Saturday, October 26, 13

Page 24: Test Driven Cocos2d

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

Page 25: Test Driven Cocos2d

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

Page 26: Test Driven Cocos2d

Saturday, October 26, 13

Page 27: Test Driven Cocos2d

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

Page 28: Test Driven Cocos2d

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

Page 29: Test Driven Cocos2d

Bombing Layer

-(void)update:(ccTime)delta{ BucketsSprite *sprite = (BucketsSprite *)[self getChildByTag:kBucket]; [sprite move:self.movement];}

Saturday, October 26, 13

Page 30: Test Driven Cocos2d

Saturday, October 26, 13

Page 31: Test Driven Cocos2d

?Saturday, October 26, 13

Page 32: Test Driven Cocos2d

Saturday, October 26, 13

Page 33: Test Driven Cocos2d

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

Page 34: Test Driven Cocos2d

@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

Page 35: Test Driven Cocos2d

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

Page 36: Test Driven Cocos2d

Saturday, October 26, 13

Page 37: Test Driven Cocos2d

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

Page 38: Test Driven Cocos2d

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

Page 39: Test Driven Cocos2d

-(id) init{ if (self = [super init]) { srand48(time(0)); } return self;}

-(float) generate{ return drand48();}

“Random”

Saturday, October 26, 13

Page 40: Test Driven Cocos2d

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

Page 41: Test Driven Cocos2d

@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

Page 42: Test Driven Cocos2d

Saturday, October 26, 13

Page 43: Test Driven Cocos2d

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

Page 44: Test Driven Cocos2d

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

Page 45: Test Driven Cocos2d

[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

Page 46: Test Driven Cocos2d

SHOW ME

Saturday, October 26, 13

Page 47: Test Driven Cocos2d

TDD and Games

• Have a Plan

• You Use the Framework

• Keep Views dumbBullets Suck - Hi Eric

Saturday, October 26, 13

Page 48: Test Driven Cocos2d

Saturday, October 26, 13

Page 49: Test Driven Cocos2d

Saturday, October 26, 13

Page 50: Test Driven Cocos2d

Saturday, October 26, 13

Page 51: Test Driven Cocos2d

Saturday, October 26, 13

Page 52: Test Driven Cocos2d

Saturday, October 26, 13

Page 53: Test Driven Cocos2d

5!

Saturday, October 26, 13

Page 54: Test Driven Cocos2d

@paytonruleswww.paytonrules.comwww.8thlight.comwww.github.com/paytonrules

Saturday, October 26, 13