Test Driven Cocos2d

Post on 25-Jan-2015

213 views 2 download

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

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