74
Sprite Kit - Case Study Michael Pan

Shootting Game

Embed Size (px)

DESCRIPTION

Sprite Kit Case - Shooting Game

Citation preview

Page 1: Shootting Game

Sprite Kit - Case StudyMichael Pan

Page 2: Shootting Game

For what

Storybook!

2D game

Page 3: Shootting Game

Storybook

https://www.youtube.com/watch?v=r_3TJtK1PjU

Page 4: Shootting Game

About Game

2D - Corona!

3D - Unity

Page 5: Shootting Game

What we will build

http://www.raywenderlich.com/42699/spritekit-tutorial-for-beginners

Page 6: Shootting Game

Create a Sprite Kit Project

Shooter

Page 7: Shootting Game

Make it landscape only

Page 8: Shootting Game

Run the project

Page 9: Shootting Game

Download the resource

http://cdn3.raywenderlich.com/wp-content/uploads/2015/01/SpriteKitSimpleGameResources.zip

Page 10: Shootting Game

Drag resource into project

Page 11: Shootting Game

MyScene.m#import "MyScene.h"!!@interface MyScene ()!@property (nonatomic) SKSpriteNode * player;!@end!!@implementation MyScene!!-(id)initWithSize:(CGSize)size { ! if (self = [super initWithSize:size]) {! ! NSLog(@"Size: %@", NSStringFromCGSize(size));! ! self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];! self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];! self.player.position = CGPointMake(100, 100);! [self addChild:self.player];! }! return self;!}!@end

Page 12: Shootting Game

Run

Page 13: Shootting Game

Check the log

??

Page 14: Shootting Game

why

http://stackoverflow.com/questions/9539676/uiviewcontroller-returns-invalid-frame

Page 15: Shootting Game

Modify the ViewController.m- (void)viewDidLoad!{! [super viewDidLoad];!! // Configure the view.! SKView * skView = (SKView *)self.view;! skView.showsFPS = YES;! skView.showsNodeCount = YES;! ! // Create and configure the scene.! SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];! scene.scaleMode = SKSceneScaleModeAspectFill;! ! // Present the scene.! [skView presentScene:scene];!}

Page 16: Shootting Game

viewDidAppear-(void) viewDidAppear:(BOOL)animated{! [super viewDidAppear:animated];! SKView * skView = (SKView *)self.view;! ! skView.showsFPS = YES;! skView.showsNodeCount = YES;! ! // Create and configure the scene.! SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];! scene.scaleMode = SKSceneScaleModeAspectFill;! ! // Present the scene.! [skView presentScene:scene];!}

Page 17: Shootting Game

Good result

Page 18: Shootting Game

What we learnedSKScene

SKSpriteNode

self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];!self.player.position = CGPointMake(100, 100);![self addChild:self.player];

Page 19: Shootting Game

Class relationship

SKNode

SKEffectNode

SKScene

SKSpriteNode

- (void)addChild:(SKNode *)node;

Page 20: Shootting Game

PositionSKScene

(0,0)

(100,100)

Page 21: Shootting Game

Put SpriteNode on scene - right-down??SKScene

(0,0)

(100,100)

Page 22: Shootting Game

Put SpriteNode on scene - left-up ??SKScene

(0,0)

(100,100)

Page 23: Shootting Game

Put SpriteNode on scene - center ??SKScene

(0,0)

(100,100)

Page 24: Shootting Game

Anchor point

(0,0) (1,0)

(0,1) (1,1)

(0.5,0.5) default

Page 25: Shootting Game

Test - position (0,0) with default Anchor Point

self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];!self.player.position = CGPointMake(0, 0);![self addChild:self.player];

Page 26: Shootting Game

Test - position (0,0) with default Anchor Point(0,0)

self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];!self.player.position = CGPointMake(0, 0);!self.player.anchorPoint = CGPointMake(0, 0);![self addChild:self.player];

Page 27: Shootting Game

