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 アプリ開発 #8【スペシャルターゲット・得点表示】

Last updated at Posted at 2020-09-09

はじめに

今回はスペシャルターゲット(くだもの類)、パックマンがそれを食べた時やゴーストを噛み付いた時の得点表示を作成していく。

完成図は下記の通り。スペシャルターゲット(サクランボ表示)と、それをパックマンが食べると得点が表示される。

Image7.png

仕様書

まずはスペシャルターゲットの仕様の確認。

Image5.png

表示されている間にパックマンが食べると、得点が2秒だけ表示される(仕様にはないが実機確認)。

続いてパックマンがゴーストを噛み付いた時の得点表示の仕様は以下の通り。

Image51.png

こちらの得点表示は1秒間だけ(同じく仕様にはないが実機確認)。

設計方針

スペシャルターゲットは、パックマンがエサを70個、170個食べた時に、

specialTarget.start(kind: .Cherry)

とするとサクランボのスペシャルターゲットが表示され、10秒後に自動的に消えるように設計したい。

表示されている間にパックマンが食べると 100 というように得点表示する。表示の時間は2種類(2秒、1秒)あるため、得点の種類や位置に加えて時間指定も行えるようにする。

ptsManager.start(kind: .pts100, position: specialTarget.position, interval: 2000)  // 2000ms

といった具合にする。
得点表示は、スペシャルターゲットとゴースト4匹を連続で噛みつくとして、最大5つまで同時表示できるように設計しておきたい(実際はゴーストをかみついている間はパックマンの動きが停止するため、実質2つまでとなる)。

クラス構造

スペシャルターゲットと得点表示は、CgActorクラスを継承して作成する。

Image62.png

得点表示は CgScorePtsクラスとして、これを最大5つ表示する管理クラス CgScorePtsManager を作成する。

CgSpecialTarget クラス

スペシャルターゲット(くだもの類)を表示する。

class CgSpecialTarget : CgActor {

    enum EnSpecialTarget: Int {
        case Cherry
        case Strawberry
        case Orange
        case Apple
        case Melon
        case Galaxian
        case Bell
        case Key
        case None

        func getScorePts() -> CgScorePts.EnScorePts {
            switch self {
                case .Cherry:     return .pts100
                case .Strawberry: return .pts300
                case .Orange:     return .pts500
                case .Apple:      return .pts700
                case .Melon:      return .pts1000
                case .Galaxian:   return .pts2000
                case .Bell:       return .pts3000
                case .Key:        return .pts5000
                case .None:       return .pts0
            }
        }

        func getTexture() -> Int {
            switch self {
                case .Cherry:     return 16*3+2
                case .Strawberry: return 16*3+3
                case .Orange:     return 16*3+4
                case .Apple:      return 16*3+5
                case .Melon:      return 16*3+6
                case .Galaxian:   return 16*3+7
                case .Bell:       return 16*3+8
                case .Key:        return 16*3+9
                case .None:       return 16*3+10
            }
        }
    }

    private var kindOfSpecialTarget: EnSpecialTarget = .None
    private var timer_disappearSpecialTarget: CbTimer!

    override init(binding object: CgSceneFrame, deligateActor: ActorDeligate) {
        super.init(binding: object, deligateActor: deligateActor)
        timer_disappearSpecialTarget = CbTimer(binding: self)
        actor = .SpecialTarget
        sprite_number = actor.getSpriteNumber()
    }

    // ============================================================
    //   Core operation methods for actor
    //  - Sequence: reset()->start()->update() called->stop()
    // ============================================================

    /// Reset special target state.
    override func reset() {
        super.reset()
        timer_disappearSpecialTarget.set(interval: 10000) // 10s
        timer_disappearSpecialTarget.reset()
        position.set(column: 13, row: 15, dx: 4)
    }
    
