├── .gitignore ├── FlappySwift.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── FlappySwift ├── AlertWrapper.swift ├── AppDelegate.swift ├── Background.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Bird.swift ├── FSPressableButton.swift ├── GameScene.swift ├── GameViewController.swift ├── Ground.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── Icon@2x.png │ └── Assets │ │ ├── bird1.imageset │ │ ├── Contents.json │ │ └── book_bird1.png │ │ ├── bird2.imageset │ │ ├── Contents.json │ │ └── book_bird2.png │ │ ├── bottomPipe.imageset │ │ ├── Contents.json │ │ └── bottomPipe.png │ │ ├── city.imageset │ │ ├── Contents.json │ │ └── city.png │ │ ├── ground.imageset │ │ ├── Contents.json │ │ └── city_ground.png │ │ ├── sky.imageset │ │ ├── Contents.json │ │ └── sky.png │ │ └── topPipe.imageset │ │ ├── Contents.json │ │ └── topPipe.png ├── Info.plist ├── MenuViewController.swift ├── MusicPlayer.swift ├── Pamgaea.mp3 ├── ParallaxNode.swift ├── Pipes.swift ├── PipesNode.swift ├── SKAction+FlappySwift.swift ├── SKPhysicsBody+FlappySwift.swift ├── Score.swift ├── Startable.swift ├── flap.wav ├── punch.wav ├── utilities.swift └── yeah.mp3 ├── FlappySwiftTests ├── FlappySwiftTests.swift └── Info.plist ├── FlappyTrim.mov ├── LICENSE ├── README.md ├── assets.zip ├── assets ├── Icon@2x.png ├── book_bird1.png ├── book_bird2.png ├── bottomPipe.png ├── city.png ├── city_ground.png ├── sky.png └── topPipe.png └── sounds.zip /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Xcode ### 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.xcuserstate 18 | 19 | -------------------------------------------------------------------------------- /FlappySwift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EE3E50831A9561D100CC5198 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3E50821A9561D100CC5198 /* MenuViewController.swift */; }; 11 | EE4A00821AAB98C400925811 /* punch.wav in Resources */ = {isa = PBXBuildFile; fileRef = EE4A00811AAB98C400925811 /* punch.wav */; }; 12 | EE4A00841AAB9B9700925811 /* flap.wav in Resources */ = {isa = PBXBuildFile; fileRef = EE4A00831AAB9B9700925811 /* flap.wav */; }; 13 | EE4A00861AAB9CDC00925811 /* MusicPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4A00851AAB9CDC00925811 /* MusicPlayer.swift */; }; 14 | EE4A00881AAB9E8900925811 /* Pamgaea.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EE4A00871AAB9E8900925811 /* Pamgaea.mp3 */; }; 15 | EE4A008A1AAB9F5F00925811 /* yeah.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EE4A00891AAB9F5F00925811 /* yeah.mp3 */; }; 16 | EE4A008C1AABA26D00925811 /* SKAction+FlappySwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4A008B1AABA26D00925811 /* SKAction+FlappySwift.swift */; }; 17 | EE4A008E1AABA37F00925811 /* utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4A008D1AABA37F00925811 /* utilities.swift */; }; 18 | EE4A00901AABB07900925811 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE4A008F1AABB07900925811 /* GameKit.framework */; }; 19 | EEB24C1F2DEE359700EBD4FF /* FSPressableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB24C1E2DEE359700EBD4FF /* FSPressableButton.swift */; }; 20 | EEB24C212DEE373900EBD4FF /* AlertWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB24C202DEE373900EBD4FF /* AlertWrapper.swift */; }; 21 | EEB782B91A9D448C00811E2B /* Score.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB782B81A9D448C00811E2B /* Score.swift */; }; 22 | EEBF6FBB1A954FF100C6D0D2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBF6FBA1A954FF100C6D0D2 /* AppDelegate.swift */; }; 23 | EEBF6FBF1A954FF100C6D0D2 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBF6FBE1A954FF100C6D0D2 /* GameScene.swift */; }; 24 | EEBF6FC11A954FF100C6D0D2 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBF6FC01A954FF100C6D0D2 /* GameViewController.swift */; }; 25 | EEBF6FC41A954FF100C6D0D2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EEBF6FC21A954FF100C6D0D2 /* Main.storyboard */; }; 26 | EEBF6FC61A954FF100C6D0D2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EEBF6FC51A954FF100C6D0D2 /* Images.xcassets */; }; 27 | EEBF6FC91A954FF100C6D0D2 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = EEBF6FC71A954FF100C6D0D2 /* LaunchScreen.xib */; }; 28 | EECD56931A963F4000EAFC65 /* Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECD568F1A963F4000EAFC65 /* Background.swift */; }; 29 | EECD56951A963F4000EAFC65 /* Startable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECD56911A963F4000EAFC65 /* Startable.swift */; }; 30 | EECD56991A96400D00EAFC65 /* ParallaxNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECD56981A96400D00EAFC65 /* ParallaxNode.swift */; }; 31 | EEE231821A9AB72100F248BA /* Bird.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE231811A9AB72100F248BA /* Bird.swift */; }; 32 | EEE231841A9ABDA400F248BA /* SKPhysicsBody+FlappySwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE231831A9ABDA400F248BA /* SKPhysicsBody+FlappySwift.swift */; }; 33 | EEE472D21A9BDEF600EB6916 /* Pipes.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE472D11A9BDEF600EB6916 /* Pipes.swift */; }; 34 | EEE472D41A9BE08500EB6916 /* PipesNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE472D31A9BE08500EB6916 /* PipesNode.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | EE3E50821A9561D100CC5198 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 39 | EE4A00811AAB98C400925811 /* punch.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = punch.wav; sourceTree = ""; }; 40 | EE4A00831AAB9B9700925811 /* flap.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = flap.wav; sourceTree = ""; }; 41 | EE4A00851AAB9CDC00925811 /* MusicPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicPlayer.swift; sourceTree = ""; }; 42 | EE4A00871AAB9E8900925811 /* Pamgaea.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Pamgaea.mp3; sourceTree = ""; }; 43 | EE4A00891AAB9F5F00925811 /* yeah.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = yeah.mp3; sourceTree = ""; }; 44 | EE4A008B1AABA26D00925811 /* SKAction+FlappySwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+FlappySwift.swift"; sourceTree = ""; }; 45 | EE4A008D1AABA37F00925811 /* utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = utilities.swift; sourceTree = ""; }; 46 | EE4A008F1AABB07900925811 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; }; 47 | EEB24C1E2DEE359700EBD4FF /* FSPressableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSPressableButton.swift; sourceTree = ""; }; 48 | EEB24C202DEE373900EBD4FF /* AlertWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertWrapper.swift; sourceTree = ""; }; 49 | EEB782B81A9D448C00811E2B /* Score.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Score.swift; sourceTree = ""; }; 50 | EEBF6FB51A954FF100C6D0D2 /* FlappySwift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlappySwift.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | EEBF6FB91A954FF100C6D0D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | EEBF6FBA1A954FF100C6D0D2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | EEBF6FBE1A954FF100C6D0D2 /* GameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; 54 | EEBF6FC01A954FF100C6D0D2 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; }; 55 | EEBF6FC31A954FF100C6D0D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | EEBF6FC51A954FF100C6D0D2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 57 | EEBF6FC81A954FF100C6D0D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 58 | EEBF6FD31A954FF100C6D0D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | EEBF6FD41A954FF100C6D0D2 /* FlappySwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlappySwiftTests.swift; sourceTree = ""; }; 60 | EECD568F1A963F4000EAFC65 /* Background.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Background.swift; sourceTree = ""; }; 61 | EECD56911A963F4000EAFC65 /* Startable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Startable.swift; sourceTree = ""; }; 62 | EECD56981A96400D00EAFC65 /* ParallaxNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParallaxNode.swift; sourceTree = ""; }; 63 | EEE231811A9AB72100F248BA /* Bird.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bird.swift; sourceTree = ""; }; 64 | EEE231831A9ABDA400F248BA /* SKPhysicsBody+FlappySwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKPhysicsBody+FlappySwift.swift"; sourceTree = ""; }; 65 | EEE472D11A9BDEF600EB6916 /* Pipes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pipes.swift; sourceTree = ""; }; 66 | EEE472D31A9BE08500EB6916 /* PipesNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PipesNode.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | EEBF6FB21A954FF100C6D0D2 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | EE4A00901AABB07900925811 /* GameKit.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 27DF251E19E9AE78106BB040 /* Frameworks */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | EE4A008F1AABB07900925811 /* GameKit.framework */, 85 | ); 86 | name = Frameworks; 87 | sourceTree = ""; 88 | }; 89 | EE4A00801AAB98B700925811 /* sounds */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | EE4A00891AAB9F5F00925811 /* yeah.mp3 */, 93 | EE4A00871AAB9E8900925811 /* Pamgaea.mp3 */, 94 | EE4A00831AAB9B9700925811 /* flap.wav */, 95 | EE4A00811AAB98C400925811 /* punch.wav */, 96 | ); 97 | name = sounds; 98 | sourceTree = ""; 99 | }; 100 | EEBF6FAC1A954FF100C6D0D2 = { 101 | isa = PBXGroup; 102 | children = ( 103 | EEBF6FB71A954FF100C6D0D2 /* FlappySwift */, 104 | EEBF6FD11A954FF100C6D0D2 /* FlappySwiftTests */, 105 | EEBF6FB61A954FF100C6D0D2 /* Products */, 106 | 27DF251E19E9AE78106BB040 /* Frameworks */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | EEBF6FB61A954FF100C6D0D2 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | EEBF6FB51A954FF100C6D0D2 /* FlappySwift.app */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | EEBF6FB71A954FF100C6D0D2 /* FlappySwift */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | EEB24C202DEE373900EBD4FF /* AlertWrapper.swift */, 122 | EEB24C1E2DEE359700EBD4FF /* FSPressableButton.swift */, 123 | EE4A008D1AABA37F00925811 /* utilities.swift */, 124 | EE4A008B1AABA26D00925811 /* SKAction+FlappySwift.swift */, 125 | EE4A00851AAB9CDC00925811 /* MusicPlayer.swift */, 126 | EECD568E1A963F2A00EAFC65 /* Actors */, 127 | EEBF6FBA1A954FF100C6D0D2 /* AppDelegate.swift */, 128 | EE3E50821A9561D100CC5198 /* MenuViewController.swift */, 129 | EEBF6FBE1A954FF100C6D0D2 /* GameScene.swift */, 130 | EEBF6FC01A954FF100C6D0D2 /* GameViewController.swift */, 131 | EEBF6FC21A954FF100C6D0D2 /* Main.storyboard */, 132 | EEBF6FC51A954FF100C6D0D2 /* Images.xcassets */, 133 | EEBF6FC71A954FF100C6D0D2 /* LaunchScreen.xib */, 134 | EEBF6FB81A954FF100C6D0D2 /* Supporting Files */, 135 | ); 136 | path = FlappySwift; 137 | sourceTree = ""; 138 | }; 139 | EEBF6FB81A954FF100C6D0D2 /* Supporting Files */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | EE4A00801AAB98B700925811 /* sounds */, 143 | EEBF6FB91A954FF100C6D0D2 /* Info.plist */, 144 | ); 145 | name = "Supporting Files"; 146 | sourceTree = ""; 147 | }; 148 | EEBF6FD11A954FF100C6D0D2 /* FlappySwiftTests */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | EEBF6FD41A954FF100C6D0D2 /* FlappySwiftTests.swift */, 152 | EEBF6FD21A954FF100C6D0D2 /* Supporting Files */, 153 | ); 154 | path = FlappySwiftTests; 155 | sourceTree = ""; 156 | }; 157 | EEBF6FD21A954FF100C6D0D2 /* Supporting Files */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | EEBF6FD31A954FF100C6D0D2 /* Info.plist */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | EECD568E1A963F2A00EAFC65 /* Actors */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | EECD56971A96400000EAFC65 /* Nodes */, 169 | EECD568F1A963F4000EAFC65 /* Background.swift */, 170 | EECD56911A963F4000EAFC65 /* Startable.swift */, 171 | EEE231811A9AB72100F248BA /* Bird.swift */, 172 | EEE472D11A9BDEF600EB6916 /* Pipes.swift */, 173 | EEE472D31A9BE08500EB6916 /* PipesNode.swift */, 174 | EEB782B81A9D448C00811E2B /* Score.swift */, 175 | ); 176 | name = Actors; 177 | sourceTree = ""; 178 | }; 179 | EECD56971A96400000EAFC65 /* Nodes */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | EECD56981A96400D00EAFC65 /* ParallaxNode.swift */, 183 | EEE231831A9ABDA400F248BA /* SKPhysicsBody+FlappySwift.swift */, 184 | ); 185 | name = Nodes; 186 | sourceTree = ""; 187 | }; 188 | /* End PBXGroup section */ 189 | 190 | /* Begin PBXNativeTarget section */ 191 | EEBF6FB41A954FF100C6D0D2 /* FlappySwift */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = EEBF6FD81A954FF100C6D0D2 /* Build configuration list for PBXNativeTarget "FlappySwift" */; 194 | buildPhases = ( 195 | EEBF6FB11A954FF100C6D0D2 /* Sources */, 196 | EEBF6FB21A954FF100C6D0D2 /* Frameworks */, 197 | EEBF6FB31A954FF100C6D0D2 /* Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | ); 203 | name = FlappySwift; 204 | productName = FlappySwift; 205 | productReference = EEBF6FB51A954FF100C6D0D2 /* FlappySwift.app */; 206 | productType = "com.apple.product-type.application"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | EEBF6FAD1A954FF100C6D0D2 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | LastUpgradeCheck = 0710; 215 | ORGANIZATIONNAME = "Effective Code"; 216 | TargetAttributes = { 217 | EEBF6FB41A954FF100C6D0D2 = { 218 | CreatedOnToolsVersion = 6.1.1; 219 | DevelopmentTeam = S2SVQZ2HDR; 220 | SystemCapabilities = { 221 | com.apple.GameCenter = { 222 | enabled = 1; 223 | }; 224 | }; 225 | }; 226 | }; 227 | }; 228 | buildConfigurationList = EEBF6FB01A954FF100C6D0D2 /* Build configuration list for PBXProject "FlappySwift" */; 229 | compatibilityVersion = "Xcode 3.2"; 230 | developmentRegion = English; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | English, 234 | en, 235 | Base, 236 | ); 237 | mainGroup = EEBF6FAC1A954FF100C6D0D2; 238 | productRefGroup = EEBF6FB61A954FF100C6D0D2 /* Products */; 239 | projectDirPath = ""; 240 | projectRoot = ""; 241 | targets = ( 242 | EEBF6FB41A954FF100C6D0D2 /* FlappySwift */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | EEBF6FB31A954FF100C6D0D2 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | EEBF6FC91A954FF100C6D0D2 /* LaunchScreen.xib in Resources */, 253 | EE4A00821AAB98C400925811 /* punch.wav in Resources */, 254 | EEBF6FC61A954FF100C6D0D2 /* Images.xcassets in Resources */, 255 | EE4A00881AAB9E8900925811 /* Pamgaea.mp3 in Resources */, 256 | EE4A00841AAB9B9700925811 /* flap.wav in Resources */, 257 | EE4A008A1AAB9F5F00925811 /* yeah.mp3 in Resources */, 258 | EEBF6FC41A954FF100C6D0D2 /* Main.storyboard in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXSourcesBuildPhase section */ 265 | EEBF6FB11A954FF100C6D0D2 /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | EEB24C212DEE373900EBD4FF /* AlertWrapper.swift in Sources */, 270 | EE4A00861AAB9CDC00925811 /* MusicPlayer.swift in Sources */, 271 | EECD56991A96400D00EAFC65 /* ParallaxNode.swift in Sources */, 272 | EEE472D21A9BDEF600EB6916 /* Pipes.swift in Sources */, 273 | EE4A008C1AABA26D00925811 /* SKAction+FlappySwift.swift in Sources */, 274 | EEE231841A9ABDA400F248BA /* SKPhysicsBody+FlappySwift.swift in Sources */, 275 | EEBF6FBF1A954FF100C6D0D2 /* GameScene.swift in Sources */, 276 | EE4A008E1AABA37F00925811 /* utilities.swift in Sources */, 277 | EEBF6FC11A954FF100C6D0D2 /* GameViewController.swift in Sources */, 278 | EEBF6FBB1A954FF100C6D0D2 /* AppDelegate.swift in Sources */, 279 | EE3E50831A9561D100CC5198 /* MenuViewController.swift in Sources */, 280 | EEB24C1F2DEE359700EBD4FF /* FSPressableButton.swift in Sources */, 281 | EECD56951A963F4000EAFC65 /* Startable.swift in Sources */, 282 | EEE472D41A9BE08500EB6916 /* PipesNode.swift in Sources */, 283 | EEE231821A9AB72100F248BA /* Bird.swift in Sources */, 284 | EEB782B91A9D448C00811E2B /* Score.swift in Sources */, 285 | EECD56931A963F4000EAFC65 /* Background.swift in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXSourcesBuildPhase section */ 290 | 291 | /* Begin PBXVariantGroup section */ 292 | EEBF6FC21A954FF100C6D0D2 /* Main.storyboard */ = { 293 | isa = PBXVariantGroup; 294 | children = ( 295 | EEBF6FC31A954FF100C6D0D2 /* Base */, 296 | ); 297 | name = Main.storyboard; 298 | sourceTree = ""; 299 | }; 300 | EEBF6FC71A954FF100C6D0D2 /* LaunchScreen.xib */ = { 301 | isa = PBXVariantGroup; 302 | children = ( 303 | EEBF6FC81A954FF100C6D0D2 /* Base */, 304 | ); 305 | name = LaunchScreen.xib; 306 | sourceTree = ""; 307 | }; 308 | /* End PBXVariantGroup section */ 309 | 310 | /* Begin XCBuildConfiguration section */ 311 | EEBF6FD61A954FF100C6D0D2 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_CONSTANT_CONVERSION = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | ENABLE_TESTABILITY = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_DYNAMIC_NO_PIC = NO; 334 | GCC_OPTIMIZATION_LEVEL = 0; 335 | GCC_PREPROCESSOR_DEFINITIONS = ( 336 | "DEBUG=1", 337 | "$(inherited)", 338 | ); 339 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 347 | MTL_ENABLE_DEBUG_INFO = YES; 348 | ONLY_ACTIVE_ARCH = YES; 349 | SDKROOT = iphoneos; 350 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 351 | SWIFT_VERSION = 5.9; 352 | }; 353 | name = Debug; 354 | }; 355 | EEBF6FD71A954FF100C6D0D2 /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_WARN_BOOL_CONVERSION = YES; 364 | CLANG_WARN_CONSTANT_CONVERSION = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INT_CONVERSION = YES; 369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 370 | CLANG_WARN_UNREACHABLE_CODE = YES; 371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 372 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 373 | COPY_PHASE_STRIP = YES; 374 | ENABLE_NS_ASSERTIONS = NO; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | GCC_C_LANGUAGE_STANDARD = gnu99; 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 384 | MTL_ENABLE_DEBUG_INFO = NO; 385 | SDKROOT = iphoneos; 386 | SWIFT_VERSION = 5.9; 387 | VALIDATE_PRODUCT = YES; 388 | }; 389 | name = Release; 390 | }; 391 | EEBF6FD91A954FF100C6D0D2 /* Debug */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 395 | CODE_SIGN_IDENTITY = "iPhone Developer"; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | INFOPLIST_FILE = FlappySwift/Info.plist; 398 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 399 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 400 | PRODUCT_BUNDLE_IDENTIFIER = "uk.co.EffectiveCode.$(PRODUCT_NAME:rfc1034identifier)"; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | PROVISIONING_PROFILE = ""; 403 | SWIFT_VERSION = 5.9; 404 | }; 405 | name = Debug; 406 | }; 407 | EEBF6FDA1A954FF100C6D0D2 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 411 | CODE_SIGN_IDENTITY = "iPhone Developer"; 412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 413 | INFOPLIST_FILE = FlappySwift/Info.plist; 414 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 416 | PRODUCT_BUNDLE_IDENTIFIER = "uk.co.EffectiveCode.$(PRODUCT_NAME:rfc1034identifier)"; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | PROVISIONING_PROFILE = ""; 419 | SWIFT_VERSION = 5.9; 420 | }; 421 | name = Release; 422 | }; 423 | /* End XCBuildConfiguration section */ 424 | 425 | /* Begin XCConfigurationList section */ 426 | EEBF6FB01A954FF100C6D0D2 /* Build configuration list for PBXProject "FlappySwift" */ = { 427 | isa = XCConfigurationList; 428 | buildConfigurations = ( 429 | EEBF6FD61A954FF100C6D0D2 /* Debug */, 430 | EEBF6FD71A954FF100C6D0D2 /* Release */, 431 | ); 432 | defaultConfigurationIsVisible = 0; 433 | defaultConfigurationName = Release; 434 | }; 435 | EEBF6FD81A954FF100C6D0D2 /* Build configuration list for PBXNativeTarget "FlappySwift" */ = { 436 | isa = XCConfigurationList; 437 | buildConfigurations = ( 438 | EEBF6FD91A954FF100C6D0D2 /* Debug */, 439 | EEBF6FDA1A954FF100C6D0D2 /* Release */, 440 | ); 441 | defaultConfigurationIsVisible = 0; 442 | defaultConfigurationName = Release; 443 | }; 444 | /* End XCConfigurationList section */ 445 | }; 446 | rootObject = EEBF6FAD1A954FF100C6D0D2 /* Project object */; 447 | } 448 | -------------------------------------------------------------------------------- /FlappySwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlappySwift/AlertWrapper.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIApplication { 4 | static func topViewController(base: UIViewController? = UIApplication.shared.connectedScenes 5 | .compactMap { ($0 as? UIWindowScene)?.keyWindow } 6 | .first?.rootViewController) -> UIViewController? { 7 | if let nav = base as? UINavigationController { 8 | return topViewController(base: nav.visibleViewController) 9 | } 10 | if let tab = base as? UITabBarController, let selected = tab.selectedViewController { 11 | return topViewController(base: selected) 12 | } 13 | if let presented = base?.presentedViewController { 14 | return topViewController(base: presented) 15 | } 16 | return base 17 | } 18 | } 19 | 20 | class AlertWrapper { 21 | struct Action { 22 | let title: String 23 | let style: UIAlertAction.Style 24 | let handler: (() -> Void)? 25 | init(title: String, style: UIAlertAction.Style = .default, handler: (() -> Void)? = nil) { 26 | self.title = title 27 | self.style = style 28 | self.handler = handler 29 | } 30 | } 31 | 32 | static func showAlert(on viewController: UIViewController, title: String, message: String, buttonTitle: String, completion: (() -> Void)? = nil) { 33 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 34 | let action = UIAlertAction(title: buttonTitle, style: .default) { _ in 35 | completion?() 36 | } 37 | alert.addAction(action) 38 | viewController.present(alert, animated: true, completion: nil) 39 | } 40 | 41 | static func showAlertWithActions(on viewController: UIViewController, title: String, message: String, actions: [Action]) { 42 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 43 | for action in actions { 44 | alert.addAction(UIAlertAction(title: action.title, style: action.style) { _ in 45 | action.handler?() 46 | }) 47 | } 48 | viewController.present(alert, animated: true, completion: nil) 49 | } 50 | 51 | static func showAlertOnTop(title: String, message: String, buttonTitle: String, completion: (() -> Void)? = nil) { 52 | if let topVC = UIApplication.topViewController() { 53 | showAlert(on: topVC, title: title, message: message, buttonTitle: buttonTitle, completion: completion) 54 | } 55 | } 56 | 57 | static func showAlertWithActionsOnTop(title: String, message: String, actions: [Action]) { 58 | if let topVC = UIApplication.topViewController() { 59 | showAlertWithActions(on: topVC, title: title, message: message, actions: actions) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /FlappySwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 18/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | let viewController = MenuViewController() 18 | let mainWindow = UIWindow(frame: UIScreen.main.bounds) 19 | mainWindow.backgroundColor = .white 20 | mainWindow.rootViewController = viewController 21 | mainWindow.makeKeyAndVisible() 22 | window = mainWindow 23 | return true 24 | } 25 | 26 | func applicationWillResignActive(_ application: UIApplication) { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 29 | } 30 | 31 | func applicationDidEnterBackground(_ application: UIApplication) { 32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | func applicationWillEnterForeground(_ application: UIApplication) { 37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | func applicationDidBecomeActive(_ application: UIApplication) { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /FlappySwift/Background.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Background.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 26/08/2014. 6 | // Copyright (c) 2014 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class Background { 12 | private let parallaxNode: ParallaxNode 13 | private let duration: Double 14 | 15 | func zPosition(_ zPosition: CGFloat) { 16 | parallaxNode.zPosition(zPosition) 17 | } 18 | 19 | func parallaxNodePositionYOffset(_ yOffset: CGFloat) { 20 | parallaxNode.setYOffset(yOffset) 21 | } 22 | 23 | init(textureNamed textureName: String, duration: Double) { 24 | parallaxNode = ParallaxNode(textureNamed: textureName) 25 | self.duration = duration 26 | } 27 | 28 | @discardableResult 29 | func addTo(_ parentNode: SKSpriteNode, zPosition: CGFloat) -> Self { 30 | parallaxNode.addTo(parentNode, zPosition: zPosition) 31 | return self 32 | } 33 | } 34 | 35 | // Startable 36 | extension Background: Startable { 37 | func start() { 38 | parallaxNode.start(duration: duration) 39 | } 40 | 41 | func stop() { 42 | parallaxNode.stop() 43 | } 44 | } -------------------------------------------------------------------------------- /FlappySwift/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /FlappySwift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /FlappySwift/Bird.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bird.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 23/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class Bird: Startable { 12 | private var node: SKSpriteNode! 13 | private let textureNames: [String] 14 | private var dying = false 15 | 16 | var position: CGPoint { 17 | set { node.position = newValue } 18 | get { return node.position } 19 | } 20 | 21 | init(textureNames: [String]) { 22 | self.textureNames = textureNames 23 | node = createNode() 24 | } 25 | 26 | @discardableResult 27 | func addTo(_ scene: SKSpriteNode) -> Bird { 28 | scene.addChild(node) 29 | return self 30 | } 31 | } 32 | 33 | // Creators 34 | private extension Bird { 35 | func createNode() -> SKSpriteNode { 36 | let birdNode = SKSpriteNode(imageNamed: textureNames.first!) 37 | birdNode.zPosition = 2.0 38 | birdNode.physicsBody = SKPhysicsBody.rectSize(birdNode.size.scale(factor: 0.8)) { 39 | body in 40 | body.isDynamic = true 41 | body.categoryBitMask = BodyType.bird.rawValue 42 | body.collisionBitMask = BodyType.bird.rawValue 43 | body.contactTestBitMask = BodyType.ground.rawValue | 44 | BodyType.pipe.rawValue | 45 | BodyType.gap.rawValue 46 | } 47 | return birdNode 48 | } 49 | } 50 | 51 | // Startable 52 | extension Bird { 53 | func start() { 54 | animate() 55 | } 56 | 57 | func stop() { 58 | node.physicsBody!.isDynamic = false 59 | node.removeAllActions() 60 | } 61 | } 62 | 63 | // Actions 64 | extension Bird { 65 | func flap() { 66 | if !dying { 67 | node.physicsBody!.velocity = CGVector(dx: 0, dy: 0) 68 | node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 6)) 69 | } 70 | } 71 | 72 | func pushDown() { 73 | dying = true 74 | node.physicsBody!.applyImpulse(CGVector(dx: 0, dy: -10)) 75 | } 76 | 77 | func update() { 78 | switch node.physicsBody!.velocity.dy { 79 | case let dy where dy > 30.0: 80 | node.zRotation = (3.14/6.0) 81 | case let dy where dy < -100.0: 82 | node.zRotation = -1*(3.14/4.0) 83 | default: 84 | node.zRotation = 0.0 85 | } 86 | } 87 | } 88 | 89 | // Private 90 | extension Bird { 91 | private func animate() { 92 | let animationFrames = textureNames.map { texName in 93 | SKTexture(imageNamed: texName) 94 | } 95 | node.run( 96 | SKAction.repeatForever( 97 | SKAction.animate(with: animationFrames, timePerFrame: 0.5) 98 | )) 99 | } 100 | } 101 | 102 | // CGSize Private 103 | extension CGSize { 104 | func scale(factor: CGFloat) -> CGSize { 105 | return CGSize(width: self.width * factor, height: self.height * factor) 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /FlappySwift/FSPressableButton.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class FSPressableButton: UIButton { 4 | enum Style { 5 | case grapefruit 6 | case aqua 7 | } 8 | 9 | private let style: Style 10 | private var originalBackgroundColor: UIColor? 11 | private var shadowLayer: CALayer? 12 | 13 | init(frame: CGRect, style: Style) { 14 | self.style = style 15 | super.init(frame: frame) 16 | setupStyle() 17 | setupShadow() 18 | addTarget(self, action: #selector(pressDown), for: [.touchDown, .touchDragEnter]) 19 | addTarget(self, action: #selector(pressUp), for: [.touchUpInside, .touchCancel, .touchDragExit]) 20 | } 21 | 22 | required init?(coder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | private func setupStyle() { 27 | layer.cornerRadius = 12 28 | layer.masksToBounds = false 29 | titleLabel?.font = UIFont.boldSystemFont(ofSize: 22) 30 | switch style { 31 | case .grapefruit: 32 | backgroundColor = UIColor(red: 1.0, green: 0.36, blue: 0.36, alpha: 1.0) 33 | setTitleColor(.white, for: .normal) 34 | case .aqua: 35 | backgroundColor = UIColor(red: 0.0, green: 0.78, blue: 0.98, alpha: 1.0) 36 | setTitleColor(.white, for: .normal) 37 | } 38 | originalBackgroundColor = backgroundColor 39 | } 40 | 41 | private func setupShadow() { 42 | shadowLayer = CALayer() 43 | shadowLayer?.shadowColor = UIColor.black.cgColor 44 | shadowLayer?.shadowOffset = CGSize(width: 0, height: 4) 45 | shadowLayer?.shadowRadius = 6 46 | shadowLayer?.shadowOpacity = 0.3 47 | shadowLayer?.frame = bounds 48 | shadowLayer?.cornerRadius = layer.cornerRadius 49 | if let shadowLayer = shadowLayer { 50 | layer.insertSublayer(shadowLayer, at: 0) 51 | } 52 | } 53 | 54 | override func layoutSubviews() { 55 | super.layoutSubviews() 56 | shadowLayer?.frame = bounds 57 | } 58 | 59 | @objc private func pressDown() { 60 | UIView.animate(withDuration: 0.1) { 61 | self.backgroundColor = self.originalBackgroundColor?.withAlphaComponent(0.7) 62 | self.transform = CGAffineTransform(scaleX: 0.97, y: 0.97) 63 | } 64 | } 65 | 66 | @objc private func pressUp() { 67 | UIView.animate(withDuration: 0.1) { 68 | self.backgroundColor = self.originalBackgroundColor 69 | self.transform = .identity 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /FlappySwift/GameScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 18/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | import UIKit 11 | 12 | class GameScene: SKScene { 13 | private var screenNode: SKSpriteNode! 14 | private var bird: Bird! 15 | private var actors: [Startable]! 16 | private var score = Score() 17 | 18 | var onPlayAgainPressed: (() -> Void)! 19 | var onCancelPressed: (() -> Void)! 20 | 21 | // Debug flag to hide background for better physics debug visibility 22 | var hideBackgroundForDebug: Bool = false 23 | 24 | override func didMove(to view: SKView) { 25 | physicsWorld.contactDelegate = self 26 | physicsWorld.gravity = CGVector(dx: 0, dy: -3) 27 | 28 | screenNode = SKSpriteNode(color: .clear, size: self.size) 29 | screenNode.anchorPoint = CGPoint(x: 0, y: 0) 30 | addChild(screenNode) 31 | 32 | var actorsList: [Startable] = [] 33 | if !hideBackgroundForDebug { 34 | // Add sky color fill at the top 35 | let skyColor = UIColor(red: 143.0/255.0, green: 233.0/255.0, blue: 227.0/255.0, alpha: 1.0) // #8FE9E3 36 | let skyFill = SKSpriteNode(color: skyColor, size: CGSize(width: self.size.width, height: self.size.height)) 37 | skyFill.anchorPoint = CGPoint(x: 0, y: 0) 38 | skyFill.position = CGPoint(x: 0, y: 0) 39 | skyFill.zPosition = -2 40 | screenNode.addChild(skyFill) 41 | 42 | // Add ground color fill at the bottom 43 | let groundColor = UIColor(red: 164.0/255.0, green: 121.0/255.0, blue: 65.0/255.0, alpha: 1.0) // #A47941 44 | let groundFillHeight: CGFloat = 80 45 | let groundFill = SKSpriteNode(color: groundColor, size: CGSize(width: self.size.width, height: groundFillHeight)) 46 | groundFill.anchorPoint = CGPoint(x: 0, y: 0) 47 | groundFill.position = CGPoint(x: 0, y: 0) 48 | groundFill.zPosition = 9 49 | screenNode.addChild(groundFill) 50 | 51 | // Move backgrounds up by 40 points 52 | let backgroundYOffset: CGFloat = 40 53 | let sky = Background(textureNamed: "sky", duration: 60.0) 54 | sky.zPosition(0) 55 | sky.addTo(screenNode, zPosition: 0) 56 | sky.parallaxNodePositionYOffset(backgroundYOffset) 57 | actorsList.append(sky) 58 | 59 | let city = Background(textureNamed: "city", duration: 20.0) 60 | city.zPosition(1) 61 | city.addTo(screenNode, zPosition: 1) 62 | city.parallaxNodePositionYOffset(backgroundYOffset) 63 | actorsList.append(city) 64 | 65 | let ground = Background(textureNamed: "ground", duration: 5.0) 66 | ground.zPosition(10) 67 | ground.addTo(screenNode, zPosition: 10) 68 | ground.parallaxNodePositionYOffset(backgroundYOffset) 69 | actorsList.append(ground) 70 | } 71 | 72 | screenNode.addChild(bodyTextureName("ground")) 73 | bird = Bird(textureNames: ["bird1.png", "bird2.png"]).addTo(screenNode) 74 | bird.position = CGPoint(x: 30.0, y: 400.0) 75 | let pipes = Pipes(topPipeTexture: "topPipe.png", bottomPipeTexture: "bottomPipe").addTo(screenNode) 76 | // Ensure all pipes are below the ground 77 | for child in screenNode.children { 78 | if child.name == "PIPES" { 79 | child.zPosition = 5 80 | } 81 | } 82 | score.addTo(screenNode) 83 | actors = actorsList + [bird, pipes] 84 | for actor in actors { 85 | actor.start() 86 | } 87 | } 88 | override func update(_ currentTime: TimeInterval) { 89 | bird.update() 90 | } 91 | 92 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 93 | run(SKAction.playSoundFileNamed("flap.wav", waitForCompletion: false)) 94 | bird.flap() 95 | } 96 | } 97 | private extension GameScene { 98 | func bodyTextureName(_ textureName: String) -> SKNode { 99 | let image = UIImage(named: textureName) 100 | let width = image!.size.width 101 | let height = image!.size.height 102 | let groundBody = SKNode() 103 | groundBody.position = CGPoint(x: width/2, y: height/2) 104 | groundBody.physicsBody = SKPhysicsBody.rectSize(CGSize(width: width, height: height)) { body in 105 | body.isDynamic = false 106 | body.affectedByGravity = false 107 | body.categoryBitMask = BodyType.ground.rawValue 108 | body.collisionBitMask = BodyType.ground.rawValue 109 | } 110 | return groundBody 111 | } 112 | } 113 | 114 | // Contacts 115 | extension GameScene: SKPhysicsContactDelegate { 116 | func didBegin(_ contact: SKPhysicsContact) { 117 | let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask 118 | switch (contactMask) { 119 | case BodyType.pipe.rawValue | BodyType.bird.rawValue: 120 | print("Contact with a pipe") 121 | run(SKAction.playSoundFileNamed("punch.wav", waitForCompletion: false)) 122 | bird.pushDown() 123 | case BodyType.ground.rawValue | BodyType.bird.rawValue: 124 | print("Contact with ground") 125 | run(SKAction.playSoundFileNamed("punch.wav", waitForCompletion: false)) 126 | for actor in actors { 127 | actor.stop() 128 | } 129 | let shakeAction = SKAction.shake(duration: 0.1, amplitudeX: 20, amplitudeY: 20) 130 | screenNode.run(shakeAction) 131 | execAfter(delay: 1) { 132 | self.askToPlayAgain() 133 | } 134 | default: 135 | return 136 | } 137 | } 138 | 139 | func didEnd(_ contact: SKPhysicsContact) { 140 | let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask 141 | switch (contactMask) { 142 | case BodyType.gap.rawValue | BodyType.bird.rawValue: 143 | print("Contact with gap") 144 | run(SKAction.playSoundFileNamed("yeah.mp3", waitForCompletion: false)) 145 | score.increase() 146 | default: 147 | return 148 | } 149 | } 150 | } 151 | 152 | // Private 153 | private extension GameScene { 154 | func askToPlayAgain() { 155 | AlertWrapper.showAlertWithActionsOnTop( 156 | title: "Ouch!!", 157 | message: "Congratulations! Your score is \(score.currentScore). Play again?", 158 | actions: [ 159 | AlertWrapper.Action(title: "OK", style: .default, handler: { [weak self] in self?.onPlayAgainPressed() }), 160 | AlertWrapper.Action(title: "Cancel", style: .cancel, handler: { [weak self] in self?.onCancelPressed() }) 161 | ] 162 | ) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /FlappySwift/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 18/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | 12 | class GameViewController: UIViewController { 13 | private let skView = SKView() 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | skView.frame = view.bounds 18 | view.addSubview(skView) 19 | skView.showsPhysics = false 20 | createTheScene() 21 | } 22 | 23 | private func createTheScene() { 24 | let scene = GameScene(size: skView.frame.size) 25 | scene.scaleMode = .aspectFill 26 | skView.showsFPS = true 27 | skView.showsNodeCount = true 28 | skView.ignoresSiblingOrder = true 29 | 30 | scene.onPlayAgainPressed = { [weak self] in 31 | self?.createTheScene() 32 | } 33 | 34 | scene.onCancelPressed = { [weak self] in 35 | self?.dismiss(animated: true, completion: nil) 36 | } 37 | skView.presentScene(scene) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /FlappySwift/Ground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ground.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 23/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class Ground { 12 | private var parallaxNode: ParallaxNode! 13 | private let textureName: String 14 | private let duration: Double 15 | 16 | init(textureNamed textureName: String, duration: Double) { 17 | parallaxNode = ParallaxNode(textureNamed: textureName) 18 | self.textureName = textureName 19 | self.duration = duration 20 | } 21 | 22 | @discardableResult 23 | func addTo(_ parentNode: SKSpriteNode) -> Self { 24 | parallaxNode.addTo(parentNode) 25 | return self 26 | } 27 | } 28 | 29 | // Startable 30 | extension Ground: Startable { 31 | func start() { 32 | parallaxNode.start(duration: duration) 33 | } 34 | 35 | func stop() { 36 | parallaxNode.stop() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "size" : "60x60", 25 | "idiom" : "iphone", 26 | "filename" : "Icon@2x.png", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "idiom" : "iphone", 31 | "size" : "60x60", 32 | "scale" : "3x" 33 | } 34 | ], 35 | "info" : { 36 | "version" : 1, 37 | "author" : "xcode" 38 | } 39 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bird1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "book_bird1.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bird1.imageset/book_bird1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/bird1.imageset/book_bird1.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bird2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "book_bird2.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bird2.imageset/book_bird2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/bird2.imageset/book_bird2.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bottomPipe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "bottomPipe.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/bottomPipe.imageset/bottomPipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/bottomPipe.imageset/bottomPipe.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/city.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "city.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/city.imageset/city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/city.imageset/city.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/ground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "city_ground.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/ground.imageset/city_ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/ground.imageset/city_ground.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/sky.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "sky.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/sky.imageset/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/sky.imageset/sky.png -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/topPipe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "topPipe.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlappySwift/Images.xcassets/Assets/topPipe.imageset/topPipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Images.xcassets/Assets/topPipe.imageset/topPipe.png -------------------------------------------------------------------------------- /FlappySwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | MinimumOSVersion 26 | 15.0 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | gamekit 35 | 36 | UIStatusBarHidden 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /FlappySwift/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 19/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MenuViewController: UIViewController { 12 | private let playButton = FSPressableButton(frame: CGRect(x: 0, y: 0, width: 260, height: 50), style: .grapefruit) 13 | private var player: MusicPlayer? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | do { 18 | player = try MusicPlayer(filename: "Pamgaea", type: "mp3") 19 | player!.play() 20 | } catch { 21 | print("Error playing soundtrack") 22 | } 23 | 24 | setup() 25 | layoutView() 26 | style() 27 | render() 28 | } 29 | } 30 | 31 | // MARK: Setup 32 | private extension MenuViewController { 33 | func setup() { 34 | playButton.addTarget(self, action: #selector(onPlayPressed(_:)), for: .touchUpInside) 35 | playButton.translatesAutoresizingMaskIntoConstraints = false 36 | view.addSubview(playButton) 37 | } 38 | 39 | @objc func onPlayPressed(_ sender: UIButton) { 40 | let vc = GameViewController() 41 | vc.modalTransitionStyle = .crossDissolve 42 | vc.modalPresentationStyle = .fullScreen 43 | present(vc, animated: true, completion: nil) 44 | } 45 | } 46 | 47 | // MARK: Layout 48 | extension MenuViewController { 49 | func layoutView() { 50 | // Play Button Constraints 51 | NSLayoutConstraint.activate([ 52 | playButton.heightAnchor.constraint(equalToConstant: 80), 53 | playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), 54 | playButton.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -40), 55 | playButton.bottomAnchor.constraint(equalTo: view.centerYAnchor, constant: -60) 56 | ]) 57 | } 58 | } 59 | 60 | // MARK: Style 61 | private extension MenuViewController { 62 | func style() { 63 | playButton.setTitle("Play", for: .normal) 64 | } 65 | } 66 | 67 | // MARK: Render 68 | private extension MenuViewController { 69 | func render() { 70 | playButton.setTitle("Play", for: .normal) 71 | } 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /FlappySwift/MusicPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicPlayer.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 07/03/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | enum MusicPlayerError: Error { 13 | case resourceNotFound 14 | } 15 | 16 | class MusicPlayer { 17 | private var player: AVAudioPlayer? = nil 18 | 19 | init(filename: String, type: String) throws { 20 | if let resource = Bundle.main.path(forResource: filename, ofType: type) { 21 | let url = URL(fileURLWithPath: resource) 22 | player = try AVAudioPlayer(contentsOf: url) 23 | player?.numberOfLoops = -1 24 | player?.prepareToPlay() 25 | } else { 26 | throw MusicPlayerError.resourceNotFound 27 | } 28 | } 29 | 30 | func play() { 31 | player?.play() 32 | } 33 | func stop() { 34 | player?.stop() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlappySwift/Pamgaea.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/Pamgaea.mp3 -------------------------------------------------------------------------------- /FlappySwift/ParallaxNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParallaxNode.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 26/08/2014. 6 | // Copyright (c) 2014 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class ParallaxNode { 12 | private let node: SKSpriteNode 13 | 14 | init(textureNamed: String) { 15 | let leftHalf = createHalfNodeTexture(textureNamed: textureNamed, offsetX: 0) 16 | let rightHalf = createHalfNodeTexture(textureNamed: textureNamed, offsetX: leftHalf.size.width) 17 | 18 | let size = CGSize(width: leftHalf.size.width + rightHalf.size.width, 19 | height: leftHalf.size.height) 20 | 21 | node = SKSpriteNode(color: .clear, size: size) 22 | node.anchorPoint = .zero 23 | node.position = .zero 24 | node.addChild(leftHalf) 25 | node.addChild(rightHalf) 26 | } 27 | 28 | @discardableResult 29 | func zPosition(_ zPosition: CGFloat) -> ParallaxNode { 30 | node.zPosition = zPosition 31 | return self 32 | } 33 | 34 | @discardableResult 35 | func addTo(_ parentNode: SKSpriteNode, zPosition: CGFloat = 0) -> ParallaxNode { 36 | parentNode.addChild(node) 37 | node.zPosition = zPosition 38 | return self 39 | } 40 | 41 | func setYOffset(_ yOffset: CGFloat) { 42 | node.position.y = yOffset 43 | } 44 | } 45 | 46 | // MARK: Startable 47 | extension ParallaxNode { 48 | 49 | func start(duration: TimeInterval) { 50 | node.run(SKAction.repeatForever(SKAction.sequence( 51 | [ 52 | SKAction.moveTo(x: -node.size.width/2.0, duration: duration), 53 | SKAction.moveTo(x: 0, duration: 0) 54 | ] 55 | ))) 56 | } 57 | 58 | func stop() { 59 | node.removeAllActions() 60 | } 61 | } 62 | 63 | // MARK: Private 64 | private func createHalfNodeTexture(textureNamed: String, offsetX: CGFloat) -> SKSpriteNode { 65 | let node = SKSpriteNode(imageNamed: textureNamed, normalMapped: true) 66 | node.anchorPoint = .zero 67 | node.position = CGPoint(x: offsetX, y: 0) 68 | return node 69 | } 70 | -------------------------------------------------------------------------------- /FlappySwift/Pipes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pipes.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 23/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class Pipes { 12 | private class var createActionKey: String { return "createActionKey" } 13 | private var parentNode: SKSpriteNode! 14 | private let topPipeTexture: String 15 | private let bottomPipeTexture: String 16 | 17 | init(topPipeTexture: String, bottomPipeTexture: String) { 18 | self.topPipeTexture = topPipeTexture 19 | self.bottomPipeTexture = bottomPipeTexture 20 | } 21 | 22 | @discardableResult 23 | func addTo(_ parentNode: SKSpriteNode) -> Pipes { 24 | self.parentNode = parentNode 25 | return self 26 | } 27 | } 28 | 29 | // MARK: Startable 30 | extension Pipes: Startable { 31 | func start() { 32 | let createAction = SKAction.repeatForever( 33 | SKAction.sequence( 34 | [ 35 | SKAction.run { [weak self] in 36 | self?.createNewPipesNode() 37 | }, 38 | SKAction.wait(forDuration: 3) 39 | ] 40 | ) ) 41 | 42 | parentNode.run(createAction, withKey: Pipes.createActionKey) 43 | } 44 | 45 | func stop() { 46 | parentNode.removeAction(forKey: Pipes.createActionKey) 47 | 48 | let pipeNodes = parentNode.children.filter { 49 | $0.name == PipesNode.kind 50 | } 51 | for pipe in pipeNodes { 52 | pipe.removeAllActions() 53 | } 54 | } 55 | } 56 | 57 | // MARK: Private 58 | private extension Pipes { 59 | func createNewPipesNode() { 60 | PipesNode(topPipeTexture: topPipeTexture, bottomPipeTexture: bottomPipeTexture, centerY: centerPipes()).addTo(parentNode).start() 61 | } 62 | 63 | func centerPipes() -> CGFloat { 64 | return parentNode.size.height/2 - 100 + 20 * CGFloat(arc4random_uniform(10)) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /FlappySwift/PipesNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PipesNode.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 23/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | import Foundation 11 | 12 | // Add BodyType enum here to resolve scope issues 13 | enum BodyType: UInt32 { 14 | case bird = 1 // (1 << 0) 15 | case ground = 2 // (1 << 1) 16 | case pipe = 4 // (1 << 2) 17 | case gap = 8 // (1 << 3) 18 | } 19 | 20 | class PipesNode { 21 | class var kind: String { return "PIPES" } 22 | private let gapSize: CGFloat = 50 23 | 24 | private let pipesNode: SKNode 25 | private let finalOffset: CGFloat 26 | private let startingOffset: CGFloat 27 | 28 | init(topPipeTexture: String, bottomPipeTexture: String, centerY: CGFloat) { 29 | pipesNode = SKNode() 30 | pipesNode.name = PipesNode.kind 31 | 32 | let pipeTop = createPipe(imageNamed: topPipeTexture) 33 | let pipeTopPosition = CGPoint(x: 0, y: centerY + pipeTop.size.height/2 + gapSize) 34 | pipeTop.position = pipeTopPosition 35 | pipesNode.addChild(pipeTop) 36 | 37 | let pipeBottom = createPipe(imageNamed: bottomPipeTexture) 38 | let pipeBottomPosition = CGPoint(x: 0 , y: centerY - pipeBottom.size.height/2 - gapSize) 39 | pipeBottom.position = pipeBottomPosition 40 | pipesNode.addChild(pipeBottom) 41 | 42 | let gapNode = createGap(size: CGSize( 43 | width: pipeBottom.size.width, 44 | height: gapSize*2)) 45 | gapNode.position = CGPoint(x: 0, y: centerY) 46 | pipesNode.addChild(gapNode) 47 | 48 | finalOffset = -pipeBottom.size.width 49 | startingOffset = -finalOffset 50 | } 51 | 52 | @discardableResult 53 | func addTo(_ parentNode: SKSpriteNode) -> PipesNode { 54 | let pipePosition = CGPoint(x: parentNode.size.width + startingOffset, y: 0) 55 | pipesNode.position = pipePosition 56 | pipesNode.zPosition = 4 57 | 58 | parentNode.addChild(pipesNode) 59 | return self 60 | } 61 | 62 | func start() { 63 | pipesNode.run(SKAction.sequence( 64 | [ 65 | SKAction.moveTo(x: finalOffset, duration: 6.0), 66 | SKAction.removeFromParent() 67 | ] 68 | )) 69 | } 70 | } 71 | 72 | // Creators 73 | private func createPipe(imageNamed: String) -> SKSpriteNode { 74 | let pipeNode = SKSpriteNode(imageNamed: imageNamed) 75 | let bodyWidth = pipeNode.size.width * 0.7 76 | let bodyHeight = pipeNode.size.height * 0.99 77 | let size = CGSize(width: bodyWidth, height: bodyHeight) 78 | pipeNode.physicsBody = SKPhysicsBody(rectangleOf: size) 79 | pipeNode.physicsBody?.isDynamic = false 80 | pipeNode.physicsBody?.affectedByGravity = false 81 | pipeNode.physicsBody?.categoryBitMask = BodyType.pipe.rawValue 82 | pipeNode.physicsBody?.collisionBitMask = BodyType.pipe.rawValue 83 | return pipeNode 84 | } 85 | 86 | private func createGap(size: CGSize) -> SKSpriteNode { 87 | let gapNode = SKSpriteNode(color: .clear, size: size) 88 | gapNode.zPosition = 6 89 | gapNode.physicsBody = SKPhysicsBody(rectangleOf: size) 90 | gapNode.physicsBody?.isDynamic = false 91 | gapNode.physicsBody?.affectedByGravity = false 92 | gapNode.physicsBody?.categoryBitMask = BodyType.gap.rawValue 93 | gapNode.physicsBody?.collisionBitMask = BodyType.gap.rawValue 94 | return gapNode 95 | } 96 | -------------------------------------------------------------------------------- /FlappySwift/SKAction+FlappySwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKAction+FlappySwift.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 07/03/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | extension SKAction { 12 | // Thanks to Benzi: http://stackoverflow.com/a/24769521/288379 13 | class func shake(duration: CGFloat, amplitudeX: Int = 3, amplitudeY: Int = 3) -> SKAction { 14 | let numberOfShakes = duration / 0.015 / 2.0 15 | var actionsArray: [SKAction] = [] 16 | for _ in 1...Int(numberOfShakes) { 17 | let dx = CGFloat(arc4random_uniform(UInt32(amplitudeX))) - CGFloat(amplitudeX / 2) 18 | let dy = CGFloat(arc4random_uniform(UInt32(amplitudeY))) - CGFloat(amplitudeY / 2) 19 | let forward = SKAction.moveBy(x: dx, y: dy, duration: 0.015) 20 | let reverse = forward.reversed() 21 | actionsArray.append(forward) 22 | actionsArray.append(reverse) 23 | } 24 | return SKAction.sequence(actionsArray) 25 | } 26 | } -------------------------------------------------------------------------------- /FlappySwift/SKPhysicsBody+FlappySwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKPhysicsBody+FlappySwift.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 23/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | extension SKPhysicsBody { 12 | typealias BodyBuilderClosure = (SKPhysicsBody) -> Void 13 | 14 | class func rectSize(_ size: CGSize, 15 | builderClosure: BodyBuilderClosure) -> SKPhysicsBody { 16 | let body = SKPhysicsBody(rectangleOf: size) 17 | builderClosure(body) 18 | return body 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FlappySwift/Score.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Score.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 24/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class Score { 12 | private let score = SKLabelNode(text: "0") 13 | var currentScore = 0 14 | 15 | @discardableResult 16 | func addTo(_ parentNode: SKSpriteNode) -> Score { 17 | score.fontName = "MarkerFelt-Wide" 18 | score.fontSize = 30 19 | score.position = CGPoint(x: parentNode.size.width/2, y: parentNode.size.height - 40) 20 | parentNode.addChild(score) 21 | return self 22 | } 23 | 24 | func increase() { 25 | currentScore += 1 26 | score.text = "\(currentScore)" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FlappySwift/Startable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Startable.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 07/09/2014. 6 | // Copyright (c) 2014 Effective Code. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | protocol Startable { 12 | func start() 13 | func stop() 14 | } -------------------------------------------------------------------------------- /FlappySwift/flap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/flap.wav -------------------------------------------------------------------------------- /FlappySwift/punch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/punch.wav -------------------------------------------------------------------------------- /FlappySwift/utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // utilities.swift 3 | // FlappySwift 4 | // 5 | // Created by Giordano Scalzo on 07/03/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func execAfter(delay: Double, closure: @escaping () -> Void) { 12 | DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure) 13 | } -------------------------------------------------------------------------------- /FlappySwift/yeah.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappySwift/yeah.mp3 -------------------------------------------------------------------------------- /FlappySwiftTests/FlappySwiftTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlappySwiftTests.swift 3 | // FlappySwiftTests 4 | // 5 | // Created by Giordano Scalzo on 18/02/2015. 6 | // Copyright (c) 2015 Effective Code. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FlappySwiftTests: XCTestCase { 12 | override func setUp() { 13 | super.setUp() 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | super.tearDown() 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | XCTAssertTrue(true, "Pass") 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /FlappySwiftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | uk.co.EffectiveCode.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlappyTrim.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/FlappyTrim.mov -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, Giordano Scalzo 4 | Copyright (c) 2015, Packt Publishing Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlappySwift 2 | 3 | > This project is the one that started my career in Swift and as a book author. — Gio Scalzo 4 | 5 | FlappySwift is a modern, clean, and modular implementation of the classic Flappy Bird game, written entirely in Swift. It was the foundation for my journey into iOS development and technical writing. 6 | 7 | ## 📽️ Demo Video 8 | 9 | https://github.com/user-attachments/assets/6428bde2-827b-4335-94f9-87ff71274703 10 | 11 | 12 | --- 13 | 14 | ## 🗂️ Module Diagram 15 | 16 | ```mermaid 17 | graph TD 18 | MenuViewController -->|presents| GameViewController 19 | GameViewController -->|hosts| GameScene 20 | GameScene -->|uses| Bird 21 | GameScene -->|uses| Pipes 22 | GameScene -->|uses| Background 23 | GameScene -->|uses| Score 24 | GameScene -->|uses| AlertWrapper 25 | GameScene -->|uses| MusicPlayer 26 | GameScene -->|uses| utilities 27 | Pipes --> PipesNode 28 | Background --> ParallaxNode 29 | MenuViewController --> FSPressableButton 30 | ``` 31 | 32 | --- 33 | 34 | ## 📝 About 35 | 36 | - **Author:** Gio Scalzo 37 | - **Original Year:** 2014 38 | - **Renewed Year:** 2025 39 | - **Language:** Swift 40 | - **License:** MIT 41 | 42 | This codebase is a great starting point for learning SpriteKit, game architecture, and modular Swift design. Enjoy hacking and flying! 43 | -------------------------------------------------------------------------------- /assets.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets.zip -------------------------------------------------------------------------------- /assets/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/Icon@2x.png -------------------------------------------------------------------------------- /assets/book_bird1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/book_bird1.png -------------------------------------------------------------------------------- /assets/book_bird2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/book_bird2.png -------------------------------------------------------------------------------- /assets/bottomPipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/bottomPipe.png -------------------------------------------------------------------------------- /assets/city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/city.png -------------------------------------------------------------------------------- /assets/city_ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/city_ground.png -------------------------------------------------------------------------------- /assets/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/sky.png -------------------------------------------------------------------------------- /assets/topPipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/assets/topPipe.png -------------------------------------------------------------------------------- /sounds.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gscalzo/FlappySwift/5f3dc16827a501a94362f4773de7378fc68b89ef/sounds.zip --------------------------------------------------------------------------------