はじめに
今回はスペシャルターゲット(くだもの類)、パックマンがそれを食べた時やゴーストを噛み付いた時の得点表示を作成していく。
完成図は下記の通り。スペシャルターゲット(サクランボ表示)と、それをパックマンが食べると得点が表示される。
仕様書
まずはスペシャルターゲットの仕様の確認。
表示されている間にパックマンが食べると、得点が2秒だけ表示される(仕様にはないが実機確認)。
続いてパックマンがゴーストを噛み付いた時の得点表示の仕様は以下の通り。
こちらの得点表示は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クラスを継承して作成する。
得点表示は 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)