Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4396

SpriteKit Animations and Texture Atlases in Swift

$
0
0
Update note: This tutorial has been updated for iOS 11, Xcode 9, and Swift 4.0 by Kevin Colligan. The original tutorial was written by Tony Dahbura.
Smoky says: Only you can start this bear!

Smoky says: Only you can start this bear!

In this SpriteKit tutorial, you’ll create an interactive animation of a walking bear and learn how to:

  • Create efficient animations with texture atlases.
  • Change the direction the bear faces based on where it’s moving.
  • Make your animated bear move in response to touch events.

This tutorial assumes you know the basics of SpriteKit. If not, you might want to start with the SpriteKit Swift 3 Tutorial for Beginners.

It’s time to get started.

Create the Swift Project

Start up Xcode, select File\New\Project…, choose the iOS\Game template and click Next.

Create project

Enter AnimatedBearSwift for the Product Name, Swift for Language, SpriteKit for Game Technology. Make sure the options for Integrate GameplayKit, Include Unit Tests, and Include UI Tests are unchecked and click Next:

Project options

Choose where to save your project, and click Create.

Now that your project is open, select one of the iPad simulators and build and run to check out the starter project. After a brief splash screen, you should see the following:

Hello World!

If you tap on the screen, you’ll see spinning geometric shapes which flare to life then fade from view. Pretty cool, but those won’t do for your bear.

You can download ready-to-animate art, courtesy of GameArtGuppy.com, by clicking the Download Materials button at the top or bottom of this tutorial. When you unzip the materials, you’ll find a folder named BearImages.atlas which contains eight numbered bear images.

Big bunch of bears

With the help of SpriteKit, you’ll cycle through these eight images to create the illusion of movement — kind of like an old-fashioned flip-book.

bear gif

You could create an animation by loading in each of these images individually. But there’s a better way: Use a texture atlas to make your animation more efficient.

Texture Atlases

If you’re new to texture atlases, you can think of them as one big mashup of all your smaller images. Rather than eight separate bear images, your texture atlas will be one big image along with a file that specifies the boundaries between each individual bear image.

SpriteKit is optimized to work with texture atlases. So using this approach can improve memory usage and rendering performance.

It’s also nearly effortless.

Just place your image files in a folder with a name that ends with .atlas — like the BearImages.atlas folder you downloaded. Xcode will notice the .atlas extension and automatically combine the images into a texture atlas for you at compile time.

Drag BearImages.atlas over your project and drop it under the AnimatedBearSwift folder in Xcode:

Import images

In the dialog box that appears, be sure that the Copy items if needed, Create groups and AnimatedBearSwift options are all checked, and click Finish:

Import images 2

If you expand the folder in Xcode it should look like this:

Import images 3

Before you start animating, get your Xcode template ready by completing a few small tasks.

First, click on AnimatedBearSwift in the Project navigator. Make sure that the AnimatedBearSwift target is selected. In the Deployment Info section, choose iPad for Devices and uncheck the Portrait and Upside Down options so only Landscape Left and Landscape Right are left checked, as shown below:

Choose iPad, landscape orientation and delete the GameScene.sks file

Next, find GameScene.sks in the project navigator and press Delete. Choose Move to Trash when prompted.

Be sure you’re deleting GameScene.sks, and not GameScene.swift. GameScene.sks is a scene editor which allows developers to visually lay out sprites and other components of a scene. For this tutorial, you’ll build your scene programmatically.

Similarly, delete Actions.sks as you also don’t need that for this tutorial.

With that out of the way, it’s time get that bear moving :] !

A Simple Animation

Start by plopping the bear in the middle of the screen and looping the animation, just to make sure things are working.

Open GameViewController.swift and replace the contents with the following:

import UIKit
import SpriteKit

class GameViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    if let view = view as? SKView {
      // Create the scene programmatically
      let scene = GameScene(size: view.bounds.size)
      scene.scaleMode = .resizeFill
      view.ignoresSiblingOrder = true
      view.showsFPS = true
      view.showsNodeCount = true
      view.presentScene(scene)
    }
  }
  
  override var prefersStatusBarHidden: Bool {
    return true
  }
}

This implementation has just what you need to start, so you won’t need the starter code generated by Xcode.

GameViewController is a subclass of UIViewController that has its root view set to an SKView, which is a view that contains a SpriteKit scene.

Here, you’ve overridden viewDidLoad() to create a new instance of GameScene on startup. You define the scene to have the same size as the view, set the scaleMode along with other basic properties and present the scene. For this tutorial, the rest of your code will be in GameScene.swift.

