GKObstacleで障害物を作る
はじめに
Pathfindingの記事ではObstacleを使って目的のゴールまでの障害を作成しましたが、Agents, Goals & BehavioursでもObstacleを作成しゴールにたどり着くまでの道のりに障害物を配置することが可能です。
GamePlayKitの障害物について
GamePlayKitにはGKObstacleというクラスが存在します。
私たち開発者が直接GKObstacleクラスを使うのではなく、GKObstacleを継承したGKCircleObstacle,GKShapeObstacle,GKPolygonObstacleのどれのインスタンスを作成し使用することになります。
今回はSpriteKitを使用して2次元的な障害物を作りたいので、GKPolygonObstacleを使ってみました。
GKPolygonObstacle
GKPolygonObstacleのインスタンスを作るのは簡単で以下のように障害物にしたいSKNodeの配列を渡してあげるだけでOKです。
let obstacle: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 40)
shape.position.y = 100
shape.fillColor = .white
return shape
}()
let polygonObstacle = SKNode.obstacles(fromNodeBounds: [obstacle])
障害物を計算に適用させる
作成した障害物ですが、Agents, Goals & Behavioursの場合GKAgentの動きを計算する要素に関してはGKBehaviorにGKGoalとして渡してあげれば良いので、以下のように目的地を障害物を一緒に渡してあげれば自動的に計算してくれます。
GKBehavior(goals: [GKGoal(toSeekAgent: playerAgent),
GKGoal(toAvoid: polygonObstacle, maxPredictionTime: 2)])
maxPredictionTimeは、GKAgentが何秒前に障害物との衝突予測を行うかという時間です。上記のコードでは2秒を設定しているので、障害物に衝突する2秒前から回避の計算が移動座標に組み込まれる形になっています。
コード
class GameScene: SKScene {
let player: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 20)
shape.fillColor = .purple
return shape
}()
let enemy: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 10)
shape.position.y = 300
shape.fillColor = .white
return shape
}()
let obstacle: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 40)
shape.position.y = 100
shape.fillColor = .white
return shape
}()
let agentSystem = GKComponentSystem(componentClass: GKAgent2D.self)
let playerAgent = GKAgent2D()
var prevTime: TimeInterval = 0
lazy var enemyAgent: GKAgent2D = {
let agent = GKAgent2D()
agent.maxAcceleration = 1000
agent.maxSpeed = 100
agent.position = vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y))
agent.delegate = self
let polygonObstacle = SKNode.obstacles(fromNodeBounds: [obstacle])
agent.behavior = GKBehavior(goals: [GKGoal(toSeekAgent: playerAgent),
GKGoal(toAvoid: polygonObstacle, maxPredictionTime: 2)])
return agent
}()
override func didMove(to view: SKView) {
super.didMove(to: view)
self.addChild(player)
self.addChild(enemy)
self.addChild(obstacle)
agentSystem.addComponent(enemyAgent)
}
override func update(_ currentTime: TimeInterval) {
let delta = prevTime == 0 ? 0 : currentTime - prevTime
prevTime = currentTime
agentSystem.update(deltaTime: delta)
playerAgent.position = vector_float2(x: Float(player.position.x), y: Float(player.position.y))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touches.forEach {
let touchPos = $0.location(in: self)
player.position = touchPos
}
}
}
extension GameScene: GKAgentDelegate {
func agentDidUpdate(_ agent: GKAgent) {
if let agent = agent as? GKAgent2D {
enemy.position = CGPoint(x: CGFloat(agent.position.x), y: CGFloat(agent.position.y))
}
}
}
オブジェクトを増やしてみた
タップした位置についてくるオブジェクトがかわいい!
速度や加速度をいじると動き方が変わるのでいい感じに遊べます
class GameScene: SKScene {
let player: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 20)
shape.fillColor = .purple
return shape
}()
var enemys: [SKShapeNode] = []
var enemyAgents:[GKAgent] = []
let obstacle: [SKShapeNode] = {
var shapes: [SKShapeNode] = []
let shapePos = [CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: -100),
CGPoint(x: -100, y: 100),
CGPoint(x: -100, y: -100),
CGPoint(x: 100, y: 300),
CGPoint(x: -100, y: 300),
CGPoint(x: 100, y: -300),
CGPoint(x: -100, y: -300),
]
for i in 0..<shapePos.count {
let shape = SKShapeNode(circleOfRadius: 20)
shape.fillColor = .white
shape.position = shapePos[i]
shapes.append(shape)
}
return shapes
}()
let agentSystem = GKComponentSystem(componentClass: GKAgent2D.self)
let playerAgent = GKAgent2D()
var prevTime: TimeInterval = 0
override func didMove(to view: SKView) {
super.didMove(to: view)
createEnemy(count: 300)
self.addChild(player)
enemys.forEach{self.addChild($0)}
obstacle.forEach{self.addChild($0)}
}
private func createEnemy(count: Int){
for _ in 0..<count {
let enemyAgent: GKAgent2D = {
let enemy: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 3)
shape.position = CGPoint(x: CGFloat.random(in: -200...200), y: CGFloat.random(in: -200...200))
shape.strokeColor = .lightGray
shape.lineWidth = 3
return shape
}()
let agent = GKAgent2D()
agent.maxAcceleration = 1000
agent.maxSpeed = Float.random(in: 100...200)
agent.position = vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y))
agent.delegate = self
let polygonObstacle = SKNode.obstacles(fromNodeBounds: obstacle)
agent.behavior = GKBehavior(goals: [GKGoal(toSeekAgent: playerAgent),
GKGoal(toAvoid: polygonObstacle, maxPredictionTime: 2)])
enemys.append(enemy)
return agent
}()
enemyAgents.append(enemyAgent)
agentSystem.addComponent(enemyAgent)
}
}
override func update(_ currentTime: TimeInterval) {
let delta = prevTime == 0 ? 0 : currentTime - prevTime
prevTime = currentTime
agentSystem.update(deltaTime: delta)
playerAgent.position = vector_float2(x: Float(player.position.x), y: Float(player.position.y))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touches.forEach {
let touchPos = $0.location(in: self)
player.position = touchPos
}
}
}
extension GameScene: GKAgentDelegate {
func agentDidUpdate(_ agent: GKAgent) {
if let agent = agent as? GKAgent2D,
let index = enemyAgents.firstIndex(where: { $0 == agent }) {
let enemy = enemys[index]
enemy.position = CGPoint(x: CGFloat(agent.position.x), y: CGFloat(agent.position.y))
}
}
}