This time, we created details of the difficulty level and speed level that change depending on the round, and combined each screen mode created in # 5 [Sequence design]. Then, bring it to the almost completed state (the following is an image movie). The source code is available on GitHub, so please refer to it.
The specifications of the difficulty level and speed level that change depending on the round are as follows.
This time, the foreign version of the difficulty table and the spurt ② setting will not be created.
Implement a difficulty table and a speed table as an array of tuples. Below, except for the combination of each screen mode, all are included in the CgContext class.
enum EnLevel: Int {
case Level_A = 0, Level_B, Level_C, Level_D
}
let table_difficultySettings: [(round: Int, levelOfSpeed: EnLevel, timeWithPower: Int, numberOfFeedsRemaingToSpurt: Int, levelOfAppearance: EnLevel, kindOfSpecialTarget: CgSpecialTarget.EnSpecialTarget, timeNotToEat: Int, intermission: Int)] = [
//round, speedLevel, PowerTime[ms], Spurtfeeds, GhostAppear, SpecialTarget, NoEatTime[ms], Intermission
( 1, .Level_A, 6000, 20, .Level_A, .Cherry, 4000, 0 ),
( 2, .Level_B, 5000, 30, .Level_B, .Strawberry, 4000, 1 ),
( 3, .Level_B, 4000, 40, .Level_C, .Orange, 3000, 0 ),
( 4, .Level_B, 3000, 40, .Level_C, .Orange, 3000, 0 ),
( 5, .Level_C, 2000, 40, .Level_C, .Apple, 3000, 2 ),
( 6, .Level_C, 5000, 50, .Level_C, .Apple, 3000, 0 ),
( 7, .Level_C, 2000, 50, .Level_C, .Melon, 3000, 0 ),
( 8, .Level_C, 2000, 50, .Level_C, .Melon, 3000, 0 ),
( 9, .Level_C, 1000, 60, .Level_C, .Galaxian, 3000, 3 ),
( 10, .Level_C, 5000, 60, .Level_C, .Galaxian, 3000, 0 ),
( 11, .Level_C, 2000, 60, .Level_C, .Bell, 3000, 0 ),
( 12, .Level_C, 1000, 80, .Level_C, .Bell, 3000, 0 ),
( 13, .Level_C, 1000, 80, .Level_C, .Key, 3000, 3 ),
( 14, .Level_C, 3000, 80, .Level_C, .Key, 3000, 0 ),
( 15, .Level_C, 1000, 100, .Level_C, .Key, 3000, 0 ),
( 16, .Level_C, 1000, 100, .Level_C, .Key, 3000, 0 ),
( 17, .Level_C, 0, 100, .Level_C, .Key, 3000, 3 ),
( 18, .Level_C, 1000, 100, .Level_C, .Key, 3000, 0 ),
( 19, .Level_C, 0, 100, .Level_C, .Key, 3000, 0 ),
( 20, .Level_C, 0, 100, .Level_C, .Key, 3000, 0 ),
( 21, .Level_C, 0, 100, .Level_C, .Key, 3000, 0 ),
( 22, .Level_D, 0, 100, .Level_C, .Key, 3000, 0 )
]
let table_speedSettings: [ (eatNone: Int, eatFeed: Int, eatPow: Int, eatNoneInPow: Int, eatFeedInPow: Int, eatPowInPow: Int,
ghost: Int, ghostInSpurt: Int, ghostInPow: Int, ghostInWarp: Int) ]
= [
// Level A
( eatNone: 16, eatFeed: 15, eatPow: 13, eatNoneInPow: 18, eatFeedInPow: 17, eatPowInPow: 15,
ghost: 15, ghostInSpurt: 16, ghostInPow: 10, ghostInWarp: 8 ),
// Level B
( eatNone: 18, eatFeed: 17, eatPow: 15, eatNoneInPow: 19, eatFeedInPow: 18, eatPowInPow: 16,
ghost: 17, ghostInSpurt: 18, ghostInPow: 11, ghostInWarp: 9 ),
// Level C
( eatNone: 20, eatFeed: 19, eatPow: 17, eatNoneInPow: 20, eatFeedInPow: 19, eatPowInPow: 17,
ghost: 19, ghostInSpurt: 20, ghostInPow: 12, ghostInWarp: 10 ),
// Level D
( eatNone: 18, eatFeed: 17, eatPow: 15, eatNoneInPow: 18, eatFeedInPow: 17, eatPowInPow: 15,
ghost: 19, ghostInSpurt: 20, ghostInPow: 10, ghostInWarp: 9 )
]
Extract the data according to the round from this tuple array and set it in each member of the CgContext class.
/// Set difficulty of the round
func setDifficulty() {
let index = demo ? 0 : round-1
let count = table_difficultySettings.count
let table = (index < count) ? table_difficultySettings[index] : table_difficultySettings[count-1]
levelOfSpeed = table.levelOfSpeed
timeWithPower = table.timeWithPower
numberOfFeedsRemaingToSpurt = table.numberOfFeedsRemaingToSpurt
levelOfAppearance = table.levelOfAppearance
kindOfSpecialTarget = table.kindOfSpecialTarget
timeNotToEat = table.timeNotToEat
intermission = table.intermission
}
The method to get the speed of the player (Pac-Man) is as follows. Change the value to be acquired when you eat power food and reverse it and when it is not.
func getPlayerSpeed(action: CgPlayer.EnPlayerAction, with power: Bool ) -> Int {
let index = levelOfSpeed.rawValue
let count = table_speedSettings.count
let table = (index < count) ? table_speedSettings[index] : table_speedSettings[count-1]
switch action {
case .Walking where !power : return table.eatNone
case .Walking where power : return table.eatNoneInPow
case .EatingFeed where !power : return table.eatFeed
case .EatingFeed where power : return table.eatFeedInPow
case .EatingPower where !power : return table.eatPow
case .EatingPower where power : return table.eatPowInPow
case .EatingFruit where !power : return table.eatNone
case .EatingFruit where power : return table.eatNoneInPow
default: return 16
}
}
The number of foods Pac-Man has eaten since the start of play numberOfFeedsEated returns the number of ghosts that appear for each level.
func getNumberOfGhostsForAppearace() -> Int {
let numberOfGhosts: Int
// Miss Bypass Sequence
if playerMiss {
if numberOfFeedsEatedByMiss < 7 {
numberOfGhosts = 1
} else if numberOfFeedsEatedByMiss < 17 {
numberOfGhosts = 2
} else if numberOfFeedsEatedByMiss < 32 {
numberOfGhosts = 3
} else {
playerMiss = false
numberOfGhosts = getNumberOfGhostsForAppearace()
}
} else {
switch levelOfAppearance {
case .Level_A:
if numberOfFeedsEated < 30 {
numberOfGhosts = 2
} else if numberOfFeedsEated < 90 {
numberOfGhosts = 3
} else {
numberOfGhosts = 4
}
case .Level_B:
if numberOfFeedsEated < 50 {
numberOfGhosts = 3
} else {
numberOfGhosts = 4
}
case .Level_C: fallthrough
default:
numberOfGhosts = 4
}
}
return numberOfGhosts
}
Switch between ChaseMode and ScatterMode depending on the time counted from the start. Determine the ChaseMode time for each level.
func judgeGhostsWavyChase(time: Int) -> Bool {
var chaseMode: Bool = false
switch levelOfSpeed {
case .Level_A:
chaseMode = (time >= 7000 && time < 27000) || (time >= 34000 && time < 54000)
|| (time >= 59000 && time < 79000) || (time >= 84000)
case .Level_B:
chaseMode = (time >= 7000 && time < 27000) || (time >= 34000 && time < 54000)
|| (time >= 59000)
case .Level_C: fallthrough
case .Level_D:
chaseMode = (time >= 5000 && time < 25000) || (time >= 30000 && time < 50000)
|| (time >= 55000)
}
return chaseMode
}
Finally, combine each mode created so far into the CgGameMain class.
--Attract Mode: CgSceneAttractMode --Credit mode: CgSceneCreditMode * Added to GameSequences.swift created this time --Start mode: CgSceneMaze --Play mode: CgSceneMaze is running
class CgGameMain : CgSceneFrame {
enum EnMainMode: Int {
case AttractMode = 0, CreditMode, WaitForStartButton, StartMode, PlayMode
}
enum EnSubMode: Int {
case Character = 0, StartDemo, PlayDemo
}
private var scene_attractMode: CgSceneAttractMode!
private var scene_creditMode: CgSceneCreditMode!
private var scene_maze: CgSceneMaze!
private var subMode: EnSubMode = .Character
init(skscene: SKScene) {
super.init()
// Create SpriteKit managers.
self.sprite = CgSpriteManager(view: skscene, imageNamed: "pacman16_16.png ", width: 16, height: 16, maxNumber: 64)
self.background = CgCustomBackgroundManager(view: skscene, imageNamed: "pacman8_8.png ", width: 8, height: 8, maxNumber: 2)
self.sound = CgSoundManager(binding: self, view: skscene)
self.context = CgContext()
scene_attractMode = CgSceneAttractMode(object: self)
scene_creditMode = CgSceneCreditMode(object: self)
scene_maze = CgSceneMaze(object: self)
}
/// Event handler
/// - Parameters:
/// - sender: Message sender
/// - id: Message ID
/// - values: Parameters of message
override func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
if message == .Touch {
if let mode: EnMainMode = EnMainMode(rawValue: getSequence()) {
if mode == .AttractMode || mode == .WaitForStartButton {
goToNextSequence()
}
}
}
}
/// Handle sequence
/// To override in a derived class.
/// - Parameter sequence: Sequence number
/// - Returns: If true, continue the sequence, if not, end the sequence.
override func handleSequence(sequence: Int) -> Bool {
guard let mode: EnMainMode = EnMainMode(rawValue: sequence) else { return false }
switch mode {
case .AttractMode: attarctMode()
case .CreditMode: creditMode()
case .WaitForStartButton: break // Forever loop
case .StartMode: startMode()
case .PlayMode: playMode()
}
// Continue running sequence.
return true
}
// ============================================================
// Execute each mode.
// ============================================================
func attarctMode() {
switch subMode {
case .Character:
scene_attractMode.resetSequence()
scene_attractMode.startSequence()
subMode = .StartDemo
case .StartDemo:
if !scene_attractMode.enabled {
context.demo = true
sound.enableOutput(false)
scene_maze.resetSequence()
scene_maze.startSequence()
subMode = .PlayDemo
}
case .PlayDemo:
if !scene_maze.enabled {
subMode = .Character
}
}
}
func creditMode() {
context.demo = false
if scene_attractMode.enabled {
scene_attractMode.stopSequence()
scene_attractMode.clear()
}
if scene_maze.enabled {
scene_maze.stopSequence()
scene_maze.clear()
}
context.credit += 1
scene_creditMode.resetSequence()
scene_creditMode.startSequence()
sound.enableOutput(true)
sound.playSE(.Credit)
goToNextSequence()
}
func startMode() {
context.credit -= 1
scene_creditMode.stopSequence()
scene_maze.resetSequence()
scene_maze.startSequence()
goToNextSequence()
}
func playMode() {
if !scene_maze.enabled {
subMode = .Character
goToNextSequence(EnMainMode.AttractMode.rawValue)
}
}
}
In addition to character introduction, there is a play demo in attract mode. This is a demo flag, and it was easy to implement by switching between swipe operation and operation table prepared in advance.
Operation table and get method are implemented in CgContext class. The direction is taken out according to the number of frames from the start.
let table_operationInDemo: [ (frameCount: Int, direction: EnDirection) ] = [
(9, .Left), (36, .Down), (61, .Right), (82, .Down), (109, .Right), (133, .Up), (162, .Right),
(189, .Up), (215, .Right), (238, .Down), (261, .Right), (308, .Down), (335, .Left), (523, .Up),
(555, .Right), (569, .Up), (609, .Left), (632, .Up), (648, .Right), (684, .Up), (732, .Left),
(831, .Down), (864, .Left), (931, .Up), (948, .Left), (970, .Up), (1063, .Right), (1113, .Down),
(1157, .Right), (1218, .Down)
]
func getOperationForDemo() -> EnDirection {
guard(demoSequence < table_operationInDemo.count) else { return .None }
let table = table_operationInDemo[demoSequence]
var direction: EnDirection = .None
if counterByFrame >= table.frameCount {
direction = table.direction
demoSequence += 1
}
return direction
}
Get the operation direction with the getOperationForDemo method and set it in the player. Added to the sequenceUpdating sequence of the CgSceneMaze class.
func sequenceUpdating() {
// Operate player in demonstration automatically.
if context.demo {
let direction = context.getOperationForDemo()
if direction != .None {
player.targetDirecition = direction
}
}
//Omitted below
Finally, it was almost completed.
There is no problem with the operating speed of the game. The source code is still around 5000 lines, which is fairly easy to do.
Next time, I'm making my own work, so I'd like to make various arrangements to complete it.