検索
連載

デザインパターン「ファーストクラスコレクション」でSwiftコードの保守性・可読性を上げる方法をゲームのコードから学ぶiOS SDKとSwiftで始めるゲーム作成入門(4)(2/2 ページ)

iPhoneゲームをSwift言語で作成してみたいという初心者向けにiOSのゲームフレームワークを使った作り方を一から解説する入門連載。今回は、今後発生し得る要望を意識したクラス設計のために、デザインパターン「ファーストクラスコレクション」を適用する方法を解説。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

敵を複数体出現させる

 GameSceneクラスがすっきりしたところで機能を追加します。以下のようにGameSceneに敵が複数体出現する処理を追加します。

class GameScene: SKScene, SKPhysicsContactDelegate {
    enum State {
        case Playing
        case GameClear
        case GameOver
    }
    var state = State.Playing
    var enemies = [SKSpriteNode]()
    let char = SKSpriteNode(imageNamed: "Char")
    var routes = [float2]()
 
    override func didMoveToView(view: SKView) {
        // 省略
 
        routes = routesWithField(field)
 
        // 敵を10体出現させる処理
        (0...10).forEach {
            performSelector("createEnemy", withObject: nil, afterDelay: Double($0))
        }
    }
 
    func createEnemy() {
        guard let view = view else {
            return
        }
 
        let enemy = SKSpriteNode(imageNamed: "Enemy")
        var routes = self.routes
        var prevPosition = routes.removeFirst()
        let actions = routes.map { p -> SKAction in
            let dx = p.x - prevPosition.x
            let dy = p.y - prevPosition.y
            let duration = Double(sqrt(dx * dx + dy * dy) / 100)
            prevPosition = p
            return SKAction.moveTo(CGPoint(x: Double(p.x), y: Double(p.y)), duration: duration)
        }
 
        let fieldImageLength = view.frame.width / 10
        enemy.name = "enemy"
        enemy.position = CGPoint(x: fieldImageLength * 2, y: view.frame.height)
        enemy.physicsBody = SKPhysicsBody(rectangleOfSize: enemy.size)
        enemy.runAction(SKAction.sequence(actions)) {
            self.state = .GameOver
 
            let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6")
            myLabel.text = "ゲームオーバー"
            myLabel.fontSize = 45
            myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20)
            self.addChild(myLabel)
        }
        addChild(enemy)
 
        enemies.append(enemy)
    }
 
    func didBeginContact(contact: SKPhysicsContact) {
        [contact.bodyA, contact.bodyB].forEach {
            if $0.node?.name == "enemy" {
                $0.node?.removeFromParent()
                $0.node?.removeAllActions()
            }
        }
 
        if enemies.filter({ $0.parent != nil }).count == 0 {
            state = .GameClear
 
            let myLabel = SKLabelNode(fontNamed: "HiraginoSans-W6")
            myLabel.text = "ゲームクリア"
            myLabel.fontSize = 45
            myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame) - 20)
            addChild(myLabel)
        }
    }
 
    // 省略
}

 今回はdidMoveToViewメソッド内で行っていたEnemyの生成を「createEnemy」メソッド(24〜56行目)に移動しました。そのcreateEnemyメソッドを1秒ごとに呼ぶことで敵を複数体出現させています。

 1秒ごとに呼び出す処理にはperformSelectorメソッドを使っています(20行目)。このメソッドはメソッドの遅延実行ができるメソッドで引数のafterDelayを調整することで遅延時間を変更できます。

ファーストクラスコレクションでEnemy一覧を扱う処理の分割

 最後にコードを少しだけ読みやすくしましょう。先ほどはフィールドの生成処理を分割しましたが、今回はEnemyの一覧の管理を分割します。「EnemyList」というクラスを作って以下のように修正します。

import UIKit
import SpriteKit
 
class EnemyList {
    private var enemies = [SKSpriteNode]()
 
    func appendEnemy(enemy: SKSpriteNode) {
        enemies.append(enemy)
    }
 
    func isAllEnemyRemoved() -> Bool {
        return enemies.filter { $0.parent != nil }.count == 0
    }
}

 次にGameSceneを以下のように3カ所書き換えます。

class GameScene: SKScene, SKPhysicsContactDelegate {
    // 省略
    // var enemies = [SKSpriteNode]()
    var enemyList = EnemyList()
    // 省略
 
    func createEnemy() {
        // 省略
        // enemies.append(enemy)
        enemyList.appendEnemy(enemy)
    }
 
    func didBeginContact(contact: SKPhysicsContact) {
        // 省略
        // if enemies.filter({ $0.parent != nil }).count == 0 {
        if enemyList.isAllEnemyRemoved() {
            // 省略
        }
    }
    // 省略
}

 EnemyListように配列を扱うクラスを「ファーストクラスコレクション」と呼びます。今回のように配列を扱うクラスを作っておけば、配列に関する処理をこのクラスに集約できます。

 今回のケースでは、ファーストクラスコレクションにする恩恵はそこまで大きくありませんが、今後「特定条件で全てのEnemyの速度を倍にする」「プレイヤーが特定アイテムを使った時に全てのEnemyを一時停止させたい」などの要望が出てきたときに恩恵を感じることができるかと思います。

次回は、iOS 9で加わったGameplayKitのStateMachineを使う

 今回はコードの可読性・拡張性を意識しながら機能の実装を進めました。本記事で紹介した生成処理の分割やファーストクラスコレクションの利用はゲーム以外の開発でも活用できると思うので、ぜひ試してみてください。

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

 次回は、味方の自由な設置や強化処理を作っていきます。iOS 9で加わったGameplayKitのStateMachineを利用し、Stateパターンを使って味方の攻撃可能状態・攻撃不可状態の処理を分離する方法を学びましょう。

筆者紹介

杉本裕樹

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

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


Copyright © ITmedia, Inc. All Rights Reserved.

前のページへ |       
ページトップに戻る