To help distinguish your monster generators from standard monsters, you’ll create an animation component, which you’ll use to animate the monsters. First you’ll create an extension to help build the textures. Then, you’ll build out the animation component and define the different types of animations.
In Load the Textures, you built a small helper method to help load textures for animation. This time around, however, you’ll create an extension method for the SKTexture class instead. Extensions are generally preferred as they tend to be more reusable than helper methods that can easily get buried within the underbelly of your code.
In the Project Navigator, and inside the Extensions group, create a new file (⌘N) using the iOS Swift Filetemplate. Name the new file SKTexture+LoadTextures.swift and replace its contents with the following:
| import SpriteKit |
| |
| extension SKTexture { |
| static 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 code should look familiar to you since you used something similar in Gloop Drop. In case it doesn’t (or if you forgot how it works), you’re essentially looping through the input values to build an array of textures that you’ll use for the animation.
Excellent, you now have a handy way of loading textures. It’s time to put this method to good use in a new animation component.
Inside the Components group, create a new file (⌘N) using the iOS Swift File template. Name the new file AnimationComponent.swift and replace its contents with the following:
| import SpriteKit |
| import GameplayKit |
| |
| // MARK: - COMPONENT CODE STARTS HERE |
| |
| class AnimationComponent: GKComponent { |
| |
| override func didAddToEntity() { |
| |
| } |
| |
| override class var supportsSecureCoding: Bool { |
| true |
| } |
| } |
This code sets you up with a base starting point for your new animation component.
However, before you get into the finer implementation details, let’s use a struct to define exactly what an animation is.
At the top of this file (and below the import statements), add the following:
| struct Animation { |
| let textures: [SKTexture] |
| var timePerFrame: TimeInterval |
| |
| let repeatTexturesForever: Bool |
| let resizeTexture: Bool |
| let restoreTexture: Bool |
| |
| init(textures: [SKTexture], |
| timePerFrame: TimeInterval = TimeInterval(1.0 / 5.0), |
| repeatTexturesForever: Bool = true, resizeTexture: Bool = true, |
| restoreTexture: Bool = true) { |
| |
| self.textures = textures |
| self.timePerFrame = timePerFrame |
| self.repeatTexturesForever = repeatTexturesForever |
| self.resizeTexture = resizeTexture |
| self.restoreTexture = restoreTexture |
| } |
| } |
Here, you’re setting up a handful of properties that define an animation; this includes properties like the texture array, the time per frame, and how often to repeat the animation. But before you can complete and use the animation component, you need to specify the different types of animation for your game objects.
Inside the Entities group, create another new file (⌘N) using the iOS Swift File template. Name this new file GameObjects.swift and replace its contents with the following code:
| import SpriteKit |
| import GameplayKit |
| |
| enum GameObjectType: String { |
| |
| // Monsters |
| case skeleton |
| case goblin |
| } |
| |
| struct GameObject { |
| |
| static let defaultGeneratorType = GameObjectType.skeleton.rawValue |
| static let defaultAnimationType = GameObjectType.skeleton.rawValue |
| |
| static let skeleton = Skeleton() |
| static let goblin = Goblin() |
| |
| struct Goblin { |
| let animationSettings = Animation(textures: |
| SKTexture.loadTextures(atlas: "monster_goblin", |
| prefix: "goblin_", startsAt: 0, stopsAt: 1)) |
| } |
| |
| struct Skeleton { |
| let animationSettings = Animation(textures: |
| SKTexture.loadTextures(atlas: "monster_skeleton", |
| prefix: "skeleton_", startsAt: 0, stopsAt: 1), |
| timePerFrame: TimeInterval(1.0 / 25.0)) |
| } |
| } |
This uses the new SKTexture extension to create some static properties that hold information about your game objects, including its animation type. As your game grows, you can add additional properties and objects here, keeping everything you need about your game objects in one place.
Below the code you just added, yet still inside the GameObject struct, add the following code:
| static func forAnimationType(_ type: GameObjectType?) -> Animation? { |
| switch type { |
| case .skeleton: |
| return GameObject.skeleton.animationSettings |
| case .goblin: |
| return GameObject.goblin.animationSettings |
| default: |
| return nil |
| } |
| } |
This code defines a static method that you can use to grab an object’s animation set up.
Switch back to the AnimationComponent.swift file and then add the following new property:
| @GKInspectable var animationType: String = GameObject.defaultAnimationType |
You can use this property to define the animation type, setting the default property to whatever string value you defined in the GameObjects.swift file—in this case, skeleton.
Scroll down a bit, and in the didAddToEntity() method, add the following code:
| guard let animation = |
| GameObject.forAnimationType(GameObjectType(rawValue: animationType)) else { |
| return |
| } |
| |
| let textures = animation.textures |
| let timePerFrame = animation.timePerFrame |
| let animationAction = SKAction.animate(with: textures, |
| timePerFrame: timePerFrame) |
| |
| if animation.repeatTexturesForever == true { |
| let repeatAction = SKAction.repeatForever(animationAction) |
| componentNode.run(repeatAction) |
| } else { |
| componentNode.run(animationAction) |
| } |
This code uses the static method to retrieve and build the desired game object’s animation.
Notice the animationType inspectable property? If you recall, in the GeneratorComponent class, you had a monsterType inspectable property where you set its default string value to skeleton. Rather than hardcode that value (hardcoding should be avoided when possible), you can now update this property to use your new defaultGeneratorType that you created in the GameObject struct—again, keeping all of your game objects and related setup in one place.
Switch to the GeneratorComponent.swift file and replace the following line:
| @GKInspectable var monsterType: String = "skeleton" |
with this one instead:
| @GKInspectable var monsterType: String = GameObject.defaultGeneratorType |
With this change, you make it much easier to change your default generator type in the future. For example, suppose you initially wanted skeleton monsters as the default monster type. You might create multiple components and/or other code where you’d hardcode skeleton for the value, as you previously did here. Then suppose, some time later in the future, you decide that a ghost monster would have been a better choice. In that case, you’d have to update all of those hardcoded values. However, by creating a single default type and using that instead, you only need to update your value in one spot.
All right, it’s time to get the monster generators added to the scene.
Save the GeneratorComponent.swift file and switch back to the GameScene.sks file. Remove the monster node and add two new Color Sprite nodes. Switch to the Attributes Inspector, and set up the two sprite nodes like so:
The final step is to add the components to each of the monster generator nodes. Val is a warrior at heart, so she’ll need quite a few monsters to give her a worthy challenge. Switch to the Components Inspector, and with the generator_goblin node selected, add the following components with the following settings:
Switch to the generator_skeleton node and add its components with the following settings:
Build and run the project. You’ll notice that each monster generator spits out its monsters, each with its own health indicator. You’ll also see that the generators are now animated with blinking eyes.