GameplayKit > Scene Editor Navigation Graph > How to use it for pathfinding?

1.2k views Asked by At

Not long ago, Apple introduced GameplayKit elements to Xcode's Scene Editor, which is great. However, I seem to have issues with the Navigation Graph element: Scene Editor Navigation Graph

What I try to achieve is to draw a GKGraph from the Scene Editor, retrieve it in code, and use it as a base for pathfinding.

So I draw a GKGraph first:

Scene Editor drawn GKGraph

Then I retrieve it in GameScene.swift like this:

graph = self.graphs.values.first

where graphs is a predefined array by Apple to store the GKGraphs from the Scene Editor. So far so good.

Then I want for the player to find the path to the tap location on screen. For this, I use UITapGestureRecognizer with the following callback method:

var moving = false
func moveToLocation(recognizer: UITapGestureRecognizer) {
    var tapLocation = recognizer.location(in: recognizer.view!)
    tapLocation = self.convertPoint(fromView: tapLocation)

    guard (!moving) else { return }
    moving = true

    let startNode = GKGraphNode2D(point: vector_float2(Float(player.visualComponent.node.position.x), Float(player.visualComponent.node.position.y)))
    let endNode = GKGraphNode2D(point: vector_float2(Float(tapLocation.x), Float(tapLocation.y)))

    NSLog(graph.nodes.debugDescription)

    graph.connectToLowestCostNode(node: startNode, bidirectional: true)
    graph.connectToLowestCostNode(node: endNode, bidirectional: true)

    NSLog(graph.nodes.debugDescription)

    let path: [GKGraphNode] = graph.findPath(from: startNode, to: endNode)
    guard path.count > 0 else { moving = false; return }

    var actions = [SKAction]()

    for node: GKGraphNode in path {
        if let point2d = node as? GKGraphNode2D {
            let point = CGPoint(x: CGFloat(point2d.position.x), y: CGFloat(point2d.position.y))
            let action = SKAction.move(to: point, duration: 1.0)
            actions.append(action)
        }
    }

    graph.remove([startNode, endNode])

    // Convert those actions into a sequence action, then run it on the player node.
    let sequence = SKAction.sequence(actions)
    player.visualComponent.node.run(sequence, completion: { () -> Void in
        // When the action completes, allow the player to move again.
        self.moving = false
    })
}

Using this method, I get the player to go to the tap location. The problem is that the path returned by GameplayKit only includes the startNode and the endNode, meaning that the player does not walk on the the pre-drawn GKGraph at all but walks straight to the endNode.

Note: When I'm first running NSLog(graph.nodes.debugDescription) it lists all 10 pre-drawn nodes. When I'm then running it again after graph.connectToLowestCostNode, I get all 12 nodes. So my graph seems to contain everything needed for pathfinding.

What do you think is the problem? Why is graph.findPath not using the pre-drawn 10 nodes to define the path? Your answers would be much appreciated.

1

There are 1 answers

0
Filip Juncu On BEST ANSWER

It seems like GameplayKit's .findPath method does not work properly when called on a GKGraph created from Scene Editor. What I did is, I cloned the nodes from the pre-drawn GKGraph to a programmatically created GKGraph:

let newGraph = GKGraph()
for drawnNode in graph.nodes! {
    newGraph.add([drawnNode])
}

Then, .findPath on it works just fine:

newGraph.connectToLowestCostNode(node: startNode, bidirectional: true)
newGraph.connectToLowestCostNode(node: endNode, bidirectional: true)
let path: [GKGraphNode] = newGraph.findPath(from: startNode, to: endNode)

Variable path contains now a path generated considering all 10 + 2 nodes.

Wrap-up: You cannot directly use a Navigation Graph created in Scene Editor for pathfinding. My solution was to first clone its nodes to a programmatically created GKGraph and then use the latest to do the rest.