Class

SKSpriteNode

A node that draws a rectangular texture, image or color.

Overview

An SKSpriteNode is a node that draws a texture (optionally blended with a color), an image, a colored square. You can also provide a custom shader to create your own rendering effects.

Sprite nodes can be lit with SKLightNode objects and optionally cast and receive shadows. 3D lighting can be simulated by supplying a separate normal texture or automatically generating a normal texture from an image.

Sprites are the basic building blocks used to create the majority of your scene’s content, so understanding sprites is useful before moving on to other node classes in SpriteKit. Sprites are represented by SKSpriteNode objects. An SKSpriteNode object can be drawn either as a rectangle with a texture mapped onto it or as a colored, untextured rectangle. Textured sprites are more common, because they represent the primary way that you bring custom artwork into a scene. This custom artwork might represent characters in your game, background elements, or even user interface elements, but the basic strategy is the same. An artist creates the images, and your game or app SKSpriteNode loads them as textures. Then you create sprites with those textures and add them to the scene.

Creating a Textured Sprite Node

The simplest way to create a textured sprite node is to have SpriteKit create both the texture and the sprite for you. You store the artwork in the app bundle (ideally in an asset catalog), and then load it at runtime. Listing 1 shows how to do this.

let spaceship = SKSpriteNode(imageNamed: "rocket.png")
spaceship.position = CGPoint(x: 100, y: 100)
self.addChild(spaceship)

When you create a sprite in this fashion, you get a lot of default behavior for free:

  • The sprite node is created with a frame that matches the texture’s size.

  • The node is rendered so that it is centered on its position. The node's frame property holds the rectangle that defines the area it covers.

  • The texture is alpha blended into the frame buffer.

  • An SKTexture object is created and attached to the node. This texture object automatically loads the texture data whenever the sprite node is in the scene, is visible, and is necessary for rendering the scene. Later, if the sprite is removed from the scene or is no longer visible, SpriteKit can delete the texture data if it needs that memory for other purposes. This automatic memory management simplifies but does not eliminate the work you need to do to manage art assets in your game.

The default behavior gives you a useful foundation for creating a sprite-based game.

Although SpriteKit can create textures for you automatically when a sprite is created, in more complex games and apps you need more control over textures. For example, you might want to do any of the following:

  • Share a texture between multiple sprites.

  • Change a sprite’s texture after it is created.

  • Animate a sprite through a series of textures.

  • Create textures from data that is not directly stored in the app bundle.

  • Render a node tree into a texture. For example, you might want to take a screenshot of your gameplay to show to the player after he or she completes the level.

  • Preload textures into memory before presenting a scene.

You do all of these things by working directly with texture objects, for more information, see the SKTexture class reference.

Customizing a Textured Sprite

You can use each sprite node’s properties to independently configure four distinct rendering stages:

Often, configuring a sprite node to perform these four steps—positioning, sizing, colorizing, and blending—is based on the artwork used to create its texture. This means that you rarely set property values in isolation from the artwork. You work with your artist to ensure that your game is configuring the sprites to match the artwork.

Here are some of the possible strategies you can follow:

  • Create the sprite nodes with hardcoded values in your project. This is the fastest approach, but the least desirable in the long term, because it means that the code must be changed whenever the art assets change.

  • Create your own tools using SpriteKit that lets you fine tune the sprite’s property values. When you have a sprite node configured the way you want it, save the sprite to an archive. Your game uses the archive to create sprites at runtime.

  • Store the configuration data in a property list that is stored in your app bundle. When the sprite node is loaded, load the property list and use its values to configure the node. Your artist can then provide the correct values and change them without requiring changes to your code.

Changing a Sprite’s Texture

A sprite’s texture property points to its current texture. You can change this property to point to a new texture. The next time the scene renders a new frame, it renders with the new texture. Whenever you change the texture, you may also need to change other sprite properties—such as size, anchorPoint, and centerRect—to be consistent with the new texture. Usually, it is better to ensure that all the artwork is consistent so that the same values can be used for all of the textures. That is, the textures should have a consistent size and anchor point placement so that your game does not need to update anything other than the texture.

Because animation is a common task, you can use actions to animate a series of textures on a sprite. The following code shows how to use an array of frames created to animate a sprite’s texture.

let walkAnimation = SKAction.animate(with: monsterWalkTextures,
                                     timePerFrame: 0.1)

node.run(walkAnimation)
// insert other code here to move the monster.

SpriteKit provides the plumbing that allows you to animate or change a sprite’s texture. It doesn’t impose a specific design on your animation system. This means you need to determine what kinds of animations that a sprite may need and then design your own animation system to switch between those animations at runtime. For example, a monster might have walk, fight, idle, and death animation sequences—and it’s up to you to decide when to switch between these sequences.

Using the Anchor Point to Move the Sprite Node’s Frame

By default, the sprite node’s frame—and thus its texture—is centered on its position. However, you might want a different part of the texture to appear at the node’s position. You usually do this when the game element depicted in the texture is not centered in the texture image.

