Add Pathfinding to Your Game

Pathfinding is a big topic, so it won’t be possible to cover everything. However, this section aims to give you enough information to get started with it.

You started this project in Configure the View and Load the Scene, in which you were instructed to pay little attention to the GameplayKit-related code, including the GKScene’s graphs property.

The graphs property contains the list of the pathfinding graph objects managed by the scene, in other words, the navigation graphs you add to the scene using the Scene Editor.

At the moment, your scene doesn’t contain any navigation graphs, so the first step is to add one and to learn how the Navigation Graph Editor works.

Using the Navigation Graph Editor

Switch to the GameScene.sks file. Now, open the Object Library (L) and look for the Navigation graph object:

images/PlanningRoutesAndCreatingBelievableAI/gpk-nav-graph.png

Drag the Navigation graph object onto the scene. Note that you can’t drag this object into the Scene Graph View—you can only drag it onto the scene:

images/PlanningRoutesAndCreatingBelievableAI/gpk-add-nav-graph.png

Place this object near the player node, setting its position to (X: -75, Y: -60). You can use the Attributes Inspector to set the position, although its exact position is irrelevant. (Using the Attributes Inspector, you can also set the graph’s name; although, for this example, the default name of Graph is fine.)

To edit the graph, right-click the new graph object in the Scene Graph View and select Edit Navigation Graph:

images/PlanningRoutesAndCreatingBelievableAI/gpk-edit-nav-graph.png

This action takes you into the Navigation Graph Editor. You know you’re there because a black “instruction box” shows up at the bottom of the Scene Editor:

images/PlanningRoutesAndCreatingBelievableAI/gpk-nav-graph-editor.png

To select a node, click on it. Selected nodes look like this:

images/PlanningRoutesAndCreatingBelievableAI/gpk-selected-node.png

To move a node’s position, first select it, then drag it around. However, you must make this three distinct actions:

  • Click to select the node.
  • Click the same node again to indicate your desire to reposition the node.
  • While still holding down the mouse button, drag the node to its new location.

To remove a node—contrary to the on-screen instructions—you need to right-click it; this action deletes the node immediately. There is no undo, so be careful.

To add a new node, left-click anywhere within the scene. You can either add a stand-alone node or a node that is joined to another node using a connection. Connections define available and acceptable paths, so orphaned nodes are not something you will typically need or use.

To move around the scene—and this is the tricky part—you need to left-click a node to select it, but don’t let go of the mouse button. Instead, continue to hold it down while moving the mouse in the direction you want to move around the scene.

To exit the editor, click the X button in the black instruction box.

I won’t lie: editing a graph using the Scene Editor is a little frustrating. But once you get the hang of it, it’s not too bad.

Spend the next few minutes mapping out a path for the Key collectible. Try to go through the generators so that you can test obstacle avoidance. One example of what you might do is shown.

images/PlanningRoutesAndCreatingBelievableAI/gpk-graph.png

With your path mapped out, you’re ready to write the code necessary that’ll tell the Key to follow the path.

Following the Navigation Path

Save the GameScene.sks file and switch to the GameScene.swift file. At the bottom of the file, but still inside the GameScene class, add the following method:

 func​ ​startAdvancedNavigation​() {
 
 // Check for a navigation graph and a key node
 guard​ ​let​ sceneGraph = graphs.values.first,
 let​ keyNode = ​childNode​(withName: ​"key"​) ​as?​ ​SKSpriteNode​ ​else​ {
 return
  }
 
 // Set up the agent
 let​ agent = ​GKAgent2D​()
 
 // Set up the delegate and the initial position
  agent.delegate = keyNode
  agent.position = ​vector_float2​(​Float​(keyNode.position.x),
 Float​(keyNode.position.y))
 
 // Set up the agent's properties
  agent.mass = 1
  agent.speed = 50
  agent.maxSpeed = 100
  agent.maxAcceleration = 100
  agent.radius = 60
 
 // Find obstacles (generators)
 var​ obstacles = [​GKCircleObstacle​]()
 
 // Locate generator nodes
 enumerateChildNodes​(withName: ​"generator_*"​) {
  (node, stop) ​in
 
 // Create compatible obstacle
 let​ circle = ​GKCircleObstacle​(radius: ​Float​(node.frame.size.width/2))
  circle.position = ​vector_float2​(​Float​(node.position.x),
 Float​(node.position.y))
  obstacles.​append​(circle)
  }
 
 // Find the path
 if​ ​let​ nodesOnPath = sceneGraph.nodes ​as?​ [​GKGraphNode2D​] {
 
 // Show the path (optional code)
 for​ (index, node) ​in​ nodesOnPath.​enumerated​() {
 let​ shapeNode = ​SKShapeNode​(circleOfRadius: 10)
  shapeNode.fillColor = .green
  shapeNode.position = ​CGPoint​(x: ​CGFloat​(node.position.x),
  y: ​CGFloat​(node.position.y))
 
 // Add node number
 let​ number = ​SKLabelNode​(text: ​"​​(​index​)​​"​)
  number.position.y = 15
  shapeNode.​addChild​(number)
 
 addChild​(shapeNode)
  }
 // (end optional code)
 
 // Create a path to follow
 let​ path = ​GKPath​(graphNodes: nodesOnPath, radius: 0)
  path.isCyclical = ​true
 
 // Set up the goals
 let​ followPath = ​GKGoal​(toFollow: path, maxPredictionTime: 1.0,
  forward: ​true​)
 let​ avoidObstacles = ​GKGoal​(toAvoid: obstacles, maxPredictionTime: 1.0)
 
 // Add behavior based on goals
  agent.behavior = ​GKBehavior​(goals: [followPath, avoidObstacles])
 
 // Set goal weights
  agent.behavior?.​setWeight​(0.5, for: followPath)
  agent.behavior?.​setWeight​(100, for: avoidObstacles)
 
 // Add agent to component system
  agentComponentSystem.​addComponent​(agent)
  }
 }

There’s a lot of code here, so take a moment to look through it. Some of it looks familiar, like the agent-, goal-, and behavior-related code, but look at the code that locates the generator nodes and follows the path. Pay close attention to the circle property where you create a GKCircleObstacle obstacle. This is where you create an obstacle for the agent to avoid, which happens to be right where the generators are placed.

Next, in the didMove(to:) method, add the following code at the bottom of that method:

 startAdvancedNavigation​()

Build and run the program. Notice how the Key follows the set path while also avoiding the generators. Also notice how when Val destroys a generator, the Key continues to avoid where the generator once stood. For this example, that’s fine, but you may decide to write some additional code to remove an obstacle once its been destroyed.

Also, I had you add all of this pathfinding code into the main GameScene class; your challenge (should you choose to accept it) is to refactor this code and turn it into a reusable component.

Before you begin this challenge, there are two things to consider:

  • First, the scene graph isn’t immediately available, so you’ll need to implement a starting and stopping routine similar to what you did with the monster generator, letting you delay the start of the routine.

  • Second, I never told you how to “pause” an agent. Here’s a hint: an agent can’t do its work if it has no delegate.

If you get stuck on this challenge, you can find one possible solution in the challenge folder for this chapter’s resources along with a Challenge.md file that gives you a brief overview of the steps you need to take to solve this challenge. Good luck, and have fun.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset