Upcoming and OnDemand Webinars View full list

tvOS Games, Part 3: Animations

Steve Sparks

In this series’ previous posts, I discussed how to use the game controllers and then built the skeleton of a Robotron:2084 game. In this third post, I will cover how to turn our boring boxes into fun animated sprites. The full source is available on Github for your use.

Building Sprites

It’s fairly easy to search for Robotron sprites and early on I began doing this. It didn’t take long before I wanted to customize things and realized that my existing tools were not up to the task. After some searching around, I found an amazing tool called Aseprite that has a wonderfully retro interface of its own. Just get a load of that!

Aseprite

It took me about half a day to learn the interface and draw myself a little cowboy sprite walking:

dude

When saving the animation in PNG format, Aseprite helpfully numbered all the files dude-01.png, dude-02.png and so forth. It was a fun break from coding, so I designed a whole slew of sprites:

Left LadyFront ManGruntRight BoyFront Man

I imported all these images into my asset catalog and got to work.

Making Movables Look Like They Move

I opened up my Movable class and added some logic to rotate through whatever textures were supplied.

var spriteStep = Int(arc4random_uniform(4))
var spriteTextures : [SKTexture] = []

func nextSprite() {
    guard spriteTextures.count >= 2 else {
        return
    }

    texture = spriteTextures[spriteStep]
    if let sz = texture?.size() {
        self.size = CGSize(width: sz.width*3, height: sz.height*3)
    }

    spriteStep = (spriteStep + 1) % spriteTextures.count
}

And up in my move() method, when I update the position, I’ll swap the sprite.

lastWalkVector = vec
position = pos
nextSprite()

I knew that I was going to want to select a set of textures based on the direction the character was facing, so I’d have a dictionary where the key was the direction, and the value was an array of textures. The right and left sprite sets have four distinct steps, but the back and front sprite sets only have three, repeating the middle one. By using string interpolation and well-chosen arrays, it made generating the sets straightforward.

static let textures : [String : [SKTexture]] = {
    var ret : [String : [SKTexture]] = [:]
    for set in ["back", "front"] {
        var arr : [SKTexture] = []
        for frame in [ 1, 2, 3, 2 ] {
            arr.append(SKTexture(imageNamed: "dude-(set)-(frame)"))
        }
        ret[set] = arr
    }
    for set in ["right", "left"] {
        var arr : [SKTexture] = []
        for frame in [ 1, 2, 3, 4 ] {
            arr.append(SKTexture(imageNamed: "dude-(set)-(frame)"))
        }
        ret[set] = arr
    }
    return ret
}()

func didChangeDirection(_ direction: Movable.WalkDirection) {
    let set = direction.spriteSet()
    spriteTextures = Player.textures[set]!
}

What’s that spriteSet() call on WalkDirection?

func spriteSet() -> String {
    switch self {
    case .north: return "back"
    case .south: return "front"
    case .east: return "right"
    case .west: return "left"
    }
}

And voila! Our player has an animated sprite.

The enemy can follow simpler sprite logic, since there’s no “left”, “right”, or “back” variations – just the one face. However, the civilian is more complex since there are three types.

enum CivilianType : String {
    case lady = "lady"
    case man = "man"
    case boy = "boy"

    var pointValue : Int {
        switch(self) {
        case .lady: return 300
        case .man: return 200
        case .boy: return 100
        }
    }
    static var allTypes : [CivilianType] = [.lady, .man, .boy]
    static var allTypeNames : [String] = {
        return allTypes.map() { return $0.rawValue }
    }()
}

Our civilian class then gets a type attribute:

var type : CivilianType = {
    switch (arc4random_uniform(3)) {
    case 0:  return .lady
    case 1:  return .man
    default: return .boy
    }
}()

Simple enough, eh? Now, just as the Player had dude-(direction)-(step) logic to find the textures, now we’ll want (type)-(direction)-(step) for each type of civilian.

lazy var textureBanks : [String : [SKTexture]] = {
    return textureDictionary[self.type.rawValue]!
}()

override func didChangeDirection(_ direction: Movable.WalkDirection) {
    let set = direction.spriteSet()
    let textureBank : Array<SKTexture> = textureBanks[set]!
    self.spriteTextures = textureBank
}

static let textureDictionary : [String:[String:[SKTexture]]] = {
    var dict : [String:[String:[SKTexture]]] = [:]

    for type in allTypeNames {
        var ret : [String : [SKTexture]] = [:]
        for set in ["back", "front"] {
            var arr : [SKTexture] = []
            for frame in [ 1, 2, 3, 2 ] {
                arr.append(SKTexture(imageNamed: "(type)-(set)-(frame)"))
            }
            ret[set] = arr
        }
        for set in ["right", "left"] {
            var arr : [SKTexture] = []

            let list = [1,2,3,4]
            for frame in list {
                arr.append(SKTexture(imageNamed: "(type)-(set)-(frame)"))
            }
            ret[set] = arr
        }
        dict[type] = ret
    }

    return dict
}()

And all our sprites are animated now.

Next Up: Shooting!

So at this point I have a level with my characters running around and looking great. This is really fun, but it’s time to get to the “shoot” part of “shoot-em-up game.” Check out the next installment for details on how to do it!

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project