2

I'm working on a Gameplaykit pathfinding proof-of-concept and I can't get GKObstacleGraph to find paths correctly.

In the following code snippet (it should work in an Xcode 7.2 playground), path2 is always an empty array if there is an obstacle provided when the graph is created. If I create the obGraph object with an empty array of obstacles the findPathFromNode returns a correct path.

The obstacle created should be a simple U shaped polygon with the end point being inside the U.

import UIKit

import GameplayKit

let pts = [vector_float2(2,2),
    vector_float2(3,2),
    vector_float2(3,6),
    vector_float2(7,6),
    vector_float2(7,2),
    vector_float2(8,3),
    vector_float2(8,7),
    vector_float2(2,7),
    vector_float2(2,2)]
let obstacle1 = GKPolygonObstacle(points: UnsafeMutablePointer(pts) ,
    count: pts.count)

let obGraph = GKObstacleGraph(obstacles: [obstacle1], bufferRadius: 0)

let startPt = GKGraphNode2D(point: vector_float2(5,9))
let endPt = GKGraphNode2D(point: vector_float2(5,5))
let pt3 = GKGraphNode2D(point: vector_float2(0,0))
let pt4 = GKGraphNode2D(point: vector_float2(0,9))
let pt5 = GKGraphNode2D(point: vector_float2(5,0))
let pt6 = GKGraphNode2D(point: vector_float2(10,0))

obGraph.connectNodeUsingObstacles(startPt)
obGraph.connectNodeUsingObstacles(endPt)
obGraph.connectNodeUsingObstacles(pt3)
obGraph.connectNodeUsingObstacles(pt4)
obGraph.connectNodeUsingObstacles(pt5)
obGraph.connectNodeUsingObstacles(pt6)
startPt.connectedNodes
endPt.connectedNodes
pt3.connectedNodes

let path2 = obGraph.findPathFromNode(startPt, toNode: endPt)
print(path2)
Jack Cox
  • 3,290
  • 24
  • 25
  • Best to think of GameplayKit as a gesture of intention rather than a usable finished product. – Confused Jan 20 '16 at 17:23
  • 1
    Well, that's not very encouraging @Confused – Jack Cox Jan 20 '16 at 23:10
  • I should also point out that almost nobody uses GameplayKit. So your chances of getting a knowledgeable answer... are slim. – Confused Jan 21 '16 at 01:40
  • Not sure if you ever found a solution, but I was having similar difficulties with my application here : http://stackoverflow.com/questions/35705597/prevent-location-offset-with-spritekit-camera-node?noredirect=1#comment59095151_35705597 – Will Von Ullrich Feb 29 '16 at 21:12

2 Answers2

5

I was having the same problem as Jack. I started with Will's code example and translated it to Swift 5.0 in Xcode 10.3. I added it into Xcode's Game project template. I still had the same result: an empty array from findPath(from:to:).

After playing around with the code, I realized that anything physics-related is going to impact pathing. The only way to show code that will work for everyone is to include the creation of SKScene and all SKNode instances. Notice I set gravity to 0 in SKPhysicsWorld and I add no SKPhysicsBody to anything.

Run this in a Playground. You activate the animation by tapping anywhere in the scene.

import PlaygroundSupport
import SpriteKit
import GameKit

class GameScene: SKScene {

    let nodeToMove:SKShapeNode = {
        let n = SKShapeNode(circleOfRadius: 10)
        n.lineWidth = 2
        n.strokeColor = UIColor.orange
        n.position = CGPoint(x: -200, y: 150)
        return n
    }()

    override func sceneDidLoad() {
        addChild(nodeToMove)

        let nodeToFind = SKShapeNode(circleOfRadius: 5)
        nodeToFind.lineWidth = 2
        nodeToFind.strokeColor = UIColor.red
        addChild(nodeToFind)
        nodeToFind.position = CGPoint(x: 200, y: -150)

        let nodeToAvoid = SKShapeNode(rectOf: CGSize(width: 100, height: 100))
        nodeToAvoid.lineWidth = 4
        nodeToAvoid.strokeColor = UIColor.blue
        addChild(nodeToAvoid)
        nodeToAvoid.position = CGPoint.zero

        let polygonObstacles = SKNode.obstacles(fromNodeBounds: [nodeToAvoid])
        let graph = GKObstacleGraph(obstacles: polygonObstacles, bufferRadius: 10.0)
        let end = GKGraphNode2D(point: vector2(Float(nodeToMove.position.x), Float(nodeToMove.position.y)))
        let start = GKGraphNode2D(point: vector2(Float(nodeToFind.position.x), Float(nodeToFind.position.y)))
        graph.connectUsingObstacles(node: end)
        graph.connectUsingObstacles(node: start)

        graphNodes = graph.findPath(from: end, to: start) as!  [GKGraphNode2D]
        print("graphNodes = \(graphNodes)")
    }