    /// Start to draw special target at the specified position.
    override func start() {
        super.start()
        timer_disappearSpecialTarget.start()
        deligateActor.setTile(column: position.column, row: position.row, value: .Fruit)
        sprite.draw(sprite_number, x: position.x, y: position.y, texture: kindOfSpecialTarget.getTexture())
    }

    /// Update handler
    /// - Parameter interval: Interval time(ms) to update
    override func update(interval: Int) {
        if timer_disappearSpecialTarget.isEventFired() {
            stop()
        }
    }
    
    /// Stop drawing special target.
    override func stop() {
        super.stop()
        timer_disappearSpecialTarget.stop()
        deligateActor.setTile(column: position.column, row: position.row, value: .Road)
        sprite.clear(sprite_number)
    }

    // ============================================================
    //  General methods in this class
    // ============================================================

    func start(kind: EnSpecialTarget) {
        kindOfSpecialTarget = kind
        self.start()
    }
}

コンストラクタで CbTimer を生成して、reset() メソッドで 10秒を設定しておく。
start()メソッドが呼ばれたら、スプライトを表示しタイマーを起動させる。
update()メソッドがフレーム毎に呼ばれる中でタイマーが 0 になったら、内部のstop()メソッドを呼びスプライトを消去する。

簡単なコードとなった。

CgScorePts クラス

得点クラスはスペシャルターゲットとほぼ同様のコードとなる。1000点以上はスプライトを2つ使って表示する。

class CgScorePts : CgActor {

    enum EnScorePts: Int {
        case pts100 = 0
        case pts200
        case pts300
        case pts400
        case pts500
        case pts700
        case pts800
        case pts1000
        case pts1600
        case pts2000
        case pts3000
        case pts5000
        case pts0

        func getScore() -> Int {
            switch self {
                case .pts100  : return 100
                case .pts200  : return 200
                case .pts300  : return 300
                case .pts400  : return 400
                case .pts500  : return 500
                case .pts700  : return 700
                case .pts800  : return 800
                case .pts1000 : return 1000
                case .pts1600 : return 1600
                case .pts2000 : return 2000
                case .pts3000 : return 3000
                case .pts5000 : return 5000
                case .pts0    : return 0
            }
        }

        func get2times() -> EnScorePts {
            switch self {
                case .pts100  : return .pts200
                case .pts200  : return .pts400
                case .pts400  : return .pts800
                case .pts800  : return .pts1600
                default : return self
            }
        }
        
        func getTextures() -> (Int, Int) {
            switch self {
                case .pts100  : return (16*9   , 0)
                case .pts200  : return (16*8   , 0)
                case .pts300  : return (16*9+1 , 0)
                case .pts400  : return (16*8+1 , 0)
                case .pts500  : return (16*9+2 , 0)
                case .pts700  : return (16*9+3 , 0)
                case .pts800  : return (16*8+2 , 0)
                case .pts1000 : return (16*9+4 , 16*9+5)
                case .pts1600 : return (16*8+3 , 0)
                case .pts2000 : return (16*10+4, 16*10+5)
                case .pts3000 : return (16*11+4, 16*11+5)
                case .pts5000 : return (16*12+4, 16*12+5)
                case .pts0    : return (16*9   , 0)     //
            }
        }
    }

    private var ptsNumber: EnScorePts = .pts0
    private var timer_disappearPts: CbTimer!

    override init(binding object: CgSceneFrame, deligateActor: ActorDeligate) {
        super.init(binding: object, deligateActor: deligateActor)
        timer_disappearPts = CbTimer(binding: self)
        actor = .Pts
    }
        
    // ============================================================
    //   Core operation methods for actor
    //  - Sequence: reset()->start()->update() called->stop()
    // ============================================================
    
    /// Reset
    override func reset() {
        super.reset()
    }
    