A sprite node’s anchorPoint property determines which point in the frame is positioned at its position. Anchor points are specified in the unit coordinate system, shown in the following illustration. The unit coordinate system places the origin at the bottom left corner of the frame and (1,1) at the top right corner of the frame. A sprite’s anchor point defaults to (0.5,0.5), which corresponds to the center of the frame.

The unit coordinate system

Although you are moving the frame, you do this because you want the corresponding portion of the texture to be centered on the position. The following figure shows a pair of texture images. In the first, the default anchor point centers the texture on the position. In the second, a point at the top of the image is selected instead. You can see that when the node is rotated, the texture image rotates around this point.

Changing a sprite node’s anchor point

The following code shows how to place the anchor point on the rocket’s nose cone. Usually, you set the anchor point when the sprite node is initialized, because it corresponds to the artwork. However, you can set this property at any time. The frame is immediately updated, and the node onscreen is updated the next time the scene is rendered.

rocket.anchorPoint = CGPoint(x: 0.5, y: 1.0)

Resizing a Sprite Node

The size of the sprite node’s frame property is determined by the values of three other properties:

  • The sprite node’s size property holds its base (unscaled) size. When a sprite is initialized using init(imageNamed:), the value of this property is initialized to be equal to the size of the supplied image.

  • The base size is then scaled by the sprite’s xScale and yScale properties inherited from the SKNode class.

For example, if the sprite node’s base size is 32 x 32 pixels and it has an xScale value of 1.0 and a yScale value of 2.0, the size of its frame is 32 x 64 pixels.

When a sprite node’s frame is larger than its texture, the texture is stretched to cover its frame. Normally, the texture is stretched uniformly across the frame, as shown in the following figure.

A texture is stretched to cover the sprite’s frame

However, sometimes you want to use sprite nodes to build user interface elements, such as buttons or health indicators. Often, these elements contain fixed-size elements, such as end caps, that should not be stretched. In this case, use a portion of the texture without stretching, and then stretch the remaining part of the texture over the rest of the frame.

The sprite’s centerRect property, which is specified in unit coordinates of the texture, controls the scaling behavior. The default value is a rectangle that covers the entire texture, which is why the entire texture is stretched across the frame. If you specify a rectangle that only covers a portion of the texture, you create a 3 x 3 grid. Each box in the grid has its own scaling behavior:

  • The portions of the texture in the four corners of the grid are drawn without any scaling.

  • The center of the grid is scaled in both dimensions.

  • The upper- and lower-middle parts are only scaled horizontally.

  • The left- and right-middle parts are only scaled vertically.

The following figure shows a close-up view of a texture you might use to draw a user interface button. The complete texture is 28 x 28 pixels. The corner pieces are each 12 x 12 pixels and the center is 4 X 4 pixels.

A stretchable button texture

The following code shows how this button sprite would be initialized. The centerRect property is computed based on the design of the texture.

let button = SKSpriteNode(imageNamed: "stretchable_button.png")
button.centerRect = CGRect(x: 12.0/28.0,
                           y: 12.0/28.0,
                           width: 4.0/28.0,
                           height: 4.0/28.0)

The following figure shows that the corners remain the same, even when the button is drawn at different sizes.

Applying the button texture to buttons of different sizes

Colorizing a Sprite Node

You can use the color and colorBlendFactor properties to colorize the texture applied to a sprite node. The color blend factor defaults to 0.0, which indicates that the texture should be used unmodified. As you increase this number, more of the texture color is replaced with the blended color. For example, when a monster in your game takes damage, you might want to add a red tint to the character. The following code shows how you would apply a tint to the sprite.

monsterSprite.color = .red
monsterSprite.colorBlendFactor = 0.5
Colorizing adjusts the color of the texture

You can also animate the color and color blend factors using actions. The following code shows how to briefly tint the sprite and then return it to normal.

let pulsedRed = SKAction.sequence([
    SKAction.colorize(with: .red, colorBlendFactor: 1.0, duration: 0.15),
    SKAction.wait(forDuration: 0.1),
    SKAction.colorize(withColorBlendFactor: 0.0, duration: 0.15)])
spaceship.run(pulsedRed)

Working with Lighting

You can use a sprite’s lighting properties, lightingBitMask, shadowCastBitMask and shadowedBitMask, to apply effects such as illumination and shadow casting and receiving. These can be used in conjunction with normal mapping to simulate 3D lighting.

The following figure shows a normal mapped sprite node acting as background and two shadow casting sprite nodes (each with a rabbit texture).

The SKLightNode object's categoryBitMask matches the lighting bit mask of the background, and the lighting and shadow bit masks of the two rabbits:

// Create the background sprite node
let background = SKSpriteNode(texture: noiseTexture,
                              normalMap: noiseTexture.generatingNormalMap())
background.position = spriteKitViewController.center
background.lightingBitMask = 0b0001
scene.addChild(background)
     
let x: CGFloat = 150
let y = spriteKitViewController.scene.size.width - 150
     
// Create a light
let lightNode = SKLightNode()
lightNode.position = CGPoint(x: scene.size.width / 2, y: y)
lightNode.categoryBitMask = 0b0001
lightNode.lightColor = .white
scene.addChild(lightNode)
     
