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の一覧の管理を分割します。「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を利用し、Stateパターンを使って味方の攻撃可能状態・攻撃不可状態の処理を分離する方法を学びましょう。
田町のベンチャーで働くエンジニア。
仕事ではiPhoneアプリの開発やRailsを使ったWebサービス開発を行っている。最近のマイブームはUnityを使った3Dゲーム開発。
Copyright © ITmedia, Inc. All Rights Reserved.