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

iOS GameplayKitのRule SystemsでゲームAIプログラミングはどう変わるのかゲームの「敵」キャラで分かる「人工知能」の作り方(3) (2/2 ページ)

[杉本裕樹,マネーフォワード]
前のページへ 1|2       

条件式を「Rule Systems」で管理する

 「Rule Systems」とは、今回のような複数の条件を管理するときに使える機能です。条件1つ1つをオブジェクトとして扱い、それらを使ってパラメータの計算が行えます。

 条件をオブジェクトとして扱うので、条件の使い回しや条件の動的な変更が行えることが利点です。

「Rule Systems」を使った、条件の設定

 それでは、「経過時間を元に鬼の速度を変える」処理を「Rule Systems」を使って置き換えてみます。GameSceneを下のように修正してください。

class GameScene: SKScene {
    // 省略
    let system = GKRuleSystem() // 今回追加
 
    override func didMove(to view: SKView) {
        // 省略
 
        system.add([
            GKRule(predicate: NSPredicate(format: "modulus:by:($time, 30) >= 25"), assertingFact: NSString(string: "enemySpeedRate"), grade: 0.3),
            GKRule(predicate: NSPredicate(format: "$time >= 60"), assertingFact: NSString(string: "enemySpeedRate"), grade: 0.1),
            GKRule(predicate: NSPredicate(format: "$time >= 120"), assertingFact: NSString(string: "enemySpeedRate"), grade: 0.1),
            GKRule(predicate: NSPredicate(format: "$time >= 180"), assertingFact: NSString(string: "enemySpeedRate"), grade: 0.1),
            ])
    }
 
    // 省略
}

 GKRuleSystemクラスのインスタンス作成と、GKRuleSystemへのGKRuleの追加を行っています。

 GKRuleSystemクラスはルールを管理するクラスです。このクラスに条件式や値を渡してパラメータを算出します。

 GKRuleは1つ1つの条件式に相当します。下の例ですと「time値が180以上のときにenemySpeedRateパラメータに0.1を追加する」という意味になります。

GKRule(predicate: NSPredicate(format: "$time >= 180"), assertingFact: NSString(string: "enemySpeedRate"), grade: 0.1),

パラメータ算出処理

 ここまでで、条件の設定を行いました。最後に設定した条件を元にしたパラメータ算出処理を実装します。

 GameSceneのupdateメソッドを下記のように修正してください。

class GameScene: SKScene {
    // 省略
 
    override func update(_ currentTime: TimeInterval) {
        if prevTime == 0 {
            prevTime = currentTime
            startTime = currentTime
        }
        agentSystem.update(deltaTime: currentTime - prevTime)
        playerAgent.position = vector_float2(x: Float(player.position.x), y: Float(player.position.y))
        let time = Int(currentTime - startTime)
        timeLabel.text = "\(time)秒"
        // 今回修正ここから
//        var enemySpeedRate: Float = 1.0
//        if time % 30 >= 25 {
//            enemySpeedRate += 3.0
//        }
//        if time >= 60 {
//            enemySpeedRate += 1.0
//        }
//        if time >= 120 {
//            enemySpeedRate += 1.0
//        }
//        if time >= 180 {
//            enemySpeedRate += 4.0
//        }
        system.state["time"] = time
        system.reset()
        system.assertFact(NSString(string: "enemySpeedRate"), grade: 0.1)
        system.evaluate()
        let enemySpeedRate = system.grade(forFact: NSString(string: "enemySpeedRate")) * 10
        // 今回修正ここまで
        enemyAgents.forEach {
            $0.maxAcceleration = 30 * enemySpeedRate
            $0.maxSpeed = 20 * enemySpeedRate
        }
    }
}

 これで「Rule Systems」を使って置き換えは完了です。続けて今回追加した下5行について説明していきます。

system.state["time"] = time
system.reset()
system.assertFact(NSString(string: "enemySpeedRate"), grade: 0.1)
system.evaluate()
let enemySpeedRate = system.grade(forFact: NSString(string: "enemySpeedRate")) * 10

 1行目と3行目は値のセットとパラメータの初期化です。セットした値を使って4行目でenemySpeedRateを計算しています。そのまま計算すると前回の結果を引き継いでしまうので、2行目で計算結果をリセットしています。

 最後に5行目で計算結果を取得しています。最後に「x10」としているのは、gradeが1.0以上の値を扱えないためです。

コラム「条件式をGKRuleインスタンスとして扱うメリット」

 今回は「Rule Systems」を使って、if文で書かれた条件式をGKRuleインスタンスとして扱いました。本稿では敵は1種類でしたが、もし敵の種類が増えて条件式を使い回したいときには「Rule Systems」を使うメリットが大きくなります。

 例えば敵が3種類いて、「敵1は25〜30秒で動きが速くなる」「敵2は1分ごとに速くなる」「敵3は敵1と敵2の両方の性質を持っている」とします。よくある実装方法だと、敵1・敵2・敵3のそれぞれに対応するクラスを作ってそれぞれのクラスに条件式を記述します。しかし「Rule Systems」で条件式をGKRuleインスタンスとして扱っていれば、「敵1と敵3には“25〜30秒で動きが速くなる”というGKRuleインスタンスを付与、敵2と敵3には“1分ごとに速くなる”というGKRuleインスタンスを付与する」といった具合に条件式の使い回しを行えます。

 このように敵の種類が増え、加えて条件式が複雑になってくると「Rule Systems」の利点が大きくなります。


GKRuleはサブクラスを作って複雑な条件にすることもできる

 GKRuleはブロックで判定式を渡したりサブクラスを作ったりすることもできるので、複雑な条件にも対応可能です。

GKRule(blockPredicate: { system in
    guard let time = (system.state["time"] as? NSNumber)?.floatValue else {
        return false
    }
    return time >= 10
}, action: { system in
    system.assertFact(NSString(string: "enemySpeedRate"), grade: 0.3)
})
 
class MyRule: GKRule {
    override func evaluatePredicate(in system: GKRuleSystem) -> Bool {
        guard let time = (system.state["time"] as? NSNumber)?.floatValue else {
            return false
        }
        return time >= 10
    }
 
    override func performAction(in system: GKRuleSystem) {
        system.assertFact(NSString(string: "enemySpeedRate"), grade: 0.3)
    }
}

次回は、3Dゲームにおける「Agents, Goals, and Behaviors」の使い方

 今回は、前回までの「GKAgent2D」「GKGoal」「GKBehavior」に加えて、「GKRule」「GKRuleSystem」を使って、さらにリアリティーのある動きをゲームAIに加えることができました。今回まででiOS GameplayKitの「Agents, Goals, and Behaviors」でどんなことができるかが大体理解できたかと思います。

 今回のソースは、こちらからダウンロードできます。

 さて、次回は最終回です。これまでは2Dゲームを題材にしてきましたが、3Dゲームにおける「Agents, Goals, and Behaviors」の使い方を見ていきます。これまで紹介した「GKAgent2D」が「GKAgent3D」に変わり、SpriteKitも3Dゲームのための「SceneKit」に変わりますが、これまで紹介した使い方の応用として見てください。

筆者紹介

杉本裕樹

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

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


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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