Shootting Game
-
Upload
michael-pan -
Category
Entertainment & Humor
-
view
465 -
download
1
description
Transcript of Shootting Game
Sprite Kit - Case StudyMichael Pan
For what
Storybook!
2D game
What we will build
http://www.raywenderlich.com/42699/spritekit-tutorial-for-beginners
Create a Sprite Kit Project
Shooter
Make it landscape only
Run the project
Download the resource
http://cdn3.raywenderlich.com/wp-content/uploads/2015/01/SpriteKitSimpleGameResources.zip
Drag resource into project
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
Run
Check the log
??
why
http://stackoverflow.com/questions/9539676/uiviewcontroller-returns-invalid-frame
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];!}
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];!}
Good result
What we learnedSKScene
SKSpriteNode
self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];!self.player.position = CGPointMake(100, 100);![self addChild:self.player];
Class relationship
SKNode
SKEffectNode
SKScene
SKSpriteNode
- (void)addChild:(SKNode *)node;
PositionSKScene
(0,0)
(100,100)
Put SpriteNode on scene - right-down??SKScene
(0,0)
(100,100)
Put SpriteNode on scene - left-up ??SKScene
(0,0)
(100,100)
Put SpriteNode on scene - center ??SKScene
(0,0)
(100,100)
Anchor point
(0,0) (1,0)
(0,1) (1,1)
(0.5,0.5) default
Test - position (0,0) with default Anchor Point
self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];!self.player.position = CGPointMake(0, 0);![self addChild:self.player];
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];
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];!}
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]]];! !}
What we learned
SKAction!
moveTo:duration:!
removeFromParent!
sequence:
Class viewNSObject
SKNode
SKSpriteNode
SKAction
- (void)runAction:(SKAction *)action;
UIResponder
- (void)update:(NSTimeInterval)currentTime
every 1/60 second will be called automatically
Stabilise the time interval@interface MyScene ()!@property (nonatomic) SKSpriteNode * player;!@property (nonatomic) NSTimeInterval lastSpawnTimeInterval;!@property (nonatomic) NSTimeInterval lastUpdateTimeInterval;!@end
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];! !}
Every second add a enemy
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {! ! self.lastSpawnTimeInterval += timeSinceLast;! if (self.lastSpawnTimeInterval > 1) {! self.lastSpawnTimeInterval = 0;! [self addMonster];! }!}!
Run the app
Throw projectile
touch
Throw projectile - vector(spriteX, spriteY)
offsetX
offsetY (touchX, touchY)
offsetX = touchX - spriteXoffsetY = touchY - spriteY
Define helper function - offsetCGPoint subPoint(CGPoint a, CGPoint b ){! CGPoint subPoint = CGPointMake(a.x - b.x, a.y - b.y);! return subPoint;!}
Unit value of vector
offsetX = touchX - spriteXoffsetY = touchY - spriteY
unitValue = sqrt(offsetX^2 + offsetY^2)
Normalised vector = (offsetX / unitValue , offsetY / unitValue)
offsetX
offsetYunitValue
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;!}
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];!}
Multiply vector - helper functionCGPoint multiplyVector(CGPoint vector, CGFloat amount){! CGPoint newVec = CGPointMake(vector.x*amount, vector.y*amount);! return newVec;!}
Calculate projectile destination
(offsetX, offsetY)
(offsetX/unitValue, offsetY/unitValue)
newVec = multiplyVector(offset, 1000)
(player.x+newVec.x, player.y+newVec.y)
Add point with offsetCGPoint addOffset(CGPoint a, CGPoint offset){! CGPoint newVec = CGPointMake(a.x+offset.x, a.y+offset.y);! return newVec;!}
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]]];!}
Run
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];
Run
Collision Detection
Use the power of Physic Engine
Set gravity!
!
Set contact delegate
self.physicsWorld.gravity = CGVectorMake(0,0);
self.physicsWorld.contactDelegate = self;
@interface MyScene ()<SKPhysicsContactDelegate>!@property (nonatomic) SKSpriteNode * player;!@property (nonatomic) NSTimeInterval lastSpawnTimeInterval;!@property (nonatomic) NSTimeInterval lastUpdateTimeInterval;!@end
SKPhysicsContactDelegate
physicsWorld@interface SKScene : SKEffectNode!!
@property (SK_NONATOMIC_IOSONLY, readonly) SKPhysicsWorld *physicsWorld;!!
@end
physicsBody
monster body!
!
projectile body
monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size];
projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2];
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
Two category ids
static const uint32_t projectileCategory = 0x1 << 0;!static const uint32_t monsterCategory = 0x1 << 1;
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!}
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;
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;! }!}
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];! }!}
Dismiss collided objects- (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster {! NSLog(@"Hit");! [projectile removeFromParent];! [monster removeFromParent];!}
Run
About Music
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
sound effect - touchesBegan:withEvent:-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{!! [self runAction:[SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO]];!!
}
Run
Change Scene
Create a new Scene
GameOverScene.h#import <SpriteKit/SpriteKit.h>!!
@interface GameOverScene : SKScene!-(id)initWithSize:(CGSize)size won:(BOOL)won;!@end
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
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;!}
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;!}
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]]];!}
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];! }!}
Question ?