GamePlayKitのAgents, Goals & Behavioursを触ってみた
はじめに
Agents, Goals & BehavioursはGamePlayKitに搭載されている機能の1つでAIで、ターゲットとなるゴールの位置に向かってAgentsを移動させることのできる機能です。
以下の画像はAgents, Goals & Behavioursで実装されていたサンプルの動きをです。この完成形を目指して遊んでみました。
Agents, Goals & Behavioursを実装してみる
できるだけミニマムな形で実装をしてみました。
playerはとしてタップした位置に移動させ、agentDidUpdateでenemyの位置を変更させることで追いかける処理を作っています。
class GameScene: SKScene {
let player: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 20)
shape.strokeColor = .gray
return shape
}()
let enemy: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 10)
shape.position.y = 100
shape.strokeColor = .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 = 300
agent.maxSpeed = 500
agent.position = vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y))
agent.delegate = self
//敵の行動を作成、playerAgentをゴールに設定
agent.behavior = GKBehavior(goals: [GKGoal(toSeekAgent: playerAgent)])
return agent
}()
override func didMove(to view: SKView) {
super.didMove(to: view)
self.addChild(player)
self.addChild(enemy)
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))
}
}
}
背景黒いのでちょっとみづらいですが追いかけてます
上記のコードの中でわかりにくいのはupdateで時間を計算してagentSystemのupdateに渡しているところだと思います。
以下のドキュメントに書いてあるように最後の更新された以降のデルタタイムでコンポーネントの更新を行うようです、 しっかりと理解できているわけではないですが、currentTimeをそのまま渡すとobjectが明後日の方向に吹き飛んでしまうので時間の計算は必須です。
/**
* Updates each component with the given delta time since the last update. Each component thus performs its time
* based logic with a single message.
*/
追いかける敵を増やしてみる
class GameScene: SKScene {
let player: SKShapeNode = {
let shape = SKShapeNode(circleOfRadius: 20)
shape.fillColor = .lightGray
shape.strokeColor = .gray
return shape
}()
var enemies:[SKShapeNode] = []
let agentSystem = GKComponentSystem(componentClass: GKAgent2D.self)
let playerAgent = GKAgent2D()
var prevTime: TimeInterval = 0
var enemyAgents: [GKAgent2D] = []
override func didMove(to view: SKView) {
super.didMove(to: view)
self.addChild(player)
createEnemy(count: 800)
}
private func createEnemy(count: Int){
for _ in 0..<count {
let enemy = SKShapeNode(circleOfRadius: 10)
enemy.position = CGPoint(x: CGFloat.random(in: -200...200), y: CGFloat.random(in: -200...200))
enemy.strokeColor = .white
self.addChild(enemy)
enemies.append(enemy)
let agent = GKAgent2D()
agent.maxAcceleration = Float.random(in: 10...300)
agent.maxSpeed = Float.random(in: 200...500)
agent.position = vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y))
agent.delegate = self
agent.behavior = GKBehavior(goals: [GKGoal(toSeekAgent: playerAgent)])
agentSystem.addComponent(agent)
enemyAgents.append(agent)
}
}
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 = enemies[index]
enemy.position = CGPoint(x: CGFloat(agent.position.x), y: CGFloat(agent.position.y))
}
}
}
参考文献
A Look at Agents, Goals & Behaviours in GameplayKit
iOS GameplayKitの「Agents, Goals, and Behaviors」で作る、鬼ごっごの鬼AI (3/3)