Intro to Sprite Kit using Swift
Have you started looking at Apple's Objective-C replacement, Swift, yet? If not, now's the time. You'll use the same APIs with Swift, but the language syntax is similar to that of JavaScript or Ruby, which makes it a bit easier to write. While Apple provides some Swift tutorials, I'm mostly interested in using it with Sprite Kit, Apple's home-grown casual game framework. The documentation provided on Apple's developer gives an intro to using Sprite Kit with Objective-C, but I thought I'd translate that into Swift for your enlightenment and education.
Setup
To start off, open Xcode 6, and choose "Create a new Xcode project." Select "Single View Application" for your application template, and then name your project. I'll name mine "SwiftDemo." Once you choose a location for the project files to be saved, you'll be plopped into the "General" settings panel for the app. The next step is to add SpriteKit.framework
to the project. Scroll to the bottom of the panel and find the section titled "Linked Frameworks and Libraries," and click the plus icon. Type "SpriteKit" in the search input, select SpriteKit.framework
, then click "Add."
The next task is to set up the app's view controller to use Sprite Kit. Select `Main.storyboard` from the project pane to open it. It has a single view controller and view. Click the large white box, which represents the view. In the Utilities pane on the right side of the window, change the class of the view to SKView
. Next, select the ViewController.swift
file from the project pane to open it. Change the import UIKit
statement to import SpriteKit
right below the copyright comment. Next, add some code to the controller's viewDidLoad()
method to show some Sprite Kit diagnostic info:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var spriteView:SKView = self.view as SKView
spriteView.showsDrawCount = true
spriteView.showsNodeCount = true
spriteView.showsFPS = true
}
We're now ready to add the first SKScene
to the app. Scenes correspond to the various sections of functionality in your game, such as title, level select, options, and actual game play. Right-click on the project pane and select "New File." Select "Swift" for the file type, and name it HelloScene.swift
. The newly created file will be empty, except for an import Foundation
line. Change it to import SpriteKit
, then create an empty subclass of SKScene
:
class HelloScene:SKScene {
}
Now let's go back to the view controller and have it present the newly-created (and empty) scene.
override func viewWillAppear(animated: Bool) {
var helloScene:HelloScene = HelloScene(size: CGSizeMake(768, 1024))
var spriteView:SKView = self.view as SKView
spriteView.presentScene(helloScene)
}
Try to build and run the project. You should see a blank screen on the simulator with diagnostic information at the bottom.
Now we'll work on actually adding content to the scene we just created. Add the following into the HelloScene
class:
var contentCreated:Bool = false
override func didMoveToView(view: SKView!) {
if !contentCreated {
createSceneContents()
contentCreated = true
}
}
During the course of your game, scenes will be instantiated, and shuffled around, and deallocated when no longer needed. When the scene moves into view, you want to ensure that it contains your content, but only if it hasn't yet been created. If the scene stays in memory and is re-presented, your initialization code could run twice, which is why we keep track of it with a boolean. Let's implement the createSceneContents()
method.
func createSceneContents() {
backgroundColor = SKColor.blueColor()
scaleMode = SKSceneScaleMode.AspectFit
addChild(newHelloNode())
}
This function does a few things. It changes the background color (obvious), sets the scale mode, and then adds the return value of a newHelloNode()
function to the scene. The scaleMode
attribute can have two values, SKSceneScaleMode.AspectFit
or SKSceneScaleMode.AspectFill
. Both modes will ensure your game's aspect ratio stays intact, but "AspectFit" will shrink the view to fit the scene (common side effect: letterboxing), while "AspectFill" fills the scene (but some content might be cut off). It's up to you to decide what technique best fits your game.
Next let's implement the newHelloNode
function that adds actual content to the scene. It's pretty straightforward, just creating a label, giving it some properties, then returning it.
func newHelloNode() -> SKLabelNode {
var helloNode:SKLabelNode = SKLabelNode(fontNamed: "Chalkduster")
helloNode.text = "Hello, World!"
helloNode.fontSize = 42
helloNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
return helloNode
}
Now when you build and run the project, you should see a blue background with the text "Hello, World!" centered in the screen (notice the letterboxing due to our use of SKSceneScaleMode.AspectFit
).
Animation
Fluid animation is absolutely necessary in any game. While it might not directly affect game play, its' presence makes the game that much more engaging and "juicy." We'll add some animation to the label node in our scene. Add the following line to the newHelloNode()
function:
helloNode.name = "helloNode"
All nodes have a "name" property, which you can use to identify or find them after they are initialized. Next, override the touchesBegan(touches: NSSet!, withEvent event: UIEvent!)
method, and add the following:
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
var helloNode:SKNode = childNodeWithName("helloNode")
if helloNode != nil {
helloNode.name = nil
var moveUp:SKAction = SKAction.moveByX(0, y: 100, duration: 0.5)
var zoom:SKAction = SKAction.scaleTo(2, duration: 0.25)
var pause:SKAction = SKAction.waitForDuration(0.5)
var fadeAway = SKAction.fadeOutWithDuration(0.25)
var remove = SKAction.removeFromParent()
var moveSequence = SKAction.sequence([moveUp, zoom, pause, fadeAway, remove])
helloNode.runAction(moveSequence)
}
}
This method finds a node named "helloNode," then runs a bunch of actions on it. Build and run the project, then click anywhere on the screen to make the method execute. There's a good sampling of what's available in the SKAction
class here, but it's always good to check out the SKAction class reference just to see what's possible.
Scene Transitions
As I mentioned earlier, you'll probably have multiple scenes in your game, and you'll obviously need to switch between them. Fortunately, Sprite Kit allows you to do that quite easily, and add some fancy transition effects at the same time. Create a new scene called SpaceshipScene.swift
and give it the following implementation:
class SpaceshipScene:SKScene {
var contentCreated:Bool = false
override func didMoveToView(view: SKView!) {
if !contentCreated {
createSceneContents()
contentCreated = true
}
}
func createSceneContents() {
backgroundColor = SKColor.redColor()
scaleMode = SKSceneScaleMode.AspectFit
}
}
Now, go back to HelloScene.swift
, comment out the code inside of touchesBegan(touches: NSSet!, withEvent event: UIEvent!)
, and replace it with the following:
var spaceshipScene:SKScene = SpaceshipScene(size: self.size)
var transition:SKTransition = SKTransition.doorsOpenVerticalWithDuration(0.5)
view.presentScene(spaceshipScene, transition: transition)
Build and run the project. You should see text on a blue background again. Click anywhere, and HelloScene
will transition to SpaceshipScene
with a cool "doors" effect. Again, check out the SKTransition class reference for a list of all the effects you can use.
Adding more content
Next we're going to add an object to SpaceshipScene
that's comprised of multiple Sprite Kit "nodes." Add the following code to the createSceneContents
method in SpaceshipScene
:
var spaceship:SKSpriteNode = newSpaceship()
spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 150)
addChild(spaceship)
Next, implement the newSpaceship()
method:
func newSpaceship() -> SKSpriteNode {
var hull = SKSpriteNode(color: SKColor.grayColor(), size: CGSizeMake(64, 32))
var hover:SKAction = SKAction.sequence([
SKAction.waitForDuration(1),
SKAction.moveByX(100, y: 50, duration: 1),
SKAction.waitForDuration(1),
SKAction.moveByX(-100, y: -50, duration: 1)
])
hull.runAction(SKAction.repeatActionForever(hover))
return hull
}
Build and run the project to see a gray box moving back and forth indefinitely. Next let's add "lights" to the spaceship. Insert the following right after declaring the hull
variable in the newSpaceship
method:
var light1:SKSpriteNode = newLight()
light1.position = CGPointMake(-28, 6)
hull.addChild(light1)
var light2:SKSpriteNode = newLight()
light2.position = CGPointMake(28, 6)
hull.addChild(light2)
Then implement the newLight()
method:
func newLight() -> SKSpriteNode {
var light:SKSpriteNode = SKSpriteNode(color: SKColor.yellowColor(), size: CGSizeMake(8, 8))
var blink:SKAction = SKAction.sequence([
SKAction.fadeOutWithDuration(0.25),
SKAction.fadeInWithDuration(0.25)
])
light.runAction(SKAction.repeatActionForever(blink))
return light
}
Build and run the project. You should now see two blinking lights on the "spaceship."
Physics
To wrap up, we'll add some physics interactions to SpaceshipScene
. I'll leave the reasons why you might want a realistic physics simulation in your game as an exercise to the reader (*cough*Angry Birds*cough*). First of all, change the newSpaceship()
method slightly to add a physics body to the ship hull:
hull.physicsBody = SKPhysicsBody(rectangleOfSize: hull.size)
hull.physicsBody.dynamic = false
We set dynamic = false
so the ship isn't affected by the physics system's gravity. Otherwise it would fall off the screen, since there's no floor in the game to stop it. Next add some code in createSceneContents()
to generate rocks that will bounce off the ship.
var makeRocks:SKAction = SKAction.sequence([
SKAction.runBlock(addRock),
SKAction.waitForDuration(0.1, withRange: 0.15)
])
runAction(SKAction.repeatActionForever(makeRocks))
And implement addRock()
:
func addRock() {
var rock:SKSpriteNode = SKSpriteNode(color: SKColor.brownColor(), size: CGSizeMake(8, 8))
rock.position = CGPointMake(self.size.width / 2, self.size.height - 50)
rock.name = "rock"
rock.physicsBody = SKPhysicsBody(rectangleOfSize: rock.size)
rock.physicsBody.usesPreciseCollisionDetection = true
addChild(rock)
}
Build and run the project and you'll see a bunch of rocks being generated and bouncing off the spaceship. While Apple's tutorial has you manually removing rocks that fall off the screen, apparently some changes to the Sprite Kit API now do that for you automatically (you'll notice the "node" count stays constant while the app is running).
And that's it! A very basic overview to both Sprite Kit and Swift. Try playing around with the project and see what else you can come up with. Happy hacking!
Comments
Michael Liendo wrote on :
this is EXACTLY what I was looking for! Until now, the closest thing I could find was using spriteKit with Swift with a game template, when I was simply wanting to add some particle Emitters to my single view app! Thank you!