Getting Started with SpriteKit (Tutorial)
There's really not that many tutorials out there to do with SpriteKit; I mean, it has just been released, but still. So, this tutorial is exactly the same as what I will be teaching today (Halloween) to students at NYU:Poly. The first thing you want to do is download the empty Xcode project I prepared earlier:
https://www.dropbox.com/s/fksj010ontj1r3m/Zombie.zip
Go ahead and open it up in Xcode. You'll see that it contains NO STORYBOARD! So how the hell are we going to visually build the app?! Well, it's a game, so we're going to add the characters, sound effects and animations programatically! Run the project in an iPad simulator and you'll see an empty screen with a joystick in the bottom left and a big red arcade style button in the bottom right. Excellent! Go back to Xcode and you'll also notice that it contains a class called "MyScene". Games contain views and scenes. Imagine that a scene is much like a level in Super Mario: after you complete one, then you're off to the next level. It's in these scenes where we will be adding all of our code for this game, as it only contains one level.
Click on MyScene.m and under the comment Add Background Space Image, we want to add the following:
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:@"background"]; background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame)); background.zPosition = -1; [self addChild:background];
Now run the project and you'll have a lovely purple background instead of our dull, grey one! What we've done here is create a new sprite. Sprites are the building blocks of a game in iOS. They're used for: backgrounds, player characters, enemies, weapons etc. We also changed the zPosition of the background to -1, as we always want it to remain behind all of the other sprites that we add. Let's go ahead and add our jetpack wielding, laser shooting, mother 'flippin Dinosaur! Find the method called setupOurPlayerCharacter and add the following:
self.player = [SKSpriteNode spriteNodeWithImageNamed:@"dino.png"]; self.player.position = CGPointMake(100, 100); [self addChild:self.player];
Run the project and you'll see that nothing happened, our Dinosaur is no where to be seen! It's like an asteroid came down and hit him on his virgin flight into space! Why is that? well, we forgot to CALL the [self setupOurPlayerCharacter]; method in the viewDidLoad method. Go ahead and add the following within the viewDidLoad method. Now what happens when you run the project? RAWWWWR!
Ok, our Dino is looking pretty lonely up there in space, he needs some Zombies to fight. Find the method addOneZombieAndMoveToOtherSide and add the following:
- (void)addOneZombieAndMoveHimToOtherSide { SKSpriteNode *zombie = [SKSpriteNode spriteNodeWithImageNamed:@"zombie.png"]; //Random y position int y = arc4random() % 700; zombie.position = CGPointMake(1000,y); [self addChild:zombie]; zombie.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:zombie.size]; zombie.physicsBody.dynamic = YES; zombie.physicsBody.categoryBitMask = monsterCategory; zombie.physicsBody.contactTestBitMask = projectileCategory; zombie.physicsBody.collisionBitMask = 0; // Create the actions to move the zombie SKAction * actionMove = [SKAction moveTo:CGPointMake(zombie.frame.origin.x -900, y) duration:20]; SKAction * actionMoveDone = [SKAction removeFromParent]; [zombie runAction:[SKAction sequence:@[actionMove, actionMoveDone]]]; }
We're doing a few things here, first we're creating another sprite using the zombie image in our project, then we're spawning him off the screen at a random vertical position. We're then giving him some physics settings so he can react to our laser and finally we're running an SKAction. SkActions are probably going to be the most used method when making a game in iOS, they're used for moving, resizing, changing color, changing texture, rotating; pretty much everything you can imagine you'd want to do with a sprite. In our case, we're moving our new Zombie to the opposite end of the scene, towards our Dino. Don't forget to call the method, run and see what we have:
Now for the super fun part (as if the stuff before wasn't fun!). We're going to create the flame for our Dino's jetpack. Funnily enough, this is actually the easiest part of the whole game as Xcode comes with this super awesome particle creator. Right click on the Zombie Shooter folder and click "New File".
When the window pops open, click on Resource on the left hand side, then Sprite Kit Particle file.
I used the particle template "Fire". Click next and name your new particle file "jetpackFlame". Left click on our new .sks file in the project window over to the left and make sure you're on the correct attribute inspector window over in the right hand side bar.
Now for the fun part, just mess around with the settings until you achieve that perfect Jetpack flame look. Make sure the Angle is set to 250 degrees though, we don't want the flame coming out of another part of our Dino! Go back to our MyScene.m file, and find the method addJetpackFlameToOurDinosaur, put the following inside:
NSString *firePath = [[NSBundle mainBundle] pathForResource:@"jetpackFlame" ofType:@"sks"]; SKEmitterNode *fire = [NSKeyedUnarchiver unarchiveObjectWithFile:firePath]; [self.player addChild:fire];
Here, we're creating a string to point to our newly created particle emitter file. Then we're creating a new emitter and adding it to our player. Call the method in our viewDidLoad, run the project and....
How cool is that?! So, we have our Dino, Zombie, and jetpack. Now let's start firing some lasers! Find the method called "- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {" and under where the comment says "Firing the laser!", add the following:
SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"laser.png"]; projectile.position = CGPointMake(self.player.frame.origin.x + 370, self.player.frame.origin.y + 140); [self addChild:projectile]; projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2]; projectile.physicsBody.dynamic = YES; projectile.physicsBody.categoryBitMask = projectileCategory; projectile.physicsBody.contactTestBitMask = monsterCategory; projectile.physicsBody.collisionBitMask = 0; projectile.physicsBody.usesPreciseCollisionDetection = YES; SKAction *movement =[SKAction moveTo:CGPointMake(self.player.frame.origin.x+1000, self.player.frame.origin.y) duration:2]; SKAction *remove = [SKAction removeFromParent]; [projectile runAction:[SKAction sequence:@[movement,remove]]]; [self runAction:[SKAction playSoundFileNamed:@"laser.wav" waitForCompletion:NO]];
We're doing something very, very similar to our Zombie chap. We're creating a new sprite, making it start from our player characters' mouth, adding physics to it and then creating a new SKAction to make it travel across the screen. Oh, and we're playing a sound file as well! to make it exactly like a normal Dino laser would sound.... Run our project, and...
But the laser goes straight through our Zombie without it doing anything! We want to wipe out the Space Zombie horde before it reaches Earth! What we need is collision detection, my dear. Find the method: - (void)projectile:(SKSpriteNode *)projectile didCollideWithZombie:(SKSpriteNode *)zombie, and put the following in it:
self.score = self.score +1; [self runAction:[SKAction playSoundFileNamed:@"hit.wav" waitForCompletion:NO]]; [projectile removeFromParent]; [zombie removeFromParent]; int numberOfTimesToSpawnZombie = arc4random() % 5; for (int i = 0; i isLessThan numberOfTimesToSpawnZombie; i++) { [self addOneZombieAndMoveHimToOtherSide]; }
*You see the line isLessThan in the code above? change that to a left pointy bracket (my code formatter wouldn't like the symbol as it thought I was closing off my code tags. What we're doing here is incrementing the score by one, then playing a sound effect, removing both the Zombie and the laser and then spawning up to 5 more Zombies. Run the project and fire a laser right at one of those Zombies. BANG! Ok, there's two things left. We need to record our score and then top it all off with some awesome background music! Go right to the bottom of the MyScene.m class, and you'll see a method called: -(void)update:(CFTimeInterval)currentTime. Add the following in this method:
self.scoreLabel.text = [NSString stringWithFormat:@"Score %i", self.score];
This method runs once per frame. So if our project is running at 60FPS (frames per second) then it'll run 60 times per second. It's great for keeping a check on things like score, movement etc.. Last but not least, let's add some kick ass background music to our game! In the method: playSuperAwesomeThemeSong, put the following code:
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"backgroundMusic" withExtension:@"wav"]; self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil]; self.backgroundMusicPlayer.numberOfLoops = -1; [self.backgroundMusicPlayer prepareToPlay]; [self.backgroundMusicPlayer play];
Don't forget to call this method in the viewDidLoad method. Run, and....
Any questions, don't hesitate to ask below!