連載
» 2017年07月10日 05時00分 公開

「人工知能」の作り方(終):3DゲームのAIをiOSのSceneKitとGameplayKitで作る基本 (2/2)

[杉本裕樹,マネーフォワード]
前のページへ 1|2       
●「Agents, Goals, and Behaviors」を利用したオブジェクトの移動

 続いて、サンプルの飛行機を「Agents, Goals, and Behaviors」を使って移動させるアプリを作ります。

GameViewControllerのコードを最小限に

 まずはGameViewControllerのコードを最小限にします。GameViewController.swiftを下記のように修正してください。

import UIKit
import QuartzCore
import SceneKit
 
class GameViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
 
        let scnView = view as? SCNView
        scnView?.delegate = self
        scnView?.isPlaying = true
        let scene = SCNScene(named: "art.scnassets/ship.scn")
        scnView?.scene = scene
 
        // 光源の設置
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light?.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene?.rootNode.addChildNode(lightNode)
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light?.type = .ambient
        ambientLightNode.light?.color = UIColor.darkGray
        scene?.rootNode.addChildNode(ambientLightNode)
 
        // 飛行機の角度調整
        let ship = scene?.rootNode.childNode(withName: "ship", recursively: true)
        ship?.eulerAngles = SCNVector3(x: 0, y: Float.pi, z: 0)
    }
 
    override var prefersStatusBarHidden: Bool {
        return true
    }
}
 
extension GameViewController: SCNSceneRendererDelegate {
    // 1フレームごとに呼ばれるメソッド
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        print(time)
    }
}

 これで飛行機が配置されているだけの状態になりました。

ルールを作成

 次は、実際に「Agents, Goals, and Behaviors」を使って飛行機を移動させます。

 まずはプロジェクト設定の下にあるボタンからGameplayKitを追加します。

 続けてGameViewControllerを下記のように修正します。

import UIKit
import QuartzCore
import SceneKit
import GameplayKit // 今回追加
 
class GameViewController: UIViewController {
    // 今回追加ここから
    let agentSystem = GKComponentSystem(componentClass: GKAgent3D.self)
    var prevTime: TimeInterval = 0
    let shipAgent = GKAgent3D()
    // 今回追加ここまで
 
    override func viewDidLoad() {
        // 省略
 
        // 飛行機の角度調整
        let ship = scene?.rootNode.childNode(withName: "ship", recursively: true)
        ship?.eulerAngles = SCNVector3(x: 0, y: Float.pi, z: 0)
 
        // 今回追加ここから
        let targetAgent = GKAgent3D()
        targetAgent.position = float3(0, 0, -300)
        shipAgent.maxAcceleration = 1
        shipAgent.maxSpeed = 1
        shipAgent.delegate = self
        shipAgent.behavior = GKBehavior(goals: [
            GKGoal(toSeekAgent: targetAgent),
            ])
        agentSystem.addComponent(shipAgent)
        // 今回追加ここまで
    }
 
    override var prefersStatusBarHidden: Bool {
        return true
    }
}
// 省略

 上記コードでは、飛行機のagent(shipAgent)が座標(x: 0, y: 0, z: -300)に向かうようなルールを作成しました。

ルールに沿って移動する処理を追加

 次は、飛行機オブジェクトがそのルールに沿って移動する処理を追加します。

// 省略
 
extension GameViewController: SCNSceneRendererDelegate {
    // 1フレームごとに呼ばれるメソッド
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        // 今回追加ここから
        if prevTime == 0 {
            prevTime = time
        }
        agentSystem.update(deltaTime: time - prevTime)
        // 今回追加ここまで
    }
}
 
// 今回追加ここから
extension GameViewController: GKAgentDelegate {
    func agentDidUpdate(_ agent: GKAgent) {
        if let ship = (view as? SCNView)?.scene?.rootNode.childNode(withName: "ship", recursively: true), let agent = agent as? GKAgent3D {
            ship.position = SCNVector3(agent.position)
        }
    }
}
// 今回追加ここまで

 これで飛行機が奥に向かって移動するようになりました。

2Dと3Dでの「Agents, Goals, and Behaviors」の利用方法の違い

 前項では、3Dゲームへの「Agents, Goals, and Behaviors」適用を行いました。実は2Dでも3Dでも実装の流れとしては、ほとんど同じです。

 どちらも大きな流れとしては「エージェント(GKAgent)インスタンスを作成して、そこにルール(GKRule)を追加する」というものになります。

 変更点としては、扱う位置情報が2次元から3次元になるだけなので、2Dゲームと大きな違いはなく実装できると思います。

「Agents, Goals, and Behaviors」を利用した、障害物を回避するルールの追加

 最後に障害物を設置したときの動きも見ていきます。

 鬼ごっこアプリ同様に、GKObstacleを使って障害物を設置します。GameViewControllerのviewDidLoadを下記のように修正してください。

class GameViewController: UIViewController {
    // 省略
 
    override func viewDidLoad() {
        // 省略
 
        let targetAgent = GKAgent3D()
        targetAgent.position = float3(0, 0, -300)
        shipAgent.maxAcceleration = 1
        shipAgent.maxSpeed = 1
        shipAgent.delegate = self
        // 今回修正ここから
        let obstacle = GKSphereObstacle(radius: 100)
        obstacle.position = vector_float3(0, 0, -150)
        shipAgent.behavior = GKBehavior(goals: [
            GKGoal(toSeekAgent: targetAgent),
            GKGoal(toAvoid: [obstacle], maxPredictionTime: 100.0)
            ], andWeights: [NSNumber(value: 1), NSNumber(value: 50)])
        // 今回修正ここまで
        agentSystem.addComponent(shipAgent)
    }
 
    // 省略
}

 今回追加したGKSphereObstacleは3Dゲーム用の障害物オブジェクトです。この障害物を避けるルール【GKGoal(toAvoid: [obstacle], maxPredictionTime: 100.0)】を追加することで、飛行機が斜めに移動するようになりました。

 以上で、3Dゲームにおける「Agents, Goals, and Behaviors」の基本的な使い方は終了です。

GameplayKitでゲームAI開発の工数を削減しよう

 今回のソースはkonkai.zipからダウンロードできます。

 これまで4回にわたって「Agents, Goals, and Behaviors」の使い方を見てきましたがいかがでしょうか? GameplayKitは分かりにくいところもありますが、うまく使えば開発工数を大いに削減できます。

 もし機会がありましたら、本連載を参考に試していただけると幸いです。

筆者紹介

杉本裕樹

田町のベンチャーで働くエンジニア。

仕事ではiPhoneアプリの開発やRailsを使ったWebサービス開発を行っている。最近のマイブームはUnityを使った3Dゲーム開発。


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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