連載
» 2017年04月03日 05時00分 公開

SwiftのGameplayKitでAIに追いかける、避ける、逃げる処理を追加するにはゲームの「敵」キャラで分かる「人工知能」の作り方(2)(2/4 ページ)

[杉本裕樹,マネーフォワード]

AIに「避ける」動作を追加する(GameplayKitを使わない場合)

 次は本項の目的である、AIへの「避ける」動作の追加を行います。最初にGameplayKitを使わない場合の実装方法を考えてみましょう。

 例として下のような配置のときの鬼の動きを検討します。

 鬼を動かす流れとしては、「鬼からプレイヤーへの最短経路を計算」→「それに沿って鬼を動かす」という流れになります。「それに沿って鬼を動かす」部分についてはSKActionで実装できるので、「鬼からプレイヤーへの最短経路を計算」について詳しく見ていきます。

 「鬼からプレイヤーへの最短経路を計算」の方法としては以下のような手順が考えられます。

  1. 障害物に沿った点を配置する
  2. 1.で作った点・プレイヤー・鬼をつなぐ
  3. 2.で作成した経路から最短経路を探す


1. 障害物に沿った点を配置する

 まずは鬼の通るルート候補を作ります。そのために障害物に沿って以下のような点を配置します。ここで配置した点が鬼の通過する座標になります。

2. 1で作った点・プレイヤー・鬼をつなぐ

 次は、先ほど作った点・プレイヤー・鬼を繋いで線を作ります。この線が鬼からプレイヤーへの最短経路候補になります。

 この時障害物と重なる線は作らないようにします。

3. 2で作成した経路から最短経路を探す

 最後に作成した最短経路候補の中から最短経路を探します。一番単純な方法としては、全てのルートの距離を計算して最短距離を導く方法が考えられます。

 普通に実装すると無限ループするため、「一度通った道は通らない」などのルールを追加する必要があります。

 以上が障害物があるときの鬼の最短ルートの取得方法です。実装もやや難解な上、パフォーマンスチューニングの手間も掛かりそうです。

 しかしGameplayKitの「Agents, Goals, and Behaviors」を使えばこれらの処理を簡単に実現できます。次項ではGameplayKitを使った場合の実装方法を解説します。

AIに「避ける」動作を追加する(GameplayKitを使った場合)

 まずは「Agents, Goals, and Behaviors」で使うための障害物情報を作成します。GameScene.swiftのcreateObstaclesメソッドを以下のように置き換えてください。

class GameScene: SKScene {
    let player = SKShapeNode(circleOfRadius: 10)
    var enemies = [SKShapeNode]()
    var timer: Timer?
    var prevTime: TimeInterval = 0
    var startTime: TimeInterval = 0
    var isGameFinished = false
    let playerAgent = GKAgent2D()
    let agentSystem = GKComponentSystem(componentClass: GKAgent2D.self)
    var enemyAgents = [GKAgent2D]()
    var obstacles = [GKCircleObstacle]() // 今回追加
 
    // 省略
 
    // ここから今回置き換え
    func createObstacles() {
        guard let viewFrame = view?.frame else {
            return
        }
 
        while obstacles.count < 5 {
            let point = CGPoint(
                x: CGFloat(arc4random_uniform(UInt32(viewFrame.width))) - viewFrame.width / 2,
                y: CGFloat(arc4random_uniform(UInt32(viewFrame.height))) - viewFrame.height / 2)
            let radius = Float(arc4random_uniform(50) + 50)
 
            // 障害物かPlayerが衝突していたら設置しない
            let isObstacleOverlapped = obstacles.contains {
                let dx = (Float(point.x) - $0.position.x)
                let dy = (Float(point.y) - $0.position.y)
                if sqrt(dx*dx + dy*dy) < $0.radius + radius {
                    return true
                }
                return false
            }
            let dx = point.x - player.position.x
            let dy = point.y - player.position.y
            let isPlayerOverlapped = sqrt(dx*dx + dy*dy) < CGFloat(radius) + player.frame.width
            if isObstacleOverlapped || isPlayerOverlapped {
                continue
            }
 
            let obstacleNode = SKShapeNode(circleOfRadius: CGFloat(radius))
            obstacleNode.fillColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1.0)
            obstacleNode.position = point
            obstacleNode.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(radius))
            obstacleNode.physicsBody?.pinned = true
            addChild(obstacleNode)
 
            let obstacle = GKCircleObstacle(radius: radius)
            obstacle.position = float2(x: Float(point.x), y: Float(point.y))
            obstacles.append(obstacle)
        }
    }
    // ここまで今回置き換え
 
    // 省略
}

 障害物が配置できたので、AIに障害物を避けるルールを追加します。GameSceneのcreateEnemyメソッドを以下のように修正します。

class GameScene: SKScene {
    // 省略
 
    func createEnemy() {
        let enemy = SKShapeNode(circleOfRadius: 10)
        enemy.position.x = size.width / 2
        enemy.fillColor = UIColor(red: 0.94, green: 0.14, blue: 0.08, alpha: 1.0)
        enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.frame.width / 2)
        addChild(enemy)
        enemies.append(enemy)
 
        let anemyAgent = GKAgent2D()
        anemyAgent.maxAcceleration = 200 // 今回修正、加速度を上げることで障害物を避けやすくする
        anemyAgent.maxSpeed = 70
        anemyAgent.position = vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y))
        anemyAgent.delegate = self
        anemyAgent.behavior = GKBehavior(goals: [
            GKGoal(toSeekAgent: playerAgent),
            GKGoal(toAvoid: obstacles, maxPredictionTime: 10), // 今回追加
            ], andWeights: [NSNumber(value: 1), NSNumber(value: 50)]) // 今回修正
        agentSystem.addComponent(anemyAgent)
        enemyAgents.append(anemyAgent)
    }
 
    // 省略
}

 これで鬼が障害物を回避して移動するようになりました。

 障害物を避ける処理はcreateEnemyメソッドの下記の部分です。

anemyAgent.behavior = GKBehavior(goals: [
    GKGoal(toSeekAgent: playerAgent),
    GKGoal(toAvoid: obstacles, maxPredictionTime: 10),
], andWeights: [NSNumber(value: 1), NSNumber(value: 50)])

 anemyAgentが鬼の動きを管理するクラスで、それにGKBehaviorという振る舞いを表すクラスを渡しています。そうすることで、鬼(anemyAgent)はGKBehaviorに沿った動きをするようになります。

 GKBehaviorがどのような振る舞いをするかは、「GKGoal」というクラスで設定します。今回はプレイヤーを追いかける「GKGoal(toSeekAgent: playerAgent)」というルールと、障害物を避ける「GKGoal(toAvoid: obstacles, maxPredictionTime: 10)」を設定したことで、障害物を避けつつプレイヤーを追いかけるようになりました。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。