GamePlayKitのpathfindingで障害物を避けてゴールに向かう

GamePlayKitにはゲームに役立つロジックを内包したライブラリです。今回はpathfindingという機能を使ってみました。

ネット上に転がっている情報はobj-cで書かれたものが多く、Swiftで実装しているものやチュートリアル的な記事が全然なかったので苦労しました💦

障害物を作成して経路を探索してみる

//障害物の場所
let points = [vector_float2(0, 0), vector_float2(20, 0), vector_float2(20, 20), vector_float2(0, 20)]
let obstables = GKPolygonObstacle(points: points)
//通り道を探すクラス
let graph = GKObstacleGraph(obstacles: [obstables], bufferRadius: 0.0)
let start = GKGraphNode2D(point: vector_float2(-5, 10))
let end = GKGraphNode2D(point: vector_float2(25, 10))
graph.connectUsingObstacles(node: start)
graph.connectUsingObstacles(node: end)

let nodes = graph.findPath(from: start, to: end)
print("node: \(nodes)")

printで表示される座標は

[GKGraphNode2D: {-5.00, 10.00}
GKGraphNode2D: {-0.00, -0.00}
GKGraphNode2D: {20.00, -0.00}
GKGraphNode2D: {25.00, 10.00}] です。

座標だけあってもよくわからないと思うので絵にしてみました。(適当に作っていたので障害物が(20,20)なのに長方形になってるのは許してください..)

4つの座標を順番につなぎ合わせると緑色の経路のような形になります。

オブジェクトにActionをくっつけて移動させてみる

今、アフリカで70年に一度の蝗害が起きているようで、バッタがいろんな草を食べてしまうらしいです。

ちょうど良い題材だと思ったので、サンプルプログラムではバッタをサラダに向けて移動させてみました。

class GameScene: SKScene {
    
    var battaSprite: SKSpriteNode = {
        let batta = SKSpriteNode(imageNamed: "batta")
        batta.position = CGPoint(x: -200, y: 150)
        batta.scale(to: CGSize(width: 100, height: 50))
        return batta
    }()
    
    var obstables: SKShapeNode = {
        let rect = SKShapeNode(rectOf: CGSize(width: 100, height: 300))
        return rect
    }()
    
    var saradSprite: SKSpriteNode = {
        let sarad = SKSpriteNode(imageNamed: "salad")
        sarad.position = CGPoint(x: 200, y: -150)
        sarad.scale(to: CGSize(width: 50, height: 50))
        return sarad
    }()
    
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        
        addChild(saradSprite)
        addChild(battaSprite)
        addChild(obstables)
        
        let polygonObstacles = SKNode.obstacles(fromNodeBounds: [obstables])
        let graph = GKObstacleGraph(obstacles: polygonObstacles, bufferRadius: 10.0)
        let start =  GKGraphNode2D(point: vector2(Float(battaSprite.position.x), Float(battaSprite.position.y)))
        let end = GKGraphNode2D(point: vector2(Float(saradSprite.position.x), Float(saradSprite.position.y)))
        graph.connectUsingObstacles(node: start)
        graph.connectUsingObstacles(node: end)
        
        let graphNodes = graph.findPath(from: end, to: start) as!  [GKGraphNode2D]
        let newActions: [SKAction] = graphNodes.map { n in
            print("x:\(CGFloat(n.position.x))y:\(CGFloat(n.position.y))")
            return SKAction.move(to: CGPoint(x: CGFloat(n.position.x), y: CGFloat(n.position.y)), duration: 2)
        }
        //バッタの初期位置移動するアクションを削る
        let battaMoveAction = newActions.dropLast()
        battaSprite.run(SKAction.sequence(battaMoveAction.reversed()))
    }
}

参考文献

GKObstacleGraph not finding path when obstacle introduced

GameplaykitのPathfindingを使ってみる

GKGraph

Pathfinding