Note: You use optional binding — the if let view = view as? SKView bit — to make sure the view is the correct type before proceeding.

You’re also overriding the prefersStatusBarHidden getter to hide the status bar so that all the attention will be focused on the bear.

Switch over to GameScene.swift and replace the contents with the following:

import SpriteKit

class GameScene: SKScene {
  
  private var bear = SKSpriteNode()
  private var bearWalkingFrames: [SKTexture] = []
  
  override func didMove(to view: SKView) {
    backgroundColor = .blue
  }
}

At this point you’ve just removed all the project template code to create a nice blank slate (and defined a couple private variables you’ll need later). Build and run to make sure everything builds OK — you should see a blue screen.

Note: If you are running in the simulator, you may need to manually rotate the screen by selecting Hardware\Rotate Right.

Setting up the Texture Atlas

Add a new method, just after didMove(to:):

func buildBear() {
  let bearAnimatedAtlas = SKTextureAtlas(named: "BearImages")
  var walkFrames: [SKTexture] = []

  let numImages = bearAnimatedAtlas.textureNames.count
  for i in 1...numImages {
    let bearTextureName = "bear\(i)"
    walkFrames.append(bearAnimatedAtlas.textureNamed(bearTextureName))
  }
  bearWalkingFrames = walkFrames
}

First, you create an SKTextureAtlas from the bear images. walkFrames is an array of SKTexture objects and will store a texture for each frame of the bear animation.

You populate that array by looping through your images’ names (they are named with a convention of bear1.png -> bear8.png) and grabbing the corresponding texture.

Still in buildBear(), add the following right after bearWalkingFrames = walkFrames:

let firstFrameTexture = bearWalkingFrames[0]
bear = SKSpriteNode(texture: firstFrameTexture)
bear.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(bear)

Here, you’re creating an SKSpriteNode using the first frame texture and positioning it in the center of the screen to set up the start of the animation.

Finally, you need to call the method. Add the following code to the end of didMove(to:)

buildBear()

If you were to build and run now, the bear still wouldn’t be moving. In order to do so, we need an SKAction. In the same file, add the following new method right after the buildBear() method:

func animateBear() {
  bear.run(SKAction.repeatForever(
    SKAction.animate(with: bearWalkingFrames,
                     timePerFrame: 0.1,
                     resize: false,
                     restore: true)),
    withKey:"walkingInPlaceBear")
}

This action will cause the animation to run with a 0.1 second wait-time for each frame. The "walkingInPlaceBear" key identifies this particular action with a name. That way, if you call this method again to restart the animation, it will simply replace the existing animation rather than create a new one.

The repeatForever action repeats whatever action it is provided forever, which results in the given animate action repeatedly animating through the textures in the texture atlas.

Now all you need to do is call this method to kick off the animation! Add the following line to the end of didMove(to:):

animateBear()

And that’s it! Build and run the project. You’ll see your bear happily strolling on the screen.

Does a bear walk on an iPad? Yes!

Changing Animation Facing Direction


Things are looking good, except you don’t want this bear meandering about on its own — that would be dangerous! It would be much better if you could control its direction by tapping the screen to tell it which way to go.

Still in GameScene.swift, add the following method to detect user touches:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
  let touch = touches.first!
  let location = touch.location(in: self)
  
  var multiplierForDirection: CGFloat
  if location.x < frame.midX {
    // walk left
    multiplierForDirection = 1.0
  } else {
    // walk right
    multiplierForDirection = -1.0
  } 
  
  bear.xScale = abs(bear.xScale) * multiplierForDirection
  animateBear()
}

SpriteKit calls touchesEnded(_:with:) when the user's finger lifts off the screen at the end of a tap.

In the method, you determine which side of the screen was tapped — left or right of center. You want the bear to face in the direction of the tap. You do this by making the node's xScale positive when the bear should face left (the bear walks to the left by default), and negative to flip the image when the bear should face right.

Finally, you call animateBear() to restart the animation each time you tap the screen.

Build and run the project, and you'll see your bear happily strolling on the screen as usual. Tap on the left and right sides of the screen to get the bear to change directions.

Bears have a fine sense of direction

Moving the Bear Around the Screen

Right now, it looks like the bear is walking in-place on a treadmill. The next step is to get him to meander to different places on the screen.

First, remove the call to animateBear() at the end of didMove(to:). You want the bear to start moving when the user taps the screen, not automatically.

Next, add this helper method to the class:

