0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【入門】iOS アプリ開発 #5【シーケンスの設計】

Last updated at Posted at 2020-08-15

はじめに

今回は画面モード内の処理シーケンスを作成していく。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)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?