Add enemy in MyScene.m (1)- (void)addMonster {! // Create sprite! SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];! ! // Determine where to spawn the monster along the Y axis! int minY = monster.size.height / 2;! int maxY = self.frame.size.height - monster.size.height / 2;! int rangeY = maxY - minY;! int actualY = (arc4random() % rangeY) + minY;!!

monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY);! [self addChild:monster];!}

Page 28: Shootting Game

Add enemy in MyScene.m (2)- (void)addMonster {! ! //…! // Determine speed of the monster! int minDuration = 2.0;! int maxDuration = 4.0;! int rangeDuration = maxDuration - minDuration;! int actualDuration = (arc4random() % rangeDuration) + minDuration;! ! // Create the actions! SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];! SKAction * actionMoveDone = [SKAction removeFromParent];! [monster runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];! !}

Page 29: Shootting Game

What we learned

SKAction!

moveTo:duration:!

removeFromParent!

sequence:

Page 30: Shootting Game

Class viewNSObject

SKNode

SKSpriteNode

SKAction

- (void)runAction:(SKAction *)action;

UIResponder

Page 31: Shootting Game

- (void)update:(NSTimeInterval)currentTime

every 1/60 second will be called automatically

Page 32: Shootting Game

Stabilise the time interval@interface MyScene ()!@property (nonatomic) SKSpriteNode * player;!@property (nonatomic) NSTimeInterval lastSpawnTimeInterval;!@property (nonatomic) NSTimeInterval lastUpdateTimeInterval;!@end

Page 33: Shootting Game

Codes in update:- (void)update:(NSTimeInterval)currentTime {! CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;! self.lastUpdateTimeInterval = currentTime;! if (timeSinceLast > 1) {! timeSinceLast = 1.0 / 60.0;! self.lastUpdateTimeInterval = currentTime;! }! [self updateWithTimeSinceLastUpdate:timeSinceLast];! !}

Page 34: Shootting Game

Every second add a enemy

- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {! ! self.lastSpawnTimeInterval += timeSinceLast;! if (self.lastSpawnTimeInterval > 1) {! self.lastSpawnTimeInterval = 0;! [self addMonster];! }!}!

Page 35: Shootting Game

Run the app

Page 36: Shootting Game

Throw projectile

touch

Page 37: Shootting Game

Throw projectile - vector(spriteX, spriteY)

offsetX

offsetY (touchX, touchY)

offsetX = touchX - spriteXoffsetY = touchY - spriteY

Page 38: Shootting Game

Define helper function - offsetCGPoint subPoint(CGPoint a, CGPoint b ){! CGPoint subPoint = CGPointMake(a.x - b.x, a.y - b.y);! return subPoint;!}

Page 39: Shootting Game

Unit value of vector

offsetX = touchX - spriteXoffsetY = touchY - spriteY

unitValue = sqrt(offsetX^2 + offsetY^2)

Normalised vector = (offsetX / unitValue , offsetY / unitValue)

offsetX

offsetYunitValue

Page 40: Shootting Game

Define helper function - normalised offset

CGPoint normalisedPoint(CGPoint offset){! CGFloat nValue = sqrtf(offset.x*offset.x + offset.y*offset.y);! CGPoint nPoint = CGPointMake(offset.x/nValue, offset.y/nValue);! return nPoint;!}

Page 41: Shootting Game

Get touch event - add projectile-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{! ! UITouch * touch = [touches anyObject];! ! // get the location in the scene! CGPoint location = [touch locationInNode:self] ;! ! SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"];! projectile.position = self.player.position;! ! CGPoint offset = subPoint(location, projectile.position);! ! if (offset.x <= 0) return;! ! [self addChild:projectile];!}

Page 42: Shootting Game

Multiply vector - helper functionCGPoint multiplyVector(CGPoint vector, CGFloat amount){! CGPoint newVec = CGPointMake(vector.x*amount, vector.y*amount);! return newVec;!}

Page 43: Shootting Game

Calculate projectile destination

(offsetX, offsetY)

(offsetX/unitValue, offsetY/unitValue)

newVec = multiplyVector(offset, 1000)

(player.x+newVec.x, player.y+newVec.y)

Page 44: Shootting Game

Add point with offsetCGPoint addOffset(CGPoint a, CGPoint offset){! CGPoint newVec = CGPointMake(a.x+offset.x, a.y+offset.y);! return newVec;!}

Page 45: Shootting Game

Get touch event - cal projectile destination-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{!! // !! CGPoint direction = normalisedPoint(offset);! ! !! CGPoint shootOffset = multiplyVector(direction, 1000);! !! CGPoint realDest = addOffset(projectile.position, shootOffset);! !! float velocity = 480.0/1.0;!! float realMoveDuration = self.size.width / velocity;!! SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];!! SKAction * actionMoveDone = [SKAction removeFromParent];!! [projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];!}

Page 46: Shootting Game

Run

Page 47: Shootting Game

Rotate the projectilefloat velocity = 480.0/1.0;!float realMoveDuration = self.size.width / velocity;!SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];!SKAction * actionMoveDone = [SKAction removeFromParent];!SKAction * sequence = [SKAction sequence:@[actionMove, actionMoveDone]];!!

SKAction * rotate = [SKAction rotateByAngle:4*M_PI duration:0.5];!SKAction * forever = [SKAction repeatActionForever:rotate];!SKAction * group = [SKAction group:@[forever,sequence]];![projectile runAction:group];

Page 48: Shootting Game

Run

Page 49: Shootting Game

Collision Detection

Page 50: Shootting Game

Use the power of Physic Engine

Set gravity!

!

Set contact delegate

self.physicsWorld.gravity = CGVectorMake(0,0);

self.physicsWorld.contactDelegate = self;

Page 51: Shootting Game

@interface MyScene ()<SKPhysicsContactDelegate>!@property (nonatomic) SKSpriteNode * player;!@property (nonatomic) NSTimeInterval lastSpawnTimeInterval;!@property (nonatomic) NSTimeInterval lastUpdateTimeInterval;!@end

SKPhysicsContactDelegate

Page 52: Shootting Game

physicsWorld@interface SKScene : SKEffectNode!!

@property (SK_NONATOMIC_IOSONLY, readonly) SKPhysicsWorld *physicsWorld;!!

@end

Page 53: Shootting Game

physicsBody

monster body!

!

projectile body

monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size];

projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2];

