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

ゲームの「敵」キャラで分かる「人工知能」の作り方(1):iOS GameplayKitの「Agents, Goals, and Behaviors」で作る、鬼ごっごの鬼AI (2/3)

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

プレイヤーを動かす

 次はプレイヤーを動かす処理を実装しようと思います。「GameScene.swift」を以下のように修正してください。

class GameScene: SKScene {
    let player = SKShapeNode(circleOfRadius: 10)
 
    override func didMove(to view: SKView) {
        player.fillColor = UIColor(red: 0.93, green: 0.96, blue: 0.00, alpha: 1.0)
        addChild(player)
    }
 
    // ここから今回追加分
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        touches.forEach {
            let point = $0.location(in: self)
            player.removeAllActions()
 
            let path = CGMutablePath()
            path.move(to: CGPoint())
            path.addLine(to: CGPoint(x: point.x - player.position.x, y: point.y - player.position.y))
            player.run(SKAction.follow(path, speed: 50.0))
        }
    }
    // ここまで今回追加分
}

 これでタッチした位置に向かってプレイヤーが移動するようになりました。

SKActionを使ったアニメーション

 プレイヤーの移動は「SKAction」というクラスを使いました。SKActionはSpriteKitの機能の1つで、SKNodeをアニメーションさせることができるクラスです。今回は移動に利用しましたが、オブジェクトの回転や色の段階的変化にも使えます。

鬼の出現と移動

 次は、鬼の出現と移動の部分を実装します。鬼は5秒に一度、画面の右端から出現する仕様にします。後半になるに従って鬼の数が増えて難易度が上がっていきます。

 今回は移動処理をSKActionで実装します。「GameScene.swift」を下のように修正してください。

class GameScene: SKScene {
    let player = SKShapeNode(circleOfRadius: 10)
    // ここから今回追加分
    var enemies = [SKShapeNode]()
    var timer: Timer?
    var prevTime: TimeInterval = 0
    // ここまで今回追加分
 
    override func didMove(to view: SKView) {
        player.fillColor = UIColor(red: 0.93, green: 0.96, blue: 0.00, alpha: 1.0)
        addChild(player)
        // ここから今回追加分
        setCreateEnemyTimer()
        physicsWorld.gravity = CGVector() // これがないと鬼が下に落下してしまう
        // ここまで今回追加分
    }
 
    // 省略
 
    // ここから今回追加分
    func setCreateEnemyTimer() {
        timer?.invalidate()
        // 5秒に一度、createEnemyを呼び出す処理
        timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(GameScene.createEnemy), userInfo: nil, repeats: true)
        timer?.fire()
    }
 
    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)
    }
 
    override func update(_ currentTime: TimeInterval) {
        if prevTime == 0 {
            prevTime = currentTime
        }
 
        // プレイヤーの位置が変わるので、1秒に一度移動方向を調整する
        if Int(currentTime) != Int(prevTime) {
            enemies.forEach {
                $0.removeAllActions()
                let path = CGMutablePath()
                path.move(to: CGPoint())
                path.addLine(to: CGPoint(x: player.position.x - $0.position.x, y: player.position.y - $0.position.y))
                $0.run(SKAction.follow(path, speed: 50.0))
            }
        }
        prevTime = currentTime
    }
    // ここまで今回追加分
}

 アプリを起動をすると、敵がプレイヤーに向かっていくのが分かります。

終了判定の実装

 次は鬼に捕まったときの処理を実装します。鬼とプレイヤーが衝突した時点でゲーム終了となり、何秒逃げたかを画面上に表示するようにします。

 「GameScene.swift」を下のように修正してください。

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
    // ここまで今回追加分
 
    // 省略
 
    override func update(_ currentTime: TimeInterval) {
        if prevTime == 0 {
            prevTime = currentTime
            startTime = currentTime // 今回追加分
        }
 
        if Int(currentTime) != Int(prevTime) {
            enemies.forEach {
                $0.removeAllActions()
 
                let path = CGMutablePath()
                path.move(to: CGPoint())
                path.addLine(to: CGPoint(x: player.position.x - $0.position.x, y: player.position.y - $0.position.y))
                $0.run(SKAction.follow(path, speed: 50.0))
            }
        }
 
        // ここから今回追加分
        if !isGameFinished {
            for enemy in enemies {
                let dx = enemy.position.x - player.position.x
                let dy = enemy.position.y - player.position.y
                if sqrt(dx*dx + dy*dy) < player.frame.width / 2 + enemy.frame.width / 2 {
                    isGameFinished = true
                    timer?.invalidate()
                    let label = SKLabelNode(text: "記録:\(Int(currentTime - startTime))秒")
                    label.fontSize = 80
                    label.position = CGPoint(x: 0, y: -100)
                    addChild(label)
                    break
                }
            }
        }
        // ここまで今回追加分
 
        prevTime = currentTime
    }
}

 鬼に捕まったときに記録が表示されるようになりました。

SpriteKitの衝突判定

 今回はプレイヤーと鬼の衝突判定を以下のように実装しました。

let dx = enemy.position.x - player.position.x
let dy = enemy.position.y - player.position.y
if sqrt(dx*dx + dy*dy) < player.frame.width / 2 + enemy.frame.width / 2 {
    // 省略
}

 実は衝突判定はSpriteKitでも用意されています。「SKPhysicsBody」というクラスを使うと衝突時の挙動設定や衝突時のコールバックメソッドの設定ができます。しかし衝突相手の設定などが少しややこしいので、今回は上記のように地道に計算する形にしました。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

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

メールマガジン登録

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