// Create two rabbit sprite nodes and assign them with both a lighting and a shadow cast bit mask.
for position in [CGPoint(x: x, y: y), CGPoint(x: y, y: y)] {
                    let rabbit = SKSpriteNode(imageNamed: "rabbit")
                    rabbit.position = position
                    spriteKitViewController.scene.addChild(rabbit)
                    rabbit.lightingBitMask = 0b0001
                    rabbit.shadowCastBitMask = 0b0001
}

The resulting scene shows the two rabbits casting shadows over the background (the light is rendered as a white circle). The noise texture gains a 3D look from the normal mapping:

Shadow casting and normal mapping

Applying Shaders to Sprite Nodes

You can use the shader property of a sprite node to change the appearance of a texture with a custom OpenGL ES fragment shader embedded within a SKShader object. Custom shaders offer almost limitless possibilities, from adding blurs and color treatments to textures to generating imagery such as random noise.

The following code shows a small custom shader which inverts the color of a texture while leaving the alpha or transparency unaffected:

let negativeShader = SKShader(source: "void main() { " +
    "    gl_FragColor = vec4(1.0 - SKDefaultShading().rgb, SKDefaultShading().a); " +
    "}")
rocket.shader = negativeShader

The following figure illustrates the effect of the shader. The original image, on the left, has its colors inverted by the shader:

Example of a color inverted sprite

Blending the Sprite into the Frame Buffer

The final stage of rendering is to blend the sprite’s texture into its destination frame buffer. The default behavior uses the alpha values of the texture to blend the texture with the destination pixels. However, you can use other blend modes when you want to add other special effects to a scene.

You control the sprite’s blending behavior using the blendMode property. For example, an additive blend mode is useful to combine multiple sprites together, for effects such as for fire or lighting. The following code shows how to position three overlapping sprite nodes in a circle to demonstrate the effect of different blend modes:

let blendMode = SKBlendMode.alpha
let imageNames = ["redCircle", "greenCircle", "blueCircle"]

for (index, imageName) in imageNames.enumerated() {
    let node = SKSpriteNode(imageNamed: imageName)
    
    node.alpha = 0.5
    node.blendMode = blendMode
    
    let angle = (CGFloat.pi * 2) / CGFloat(colors.count) * CGFloat(index)
    
    let positionX = 320 + sin(angle) * radius / 2
    let positionY = 320 + cos(angle) * radius / 2
    
    node.position = CGPoint(x: positionX, y: positionY)
    
    scene.addChild(node)
}

With the default blend mode of alpha, the thee circles look like:

Overlapping sprite nodes using alpha blending

However, with a blend mode of add, the color values are added together, creating a scene that looks like:

Overlapping sprite nodes using additive blending

Symbols

Initializing a New Sprite

init(color: UIColor, size: CGSize)

Initializes a colored sprite node.

init(imageNamed: String)

Initializes a textured sprite using an image file.

init(texture: SKTexture?)

Initializes a textured sprite using an existing texture object.

init(texture: SKTexture?, color: UIColor, size: CGSize)

Initializes a colored and textured sprite using an existing texture object.

init(texture: SKTexture?, size: CGSize)

Initializes a textured sprite using an existing texture object but with a specified size.

init(imageNamed: String, normalMapped: Bool)

Initializes a textured sprite using an image file, optionally adding a normal map to simulate 3D lighting.

init(texture: SKTexture?, normalMap: SKTexture?)

Initializes a textured sprite with a normal map to simulate 3D lighting.

Inspecting Physical Properties

var size: CGSize

The dimensions of the sprite, in points.

func scale(to: CGSize)

Scales to sprite node to a specified size.

var anchorPoint: CGPoint

Defines the point in the sprite that corresponds to the node’s position.

Inspecting the Sprite’s Texture

var texture: SKTexture?

The texture used to draw the sprite.

var centerRect: CGRect

A property that defines how the texture is applied to the sprite.

var colorBlendFactor: CGFloat

A floating-point value that describes how the color is blended with the sprite’s texture.

Inspecting Color Properties

var color: UIColor

The sprite’s color.

Blending the Sprite with the Framebuffer

var blendMode: SKBlendMode

The blend mode used to draw the sprite into the parent’s framebuffer.

Adding Lighting to a Sprite

var lightingBitMask: UInt32

A mask that defines how this sprite is lit by light nodes in the scenes.

var shadowedBitMask: UInt32

A mask that defines which lights add additional shadows to the sprite.

var shadowCastBitMask: UInt32

A mask that defines which lights are occluded by this sprite.

var normalTexture: SKTexture?

A texture that specifies the normal map for the sprite.

Working with Custom Shaders

var shader: SKShader?

A property that determines whether the sprite is rendered using a custom shader.

var attributeValues: [String : SKAttributeValue]

The values of each attribute associated with the node's attached shader.

func setValue(SKAttributeValue, forAttribute: String)

Sets an attribute value for an attached shader.

func value(forAttributeNamed: String)

The value of a shader attribute.

Instance Properties

var customPlaygroundQuickLook: PlaygroundQuickLook

A custom playground quick look for this instance.