Update 03/17/17: This tutorial has been updated for Xcode 8.2.1 and Swift 3. Original tutorial by Ray Wenderlich.
GameplayKit is an awesome framework introduced in iOS 9 that makes implementing many common tasks in your game much easier.
GameplayKit is a collection of tools that can help you with features like pathfinding, randomization, state machines, rule systems, and more.
In this GameplayKit tutorial, you’re going to focus on two parts of GameplayKit: its Entity-Component system, and Agents, Goals, and Behaviors.
These parts of GameplayKit help you organize your code in a different way that allows for more clean organization and code reuse, especially as your games become larger and more complex.
Let’s dive in!
Getting Started
In this GameplayKit tutorial, you will be working on a simple game called MonsterWars. Download the starter project, open it in Xcode, and build and run to check it out. You’ll see something like the following:
Right now, this game is just a shell with the UI elements added, but no gameplay. Here’s how the game will work:
- You’ll get money every so often, which you can see in the upper left. You can use it to buy units by tapping the buttons at the bottom.
- There are three enemy types: Quirk (fast and cheap), Zap (ranged attackers), and Munch (slow but has AOE chomp).
- If your units destroy the enemy’s castle, you win!
Take a look at the code to see what’s there. Once you feel comfortable, read on to learn more about the main subject of this GameplayKit tutorial: its Entity-Component system.
What is an Entity-Component System?
Quite simply, an entity-component system is an architectural approach that enables your games to grow in size and complexity without increasing the interdependencies in your code.
This works by having 2 types of modular pieces:
- Entities: An entity is a representation of an object in your game. The player, an enemy or a castle are good examples of entities. Since an entity is basically an object, you make this object perform interesting actions by applying components to it.
- Components: A component contains the logic that performs a specific job on one entity, such as modifying its appearance or shooting a rocket. You make small components for each type of action your entities can do. For example, you might make a movement component, a health component, a melee attack component, and so on.
One of the best benefits of this system is that you can reuse components on as many entities as you want, allowing you to keep your code clean and organized.
Your First Component
To get started, let’s create a component to represent a sprite on the screen. In GameplayKit you create components by subclassing GKComponent
.
To do this, select your MonsterWars group in the project navigator, right-click, select New Group, and name the group Components.
Then right-click the Components group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file SpriteComponent and click Create.
At this point, your project navigator should look like this:
Open SpriteComponent.swift and replace the contents with the following:
// 1 import SpriteKit import GameplayKit // 2 class SpriteComponent: GKComponent { // 3 let node: SKSpriteNode // 4 init(texture: SKTexture) { node = SKSpriteNode(texture: texture, color: .white, size: texture.size()) super.init() } // 5 required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
In this code you:
- Imported SpriteKit along with GameplayKit so you could access these frameworks.
- Created a component class named
SpriteComponent
by subclassingGKComponent
. - Declared a property named
node
to hold the sprite on this component. - Implemented a basic initializer that creates a sprite node from a texture passed in and assigns it to the
node
property. - Added this required method to achieve compliance with the
GKComponent
class. For now, don’t worry about the fact that it’s not being implemented.
Your First Entity
In GameplayKit, GKEntity
is the class that represents an entity. You make entities do stuff by adding components to it, like the one you just created.
It’s often helpful to create a subclass of GKEntity
for each type of object that you add to the game. In this game for example, there are five types of objects: castles, quirk monsters, zap monsters, munch monsters, and lasers.
Your GKEntity subclass should be very simple. Usually it’s just an initializer that adds the components you need for that type of object.
Let’s create an entity for the castles. To do this, right-click the MonsterWars group in the project navigator, click New Group and name the group Entities.
Then right-click the Entities group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file Castle and click Create.
At this point, your project navigator should look like this:
Open Castle.swift and replace the contents with the following:
import SpriteKit import GameplayKit // 1 class Castle: GKEntity { init(imageName: String) { super.init() // 2 let spriteComponent = SpriteComponent(texture: SKTexture(imageNamed: imageName)) addComponent(spriteComponent) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
There are just two things to point out here:
- As I mentioned earlier, it’s often convenient to subclass
GKEntity
for each type of object in your game. The alternative is to create a baseGKEntity
and dynamically add the types of components you need; but often you want to have a “cookie cutter” for a particular type of object. That is what this is! - At this point, you add just one component to the entity – the sprite component you created earlier.
Now that you’ve created a component and an entity, you’re almost ready to add it into the game.
The Entity Manager
In this section, you’re going to create a helper class to manage the entities you add to your game. It will keep a list of all the entities in the game, and have some helper methods for things like adding and removing entities.
Right-click your Entities group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file EntityManager and click Create.
Open EntityManager.swift and replace the contents with the following:
import Foundation import SpriteKit import GameplayKit class EntityManager { // 1 var entities = Set<GKEntity>() let scene: SKScene // 2 init(scene: SKScene) { self.scene = scene } // 3 func add(_ entity: GKEntity) { entities.insert(entity) if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node { scene.addChild(spriteNode) } } // 4 func remove(_ entity: GKEntity) { if let spriteNode = entity.component(ofType: SpriteComponent.self)?.node { spriteNode.removeFromParent() } entities.remove(entity) } } |
Let’s review this section by section:
- This class will keep a reference to all entities in the game, along with the scene.
- This is a simple initializer that stores the scene in the
scene
property. - This helper function handles adding entities to your game. It adds them to the list of entities, then checks to see if the entity has a
SpriteComponent
. If it does, it adds the sprite’s node to the scene. - This helper function that you will call when you want to remove an entity from your game. This does the opposite of the
add(_:)
method; if the entity has aSpriteComponent
, it removes the node from the scene, and it also removes the entity from the list of entities.
You’ll be adding more methods to this helper class in the future, but this is a good start for now. First let’s get something showing up on the screen!
Adding Your Castles
Open GameScene.swift and add this property to the bottom of your list of properties:
var entityManager: EntityManager! |
This is to store an instance of the helper class you just created.
Next add this code to the bottom of didMove(to:)
:
// 1 entityManager = EntityManager(scene: self) // 2 let humanCastle = Castle(imageName: "castle1_atk") if let spriteComponent = humanCastle.component(ofType: SpriteComponent.self) { spriteComponent.node.position = CGPoint(x: spriteComponent.node.size.width/2, y: size.height/2) } entityManager.add(humanCastle) // 3 let aiCastle = Castle(imageName: "castle2_atk") if let spriteComponent = aiCastle.component(ofType: SpriteComponent.self) { spriteComponent.node.position = CGPoint(x: size.width - spriteComponent.node.size.width/2, y: size.height/2) } entityManager.add(aiCastle) |
Let’s review this section by section:
- Creates an instance of the
EntityManager
helper class you created in the previous section. - Creates an instance of the
Castle
entity you created earlier to represent the human player. After creating the castle it retrieves the sprite component and positions it on the left hand side of the screen. Finally, it adds it to the entity manager. - Similar code to set up the AI player’s castle.
That’s it! Build and run and you’ll see your castles in the game:
Your Second Component
When you develop games with an entity-component system, all the data you need for your game objects must be stored in some kind of component.
One data point you’ll need to keep track of for this game is which team an object belongs to – team 1 or team 2. Since that information doesn’t belong on your sprite component, you might want to have an entity that doesn’t belong to either team. Let’s create a new component for that.
Right-click your Components group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file TeamComponent and click Create.
Open TeamComponent.swift and replace the contents with the following:
import SpriteKit import GameplayKit // 1 enum Team: Int { case team1 = 1 case team2 = 2 static let allValues = [team1, team2] func oppositeTeam() -> Team { switch self { case .team1: return .team2 case .team2: return .team1 } } } // 2 class TeamComponent: GKComponent { let team: Team init(team: Team) { self.team = team super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
This is a fairly simple file, so I’ll just point out two things:
- This is an enumeration to keep track of the two teams in this game – team 1 and team 2. It also has a helper method to return the opposite team, which will come in handy later.
- This is a very simple component that simply keeps track of the team for this entity.
Now that you have this new component, let’s update your castle entity to use it. Open Castle.swift and modify the initializer to take the team as a parameter:
init(imageName: String, team: Team) { |
Then add this line to the bottom of init(imageName:team:)
:
addComponent(TeamComponent(team: team)) |
This adds your new component to the castle entity. Finally, open GameScene.swift and replace the line that initializes humanCastle
with the following:
let humanCastle = Castle(imageName: "castle1_atk", team: .team1) |
Similarly, replace the line that initializes aiCastle
with the following:
let aiCastle = Castle(imageName: "castle2_atk", team: .team2) |
Build and run the game. You shouldn’t notice any changes, but you have now successfully associated a new set of data to your entity which will come in handy later.
Your Third Component
Another piece of data you need to keep track of is each player’s current coins. In this game, since there’s a single castle on each side, you’ll think of the castle as the “commander” for each player. so the castle will be a good place to store this information.
Right-click your Components group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file CastleComponent and click Create.
Open CastleComponent.swift and replace the contents with the following:
import SpriteKit import GameplayKit class CastleComponent: GKComponent { // 1 var coins = 0 var lastCoinDrop = TimeInterval(0) override init() { super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 2 override func update(deltaTime seconds: TimeInterval) { super.update(deltaTime: seconds) // 3 let coinDropInterval = TimeInterval(0.5) let coinsPerInterval = 10 if (CACurrentMediaTime() - lastCoinDrop > coinDropInterval) { lastCoinDrop = CACurrentMediaTime() coins += coinsPerInterval } } } |
This component is a little different than the others, so let’s review this in more detail.
- These 2 properties store the number of coins in the castle and the last time coins were earned.
- SpriteKit calls
update(deltaTime:)
on each frame of the game. Note that SpriteKit does not call this method automatically; there’s a little bit of setup to get this to happen, which you’ll do shortly. - This code spawns coins periodically.
Switch to Castle.swift and add this to the bottom of init(imageName:team:)
:
addComponent(CastleComponent()) |
Next, you need to add the code I mentioned earlier to get your updateWithDeltaTime(_:)
method to be called. To do this, switch to EntityManager.swift and add this new property to the top of the class:
lazy var componentSystems: [GKComponentSystem] = { let castleSystem = GKComponentSystem(componentClass: CastleComponent.self) return [castleSystem] }() |
Think of GKComponentSystem
as a class that stores a collection of components. Here, you create a GKComponentSystem
to keep track of all of the CastleComponent
instances in your game.
You then put the GKComponentSystem
that stores components into an array. Right now it’s the only object in the array, but you’ll be adding more to this later.
Add this to the end of add(_:)
:
for componentSystem in componentSystems { componentSystem.addComponent(foundIn: entity) } |
Here whenever you add a new entity, you add it to each of the component systems in your array (right now, it only contains the castle component system). Don’t worry – if your entity does not contain a castle component, nothing will happen.
Add this line to the property declarations:
var toRemove = Set<GKEntity>() |
Then add this to the end of remove(_:)
:
toRemove.insert(entity) |
Note that instead of removing the entity directly from the component system, you add it to a toRemove
set, so you can remove it later. This is going to make it easier in the next step to remove an entity while you are enumerating the objects in a component system, since Swift does not let you modify a collection while you are iterating through it.
Add this new method to the bottom of the class:
func update(_ deltaTime: CFTimeInterval) { // 1 for componentSystem in componentSystems { componentSystem.update(deltaTime: deltaTime) } // 2 for currentRemove in toRemove { for componentSystem in componentSystems { componentSystem.removeComponent(foundIn: currentRemove) } } toRemove.removeAll() } |
Let’s review this section by section:
-
Here you loop through all the component systems in the array and call
update(deltaTime:)
on each one. This causes each component system to callupdate(deltaTime:)
on each component in their system in turn.This actually demonstrates the whole purpose and benefit of using
GKComponentSystem
. The way this is set up, components are updated one system at a time. In games, it’s often convenient to have precise control over the ordering of the processing of each system (physics, rendering, etc). - Here’s where you loop through anything in the
toRemove
array and remove those entities from the component systems.
There’s one last helper method to add to this file. Add this method to the bottom of the class:
func castle(for team: Team) -> GKEntity? { for entity in entities { if let teamComponent = entity.component(ofType: TeamComponent.self), let _ = entity.component(ofType: CastleComponent.self) { if teamComponent.team == team { return entity } } } return nil } |
Basically, this is a handy method to get the castle for a particular team. In here you loop through all of the entities in the game and check to see any entities that have both a TeamComponent
and a CastleComponent
– which should be the two castles in the game. You then check to see if the team matches the passed in parameter and return that.
Let’s hook this up to the game scene now. Open GameScene.swift, scroll down to the bottom of the file and add this code to the bottom of the update(_:)
method:
let deltaTime = currentTime - lastUpdateTimeInterval lastUpdateTimeInterval = currentTime entityManager.update(deltaTime) if let human = entityManager.castle(for: .team1), let humanCastle = human.component(ofType: CastleComponent.self) { coin1Label.text = "\(humanCastle.coins)" } if let ai = entityManager.castle(for: .team2), let aiCastle = ai.component(ofType: CastleComponent.self) { coin2Label.text = "\(aiCastle.coins)" } |
Here you call the update(_:)
method on the entity manager. Then you find the castle (and castle component) for each team, and update the labels with the current coin values for each castle.
Build and run, and see the money begin to roll in!
Spawning The Monsters
This game is ready for some monsters! Let’s modify the game so you can spawn Quirk monsters.
Right-click your Entities group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file Quirk and click Create.
Open Quirk.swift and replace the contents with the following:
import SpriteKit import GameplayKit class Quirk: GKEntity { init(team: Team) { super.init() let texture = SKTexture(imageNamed: "quirk\(team.rawValue)") let spriteComponent = SpriteComponent(texture: texture) addComponent(spriteComponent) addComponent(TeamComponent(team: team)) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
This is very similar to how you set up the castle entity. Here you set the texture according to the team and add the sprite component to the entity. Additionally you also add a team component to complete all this entity needs.
Now it’s time to create an instance of the Quirk entity. Last time, you created the Castle entity directly in GameScene
, but this time you’ll move the code to spawn a quirk monster into EntityManager
.
To do this, switch to EntityManager.swift and add this method to the bottom of the class:
func spawnQuirk(team: Team) { // 1 guard let teamEntity = castle(for: team), let teamCastleComponent = teamEntity.component(ofType: CastleComponent.self), let teamSpriteComponent = teamEntity.component(ofType: SpriteComponent.self) else { return } // 2 if teamCastleComponent.coins < costQuirk { return } teamCastleComponent.coins -= costQuirk scene.run(SoundManager.sharedInstance.soundSpawn) // 3 let monster = Quirk(team: team) if let spriteComponent = monster.component(ofType: SpriteComponent.self) { spriteComponent.node.position = CGPoint(x: teamSpriteComponent.node.position.x, y: CGFloat.random(min: scene.size.height * 0.25, max: scene.size.height * 0.75)) spriteComponent.node.zPosition = 2 } add(monster) } |
Let’s review this section by section:
- Monsters should be spawned near their team’s castle. To do this, you need the position of the castle’s sprite, so this is some code to look up that information in a dynamic way.
- This checks to see if there are enough coins to spawn the monster, and if so subtracts the appropriate coins and plays a sound.
- This is the code to create a
Quirk
entity and position it near the castle (at a random y-value).
Finally, switch to GameScene.swift and add this to the end of quirkPressed()
:
entityManager.spawnQuirk(team: .team1) |
Build and run. You can now tap the Quirk button to spawn some monsters!
Agents, Goals, and Behaviors
So far, the quirk monsters are just sitting right there doing nothing. This game needs movement!
Luckily, GameplayKit comes with a set of classes collectively known as “agents, goals, and behaviors” that makes moving objects in your game in complex ways super easy. Here’s how it works:
GKAgent2D
is a subclass ofGKComponent
that handles moving objects in your game. You can set different properties on it like max speed, acceleration, and so on, and theGKBehavior
to use.GKBehavior
is a class that contains a set ofGKGoals
, representing how you would like your objects to move.GKGoal
represents a movement goal you might have for your agents – for example to move toward another agent.
So basically, you configure these objects and add the GKAgent
component to your class, and GameplayKit will move everything for you from there!
GKAgent2D
doesn’t move your sprites directly, it just updates its own position appropriately. You need to write a bit of glue code to match up the sprite position with the GKAgent
position.Let’s start by creating the behavior and goals. Right-click your Components group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file MoveBehavior and click Create.
Open MoveBehavior.swift and replace the contents with the following:
import GameplayKit import SpriteKit // 1 class MoveBehavior: GKBehavior { init(targetSpeed: Float, seek: GKAgent, avoid: [GKAgent]) { super.init() // 2 if targetSpeed > 0 { // 3 setWeight(0.1, for: GKGoal(toReachTargetSpeed: targetSpeed)) // 4 setWeight(0.5, for: GKGoal(toSeekAgent: seek)) // 5 setWeight(1.0, for: GKGoal(toAvoid: avoid, maxPredictionTime: 1.0)) } } } |
There’s a lot of new stuff here, so let’s review this section by section:
- You create a
GKBehavior
subclass here so you can easily configure a set of movement goals. - If the speed is less than 0, don’t set any goals as the agent should not move.
- To add a goal to your behavior, you use the
setWeight(_:for:)
method. This allows you to specify a goal, along with a weight of how important it is – larger weight values take priority. In this instance, you set a low priority goal for the agent to reach the target speed. - Here you set a medium priority goal for the agent to move toward another agent. You will use this to make your monsters move toward the closest enemy.
- Here you set a high priority goal to avoid colliding with a group of other agents. You will use this to make your monsters stay away from their allies so they are nicely spread out.
Now that you’ve created your behavior and goals, you can set up your agent. Right-click your Components group, select New File.., select the iOS/Source/Swift File template, and click Next. Name the new file MoveComponent and click Create.
Open MoveComponent.swift and replace the contents with the following:
import SpriteKit import GameplayKit // 1 class MoveComponent: GKAgent2D, GKAgentDelegate { // 2 let entityManager: EntityManager // 3 init(maxSpeed: Float, maxAcceleration: Float, radius: Float, entityManager: EntityManager) { self.entityManager = entityManager super.init() delegate = self self.maxSpeed = maxSpeed self.maxAcceleration = maxAcceleration self.radius = radius print(self.mass) self.mass = 0.01 } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 4 func agentWillUpdate(_ agent: GKAgent) { guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else { return } position = float2(spriteComponent.node.position) } // 5 func agentDidUpdate(_ agent: GKAgent) { guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else { return } spriteComponent.node.position = CGPoint(position) } } |
There’s lots of new stuff here as well, so let’s review this section by section:
- Remember that
GKAgent2D
is a subclass ofGKComponent
. You subclass it here so customize its functionality. Also, you implementGKAgentDelegate
– this is how you’ll match up the position of the sprite with the agent’s position. - You’ll need a reference to the
entityManger
so you can access the other entities in the game. For example, you need to know about your closest enemy (so you can seek to it) and your full list of allies (so you can spread apart from them). GKAgent2D
has various properties like max speed, acceleration, and so on. Here you configure them based on passed in parameters. You also set this class as its own delegate, and make the mass very small so objects respond to direction changes more easily.- Before the agent updates its position, you set the position of the agent to the sprite component’s position. This is so that agents will be positioned in the correct spot to start. Note there’s some funky conversions going on here – GameplayKit uses
float2
instead ofCGPoint
, gah! - Similarly, after the agent updates its position
agentDidUpdate(_:)
is called. You set the sprite’s position to match the agent’s position.
You still have a bit more to do in this file, but first you need to add some helper methods. Start by opening EntityManager.swift and add these new methods:
func entities(for team: Team) -> [GKEntity] { return entities.flatMap{ entity in if let teamComponent = entity.component(ofType: TeamComponent.self) { if teamComponent.team == team { return entity } } return nil } } func moveComponents(for team: Team) -> [MoveComponent] { let entitiesToMove = entities(for: team) var moveComponents = [MoveComponent]() for entity in entitiesToMove { if let moveComponent = entity.component(ofType: MoveComponent.self) { moveComponents.append(moveComponent) } } return moveComponents } |
entities(for:)
returns all entities for a particular team, and moveComponents(for:)
returns all move components for a particular team. You’ll need these shortly.
Switch back to MoveComponent.swift and add this new method:
func closestMoveComponent(for team: Team) -> GKAgent2D? { var closestMoveComponent: MoveComponent? = nil var closestDistance = CGFloat(0) let enemyMoveComponents = entityManager.moveComponents(for: team) for enemyMoveComponent in enemyMoveComponents { let distance = (CGPoint(enemyMoveComponent.position) - CGPoint(position)).length() if closestMoveComponent == nil || distance < closestDistance { closestMoveComponent = enemyMoveComponent closestDistance = distance } } return closestMoveComponent } |
This is some code to find the closest move component on a particular team from the current move component. You will use this to find the closest enemy now.
Add this new method to the bottom of the class:
override func update(deltaTime seconds: TimeInterval) { super.update(deltaTime: seconds) // 1 guard let entity = entity, let teamComponent = entity.component(ofType: TeamComponent.self) else { return } // 2 guard let enemyMoveComponent = closestMoveComponent(for: teamComponent.team.oppositeTeam()) else { return } // 3 let alliedMoveComponents = entityManager.moveComponents(for: teamComponent.team) // 4 behavior = MoveBehavior(targetSpeed: maxSpeed, seek: enemyMoveComponent, avoid: alliedMoveComponents) } |
This is the update loop that puts it all together.
- Here you find the team component for the current entity.
- Here you use the helper method you wrote to find the closest enemy.
- Here you use the helper method you wrote to find all your allies move components.
- Finally, you reset the behavior with the updated values.
Almost done; just a few cleanup items to do. Open EntityManager.swift and update the line that sets up the componentSystems
property as follows:
lazy var componentSystems: [GKComponentSystem] = { let castleSystem = GKComponentSystem(componentClass: CastleComponent.self) let moveSystem = GKComponentSystem(componentClass: MoveComponent.self) return [castleSystem, moveSystem] }() |
Remember, this is necessary so that your update(_:)
method gets called on your new MoveComponent
.
Next open Quirk.swift and modify your initializer to take the entityManager
as a parameter:
init(team: Team, entityManager: EntityManager) { |
Then add this to the bottom of init(team:entityManager:)
:
addComponent(MoveComponent(maxSpeed: 150, maxAcceleration: 5, radius: Float(texture.size().width * 0.3), entityManager: entityManager)) |
This creates your move component with some values that work well for the quick Quirk monster.
You need a move component for the castle too – this way they can be one of the agents considered for the “closest possible enemy”. To do this, open Castle.swift and modify your initializer to take the entityManager
as a parameter:
init(imageName: String, team: Team, entityManager: EntityManager) { |
Then add this to the bottom of init(imageName:team:entityManager:)
:
addComponent(MoveComponent(maxSpeed: 0, maxAcceleration: 0, radius: Float(spriteComponent.node.size.width / 2), entityManager: entityManager)) |
Finally, move to EntityManager.swift and inside spawnQuirk(team:)
, modify the line that creates the Quirk
instance as follows:
let monster = Quirk(team: team, entityManager: self) |
Also open GameScene.swift and modify the line in didMove(to:)
that creates the humanCastle
:
let humanCastle = Castle(imageName: "castle1_atk", team: .team1, entityManager: entityManager) |
And similarly for aiCastle
:
let aiCastle = Castle(imageName: "castle2_atk", team: .team2, entityManager: entityManager) |
Build and run, and enjoy your moving monsters:
Congratulations! At this point you have a good understanding of how to use the new Entity-Component system in GameplayKit, along with using Agents, Goals, and Behaviors for movement.
Where to Go From Here?
Here is the finished example project from this GameplayKit tutorial.
At this point, you can repeat the process described here to add more components to your game – for example a component to deal melee damage, a component to fire lasers, a component to display a health bar, etc.
I thought about extending the tutorial to show how to do all this, but this tutorial has gone on long enough. So instead, I created a project for the complete game that you can download and take a look at for reference.
I hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!
The post GameplayKit Tutorial: Entity-Component System, Agents, Goals, and Behaviors appeared first on Ray Wenderlich.