Page 54: Shootting Game

physics attributes// movable!monster.physicsBody.dynamic = YES;!!

// like ID!monster.physicsBody.categoryBitMask = monsterCategory;!!

// which ID will be contact!monster.physicsBody.contactTestBitMask = projectileCategory;!!

// can be contact or not!monster.physicsBody.collisionBitMask = 0

Page 55: Shootting Game

Two category ids

static const uint32_t projectileCategory = 0x1 << 0;!static const uint32_t monsterCategory = 0x1 << 1;

Page 56: Shootting Game

addMonster- (void)addMonster { !! // ignore …! // physic! monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size]; // 1! monster.physicsBody.dynamic = YES; // 2! monster.physicsBody.categoryBitMask = monsterCategory; // 3! monster.physicsBody.contactTestBitMask = projectileCategory; // 4! monster.physicsBody.collisionBitMask = 0; // 5!}

Page 57: Shootting Game

touchesBegan:withEvent:projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2];!projectile.physicsBody.dynamic = YES;!projectile.physicsBody.categoryBitMask = projectileCategory;!projectile.physicsBody.contactTestBitMask = monsterCategory;!projectile.physicsBody.collisionBitMask = 0;

Page 58: Shootting Game

SKPhysicsContactDelegate - impl- (void)didBeginContact:(SKPhysicsContact *)contact!{! SKPhysicsBody *firstBody, *secondBody;! ! if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)! {! firstBody = contact.bodyA;! secondBody = contact.bodyB;! }! else! {! firstBody = contact.bodyB;! secondBody = contact.bodyA;! }!}