func bearMoveEnded() {
  bear.removeAllActions()
}

This will remove all actions and stop the animation. You'll call this later when the bear reaches the edge of the screen.

Before taking your bear out for a walk, clean-up touchesEnded(_:with:) by moving all bear-related code to its own method. Replace the entire method with:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
  let touch = touches.first!
  let location = touch.location(in: self)
  moveBear(location: location)
}

The code above records the touch location and sends it over to a new method, moveBear(location:). It also spawns a warning from Xcode because you haven't yet created this method. Add the following code right after animateBear():

func moveBear(location: CGPoint) {
  // 1
  var multiplierForDirection: CGFloat
  
  // 2
  let bearSpeed = frame.size.width / 3.0
  
  // 3
  let moveDifference = CGPoint(x: location.x - bear.position.x, y: location.y - bear.position.y)
  let distanceToMove = sqrt(moveDifference.x * moveDifference.x + moveDifference.y * moveDifference.y)
  
  // 4
  let moveDuration = distanceToMove / bearSpeed
  
  // 5
  if moveDifference.x < 0 {
    multiplierForDirection = 1.0
  } else {
    multiplierForDirection = -1.0
  }
  bear.xScale = abs(bear.xScale) * multiplierForDirection
}

Here's what's going on step-by-step:

  1. You declare multiplierDirection, a CGFloat variable you'll use to set the bear's direction, just as you did earlier in touchesEnded().
  2. You define the bearSpeed to be equal to the screen width divided by 3, so the bear will be able to travel the width of the scene in 3 seconds.
  3. You need to figure out how far the bear needs to move along both the x and y axes, by subtracting the bear's position from the touch location. You then calculate the distance the bear moves along a straight line (the hypotenuse of a triangle formed from the bear's current position and the tap point). For a full tutorial on the math of game programming, check out Trigonometry for Games.
  4. You calculate how long it should take the bear to move along this length, by dividing the length by the desired speed.
  5. Finally, you look to see if the bear is moving to the right or to the left by looking at the x axis of the move difference. If it's less than 0, he's moving to the left; otherwise, to the right. You use the same technique of creating a multiplier for the xScale to flip the sprite.

Now all that's left is to run the appropriate actions. That's another substantial chunk of code — add the following to the end of moveBear(location:):

// 1
if bear.action(forKey: "walkingInPlaceBear") == nil {
  // if legs are not moving, start them
  animateBear()
}

// 2
let moveAction = SKAction.move(to: location, duration:(TimeInterval(moveDuration)))

// 3
let doneAction = SKAction.run({ [weak self] in
  self?.bearMoveEnded()
})

// 4
let moveActionWithDone = SKAction.sequence([moveAction, doneAction])
bear.run(moveActionWithDone, withKey:"bearMoving")

Here's what's happening in this second half of moveBear(location:):

  1. Start the legs moving on your bear if he is not already moving his legs.
  2. Create a move action specifying where to move and how long it should take.
  3. Create a done action that will run a block to stop the animation.
  4. Combine these two actions into a sequence of actions, which means they will run in order sequentially — the first runs to completion, then the second runs. You have the bear sprite run this sequence with the key "bearMoving". As with the animation action, using a unique key here will ensure there is only one movement action running at a time.

Note: SpriteKit supports sequential and grouped actions. A sequential action means each specified action runs one after the other (sequentially). Sometimes you may want multiple actions to run at the same time. This can be accomplished by specifying a group action where all the actions specified run in parallel.

You also have the flexibility to set up a series of sequential actions that contain grouped actions and vice versa! For more details see the SKAction class in Apple's SpriteKit documentation.

That was a lot of code — but was it worth it? Build and run to see! Now you can tap the screen to move your bear around.

Your walking bear is now complete!

Where To Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

For more animation fun, here are a few ideas to try out:

  1. What if you wanted the bear to moonwalk? Hint: Try building the array of images backwards!
  2. Try accelerating or slowing down the frame times in animateBear().
  3. Try animating multiple bears on the screen at the same time. Hint: Create multiple sprite nodes with actions.

At this point, you should know all you need to start adding animations to your projects using texture atlases. Have some fun and experiment by creating your own animations and seeing what you can do!

If you want to learn more about SpriteKit and Swift, you should check out our book 2D Apple Games by Tutorials. We'll teach you everything you need to know — physics, tile maps, particle systems and even making your own 2D game art.

In the meantime, if you have any questions or comments, please join the forum discussion below!

The post SpriteKit Animations and Texture Atlases in Swift appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4396

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>