In this series, we're learning how to use SpriteKit to build 2D games for iOS. In this post, we'll continue our exploration of SpriteKit nodes, and learn about a special kind of node called a "sprite"—an SKSpriteNode
.
To follow along with this tutorial, just download the accompanying GitHub repo. It has a folder called ExampleProject Starter. Open the project in that folder in Xcode, and you're ready to go!
Sprite Nodes
An SKSpriteNode
is drawn either as a rectangle with a texture mapped onto it, or as a colored untextured rectangle. Creating a SKSpriteNode
with a texture mapped onto it is the most common, as this is how you bring your game's artwork to life.
Add the following to the didMove(to:)
method within GameScene.swift.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") startGameLabel.name = "startgame" startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(startGameLabel) let redSprite = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) addChild(redSprite) }
Here we are using the convenience intiailizer init(color:size:)
which will draw a rectangular sprite with the color and size you pass in as parameters. If you test the project now, you will see half of the red square showing.
You might be wondering why only half of the sprite is showing since we already determined that SKNode
's origins are at (0,0). This because the SKSpriteNode
's frame and therefore its texture is centered on its position. To change this behaviour, you can change the sprite's anchorPoint
property, which determines the point at which the frame is positioned. The diagram below shows how the anchorPoint
works.
The anchorPoint
is specified in the unit coordinate system, which places (0,0) at the bottom left and (1,1) at the top right corner of the frame. The default for SKSpriteNode
s is (0.5,0.5).
Go ahead and change the anchorPoint
property to (0,0) and notice the difference it makes.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") startGameLabel.name = "startgame" startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(startGameLabel) let redSprite = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) redSprite.anchorPoint = CGPoint(x: 0, y:0) addChild(redSprite) }
Now, if you test, you will see that the sprite is lined up perfectly with the bottom left of the scene.
Now let's move it to the top center of the scene by changing its position property. Replace your didMove(to:)
function with the following:
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") startGameLabel.name = "startgame" startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(startGameLabel) let redSprite = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) redSprite.anchorPoint = CGPoint(x: 0, y:0) redSprite.position = CGPoint(x: size.width/2 - 100, y: size.height - 210) addChild(redSprite) }
Notice how we had to subtract from both the x
and y
values to center the sprite. If we had left the anchorPoint
at its default then it would already have been centered on the x
axis. It is important to remember that when you change the anchor point, you may have to make some adjustments in your positioning.
Textured Sprites
That red box is good for practice with positioning, but you'll usually want to texture your sprite with artwork for your game.
Let's create a textured SKSpriteNode
. Add the following code at the bottom of the didMove(to:)
method.
override func didMove(to view: SKView) { ... let player = SKSpriteNode(imageNamed: "player") player.position = CGPoint(x: size.width/2 , y: 300) addChild(player) }
Here we use the convenience initializer init(imageNamed:)
, which takes as a parameter the name of an image without the extension. This is the easiest way to create a textured sprite as it creates the texture for you from the image you pass in.
The other way to create a textured SKSpriteNode
is to create an SKTexture
beforehand, and use one of the intializers that take a texture as a parameter.
Let's create a couple more SKSpriteNode
s and change some of their properties. Again, add these to the bottom of your didMove(to:)
function.
override func didMove(to view: SKView) { ... let enemy1 = SKSpriteNode(imageNamed: "enemy1") enemy1.position = CGPoint(x: 100 , y: 300) enemy1.xScale = 2 addChild(enemy1) let enemy2 = SKSpriteNode(imageNamed: "enemy2") enemy2.position = CGPoint(x: size.width - 100 , y: 300) enemy2.zRotation = 3.14 * 90 / 180 addChild(enemy2) }
Here we create two SKSpriteNode
s, enemy1
and enemy2
. We set the xScale
on enemy1
to 2 and change the zRotation
on enemy2
to rotate it by 90 degrees. (The zRotation
property takes it values in radians, and a positive value indicates a counterclockwise rotation.)
We've experimented with changing a few properties on a sprite. Take a look at the documentation for SKNodes and SKSpriteNodes and try changing a few of the other properties to see the effects they have.
Sprite nodes are good for basic rectangles and textures, but sometimes a more complex shape will be needed. The SKShapeNode
has you covered in those cases. We'll take a look at shape nodes next.
Shape Nodes
Another useful node is the SKShapeNode
. This node renders a shape defined by a Core Graphics path. SKShapeNode
s are useful for content that cannot be easily realized with an SKSpriteNode
. This class is more memory intensive and has lower performance than using an SKSpriteNode
, so you should try to use it sparingly.
To assign a shape to SKShapeNode
, you can set a CGPath
to the node's path
property. However, there are a few initializers that offer predefined shapes such as rectangles, circles, and ellipses. Let's create a circle using the convenience initializer init(circleOfRadius:)
.
Then, add the following to the bottom of the didMove(to:)
method.
override func didMove(to view: SKView) { ... let circle = SKShapeNode(circleOfRadius: 50) circle.strokeColor = SKColor.green circle.fillColor = SKColor.blue circle.lineWidth = 8 circle.position = CGPoint(x: size.width/2, y: 400) addChild(circle) }
We change a few properties on the shape node, position it, and add it to the scene. It is very easy to use the predefined shape initializers. However, creating a complex CGPath
manually takes a considerable amount of time and is not for the faint of heart as it usually involves some complex math.
Thankfully, there is a tool that lets you draw shapes visually and export their CGPath
as Swift code. Check out PaintCode if you want to learn more.
Sprite Nodes and Shape Nodes will cover most cases, but sometimes you may wish to display video in your apps. The SKVideoNode
, which we'll take a look at next, has you covered.
Video Nodes
The last node we will be taking a look at is the SKVideoNode
. As the name implies, this node allows you to play video within your games.
There are a few different ways to create an SKVideoNode
. One uses an instance of an AVPlayer
, another just uses the name of a video file that is stored in the app bundle, and the third way is to use a URL
.
One thing to keep in mind is that the video's size
property will initially be the same as the size of the target video. You can change this size
property, though, and the video will be stretched to the new size.
Another thing to be aware of is that the SKVideoNode
offers play()
and pause()
methods only. If you wanted more control over your videos, you would initialize an SKVideoNode
with an existing AVPlayer
and use that to control your videos.
Let's use the simplest method to create an SKVideoNode
. Add the following to the bottom of the didMove(to:)
method.
override func didMove(to view: SKView) { ... let video = SKVideoNode(fileNamed: "video.mov") video.position = CGPoint(x: size.width/2,y: 600) addChild(video) video.play() }
Here we used the intiailizer init(fileNamed:)
to create a video. You pass in the video's name along with the extension. I have not included a video along with the project's source code, but if you want to see this work, you can add a video named "video.mov" to your project.
Conclusion
This completes our study on nodes. After reading this post and the previous one, you should have a good understanding of SKNode
s and their subclasses. In the next part of this series, we will take a look at SKActions
and using physics within our games. Thanks for reading, and I will see you there!
In the meantime, check out some of our other great courses and tutorials on creating iOS apps with Swift and SpriteKit.
- SpriteKitCreate Space Invaders with Swift and Sprite Kit: Introducing Sprite Kit
- iOS SDKCreate a Blackjack Game in Swift 3 and SpriteKit
- iOSSpriteKit From Scratch: Fundamentals
Also, check out our SpriteKit courses! These will take you through all the steps of building your first SpriteKit game for iOS, even if you've never coded with SpriteKit before.
- SwiftCode a Side-Scrolling Game With Swift 3 and SpriteKit
- Game DevelopmentGame Development With Swift and SpriteKit