Page 59: Shootting Game

SKPhysicsContactDelegate - impl(2)- (void)didBeginContact:(SKPhysicsContact *)contact!{! if ((firstBody.categoryBitMask & projectileCategory) != 0 &&! (secondBody.categoryBitMask & monsterCategory) != 0){!!

[self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node];! }!}

Page 60: Shootting Game

Dismiss collided objects- (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster {! NSLog(@"Hit");! [projectile removeFromParent];! [monster removeFromParent];!}

Page 61: Shootting Game

Run

Page 62: Shootting Game

About Music

Page 63: Shootting Game

Background Music - ViewController.m#import <AVFoundation/AVFoundation.h>!!@interface ViewController ()!@property (nonatomic) AVAudioPlayer * backgroundMusicPlayer;!@end!!@implementation ViewController!-(void) viewDidAppear:(BOOL)animated{!! NSError *error;!! NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"background-music-aac" withExtension:@"caf"];!! self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];!! self.backgroundMusicPlayer.numberOfLoops = -1;!! [self.backgroundMusicPlayer prepareToPlay];!! [self.backgroundMusicPlayer play];!}!@end

Page 64: Shootting Game

sound effect - touchesBegan:withEvent:-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{!! [self runAction:[SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO]];!!

}

Page 65: Shootting Game

Run

Page 66: Shootting Game

Change Scene

Page 67: Shootting Game

Create a new Scene

Page 68: Shootting Game

GameOverScene.h#import <SpriteKit/SpriteKit.h>!!

@interface GameOverScene : SKScene!-(id)initWithSize:(CGSize)size won:(BOOL)won;!@end

Page 69: Shootting Game

GameOverScene.m#import "GameOverScene.h"!#import "MyScene.h"!@implementation GameOverScene!!-(id)initWithSize:(CGSize)size won:(BOOL)won {! if (self = [super initWithSize:size]) {! ! // 1! self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];! ! // 2! NSString * message;! if (won) {! message = @"You Won!";! } else {! message = @"You Lose :[";! }!! ! // ignore … !! }!! return self;!}!@end

Page 70: Shootting Game

GameOverScene.m - Label-(id)initWithSize:(CGSize)size won:(BOOL)won {! if (self = [super initWithSize:size]) {!! ! // ignored!! ! SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];! label.text = message;! label.fontSize = 40;! label.fontColor = [SKColor blackColor];! label.position = CGPointMake(self.size.width/2, self.size.height/2);! [self addChild:label];!! ! // ignored!! }!! return self;!}

Page 71: Shootting Game

GameOverScene.m - Another Scene-(id)initWithSize:(CGSize)size won:(BOOL)won {! if (self = [super initWithSize:size]) {!! ! // ignored!! ! [self runAction:! [SKAction sequence:@[! [SKAction waitForDuration:3.0],! [SKAction runBlock:^{! // 5! SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];! SKScene * myScene = [[MyScene alloc] initWithSize:self.size];! [self.view presentScene:myScene transition: reveal];! }]! ]]! ];!!! }!! return self;!}

Page 72: Shootting Game

Show GameOverScene - MyScene.m#import "GameOverScene.h"!!

- (void)addMonster {!! SKAction * loseAction = [SKAction runBlock:^{!! ! SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];!! ! SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO];!! ! [self.view presentScene:gameOverScene transition: reveal];!! }];!! [monster runAction:[SKAction sequence:@[actionMove, loseAction, actionMoveDone]]];!}

Page 73: Shootting Game

Show Win@interface MyScene ()<SKPhysicsContactDelegate>!@property (nonatomic) int monstersDestroyed;!@end

- (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster {! self.monstersDestroyed++;! if (self.monstersDestroyed > 5) {! SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];! SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:YES];! [self.view presentScene:gameOverScene transition: reveal];! }!}

Page 74: Shootting Game

Question ?