前節で敵の移動を実装しましたが、移動先を自分で計算して書いているので柔軟性がありません。今後、もし「道に沿わずに移動できる飛行ユニットを作る」「プレーヤーが任意の場所に障害物を置ける」などの仕様が出てきたときには、今の実装方法では対応できません。
そこで移動ルートは自動で探索できるようにしようと思います。移動ルートの自動探索にはiOS 9から登場したGameplayKitという最新フレームワークを使っていきます。
今回はGameplayKitの中の「Pathfinding」という機能を使ってルートを探索します。Pathfindingのさまざまな方法でルート探索ができるのですが、今回は障害物情報を使ったルート作成を行います。
下図はPathfindingのサンプルで、ピンクの四角が障害物で赤い線がルートです。これを見ると、障害物を避けたルートを表示できているのが分かると思います。
サンプルコードは「tnantoka/GameplayKitSandbox」のものを利用させていただきました
GameplayKitの「GKObstacleGraph」クラスに障害物の位置情報・移動開始位置・移動終了位置を渡すとルートを返却してくれます。受け取ったルート情報を基にSKActionを作って敵を移動させる実装を今回は行います。
実際に処理を書いていきます。前節の「敵をルートに沿って行動させる」で作った箇所を以下のように書き換えてください。
class GameScene: SKScene, SKPhysicsContactDelegate { // …略 override func didMoveToView(view: SKView) { // …略 // 障害物情報を取得 let fields = children.filter { $0.name == "Field0" } let obstacles = SKNode.obstaclesFromNodePhysicsBodies(fields) // ルート情報を取得 let graph = GKObstacleGraph(obstacles: obstacles, bufferRadius: 10) let start = GKGraphNode2D(point: vector_float2(Float(fieldImageLength * 2), Float(view.frame.height))) let end = GKGraphNode2D(point: vector_float2(Float(fieldImageLength * 13), Float(view.frame.height - fieldImageLength * 9))) graph.connectNodeUsingObstacles(start) graph.connectNodeUsingObstacles(end) let nodes = graph.findPathFromNode(start, toNode: end) // アクションの作成 let actions = nodes.flatMap { $0 as? GKGraphNode2D }.map { SKAction.moveTo(CGPoint(x: Double($0.position.x), y: Double($0.position.y)), duration: 5) } enemy.position = CGPoint(x: fieldImageLength * 2, y: view.frame.height) enemy.physicsBody = SKPhysicsBody(rectangleOfSize: enemy.size) enemy.runAction(SKAction.sequence(actions)) { self.state = .GameOver let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6") myLabel.text = "ゲームオーバー" myLabel.fontSize = 45 myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20) self.addChild(myLabel) } addChild(enemy) } }
今回は「障害物情報を取得」「ルート情報を取得」「アクションの作成」という3種類の処理を追加しました。
障害物情報の取得では、芝画像(nameが"Field0"のSKNode)一覧を取得してobstaclesFromNodePhysicsBodiesメソッドを通して障害物一覧を取得しています。
// 障害物情報を取得 let fields = children.filter { $0.name == "Field0" } let obstacles = SKNode.obstaclesFromNodePhysicsBodies(fields)
下記は、ルート情報の取得処理です。GKObstacleGraphクラスに障害物の情報・開始位置・終了位置を渡した上でfindPathFromNodeメソッドを使って取得しています。
// ルート情報を取得 let graph = GKObstacleGraph(obstacles: obstacles, bufferRadius: 0) let start = GKGraphNode2D(point: vector_float2(Float(fieldImageLength * 2), Float(view.frame.height))) let end = GKGraphNode2D(point: vector_float2(Float(fieldImageLength * 13), Float(view.frame.height - fieldImageLength * 9))) graph.connectNodeUsingObstacles(start) graph.connectNodeUsingObstacles(end) let nodes = graph.findPathFromNode(start, toNode: end)
最後はアクションの生成です。上のfindPathFromNodeメソッドでは移動先の一覧を取得できました。ここでは取得した移動先情報を基に移動アクションの一覧を生成しています。
// アクションの作成 let actions = nodes.flatMap { $0 as? GKGraphNode2D }.map { SKAction.moveTo(CGPoint(x: Double($0.position.x), y: Double($0.position.y)), duration: 5) }
移動先を取得したら、それを使って敵を動かします。これで敵が自動生成したルートに沿って移動してくれるようになります。
enemy.runAction(SKAction.sequence(actions)) { self.state = .GameOver let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6") myLabel.text = "ゲームオーバー" myLabel.fontSize = 45 myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20) self.addChild(myLabel) }
今回はiOS 9から登場したGameplayKitという新しい機能も使ってみながら実装を進めてみました。今回のソースコード「TowerDefense.zip」は、こちらからダウンロードできます。
GameplayKitは機能の多いライブラリで、Pathfinding以外にも、さまざまな機能があります。今回の記事中でも紹介しましたが、GitHubにサンプルを置いている方もいるので、触ってみてはいかがでしょうか。
ゲームの方は、今回で敵の移動を実装できました。次回は敵の行動をさらに改善していきます。
田町のベンチャーで働くエンジニア。
仕事ではiPhoneアプリの開発やRailsを使ったWebサービス開発を行っている。最近のマイブームはUnityを使った3Dゲーム開発。
Copyright © ITmedia, Inc. All Rights Reserved.