NEW! Master Swift design patterns with my latest book! >>

How to warp a sprite using SKWarpGeometryGrid

Paul Hudson       @twostraws

SpriteKit allows you to warp sprites by dividing them up into small squares then stretching those squares into different positions. The result is that you can warp a sprite in various ways – you can effectively pull different parts of it however you want.

There’s a cost to doing this: a sprite is effectively one quad by default, which is a fancy way of saying it’s a rectangle. Behind the scenes that becomes two triangles, pieced together to look like a single rectangle. If we split that single sprite into two rows and two columns, you end up with four times as many triangles; if you split it into 10 rows and 10 columns, you end up with a huge number of triangles just to represent one tiny sprite – your game will slow down massively.

Helpfully, SpriteKit employs a technique called automatic quad subdivision: even though you create two rows and two columns, it will automatically split them further if it needs to, allowing to warp transformation to look silky smooth.

Warping a sprite is done in two steps: first you specify source points that describe where various points of your sprite are in their natural, unwarped state, and second you specify destination points that move those source points somewhere else. From that SpriteKit calculates the warp, bending the texture neatly to fit.

We’re going to use a three by three grid here, so we need nine sets of points inside an array. These must all be specified using the vector_float2 type, and be done in a precise order.

Try adding these source points as a property for your class:

let src = [
    // bottom row: left, center, right
    vector_float2(0.0, 0.0),
    vector_float2(0.5, 0.0),
    vector_float2(1.0, 0.0),

    // middle row: left, center, right
    vector_float2(0.0, 0.5),
    vector_float2(0.5, 0.5),
    vector_float2(1.0, 0.5),

    // top row: left, center, right
    vector_float2(0.0, 1.0),
    vector_float2(0.5, 1.0),
    vector_float2(1.0, 1.0)
]

I added comments in there so you can hopefully see how all nine points are positioned. For example, the middle point is X: 0.5 Y: 0.5, meaning that it’s in the center of both axes. Having your source points as a property is a good idea if you plan to use them a lot – it’s easiest to take a copy of those points then make small changes than to keep setting them up from scratch.

We’re also going to create a warp geometry that uses the src positions twice: the same for before and after. This means it will look identical to a sprite without a warp geometry attached, but it also means that when we apply a new geometry with the same number of rows and columns SpriteKit will be able to animate the change.

Here’s the code for that:

let warp = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: src, destinationPositions: src)
yourSprite.warpGeometry = warp

So far we’ve defined the source points on our warp geometry and applied it to a sprite, but we haven’t actually done the important part: creating a warp animation. To make that happen we’re going to take a copy of the source points, change any points we want, then wrap that in a new SKWarpGeometryGrid and use it with an animation.

You can specify multiple warp animations to have iOS cycle through them, and as a result the animation timing is actually an array of times relative to the start of the animation. To demonstrate this process, we’re going to make a small change to our source points, take a copy of the original warp geometry we set earlier, then animate to the new points and back to the old points over one second.

Add this code to your scene:

// take a copy of our source points
var dst = src

// pull the two bottom edges of the sprite downwards    
dst[0] = vector_float2(0, -0.5)
dst[2] = vector_float2(1, -0.5)

// create a new warp geometry by mapping from src to dst
let newWarp = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: src, destinationPositions: dst)

// pull out the existing warp geometry so we have something to animate back to
let oldWarp = spaceship.warpGeometry!

// try to create an SKAction with these two warps; each will animate over 0.5 seconds
if let action = SKAction.animate(withWarps: [newWarp, oldWarp], times: [0.5, 1]) {
    // run it on the sprite
    yourSprite.run(action)
}

Available from iOS 7.1

Did this solution work for you? Please pass it on!

Other people are reading…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

Upgrade to the premium experience

Get all 40 projects in PDF and ePub, plus exclusive content that will take your Swift learning to the next level – buy the Hacking with Swift book today!

MASTER SWIFT NOW
Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>