diff --git a/ios-spritekit-flappy-flying-bird.xcodeproj/project.pbxproj b/ios-spritekit-flappy-flying-bird.xcodeproj/project.pbxproj index 7dac6b9..c40ecd5 100644 --- a/ios-spritekit-flappy-flying-bird.xcodeproj/project.pbxproj +++ b/ios-spritekit-flappy-flying-bird.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ A903943A20AD9E7900376CC3 /* RainParticleEffect.sks in Resources */ = {isa = PBXBuildFile; fileRef = A903943820AD9E7900376CC3 /* RainParticleEffect.sks */; }; A903943B20AD9E7900376CC3 /* Particles.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A903943920AD9E7900376CC3 /* Particles.xcassets */; }; A903943F20ADB24000376CC3 /* ToggleButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A903943E20ADB24000376CC3 /* ToggleButtonNode.swift */; }; + A9092FA5216B35C800D773BC /* TriggleButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9092FA4216B35C800D773BC /* TriggleButtonNode.swift */; }; A92E72E2209D815B0015F647 /* PlayingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92E72E1209D815B0015F647 /* PlayingState.swift */; }; A92E72E4209D81820015F647 /* GameOverState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92E72E3209D81820015F647 /* GameOverState.swift */; }; A92E72E6209D818B0015F647 /* PausedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92E72E5209D818B0015F647 /* PausedState.swift */; }; @@ -110,6 +111,7 @@ A903943820AD9E7900376CC3 /* RainParticleEffect.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = RainParticleEffect.sks; sourceTree = ""; }; A903943920AD9E7900376CC3 /* Particles.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Particles.xcassets; sourceTree = ""; }; A903943E20ADB24000376CC3 /* ToggleButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleButtonNode.swift; sourceTree = ""; }; + A9092FA4216B35C800D773BC /* TriggleButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggleButtonNode.swift; sourceTree = ""; }; A92E72E1209D815B0015F647 /* PlayingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayingState.swift; sourceTree = ""; }; A92E72E3209D81820015F647 /* GameOverState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameOverState.swift; sourceTree = ""; }; A92E72E5209D818B0015F647 /* PausedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PausedState.swift; sourceTree = ""; }; @@ -227,6 +229,7 @@ children = ( A92E72ED209D87570015F647 /* ButtonNode.swift */, A903943E20ADB24000376CC3 /* ToggleButtonNode.swift */, + A9092FA4216B35C800D773BC /* TriggleButtonNode.swift */, ); path = "UI Components"; sourceTree = ""; @@ -721,6 +724,7 @@ A9EC756820A6E479007C42EC /* RoutingUtilityScene.swift in Sources */, A9D4CFD7209AF515006461AF /* SKScene+SpriteUploader.swift in Sources */, A92E72EE209D87570015F647 /* ButtonNode.swift in Sources */, + A9092FA5216B35C800D773BC /* TriggleButtonNode.swift in Sources */, A992D8B620A03B90003A9998 /* SceneOverlay.swift in Sources */, A9ACB9232099A1F200966991 /* GameScene.swift in Sources */, A9ACB98D2099F0FC00966991 /* Touchable.swift in Sources */, @@ -919,7 +923,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "eleev.astemir.io.ios-spritekit-flappy-fly-bird"; + PRODUCT_BUNDLE_IDENTIFIER = "eleev.astemir.io.ios-spritekit-flappy-fly-bird-"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; @@ -937,7 +941,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "eleev.astemir.io.ios-spritekit-flappy-fly-bird"; + PRODUCT_BUNDLE_IDENTIFIER = "eleev.astemir.io.ios-spritekit-flappy-fly-bird-"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios-spritekit-flappy-flying-bird/Assets/Scenes/Main Scenes/SettingsScene.sks b/ios-spritekit-flappy-flying-bird/Assets/Scenes/Main Scenes/SettingsScene.sks index 941be8c..6849584 100644 Binary files a/ios-spritekit-flappy-flying-bird/Assets/Scenes/Main Scenes/SettingsScene.sks and b/ios-spritekit-flappy-flying-bird/Assets/Scenes/Main Scenes/SettingsScene.sks differ diff --git a/ios-spritekit-flappy-flying-bird/Factories/PipeFactory.swift b/ios-spritekit-flappy-flying-bird/Factories/PipeFactory.swift index fba2a09..5b88238 100644 --- a/ios-spritekit-flappy-flying-bird/Factories/PipeFactory.swift +++ b/ios-spritekit-flappy-flying-bird/Factories/PipeFactory.swift @@ -35,7 +35,8 @@ struct PipeFactory { let cleanUpAction = SKAction.run { targetNode.childNode(withName: pipeName)?.removeFromParent() } - let waitAction = SKAction.wait(forDuration: 3.0) + + let waitAction = SKAction.wait(forDuration: UserDefaults.standard.getDifficultyLevel().rawValue) let pipeMoveDuration: TimeInterval = 4.5 let producePipeAction = SKAction.run { @@ -145,11 +146,11 @@ struct PipeFactory { } // Threshold node - let threshold = SKSpriteNode(color: .clear, size: CGSize(width: thresholdWidth, height: CGFloat.range(min: 800, max: 1100))) + let threshold = SKSpriteNode(color: .clear, size: CGSize(width: thresholdWidth, height: CGFloat.range(min: 700, max: 1200))) threshold.position = CGPoint(x: pipeX, y: (pipeBottom?.size.height)! + threshold.size.height / 2) threshold.physicsBody = SKPhysicsBody(rectangleOf: threshold.size) - threshold.physicsBody?.categoryBitMask = PhysicsCategories.gap.rawValue + threshold.physicsBody?.categoryBitMask = PhysicsCategories.gap.rawValue threshold.physicsBody?.contactTestBitMask = PhysicsCategories.player.rawValue threshold.physicsBody?.collisionBitMask = 0 threshold.physicsBody?.isDynamic = false diff --git a/ios-spritekit-flappy-flying-bird/Info.plist b/ios-spritekit-flappy-flying-bird/Info.plist index 394db91..cf1fedd 100644 --- a/ios-spritekit-flappy-flying-bird/Info.plist +++ b/ios-spritekit-flappy-flying-bird/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.1 + 1.4.0 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/ios-spritekit-flappy-flying-bird/Nodes/UI Components/ButtonNode.swift b/ios-spritekit-flappy-flying-bird/Nodes/UI Components/ButtonNode.swift index 18e49e4..16bec3c 100644 --- a/ios-spritekit-flappy-flying-bird/Nodes/UI Components/ButtonNode.swift +++ b/ios-spritekit-flappy-flying-bird/Nodes/UI Components/ButtonNode.swift @@ -27,10 +27,11 @@ enum ButtonIdentifier: String { case scores = "Scores" case sound = "Sound" case characters = "Characters" + case difficulty = "Difficulty" /// Convenience array of all available button identifiers. static let allButtonIdentifiers: [ButtonIdentifier] = [ - .play, .pause, .resume, .menu, .settings, .home, .retry, .cancel, .scores, sound, .characters + .play, .pause, .resume, .menu, .settings, .home, .retry, .cancel, .scores, sound, .characters, .difficulty ] /// The name of the texture to use for a button when the button is selected. diff --git a/ios-spritekit-flappy-flying-bird/Nodes/UI Components/TriggleButtonNode.swift b/ios-spritekit-flappy-flying-bird/Nodes/UI Components/TriggleButtonNode.swift new file mode 100644 index 0000000..fde205c --- /dev/null +++ b/ios-spritekit-flappy-flying-bird/Nodes/UI Components/TriggleButtonNode.swift @@ -0,0 +1,162 @@ +// +// TriggleButtonNode.swift +// ios-spritekit-flappy-flying-bird +// +// Created by Astemir Eleev on 08/10/2018. +// Copyright © 2018 Astemir Eleev. All rights reserved. +// + +import SpriteKit + +/// A type that can respond to `TriggleButtonNode` button press events. +protocol TriggleButtonNodeResponderType: class { + /// Responds to a button press. + func triggleButtonTriggered(triggle: TriggleButtonNode) +} + +class TriggleButtonNode: ButtonNode { + + // MARK: - Custom types + + enum TriggleState { + case off + case switched + case on + + static func convert(from difficultyLevel: Difficulty) -> TriggleState { + switch difficultyLevel { + case .easy: + return .off + case .medium: + return .switched + case .hard: + return .on + } + } + } + + struct Triggle { + + // MARK: - Properties + + private(set) var off: Bool + private(set) var switched: Bool + private(set) var on: Bool + private var lastTriggleState: TriggleState + + // MARK: - Initializers + + init(state: TriggleState) { + switch state { + case .off: + off = true + switched = false + on = false + lastTriggleState = .off + case .switched: + off = false + switched = true + on = false + lastTriggleState = .switched + case .on: + off = false + switched = false + on = true + lastTriggleState = .on + } + } + + // MARK: - Methods + + mutating func switchState() { + if off { + off = !off + switched = !switched + lastTriggleState = .switched + } else if switched { + switched = !switched + on = !on + lastTriggleState = .on + } else if on { + on = !on + off = !off + lastTriggleState = .off + } + } + + func state() -> TriggleState { + return lastTriggleState + } + + func toDifficultyLevel() -> Difficulty { + switch lastTriggleState { + case .off: + return Difficulty.easy + case .switched: + return Difficulty.medium + case .on: + return Difficulty.hard + } + } + + } + + // MARK: - Properties + + var triggle: Triggle { + didSet { + guard let off = state.off, let switched = state.switched, let on = state.on else { + return + } + on.isHidden = !triggle.on + off.isHidden = !triggle.off + switched.isHidden = !triggle.switched + + if isUserInteractionEnabled { + triggleResponder.triggleButtonTriggered(triggle: self) + } + } + } + + private var state: (off: SKLabelNode?, switched: SKLabelNode?, on: SKLabelNode?) = (off: nil, switched: nil, on: nil) + + var triggleResponder: TriggleButtonNodeResponderType { + guard let responder = scene as? TriggleButtonNodeResponderType else { + fatalError("ButtonNode may only be used within a `ButtonNodeResponderType` scene.") + } + return responder + } + + + // MARK: - Initialisers + + required init?(coder aDecoder: NSCoder) { + let difficultyLevel = UserDefaults.standard.getDifficultyLevel() + triggle = .init(state: TriggleState.convert(from: difficultyLevel)) + + super.init(coder: aDecoder) + + guard let offState = childNode(withName: "Easy") as? SKLabelNode else { + fatalError("Could not find SKLabel node") + } + state.off = offState + + guard let switchedState = childNode(withName: "Medium") as? SKLabelNode else { + fatalError("Could not find SKLabel node") + } + state.switched = switchedState + + guard let onState = childNode(withName: "Hard") as? SKLabelNode else { + fatalError("Could not find SKLabel node") + } + state.on = onState + + } + + // MARK: - Methods + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + triggle.switchState() + } +} diff --git a/ios-spritekit-flappy-flying-bird/Scenes/RoutingUtilityScene.swift b/ios-spritekit-flappy-flying-bird/Scenes/RoutingUtilityScene.swift index 2976d3a..bbda477 100644 --- a/ios-spritekit-flappy-flying-bird/Scenes/RoutingUtilityScene.swift +++ b/ios-spritekit-flappy-flying-bird/Scenes/RoutingUtilityScene.swift @@ -20,8 +20,6 @@ class RoutingUtilityScene: SKScene, ButtonNodeResponderType { // MARK: - Conformance to ButtonNodeResponderType func buttonTriggered(button: ButtonNode) { - debugPrint(#function + " button was triggered with identifier of : ", button.buttonIdentifier) - guard let identifier = button.buttonIdentifier else { return } diff --git a/ios-spritekit-flappy-flying-bird/Scenes/SettingsScene.swift b/ios-spritekit-flappy-flying-bird/Scenes/SettingsScene.swift index ffc2aca..cf65742 100644 --- a/ios-spritekit-flappy-flying-bird/Scenes/SettingsScene.swift +++ b/ios-spritekit-flappy-flying-bird/Scenes/SettingsScene.swift @@ -8,7 +8,7 @@ import SpriteKit -class SettingsScene: RoutingUtilityScene, ToggleButtonNodeResponderType { +class SettingsScene: RoutingUtilityScene, ToggleButtonNodeResponderType, TriggleButtonNodeResponderType { // MARK: - Overrides @@ -17,6 +17,11 @@ class SettingsScene: RoutingUtilityScene, ToggleButtonNodeResponderType { let soundButton = scene?.childNode(withName: "Sound") as? ToggleButtonNode soundButton?.isOn = UserDefaults.standard.bool(for: .isSoundOn) + + let difficultyButton = scene?.childNode(withName: "Difficulty") as? TriggleButtonNode + let difficultyLevel = UserDefaults.standard.getDifficultyLevel() + let difficultyState = TriggleButtonNode.TriggleState.convert(from: difficultyLevel) + difficultyButton?.triggle = .init(state: difficultyState) } // MARK: - Confrormance to ToggleButtonResponderType @@ -24,4 +29,13 @@ class SettingsScene: RoutingUtilityScene, ToggleButtonNodeResponderType { func toggleButtonTriggered(toggle: ToggleButtonNode) { UserDefaults.standard.set(toggle.isOn, for: .isSoundOn) } + + // MARK: - Conformance to TriggleButtonResponderType + + func triggleButtonTriggered(triggle: TriggleButtonNode) { + debugPrint("triggleButtonTriggered") + let diffuculty = triggle.triggle.toDifficultyLevel() + UserDefaults.standard.set(difficultyLevel: diffuculty) + } + } diff --git a/ios-spritekit-flappy-flying-bird/Utils/UserDefaults.swift b/ios-spritekit-flappy-flying-bird/Utils/UserDefaults.swift index 8a1898f..adad817 100644 --- a/ios-spritekit-flappy-flying-bird/Utils/UserDefaults.swift +++ b/ios-spritekit-flappy-flying-bird/Utils/UserDefaults.swift @@ -38,6 +38,15 @@ extension UserDefaults { func set(_ playableCharacter: PlayableCharacter, for setting: Setting) { set(playableCharacter.rawValue, forKey: setting.rawValue) } + + func set(difficultyLevel level: Difficulty) { + set(level.rawValue, forKey: Setting.difficulty.rawValue) + } + + func getDifficultyLevel() -> Difficulty { + let value = double(forKey: Setting.difficulty.rawValue) + return Difficulty(rawValue: value) ?? .medium + } } @@ -49,6 +58,7 @@ enum Setting: String { case lastScore case isSoundOn case character + case difficulty // MARK: - Methods @@ -57,11 +67,17 @@ enum Setting: String { Setting.bestScore.rawValue: 0, Setting.lastScore.rawValue: 0, Setting.isSoundOn.rawValue: true, - Setting.character.rawValue: PlayableCharacter.bird.rawValue + Setting.character.rawValue: PlayableCharacter.bird.rawValue, + Setting.difficulty.rawValue: Difficulty.medium.rawValue ]) } } +enum Difficulty: Double { + case easy = 4.0 + case medium = 3.5 + case hard = 3.0 +} enum PlayableCharacter: String { case bird = "bird"