    /// Start
    override func start() {
        super.start()
        timer_disappearPts.start()

        let textures: (Int,Int) = ptsNumber.getTextures()
        sprite.draw(sprite_number, x: position.x, y: position.y, texture: textures.0)
        if  textures.1 != 0 {
            sprite.draw(sprite_number+1, x: position.x+16, y: position.y, texture: textures.1)
        }
    }

    /// Update handler
    /// - Parameter interval: Interval time(ms) to update
    override func update(interval: Int) {
        if timer_disappearPts.isEventFired() {
            stop()
        }
    }
    
    /// Stop
    override func stop() {
        super.stop()
        timer_disappearPts.reset()

        sprite.clear(sprite_number)
        if ptsNumber.getTextures().1 != 0 {
            sprite.clear(sprite_number+1)
        }
    }
    
    // ============================================================
    //  General methods in this class
    // ============================================================

    func start(kind: EnScorePts, position at: CgPosition, interval time: Int) {
        ptsNumber = kind
        timer_disappearPts.set(interval: time)
        self.position.set(at)
        start()
    }
    
}

CgScorePtsManager クラス

CgScorePts のオブジェクトを5つ管理して表示の制御を行う。

class CgScorePtsManager: CbContainer {

    private let firstSpriteNumber: Int = CgActor.EnActor.Pts.getSpriteNumber()
    private let numberOfActors: Int = 5
    
    private var actors: [CgScorePts] = []
    private var doings: [CgScorePts] = []

    init(binding object: CgSceneFrame, deligateActor: ActorDeligate) {
        super.init(binding: object)
        for i in 0 ..< numberOfActors {
            let actor: CgScorePts = CgScorePts(binding: object, deligateActor: deligateActor)
            actor.sprite_number = firstSpriteNumber+i*2
            actors.append(actor)
        }
    }
    
    /// Reset
    func reset() {
        for each in actors {
            each.reset()
        }
        self.enabled = false
    }
    
    /// Start to draw Pts
    /// - Parameters:
    ///   - kind: Kind of pts
    ///   - position: Position to draw
    ///   - time: Time to disappear
    func start(kind: CgScorePts.EnScorePts, position: CgPosition, interval time: Int) {
        let actor: CgScorePts

        if actors.count == 0 {
            actor = doings.remove(at: 0)
            actor.stop()
        } else {
            actor = actors.remove(at: 0)
        }
        actor.start(kind: kind, position: position, interval: time)
        doings.append(actor)
        self.enabled = true
    }
    
    /// Update handler
    /// - Parameter interval: Interval time(ms) to update
    override func update(interval: Int) {
        for each in doings {
            if !each.enabled {
                actors.append(each)
                doings.remove(at: 0)
            }
        }

        if doings.count == 0 {
            enabled = false
        }
    }
    
    /// Stop
    func stop() {
        for each in doings {
            each.stop()
            actors.append(each)
            doings.remove(at: 0)
        }
        self.enabled = false
    }

}

コンストラクタで CgScorePtsオブジェクトを5つ生成して actors配列に格納しておく。
start()メソッドが呼ばれた時に、actors配列から CgScorePtsオブジェクトを取り出して start()し、doings配列へ移動しておく。enabled = true にすると、フレーム毎に update()が呼ばる。
update()メソッド内では、doings配列にある CgScorePtsオブジェクトが stop(enabled= false) していたら、doings 配列から actors へ戻しておく。

start()メソッドが呼ばれた時に5つ全部表示中で actors配列から取り出されなかった場合は、doings から1つ持ってきて使いまわす。

まとめ

スペシャルターゲットと得点表示のクラスを作成した。作成したソースコードは合計300行程度。

必要な役者はそろったので、次回はこれら役者のシーケンス動作を組み合わせて、ゲームとしてプレイできるものを作成していく。

次の記事

[【入門】iOS アプリ開発 #9【ゲームの状態遷移とシーケンス動作】]
(https://qiita.com/KIKU_CHU/items/6b16a2b8970513b42afe)

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?