Animated Sprite
Animated Sprite With Texture Atlas
The code below is already put together in the project AnimatedSprite. This is on Moodle and can be downloaded, unzipped and loaded in Xcode. If you wish to build up the code yourself the resources (sprite, backgroun, foreground) are here.
This project is not a complete game, it is a demonstration of sprite animation using an atlas.
Creating A Texture Atlas
- Select the Assets folder
- Right click on the
+
at the bottom of the assets window. - Select
AR and Textures
>Sprite Atlas
(You may need to scroll down to findAR and Textures
) - Drag and drop the image files you want to be part of the atlas to the atlas folder (inside the assets folder in Xcode)
- Rename the folder to something appropriate and delete the empty Sprite folder
That’s it, but making use of it requires a bit of work. The code below uses a few extra steps to make the code more reusable.
You will need create most of the files below and fill in the details as shown. Start with the New Game instructions from the SpriteKit Game section.
ContentView.swift
//
// ContentView.swift
// AnimatedSprite
//
// Created by user208560 on 3/22/22.
//
// Nothing different here, we are simply setting the scene size based on the iOS device selected
import SwiftUI
import SpriteKit
struct ContentView: View {
var scene: SKScene {
let scene = GameScene(size: UIScreen.main.bounds.size)
scene.scaleMode = .fill
return scene
}
var body: some View {
SpriteView(scene: scene)
.frame(width: scene.size.width, height: scene.size.height)
}
}
SpriteKitHelper.swift
//
// SpriteKitHelper.swift
// AnimatedSprite
//
// Created by user208560 on 3/22/22.
//
// This file adds a couple of functions to make using atlases a little easier.
import Foundation
import SpriteKit
// extension SKSpriteNode means that the methods are added to SKSpriteNode for this project
extension SKSpriteNode {
// This method loads the textures from the sprite.atlas and returns them as an array of textures
func loadTextures(atlas: String, prefix: String, StartsAt: Int, StopsAt: Int) -> [SKTexture] {
var textureArray = [SKTexture]()
let textureAtlas = SKTextureAtlas(named: atlas)
for i in StartsAt...StopsAt {
let textureName = "\(prefix)\(i)"
let temp = textureAtlas.textureNamed(textureName)
textureArray.append(temp)
}
return textureArray
}
// This method starts and progresses the animation cycle
func startAnimation(textures: [SKTexture], speed: Double, name: String, count: Int, resize: Bool, restore: Bool) {
if (action(forKey: name) == nil) {
let animation = SKAction.animate(with: textures, timePerFrame: speed, resize: resize, restore: restore)
if count == 0 {
let repeatAction = SKAction.repeatForever(animation)
run(repeatAction, withKey: name)
} else if count == 1 {
run(animation, withKey: name)
} else {
let repeatAction = SKAction.repeat(animation, count: count)
run(repeatAction, withKey: name)
}
}
}
}
Player.swift
//
// Player.swift
// AnimatedSprite
//
// Created by user208560 on 3/22/22.
//
// This project uses a player class to hold all the player information and methods rather than keeping them in GameScene like we did in the first game
import Foundation
import SpriteKit
import GameplayKit
enum PlayerAnimationType: String {
case walk
}
class Player: SKSpriteNode {
// MARK: - PROPERTIES
// Create an array to store the textures/sprites for the sprite walk animation
private var walkTextures: [SKTexture]?
public var playerSpeed: CGFloat = 1.5
// MARK: - INIT
// init() method sets up the basic information for the player object.
// it loads the textures from the atlas to walkTextures
init() {
let texture = SKTexture(imageNamed: "blob-walk_0")
super.init(texture: texture, color: .clear, size: texture.size())
self.walkTextures = self.loadTextures(atlas: "blob", prefix: "blob-walk_", StartsAt: 0, StopsAt: 2)
self.name = "player"
self.setScale(1.0)
self.anchorPoint = CGPoint(x: 0.5, y: 0.0)
}
// required to make init() above work
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// walk simply triggers the appropriate animation cycle
func walk() {
guard let walkTextures = walkTextures else {
preconditionFailure("Could not find textures")
}
startAnimation(textures: walkTextures, speed: 0.25, name: PlayerAnimationType.walk.rawValue, count: 0, resize: true, restore: true)
}
// moves the sprite, note the comments included.
func moveToPosition(pos: CGPoint, direction: String, speed: TimeInterval) {
// we have three images of a walk cycle moving to the right. We don't have images for moving to the left, instead we flip the image to face it to the left
switch direction {
case "L" :
xScale = -abs(xScale)
default :
xScale = abs(xScale)
}
// we use an SKAction to gradually move the sprite to the clicked position at its current speed
// note we have constrained the sprites movement so its y position cannot change (see setupConstraints() below)
let moveAction = SKAction.move(to: pos, duration: speed)
run(moveAction)
}
// constrains the sprites movement so it stays on the ground
func setupConstraints(floor: CGFloat) {
let range = SKRange(lowerLimit: floor, upperLimit: floor)
let lockToPlatform = SKConstraint.positionY(range)
constraints = [ lockToPlatform ]
}
}
GameScene.swift
//
// GameScene.swift
// AnimatedSprite
//
// Created by user208560 on 3/22/22.
//
// This class is the scene. It manages everything in the scene
import Foundation
import SpriteKit
import GameplayKit
class GameScene: SKScene {
// create a player object of the Player class
let player = Player()
override func didMove(to view: SKView) {
// initialise method
// set up the background image
let background = SKSpriteNode(imageNamed: "background_1")
background.anchorPoint = CGPoint(x: 0, y: 0)
background.position = CGPoint(x: 0, y: 0)
background.zPosition = -1
addChild(background)
// set up the foreground image - in this case a floor
let foreground = SKSpriteNode(imageNamed: "foreground_1")
foreground.anchorPoint = CGPoint(x: 0, y: 0)
foreground.position = CGPoint(x: 0, y: 0)
foreground.zPosition = 0
addChild(foreground)
// set up the player object
player.position = CGPoint(x: size.width/2, y: foreground.frame.maxY)
player.zPosition = 4
// enforce the constraing it stays on the floor
player.setupConstraints(floor: foreground.frame.maxY)
addChild(player)
// start the animation cycle
player.walk()
}
override func update(_ currentTime: TimeInterval) {
}
func touchDown(atPoint pos : CGPoint) {
// calculate the distance between the player location and the touch location
let distance = hypot(pos.x - player.position.x, pos.y - player.position.y)
// calculate the time required to travel the distance at the players speed
// calculatedSpeed is misnamed here, it is a time really
let calculatedSpeed = TimeInterval(distance / player.playerSpeed) / 255
if pos.x < player.position.x {
player.moveToPosition(pos: pos, direction: "L", speed: calculatedSpeed)
} else {
player.moveToPosition(pos: pos, direction: "R", speed: calculatedSpeed)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
- Previous
- Next