    var graphNodes = [GKGraphNode2D]()

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        touches.first.flatMap {_ in
            let newActions: [SKAction] = graphNodes.map { n in
                return SKAction.move(to: CGPoint(x: CGFloat(n.position.x), y: CGFloat(n.position.y)), duration: 2)
            }
            nodeToMove.run(SKAction.sequence(newActions))
        }
    }
}

let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
let scene = GameScene(size: CGSize(width: 640, height: 480))
scene.scaleMode = .aspectFill
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
scene.backgroundColor = UIColor.purple
scene.physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
sceneView.presentScene(scene)

PlaygroundSupport.PlaygroundPage.current.liveView = sceneView

The output in the console is:

graphNodes = [GKGraphNode2D: {-200.00, 150.00}, GKGraphNode2D: {62.85, 62.85}, GKGraphNode2D: {200.00, -150.00}]

The start state:

Scene before animation starts

The end state:

Scene after animation completes

Warning: I do not know why it takes an entire second between the user tap and the start of the animation. Performance tuning is a separate topic.

Jeff
  • 3,829
  • 1
  • 31
  • 49
2

(sorry in advance for obj-c not swift)

It is my impression that adding each vertex point as a connection is not necessary, merely telling the GKObstacleGraph about the GKPolygonObstacle will be enough for it to avoid the generated polgyon shape. I have used the following and received 3 nodes to create a pathway around my obstacle (with a buffer of 10.0f) seen here:

- (void)findPathWithNode:(SKNode *)nodeToFindPath {

    NSMutableArray *obstaclesToAvoid = [NSMutableArray array];

    for (SKNode *objectInScene in chapterScene.children) {
        if ([objectInScene.name isEqualToString:@"innerMapBoundary"]) {
            [obstaclesToAvoid addObject:objectInScene];
        }
    }


    /* FYI: The objectInScene is just a black SKSpriteNode with a 
       square physics body 100 x 100 rotated at 45°
    */

    NSArray *obstacles = [SKNode obstaclesFromNodePhysicsBodies:[NSArray arrayWithArray:obstaclesToAvoid]];
    GKObstacleGraph *graph = [GKObstacleGraph graphWithObstacles:obstacles bufferRadius:10.0f];


    GKGraphNode2D *end = [GKGraphNode2D nodeWithPoint:vector2((float)character.position.x, (float)character.position.y)];
    GKGraphNode2D *start = [GKGraphNode2D nodeWithPoint:vector2((float)nodeToFindPath.position.x, (float)nodeToFindPath.position.y)];


    [graph connectNodeUsingObstacles:end];
    [graph connectNodeUsingObstacles:start];


    NSArray *pathPointsFound = [graph findPathFromNode:enemy toNode:target];
    NSLog(@"Path: %@", pathPointsFound);


    GKPath *pathFound;


    // Make sure that there were at least 2 points found before creating the path
    if (pathPointsFound.count > 1) {
        for (GKGraphNode2D *nodeFound in pathPointsFound) {

            // This is just to create a visual for the points found

            vector_float2 v = (vector_float2){(float)nodeFound.position.x, (float)nodeFound.position.y};
            CGPoint p = CGPointMake(v.x, v.y);
            SKShapeNode *shapetoadd = [SKShapeNode shapeNodeWithCircleOfRadius:4];
            shapetoadd.name = @"shapeadded";
            shapetoadd.fillColor = [UIColor redColor];
            shapetoadd.position = p;
            [chapterScene addChild:shapetoadd];
        }

        pathFound = [GKPath pathWithGraphNodes:pathPointsFound radius:10.0];
    }

}

Hopefully this points you in the right direction!

Will Von Ullrich
  • 2,129
  • 2
  • 15
  • 42
  • If path is not found, how can I find a path to closest valid location ? – Coldsteel48 Sep 17 '16 at 17:57
  • Not entirely sure...seems if gameplaykit does not find a path it's not too happy. I guess you could try iterating around your current location a certain "d" distance away until you find a valid path? Of course this would be extremely expensive in terms of computation time / memory, but not sure if apple offers a way to do that – Will Von Ullrich Sep 20 '16 at 17:46
  • Yeah Apple didn't provide enough tools solving it... I asked it as a separate Question on SO http://stackoverflow.com/questions/39546759/gkobstaclegraph-how-to-find-closest-valid-point With slight modified math from this answer I managed to get the path – Coldsteel48 Sep 20 '16 at 19:53
  • Glad i could help! – Will Von Ullrich Sep 21 '16 at 14:17