はじめに
今回は画面モード内の処理シーケンスを作成していく。SpriteKit Manager を使用して、文字やキャラクタの表示・アニメーションを行う。パックマンの仕様書にあるスタートモードとクレジットモードは除き、完成映像は以下の通り。
- アトラクトモード
『仕様書より』
・モンスターキャラクター説明;逆転のデモンストレーション;得点表
パックマンがやられるまでのプレイデモンストレーションの2パターンが繰り返される。
・電源投入時は、ハイスコアと 2UP の得点、ラウンド表示のくだもの類がない。
・プレイ後は、最終プレイヤーの得点とくだもの類が表示される。
- プレイモード
『仕様書より』
・ゲームオーバーすると”GAME OVER”表示される。
- マンガ(M1)/ Intermission No.1
『仕様書より』
マンガ(M1):赤モンスターに追われたパックマンが大きくなっていじけを追う。
- マンガ(M2)/ Intermission No.2
『仕様書より』
マンガ(M2):赤モンスターの服が、クギに引っかかって破ける。
- マンガ(M3)/ Intermission No.3
『仕様書より』
マンガ(M3):服を脱がされたハダカモンスターが、服を引きずって逃げる。
マンガ仕様は、たったこれだけの記述なので、詳細の動作は YouTube などを見ながら作成する。
シーケンスの設計
シーケンス動作は「1番目の処理、時間を待って2番目の処理、・・・」と処理が進めていければ良い。簡単に switch - case で実装してみたい。
func handleSequence(sequence: Int) -> Bool {
switch sequence {
case 0:
// 初期化含めた 0番目の処理
goToNextSequence() // 16ms後に #1 へ
case 1:
// 1番目の処理
goToNextSequence(after: 800) // 800ms後に #2 へ
case 2:
// 2番目の処理
goToNextSequence(after: 480) // 480ms後に #3へ
// ・・・ 同様に ・・・
default:
// Stop and exit running sequence.
return false
}
// Continue running sequence.
return true
}
こんな感じ。
ここで『800ms』など時間をカウントするための、カウントダウン・タイマーを作成しておく。
後々、多くの場面で使用しそうだ。
CbTimer class
class CbTimer : CbObject {
private var currentTime = 0
private var settingTime = 0
private var eventFired = false
func reset() {
currentTime = settingTime
eventFired = false
self.enabled = false
}
func set(interval: Int) {
settingTime = interval
reset()
}
func start() {
self.enabled = true
}
func restart() {
reset()
start()
}
func pause() {
self.enabled = false
}
func stop() {
self.enabled = false
currentTime = 0
}
func get() -> Int {
return currentTime
}
func isCounting() -> Bool {
return self.enabled && currentTime > 0
}
func isEventFired() -> Bool {
return eventFired
}
/// Update handler
/// - Parameter interval: Interval time(ms) to update
override func update(interval: Int) {
guard isCounting() else { return }
currentTime -= interval
if currentTime <= 0 {
currentTime = 0
eventFired = true
}
}
}
カウントダウン・タイマーの CbTimerクラスは、CbObjectクラスを継承して作成する。基底クラスの updateメソッドをオーバーライドすれば、フレーム毎(16ms)に自動的にカウントダウンしてくれるように実装できる。
使い方は、CbContainerの派生クラスの中で、
var timer: CbTimer!
timer = CbTimer(binding: self) // CbContainerに紐付け
timer.set(time: 800) //ms
timer.start() // カウントダウン開始
// .. カウントダウン中 ..
if timer.isFired() { /* 指定時間が経過した時の処理 */ }
timer.restart() // タイマーの再スタート
という具合で動作する。
CbScene class
前回作った CbContainer クラスを継承して、シーケンスを実行する CbSceneクラスを作成する。
class CbScene : CbContainer {
private var timer_sequence: CbTimer!
private var current_sequence: Int = 0
private var next_sequence: Int = 0
/// Initialize self
/// - Parameter object: Parent object
override init(binding object: CbObject) {
super.init(binding: object)
timer_sequence = CbTimer(binding: self)
resetSequence()
}
/// Reset sequence settings to start
func resetSequence() {
timer_sequence.reset()
current_sequence = 0
next_sequence = 0
self.enabled = false
}
/// Start sequence of scene
func startSequence() {
self.enabled = true
}
/// Stop sequence of scene
func stopSequence() {
self.enabled = false
}
/// Implementation of update method in CbContainer
/// Handle a sequence with a timer every update cylce
override func update(interval: Int) {
if timer_sequence.enabled {
if timer_sequence.isEventFired() {
timer_sequence.reset()
current_sequence = next_sequence
} else {
return
}
}
self.enabled = handleSequence(sequence: current_sequence)
}
/// Handle sequence
/// To override in a derived class.
/// - Parameter sequence: Sequence number
/// - Returns: If true, continue the sequence, if not, end the sequence.
func handleSequence(sequence: Int) -> Bool {
// TO DO: override
// (This is pure virtual method.)
return false
}
/// Get current sequence number
/// - Returns: Sequence number(from 0 to n)
func getSequence() -> Int {
return current_sequence
}
/// Go to specified sequence number
/// - Parameter number: Sequence number(from 0 to n)
func goToSequence(number: Int) {
timer_sequence.stop()
current_sequence = number
next_sequence = number
}
/// Go to next sequence after the specified time
/// - Parameters:
/// - number: Sequence number
/// - time: Waiting time
func goToNextSequence(_ number: Int = -1, after time: Int = 0) { // ms
next_sequence = (number == -1) ? current_sequence+1 : number
if time > 0 {
timer_sequence.set(interval: time)
timer_sequence.start()
} else {
timer_sequence.stop()
current_sequence = next_sequence
}
}
}
同様に updateメソッドをオーバーライドすれば、フレーム毎(16ms)に呼ばれて、goToNextSequenceメソッドで、カウントダウン・タイマーが起動されていれば、設定時間だけ待った後に、handleSequenceメソッドが呼ばれる。
CgSceneFrame class
CbSceneクラスから、「各画面モードで使用するオブジェクト」への参照を持つクラスを作成する。具体的には、これまで作成してきた Sprite Manager, Background Manager, Sound Manager と、ゲームのデータを格納する CbContext のオブジェクトが参照できるようにする。
class CgSceneFrame: CbScene {
var context: CbContext!
var sprite: CgSpriteManager!
var background: CgCustomBackgroundManager!
var sound: CgSoundManager!
convenience init(binding object: CbObject, context: CbContext, sprite: CgSpriteManager, background: CgCustomBackgroundManager, sound: CgSoundManager) {
self.init(binding: object)
self.context = context
self.sprite = sprite
self.background = background
self.sound = sound
}
func drawFrame() {
background.draw(0, x: CGFloat(BG_WIDTH/2*8), y: CGFloat(BG_HEIGHT/2*8), columnsInWidth: BG_WIDTH, rowsInHeight: BG_HEIGHT)
}
func printFrame() {
background.print(0, color: .White, column: 0, row: 35, string: " 1UP HIGH SCORE ")
background.print(0, color: .White, column: 0, row: 34, string: " 00 00 ")
}
func printPlayerScore() {
let str = String(format: "%6d0", context.score/10)
background.print(0, color: .White, column: 0, row: 34, string: str)
}
}
この CgSceneFrameクラスを継承して、各画面モードのクラスを実装していく。
CbContext class
ゲームのデータを格納する CbContextクラスは、テストとして下記を記述しておく。
class CbContext {
enum EnLanguage: Int {
case English = 0, Japanese
}
var language: EnLanguage = .Japanese
var highScore = 8150
var score = 2020
var credit = 1
var numberOfPlayers = 3
var round = 0
}
各画面モードの実装
CgSceneFrameクラスを継承して、作成した画面モードは以下の通り。アニメーション処理は意外とアナログ的な実装となった。
- class CgSceneAttractMode
- class CgSceneMaze
- class CgSceneIntermission1
- class CgSceneIntermission2
- class CgSceneIntermission3
動作テスト
class GameScene: SKScene {
private var context : CbContext!
private var root: CbContainer!
private var gameScenes: [CbScene] = []
private var sprite: CgSpriteManager!
private var background: CgCustomBackgroundManager!
private var sound: CgSoundManager!
override func didMove(to view: SKView) {
// Create game context and root container.
context = CbContext()
root = CbContainer()
// Create SpriteKit managers.
sprite = CgSpriteManager(view: self, imageNamed: "pacman16_16.png", width: 16, height: 16, maxNumber: 64)
background = CgCustomBackgroundManager(view: self, imageNamed: "pacman8_8.png", width: 8, height: 8, maxNumber: 2)
sound = CgSoundManager(binding: root, view: self)
// Create and append scenes with scequences.
gameScenes.append(CgSceneAttractMode(binding: root, context: context, sprite: sprite, background: background, sound: sound))
gameScenes.append(CgSceneMaze(binding: root, context: context, sprite: sprite, background: background, sound: sound))
gameScenes.append(CgSceneIntermission1(binding: root, context: context, sprite: sprite, background: background, sound: sound))
gameScenes.append(CgSceneIntermission2(binding: root, context: context, sprite: sprite, background: background, sound: sound))
gameScenes.append(CgSceneIntermission3(binding: root, context: context, sprite: sprite, background: background, sound: sound))
}
var mode: Int = 4
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered.
// When the scene ends, start the next scene.
if !gameScenes[mode].enabled {
mode = mode == 4 ? 0 : mode+1
gameScenes[mode].resetSequence()
gameScenes[mode].startSequence()
context.round += 1
}
// Send update message every 16ms.
root.sendEvent(message: .Update, parameter: [16])
}
}
resetSequence()でシーケンス初期化、 startSequence()でシーケンスを起動し、enabledが false になれば、その画面モードのシーケンスが終了したことを示す。
まとめ
ゲーム全体を構成する画面モードとその内部シーケンス処理を実装した。
GitHub に公開しているテスト・プログラムを実行すると、各画面モードが切り替わり表示される。
現在ソースコードはコメント付きで、全体1800行程度となっている。
これからキャラクタの動作を作成していく。
次の記事
[【入門】iOS アプリ開発 #6【キャラクタの操作】]
(https://qiita.com/KIKU_CHU/items/b09aa2184881ae57e523)