├── .gitignore ├── BubbleGame.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── BubbleGame ├── Actions.sks ├── Actors │ ├── Bubble.h │ ├── Bubble.m │ ├── RedBall.h │ ├── RedBall.m │ ├── SpinnyNode.h │ └── SpinnyNode.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Configs │ ├── GameConfigs.h │ └── GameConfigs.m ├── GameScenes │ ├── MainGameScene.h │ └── MainGameScene.m ├── GameViewController.h ├── GameViewController.m ├── Helper │ ├── BubbleFaild.sks │ ├── BubbleSucceed.sks │ ├── Fire.sks │ ├── GameEmitter.xcassets │ │ ├── Contents.json │ │ └── Particle Sprite Atlas.spriteatlas │ │ │ ├── Contents.json │ │ │ ├── bokeh.imageset │ │ │ ├── Contents.json │ │ │ └── bokeh.png │ │ │ └── spark.imageset │ │ │ ├── Contents.json │ │ │ └── spark.png │ ├── GameEmitterManager.h │ ├── GameEmitterManager.m │ ├── GameSucceed.sks │ ├── MianSoundManager.h │ ├── MianSoundManager.m │ └── Snow.sks ├── ImageReource.xcassets │ ├── Contents.json │ ├── RedBallStopImage.imageset │ │ ├── Contents.json │ │ └── RedBallStopImage.png │ ├── background.imageset │ │ ├── Contents.json │ │ └── background.png │ ├── ball.imageset │ │ ├── Contents.json │ │ └── ball.png │ ├── ball_ice.imageset │ │ ├── Contents.json │ │ └── ball_ice.png │ ├── bubble.imageset │ │ ├── Contents.json │ │ └── bubble.png │ ├── bubble_ice.imageset │ │ ├── Contents.json │ │ └── bubble_ice.png │ ├── game_play.imageset │ │ ├── Contents.json │ │ └── game_play-1.png │ ├── game_stop.imageset │ │ ├── Contents.json │ │ └── game_stop-1.png │ ├── icon_fireworks_flower.imageset │ │ ├── Contents.json │ │ └── icon_fireworks_flower@3x.png │ ├── redBallStop.imageset │ │ ├── Contents.json │ │ └── redBallStop-1.png │ └── startGame.imageset │ │ ├── Contents.json │ │ └── startGame.png ├── Info.plist ├── Resources │ ├── 04B_25.TTF │ ├── backmusic.wav │ ├── bubble_growing.wav │ ├── bubble_made_faild_byhit.wav │ ├── bubble_made_faild_toosmall.wav │ ├── button_click.wav │ ├── game_faild.wav │ ├── game_readyGo.wav │ ├── game_succeed.wav │ ├── redball_stop.wav │ └── timer_nomuch.wav ├── UI │ ├── GameButton.h │ ├── GameButton.m │ ├── GameLevelLabel.h │ ├── GameLevelLabel.m │ ├── GameProgressBar.h │ └── GameProgressBar.m ├── Util │ ├── CommUtil.h │ ├── CommUtil.m │ ├── UIColor+ColorExtension.h │ └── UIColor+ColorExtension.m └── main.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /BubbleGame.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EC08294E222FC1DD0033A12B /* RedBall.m in Sources */ = {isa = PBXBuildFile; fileRef = EC08294D222FC1DD0033A12B /* RedBall.m */; }; 11 | EC082953222FC5470033A12B /* Bubble.m in Sources */ = {isa = PBXBuildFile; fileRef = EC082952222FC5470033A12B /* Bubble.m */; }; 12 | EC082955222FE2060033A12B /* ImageReource.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC082954222FE2060033A12B /* ImageReource.xcassets */; }; 13 | EC082959222FE3B50033A12B /* CommUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = EC082958222FE3B50033A12B /* CommUtil.m */; }; 14 | EC08295D222FEA6B0033A12B /* GameConfigs.m in Sources */ = {isa = PBXBuildFile; fileRef = EC08295C222FEA6B0033A12B /* GameConfigs.m */; }; 15 | EC494D4E222FF516002D98E5 /* Actions.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC494D4D222FF516002D98E5 /* Actions.sks */; }; 16 | EC494D51222FF7B6002D98E5 /* SpinnyNode.m in Sources */ = {isa = PBXBuildFile; fileRef = EC494D50222FF7B6002D98E5 /* SpinnyNode.m */; }; 17 | EC494D54222FF9E0002D98E5 /* UIColor+ColorExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = EC494D53222FF9E0002D98E5 /* UIColor+ColorExtension.m */; }; 18 | EC68A5252234DB5A00A2F1D2 /* backmusic.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5242234DB5A00A2F1D2 /* backmusic.wav */; }; 19 | EC68A52F2234E2DD00A2F1D2 /* game_readyGo.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5262234E2DC00A2F1D2 /* game_readyGo.wav */; }; 20 | EC68A5302234E2DD00A2F1D2 /* bubble_made_faild_byhit.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5272234E2DC00A2F1D2 /* bubble_made_faild_byhit.wav */; }; 21 | EC68A5312234E2DD00A2F1D2 /* bubble_growing.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5282234E2DC00A2F1D2 /* bubble_growing.wav */; }; 22 | EC68A5322234E2DD00A2F1D2 /* timer_nomuch.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5292234E2DD00A2F1D2 /* timer_nomuch.wav */; }; 23 | EC68A5332234E2DD00A2F1D2 /* redball_stop.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A52A2234E2DD00A2F1D2 /* redball_stop.wav */; }; 24 | EC68A5342234E2DD00A2F1D2 /* game_succeed.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A52B2234E2DD00A2F1D2 /* game_succeed.wav */; }; 25 | EC68A5352234E2DD00A2F1D2 /* bubble_made_faild_toosmall.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A52C2234E2DD00A2F1D2 /* bubble_made_faild_toosmall.wav */; }; 26 | EC68A5362234E2DD00A2F1D2 /* button_click.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A52D2234E2DD00A2F1D2 /* button_click.wav */; }; 27 | EC68A5372234E2DD00A2F1D2 /* game_faild.wav in Resources */ = {isa = PBXBuildFile; fileRef = EC68A52E2234E2DD00A2F1D2 /* game_faild.wav */; }; 28 | EC68A53A2234EB5B00A2F1D2 /* GameEmitterManager.m in Sources */ = {isa = PBXBuildFile; fileRef = EC68A5392234EB5B00A2F1D2 /* GameEmitterManager.m */; }; 29 | EC68A54F2234EE5300A2F1D2 /* BubbleFaild.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC68A54D2234EE5300A2F1D2 /* BubbleFaild.sks */; }; 30 | EC68A5502234EE5300A2F1D2 /* GameEmitter.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC68A54E2234EE5300A2F1D2 /* GameEmitter.xcassets */; }; 31 | EC68A5582234F98200A2F1D2 /* GameButton.m in Sources */ = {isa = PBXBuildFile; fileRef = EC68A5572234F98200A2F1D2 /* GameButton.m */; }; 32 | EC68A55B2235320C00A2F1D2 /* Snow.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5592235320C00A2F1D2 /* Snow.sks */; }; 33 | EC68A55E2235375B00A2F1D2 /* BubbleSucceed.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC68A55D2235375B00A2F1D2 /* BubbleSucceed.sks */; }; 34 | EC68A56022353D1D00A2F1D2 /* Fire.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC68A55F22353D1D00A2F1D2 /* Fire.sks */; }; 35 | EC68A563223541D300A2F1D2 /* GameLevelLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = EC68A562223541D300A2F1D2 /* GameLevelLabel.m */; }; 36 | EC68A5652235493500A2F1D2 /* 04B_25.TTF in Resources */ = {isa = PBXBuildFile; fileRef = EC68A5642235438B00A2F1D2 /* 04B_25.TTF */; }; 37 | EC6ADA5E22354D92008A5212 /* GameProgressBar.m in Sources */ = {isa = PBXBuildFile; fileRef = EC6ADA5D22354D92008A5212 /* GameProgressBar.m */; }; 38 | EC6ADA652235ED9F008A5212 /* GameSucceed.sks in Resources */ = {isa = PBXBuildFile; fileRef = EC6ADA632235ED9F008A5212 /* GameSucceed.sks */; }; 39 | ECD40E6D2234B20E00607DCA /* MianSoundManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD40E6C2234B20E00607DCA /* MianSoundManager.m */; }; 40 | ECF9A1B5222EA82400952DA2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF9A1B4222EA82400952DA2 /* AppDelegate.m */; }; 41 | ECF9A1BC222EA82400952DA2 /* MainGameScene.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF9A1BB222EA82400952DA2 /* MainGameScene.m */; }; 42 | ECF9A1BF222EA82400952DA2 /* GameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF9A1BE222EA82400952DA2 /* GameViewController.m */; }; 43 | ECF9A1C2222EA82400952DA2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ECF9A1C0222EA82400952DA2 /* Main.storyboard */; }; 44 | ECF9A1C4222EA82500952DA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECF9A1C3222EA82500952DA2 /* Assets.xcassets */; }; 45 | ECF9A1C7222EA82500952DA2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ECF9A1C5222EA82500952DA2 /* LaunchScreen.storyboard */; }; 46 | ECF9A1CA222EA82500952DA2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF9A1C9222EA82500952DA2 /* main.m */; }; 47 | /* End PBXBuildFile section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | EC08294C222FC1DD0033A12B /* RedBall.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RedBall.h; sourceTree = ""; }; 51 | EC08294D222FC1DD0033A12B /* RedBall.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RedBall.m; sourceTree = ""; }; 52 | EC082951222FC5470033A12B /* Bubble.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Bubble.h; sourceTree = ""; }; 53 | EC082952222FC5470033A12B /* Bubble.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Bubble.m; sourceTree = ""; }; 54 | EC082954222FE2060033A12B /* ImageReource.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ImageReource.xcassets; sourceTree = ""; }; 55 | EC082957222FE3B50033A12B /* CommUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommUtil.h; sourceTree = ""; }; 56 | EC082958222FE3B50033A12B /* CommUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CommUtil.m; sourceTree = ""; }; 57 | EC08295B222FEA6B0033A12B /* GameConfigs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameConfigs.h; sourceTree = ""; }; 58 | EC08295C222FEA6B0033A12B /* GameConfigs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameConfigs.m; sourceTree = ""; }; 59 | EC494D4D222FF516002D98E5 /* Actions.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = Actions.sks; sourceTree = ""; }; 60 | EC494D4F222FF7B6002D98E5 /* SpinnyNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpinnyNode.h; sourceTree = ""; }; 61 | EC494D50222FF7B6002D98E5 /* SpinnyNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SpinnyNode.m; sourceTree = ""; }; 62 | EC494D52222FF9E0002D98E5 /* UIColor+ColorExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+ColorExtension.h"; sourceTree = ""; }; 63 | EC494D53222FF9E0002D98E5 /* UIColor+ColorExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+ColorExtension.m"; sourceTree = ""; }; 64 | EC68A5242234DB5A00A2F1D2 /* backmusic.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = backmusic.wav; sourceTree = ""; }; 65 | EC68A5262234E2DC00A2F1D2 /* game_readyGo.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = game_readyGo.wav; sourceTree = ""; }; 66 | EC68A5272234E2DC00A2F1D2 /* bubble_made_faild_byhit.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bubble_made_faild_byhit.wav; sourceTree = ""; }; 67 | EC68A5282234E2DC00A2F1D2 /* bubble_growing.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bubble_growing.wav; sourceTree = ""; }; 68 | EC68A5292234E2DD00A2F1D2 /* timer_nomuch.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = timer_nomuch.wav; sourceTree = ""; }; 69 | EC68A52A2234E2DD00A2F1D2 /* redball_stop.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = redball_stop.wav; sourceTree = ""; }; 70 | EC68A52B2234E2DD00A2F1D2 /* game_succeed.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = game_succeed.wav; sourceTree = ""; }; 71 | EC68A52C2234E2DD00A2F1D2 /* bubble_made_faild_toosmall.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bubble_made_faild_toosmall.wav; sourceTree = ""; }; 72 | EC68A52D2234E2DD00A2F1D2 /* button_click.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = button_click.wav; sourceTree = ""; }; 73 | EC68A52E2234E2DD00A2F1D2 /* game_faild.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = game_faild.wav; sourceTree = ""; }; 74 | EC68A5382234EB5B00A2F1D2 /* GameEmitterManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameEmitterManager.h; sourceTree = ""; }; 75 | EC68A5392234EB5B00A2F1D2 /* GameEmitterManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameEmitterManager.m; sourceTree = ""; }; 76 | EC68A54D2234EE5300A2F1D2 /* BubbleFaild.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = BubbleFaild.sks; sourceTree = ""; }; 77 | EC68A54E2234EE5300A2F1D2 /* GameEmitter.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = GameEmitter.xcassets; sourceTree = ""; }; 78 | EC68A5562234F98200A2F1D2 /* GameButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameButton.h; sourceTree = ""; }; 79 | EC68A5572234F98200A2F1D2 /* GameButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameButton.m; sourceTree = ""; }; 80 | EC68A5592235320C00A2F1D2 /* Snow.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = Snow.sks; sourceTree = ""; }; 81 | EC68A55D2235375B00A2F1D2 /* BubbleSucceed.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = BubbleSucceed.sks; sourceTree = ""; }; 82 | EC68A55F22353D1D00A2F1D2 /* Fire.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = Fire.sks; sourceTree = ""; }; 83 | EC68A561223541D300A2F1D2 /* GameLevelLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameLevelLabel.h; sourceTree = ""; }; 84 | EC68A562223541D300A2F1D2 /* GameLevelLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameLevelLabel.m; sourceTree = ""; }; 85 | EC68A5642235438B00A2F1D2 /* 04B_25.TTF */ = {isa = PBXFileReference; lastKnownFileType = file; path = 04B_25.TTF; sourceTree = ""; }; 86 | EC6ADA5C22354D92008A5212 /* GameProgressBar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameProgressBar.h; sourceTree = ""; }; 87 | EC6ADA5D22354D92008A5212 /* GameProgressBar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameProgressBar.m; sourceTree = ""; }; 88 | EC6ADA632235ED9F008A5212 /* GameSucceed.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = GameSucceed.sks; sourceTree = ""; }; 89 | ECD40E6B2234B20E00607DCA /* MianSoundManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MianSoundManager.h; sourceTree = ""; }; 90 | ECD40E6C2234B20E00607DCA /* MianSoundManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MianSoundManager.m; sourceTree = ""; }; 91 | ECF9A1B0222EA82400952DA2 /* BubbleGame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BubbleGame.app; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | ECF9A1B3222EA82400952DA2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 93 | ECF9A1B4222EA82400952DA2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 94 | ECF9A1BA222EA82400952DA2 /* MainGameScene.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainGameScene.h; sourceTree = ""; }; 95 | ECF9A1BB222EA82400952DA2 /* MainGameScene.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainGameScene.m; sourceTree = ""; }; 96 | ECF9A1BD222EA82400952DA2 /* GameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameViewController.h; sourceTree = ""; }; 97 | ECF9A1BE222EA82400952DA2 /* GameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameViewController.m; sourceTree = ""; }; 98 | ECF9A1C1222EA82400952DA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 99 | ECF9A1C3222EA82500952DA2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 100 | ECF9A1C6222EA82500952DA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 101 | ECF9A1C8222EA82500952DA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 102 | ECF9A1C9222EA82500952DA2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 103 | /* End PBXFileReference section */ 104 | 105 | /* Begin PBXFrameworksBuildPhase section */ 106 | ECF9A1AD222EA82400952DA2 /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXFrameworksBuildPhase section */ 114 | 115 | /* Begin PBXGroup section */ 116 | EC08294B222FC1A00033A12B /* Actors */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | EC08294C222FC1DD0033A12B /* RedBall.h */, 120 | EC08294D222FC1DD0033A12B /* RedBall.m */, 121 | EC082951222FC5470033A12B /* Bubble.h */, 122 | EC082952222FC5470033A12B /* Bubble.m */, 123 | EC494D4F222FF7B6002D98E5 /* SpinnyNode.h */, 124 | EC494D50222FF7B6002D98E5 /* SpinnyNode.m */, 125 | ); 126 | path = Actors; 127 | sourceTree = ""; 128 | }; 129 | EC082956222FE38D0033A12B /* Util */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | EC082957222FE3B50033A12B /* CommUtil.h */, 133 | EC082958222FE3B50033A12B /* CommUtil.m */, 134 | EC494D52222FF9E0002D98E5 /* UIColor+ColorExtension.h */, 135 | EC494D53222FF9E0002D98E5 /* UIColor+ColorExtension.m */, 136 | ); 137 | path = Util; 138 | sourceTree = ""; 139 | }; 140 | EC08295A222FEA390033A12B /* Configs */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | EC08295B222FEA6B0033A12B /* GameConfigs.h */, 144 | EC08295C222FEA6B0033A12B /* GameConfigs.m */, 145 | ); 146 | path = Configs; 147 | sourceTree = ""; 148 | }; 149 | EC68A5552234F94F00A2F1D2 /* UI */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | EC68A5562234F98200A2F1D2 /* GameButton.h */, 153 | EC68A5572234F98200A2F1D2 /* GameButton.m */, 154 | EC68A561223541D300A2F1D2 /* GameLevelLabel.h */, 155 | EC68A562223541D300A2F1D2 /* GameLevelLabel.m */, 156 | EC6ADA5C22354D92008A5212 /* GameProgressBar.h */, 157 | EC6ADA5D22354D92008A5212 /* GameProgressBar.m */, 158 | ); 159 | path = UI; 160 | sourceTree = ""; 161 | }; 162 | ECD40E692234B17F00607DCA /* Resources */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | EC68A5642235438B00A2F1D2 /* 04B_25.TTF */, 166 | EC68A52A2234E2DD00A2F1D2 /* redball_stop.wav */, 167 | EC68A5282234E2DC00A2F1D2 /* bubble_growing.wav */, 168 | EC68A52D2234E2DD00A2F1D2 /* button_click.wav */, 169 | EC68A5272234E2DC00A2F1D2 /* bubble_made_faild_byhit.wav */, 170 | EC68A52C2234E2DD00A2F1D2 /* bubble_made_faild_toosmall.wav */, 171 | EC68A52E2234E2DD00A2F1D2 /* game_faild.wav */, 172 | EC68A5262234E2DC00A2F1D2 /* game_readyGo.wav */, 173 | EC68A52B2234E2DD00A2F1D2 /* game_succeed.wav */, 174 | EC68A5292234E2DD00A2F1D2 /* timer_nomuch.wav */, 175 | EC68A5242234DB5A00A2F1D2 /* backmusic.wav */, 176 | ); 177 | path = Resources; 178 | sourceTree = ""; 179 | }; 180 | ECD40E6A2234B1D700607DCA /* Helper */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | ECD40E6B2234B20E00607DCA /* MianSoundManager.h */, 184 | ECD40E6C2234B20E00607DCA /* MianSoundManager.m */, 185 | EC68A5382234EB5B00A2F1D2 /* GameEmitterManager.h */, 186 | EC68A5392234EB5B00A2F1D2 /* GameEmitterManager.m */, 187 | EC68A54D2234EE5300A2F1D2 /* BubbleFaild.sks */, 188 | EC68A5592235320C00A2F1D2 /* Snow.sks */, 189 | EC68A55D2235375B00A2F1D2 /* BubbleSucceed.sks */, 190 | EC6ADA632235ED9F008A5212 /* GameSucceed.sks */, 191 | EC68A55F22353D1D00A2F1D2 /* Fire.sks */, 192 | EC68A54E2234EE5300A2F1D2 /* GameEmitter.xcassets */, 193 | ); 194 | path = Helper; 195 | sourceTree = ""; 196 | }; 197 | ECF9A1A7222EA82400952DA2 = { 198 | isa = PBXGroup; 199 | children = ( 200 | ECF9A1B2222EA82400952DA2 /* BubbleGame */, 201 | ECF9A1B1222EA82400952DA2 /* Products */, 202 | ); 203 | sourceTree = ""; 204 | }; 205 | ECF9A1B1222EA82400952DA2 /* Products */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | ECF9A1B0222EA82400952DA2 /* BubbleGame.app */, 209 | ); 210 | name = Products; 211 | sourceTree = ""; 212 | }; 213 | ECF9A1B2222EA82400952DA2 /* BubbleGame */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | ECF9A1D0222EA84E00952DA2 /* GameScenes */, 217 | EC08294B222FC1A00033A12B /* Actors */, 218 | EC08295A222FEA390033A12B /* Configs */, 219 | EC082956222FE38D0033A12B /* Util */, 220 | ECD40E6A2234B1D700607DCA /* Helper */, 221 | EC68A5552234F94F00A2F1D2 /* UI */, 222 | ECF9A1B3222EA82400952DA2 /* AppDelegate.h */, 223 | ECF9A1B4222EA82400952DA2 /* AppDelegate.m */, 224 | ECF9A1BD222EA82400952DA2 /* GameViewController.h */, 225 | ECF9A1BE222EA82400952DA2 /* GameViewController.m */, 226 | ECF9A1C0222EA82400952DA2 /* Main.storyboard */, 227 | EC494D4D222FF516002D98E5 /* Actions.sks */, 228 | ECF9A1C3222EA82500952DA2 /* Assets.xcassets */, 229 | EC082954222FE2060033A12B /* ImageReource.xcassets */, 230 | ECD40E692234B17F00607DCA /* Resources */, 231 | ECF9A1C5222EA82500952DA2 /* LaunchScreen.storyboard */, 232 | ECF9A1C8222EA82500952DA2 /* Info.plist */, 233 | ECF9A1C9222EA82500952DA2 /* main.m */, 234 | ); 235 | path = BubbleGame; 236 | sourceTree = ""; 237 | }; 238 | ECF9A1D0222EA84E00952DA2 /* GameScenes */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | ECF9A1BA222EA82400952DA2 /* MainGameScene.h */, 242 | ECF9A1BB222EA82400952DA2 /* MainGameScene.m */, 243 | ); 244 | path = GameScenes; 245 | sourceTree = ""; 246 | }; 247 | /* End PBXGroup section */ 248 | 249 | /* Begin PBXNativeTarget section */ 250 | ECF9A1AF222EA82400952DA2 /* BubbleGame */ = { 251 | isa = PBXNativeTarget; 252 | buildConfigurationList = ECF9A1CD222EA82500952DA2 /* Build configuration list for PBXNativeTarget "BubbleGame" */; 253 | buildPhases = ( 254 | ECF9A1AC222EA82400952DA2 /* Sources */, 255 | ECF9A1AD222EA82400952DA2 /* Frameworks */, 256 | ECF9A1AE222EA82400952DA2 /* Resources */, 257 | ); 258 | buildRules = ( 259 | ); 260 | dependencies = ( 261 | ); 262 | name = BubbleGame; 263 | productName = BubbleGame; 264 | productReference = ECF9A1B0222EA82400952DA2 /* BubbleGame.app */; 265 | productType = "com.apple.product-type.application"; 266 | }; 267 | /* End PBXNativeTarget section */ 268 | 269 | /* Begin PBXProject section */ 270 | ECF9A1A8222EA82400952DA2 /* Project object */ = { 271 | isa = PBXProject; 272 | attributes = { 273 | LastUpgradeCheck = 1010; 274 | ORGANIZATIONNAME = imqiuhang; 275 | TargetAttributes = { 276 | ECF9A1AF222EA82400952DA2 = { 277 | CreatedOnToolsVersion = 10.1; 278 | }; 279 | }; 280 | }; 281 | buildConfigurationList = ECF9A1AB222EA82400952DA2 /* Build configuration list for PBXProject "BubbleGame" */; 282 | compatibilityVersion = "Xcode 9.3"; 283 | developmentRegion = en; 284 | hasScannedForEncodings = 0; 285 | knownRegions = ( 286 | en, 287 | Base, 288 | ); 289 | mainGroup = ECF9A1A7222EA82400952DA2; 290 | productRefGroup = ECF9A1B1222EA82400952DA2 /* Products */; 291 | projectDirPath = ""; 292 | projectRoot = ""; 293 | targets = ( 294 | ECF9A1AF222EA82400952DA2 /* BubbleGame */, 295 | ); 296 | }; 297 | /* End PBXProject section */ 298 | 299 | /* Begin PBXResourcesBuildPhase section */ 300 | ECF9A1AE222EA82400952DA2 /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | EC68A5652235493500A2F1D2 /* 04B_25.TTF in Resources */, 305 | EC68A56022353D1D00A2F1D2 /* Fire.sks in Resources */, 306 | ECF9A1C2222EA82400952DA2 /* Main.storyboard in Resources */, 307 | EC68A5252234DB5A00A2F1D2 /* backmusic.wav in Resources */, 308 | EC6ADA652235ED9F008A5212 /* GameSucceed.sks in Resources */, 309 | EC68A5312234E2DD00A2F1D2 /* bubble_growing.wav in Resources */, 310 | EC68A5302234E2DD00A2F1D2 /* bubble_made_faild_byhit.wav in Resources */, 311 | EC68A55E2235375B00A2F1D2 /* BubbleSucceed.sks in Resources */, 312 | EC68A5352234E2DD00A2F1D2 /* bubble_made_faild_toosmall.wav in Resources */, 313 | EC68A5322234E2DD00A2F1D2 /* timer_nomuch.wav in Resources */, 314 | EC68A5502234EE5300A2F1D2 /* GameEmitter.xcassets in Resources */, 315 | EC082955222FE2060033A12B /* ImageReource.xcassets in Resources */, 316 | EC68A5372234E2DD00A2F1D2 /* game_faild.wav in Resources */, 317 | ECF9A1C4222EA82500952DA2 /* Assets.xcassets in Resources */, 318 | EC68A55B2235320C00A2F1D2 /* Snow.sks in Resources */, 319 | EC68A5342234E2DD00A2F1D2 /* game_succeed.wav in Resources */, 320 | EC68A54F2234EE5300A2F1D2 /* BubbleFaild.sks in Resources */, 321 | EC68A5332234E2DD00A2F1D2 /* redball_stop.wav in Resources */, 322 | ECF9A1C7222EA82500952DA2 /* LaunchScreen.storyboard in Resources */, 323 | EC68A5362234E2DD00A2F1D2 /* button_click.wav in Resources */, 324 | EC494D4E222FF516002D98E5 /* Actions.sks in Resources */, 325 | EC68A52F2234E2DD00A2F1D2 /* game_readyGo.wav in Resources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | /* End PBXResourcesBuildPhase section */ 330 | 331 | /* Begin PBXSourcesBuildPhase section */ 332 | ECF9A1AC222EA82400952DA2 /* Sources */ = { 333 | isa = PBXSourcesBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | ECD40E6D2234B20E00607DCA /* MianSoundManager.m in Sources */, 337 | EC494D51222FF7B6002D98E5 /* SpinnyNode.m in Sources */, 338 | EC08294E222FC1DD0033A12B /* RedBall.m in Sources */, 339 | EC6ADA5E22354D92008A5212 /* GameProgressBar.m in Sources */, 340 | ECF9A1BC222EA82400952DA2 /* MainGameScene.m in Sources */, 341 | ECF9A1BF222EA82400952DA2 /* GameViewController.m in Sources */, 342 | EC08295D222FEA6B0033A12B /* GameConfigs.m in Sources */, 343 | EC494D54222FF9E0002D98E5 /* UIColor+ColorExtension.m in Sources */, 344 | EC68A563223541D300A2F1D2 /* GameLevelLabel.m in Sources */, 345 | EC68A5582234F98200A2F1D2 /* GameButton.m in Sources */, 346 | EC082953222FC5470033A12B /* Bubble.m in Sources */, 347 | ECF9A1CA222EA82500952DA2 /* main.m in Sources */, 348 | ECF9A1B5222EA82400952DA2 /* AppDelegate.m in Sources */, 349 | EC68A53A2234EB5B00A2F1D2 /* GameEmitterManager.m in Sources */, 350 | EC082959222FE3B50033A12B /* CommUtil.m in Sources */, 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | /* End PBXSourcesBuildPhase section */ 355 | 356 | /* Begin PBXVariantGroup section */ 357 | ECF9A1C0222EA82400952DA2 /* Main.storyboard */ = { 358 | isa = PBXVariantGroup; 359 | children = ( 360 | ECF9A1C1222EA82400952DA2 /* Base */, 361 | ); 362 | name = Main.storyboard; 363 | sourceTree = ""; 364 | }; 365 | ECF9A1C5222EA82500952DA2 /* LaunchScreen.storyboard */ = { 366 | isa = PBXVariantGroup; 367 | children = ( 368 | ECF9A1C6222EA82500952DA2 /* Base */, 369 | ); 370 | name = LaunchScreen.storyboard; 371 | sourceTree = ""; 372 | }; 373 | /* End PBXVariantGroup section */ 374 | 375 | /* Begin XCBuildConfiguration section */ 376 | ECF9A1CB222EA82500952DA2 /* Debug */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_NONNULL = YES; 381 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_ENABLE_OBJC_WEAK = YES; 387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_COMMA = YES; 390 | CLANG_WARN_CONSTANT_CONVERSION = YES; 391 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 393 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 394 | CLANG_WARN_EMPTY_BODY = YES; 395 | CLANG_WARN_ENUM_CONVERSION = YES; 396 | CLANG_WARN_INFINITE_RECURSION = YES; 397 | CLANG_WARN_INT_CONVERSION = YES; 398 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 400 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 402 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 403 | CLANG_WARN_STRICT_PROTOTYPES = YES; 404 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 405 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 406 | CLANG_WARN_UNREACHABLE_CODE = YES; 407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 408 | CODE_SIGN_IDENTITY = "iPhone Developer"; 409 | COPY_PHASE_STRIP = NO; 410 | DEBUG_INFORMATION_FORMAT = dwarf; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | ENABLE_TESTABILITY = YES; 413 | GCC_C_LANGUAGE_STANDARD = gnu11; 414 | GCC_DYNAMIC_NO_PIC = NO; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_OPTIMIZATION_LEVEL = 0; 417 | GCC_PREPROCESSOR_DEFINITIONS = ( 418 | "DEBUG=1", 419 | "$(inherited)", 420 | ); 421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 423 | GCC_WARN_UNDECLARED_SELECTOR = YES; 424 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 425 | GCC_WARN_UNUSED_FUNCTION = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 428 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 429 | MTL_FAST_MATH = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = iphoneos; 432 | }; 433 | name = Debug; 434 | }; 435 | ECF9A1CC222EA82500952DA2 /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ALWAYS_SEARCH_USER_PATHS = NO; 439 | CLANG_ANALYZER_NONNULL = YES; 440 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 441 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 442 | CLANG_CXX_LIBRARY = "libc++"; 443 | CLANG_ENABLE_MODULES = YES; 444 | CLANG_ENABLE_OBJC_ARC = YES; 445 | CLANG_ENABLE_OBJC_WEAK = YES; 446 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 447 | CLANG_WARN_BOOL_CONVERSION = YES; 448 | CLANG_WARN_COMMA = YES; 449 | CLANG_WARN_CONSTANT_CONVERSION = YES; 450 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 453 | CLANG_WARN_EMPTY_BODY = YES; 454 | CLANG_WARN_ENUM_CONVERSION = YES; 455 | CLANG_WARN_INFINITE_RECURSION = YES; 456 | CLANG_WARN_INT_CONVERSION = YES; 457 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 459 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 460 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 461 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 462 | CLANG_WARN_STRICT_PROTOTYPES = YES; 463 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 464 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 465 | CLANG_WARN_UNREACHABLE_CODE = YES; 466 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 467 | CODE_SIGN_IDENTITY = "iPhone Developer"; 468 | COPY_PHASE_STRIP = NO; 469 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 470 | ENABLE_NS_ASSERTIONS = NO; 471 | ENABLE_STRICT_OBJC_MSGSEND = YES; 472 | GCC_C_LANGUAGE_STANDARD = gnu11; 473 | GCC_NO_COMMON_BLOCKS = YES; 474 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 475 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 476 | GCC_WARN_UNDECLARED_SELECTOR = YES; 477 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 478 | GCC_WARN_UNUSED_FUNCTION = YES; 479 | GCC_WARN_UNUSED_VARIABLE = YES; 480 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 481 | MTL_ENABLE_DEBUG_INFO = NO; 482 | MTL_FAST_MATH = YES; 483 | SDKROOT = iphoneos; 484 | VALIDATE_PRODUCT = YES; 485 | }; 486 | name = Release; 487 | }; 488 | ECF9A1CE222EA82500952DA2 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 492 | CODE_SIGN_STYLE = Automatic; 493 | DEVELOPMENT_TEAM = WR6H24YNSQ; 494 | INFOPLIST_FILE = BubbleGame/Info.plist; 495 | LD_RUNPATH_SEARCH_PATHS = ( 496 | "$(inherited)", 497 | "@executable_path/Frameworks", 498 | ); 499 | PRODUCT_BUNDLE_IDENTIFIER = imqiuhang.BubbleGame; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | TARGETED_DEVICE_FAMILY = 2; 502 | }; 503 | name = Debug; 504 | }; 505 | ECF9A1CF222EA82500952DA2 /* Release */ = { 506 | isa = XCBuildConfiguration; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | CODE_SIGN_STYLE = Automatic; 510 | DEVELOPMENT_TEAM = WR6H24YNSQ; 511 | INFOPLIST_FILE = BubbleGame/Info.plist; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/Frameworks", 515 | ); 516 | PRODUCT_BUNDLE_IDENTIFIER = imqiuhang.BubbleGame; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | TARGETED_DEVICE_FAMILY = 2; 519 | }; 520 | name = Release; 521 | }; 522 | /* End XCBuildConfiguration section */ 523 | 524 | /* Begin XCConfigurationList section */ 525 | ECF9A1AB222EA82400952DA2 /* Build configuration list for PBXProject "BubbleGame" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | ECF9A1CB222EA82500952DA2 /* Debug */, 529 | ECF9A1CC222EA82500952DA2 /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | ECF9A1CD222EA82500952DA2 /* Build configuration list for PBXNativeTarget "BubbleGame" */ = { 535 | isa = XCConfigurationList; 536 | buildConfigurations = ( 537 | ECF9A1CE222EA82500952DA2 /* Debug */, 538 | ECF9A1CF222EA82500952DA2 /* Release */, 539 | ); 540 | defaultConfigurationIsVisible = 0; 541 | defaultConfigurationName = Release; 542 | }; 543 | /* End XCConfigurationList section */ 544 | }; 545 | rootObject = ECF9A1A8222EA82400952DA2 /* Project object */; 546 | } 547 | -------------------------------------------------------------------------------- /BubbleGame.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BubbleGame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BubbleGame/Actions.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Actions.sks -------------------------------------------------------------------------------- /BubbleGame/Actors/Bubble.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bubble.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CommUtil.h" 11 | 12 | typedef NS_ENUM(NSUInteger, BubbleType) { 13 | BubbleTypeNormal, 14 | BubbleTypeIce, 15 | }; 16 | 17 | @interface Bubble : SKSpriteNode 18 | 19 | + (instancetype)randomBubbleWithProb:(float)prob; 20 | + (instancetype)bubbleWithType:(BubbleType)bubbleType; 21 | + (instancetype)bubbleForIce:(BOOL)isIce; 22 | 23 | //normal or ice 24 | @property (nonatomic)BubbleType bubbleType; 25 | 26 | //正在变大中 27 | @property (nonatomic,readonly)BOOL growthing; 28 | 29 | - (void)beganGrowthingWithTargetScale:(CGFloat)scale 30 | duration:(NSTimeInterval)duration; 31 | - (void)stopGrowthing; 32 | 33 | //渐隐 然后移除 34 | - (void)fadeOut; 35 | 36 | @end 37 | 38 | -------------------------------------------------------------------------------- /BubbleGame/Actors/Bubble.m: -------------------------------------------------------------------------------- 1 | // 2 | // Bubble.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "Bubble.h" 10 | 11 | static NSString *const kGrowthingAnimationName = @"action_buuble_self_growthing"; 12 | 13 | @implementation Bubble 14 | 15 | #pragma mark - actions 16 | - (void)fadeOut { 17 | [self runAction:[SKAction sequence:@[ 18 | [SKAction fadeOutWithDuration:0.5], 19 | [SKAction removeFromParent], 20 | ]]]; 21 | } 22 | 23 | - (void)beganGrowthingWithTargetScale:(CGFloat)scale duration:(NSTimeInterval)duration{ 24 | [self stopGrowthing]; 25 | _growthing = YES; 26 | [self runAction:[SKAction scaleTo:scale duration:duration] withKey:kGrowthingAnimationName]; 27 | } 28 | 29 | - (void)stopGrowthing { 30 | _growthing = NO; 31 | [self removeActionForKey:kGrowthingAnimationName]; 32 | } 33 | 34 | #pragma mark - creating 35 | + (instancetype)randomBubbleWithProb:(float)prob { 36 | return [self bubbleWithType:[CommUtil randomHitWithProb:prob]?BubbleTypeIce:BubbleTypeNormal]; 37 | } 38 | 39 | + (instancetype)bubbleWithType:(BubbleType)bubbleType { 40 | Bubble *bubble = bubbleType==BubbleTypeNormal?[self spriteNodeWithImageNamed:@"bubble"]:[self spriteNodeWithImageNamed:@"bubble_ice"]; 41 | 42 | bubble.bubbleType = bubbleType; 43 | bubble.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble.size.width/2.f]; 44 | [bubble setScale:0.f]; 45 | bubble.physicsBody.affectedByGravity = NO; 46 | bubble.physicsBody.mass = 500;//质量是红球的50倍 47 | bubble.physicsBody.restitution = 0.2;//动量传递衰减80%(即看着弹性很小,设置为1碰撞后可以一直弹) 48 | bubble.physicsBody.angularDamping = 0.4; 49 | bubble.physicsBody.linearDamping = 0.3f; 50 | 51 | return bubble; 52 | } 53 | 54 | + (instancetype)bubbleForIce:(BOOL)isIce { 55 | return [self bubbleWithType:isIce?BubbleTypeIce:BubbleTypeNormal]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /BubbleGame/Actors/RedBall.h: -------------------------------------------------------------------------------- 1 | // 2 | // RedBall.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CommUtil.h" 11 | #import "GameConfigs.h" 12 | 13 | typedef NS_ENUM(NSUInteger, RedBallEffectType) { 14 | RedBallEffectTypeNone, 15 | RedBallEffectTypeIceing, 16 | }; 17 | 18 | @interface RedBall : SKSpriteNode 19 | 20 | @property (nonatomic,readwrite)RedBallEffectType effectType; 21 | 22 | //random 23 | + (instancetype)redBall; 24 | 25 | @end 26 | 27 | -------------------------------------------------------------------------------- /BubbleGame/Actors/RedBall.m: -------------------------------------------------------------------------------- 1 | // 2 | // RedBall.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "RedBall.h" 10 | 11 | @implementation RedBall 12 | 13 | + (instancetype)redBall { 14 | 15 | RedBall *redBall = [RedBall spriteNodeWithImageNamed:@"ball"]; 16 | [redBall setScale:5]; 17 | redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2.f]; 18 | 19 | //------------------------init configs--------------------------------- 20 | redBall.physicsBody.affectedByGravity = NO;//不受重力影响 21 | redBall.physicsBody.mass = 10;//质量 10kg 22 | redBall.physicsBody.angularDamping = 0.f;//角动量摩擦力 23 | redBall.physicsBody.linearDamping = 0.f;//线性摩擦力 24 | redBall.physicsBody.restitution = 1.f;//反弹的动量 1即无限反弹,不丢失动量,相当于一个永动机=。= 25 | redBall.physicsBody.friction = 0.f;//摩擦力 26 | redBall.physicsBody.allowsRotation = YES;//允许受到角动量 27 | redBall.physicsBody.usesPreciseCollisionDetection = YES;//独立计算碰撞 28 | 29 | return redBall; 30 | } 31 | 32 | - (void)setEffectType:(RedBallEffectType)effectType { 33 | if (effectType==_effectType) { 34 | return; 35 | } 36 | _effectType = effectType; 37 | 38 | //也可以通过action动画b换图 39 | self.texture = [SKTexture textureWithImageNamed:effectType==RedBallEffectTypeIceing?@"ball_ice":@"ball"]; 40 | 41 | //碰撞后拿出原有的角速度的方向 赋值到新速度,方向不变 d速度减半或者恢复 42 | //当然也可以角速度直接操作 angularVelocity 43 | CGVector originalVelocity = self.physicsBody.velocity ; 44 | CGFloat dxv = originalVelocity.dx>=0?1:-1; 45 | CGFloat dyv = originalVelocity.dy>=0?1:-1; 46 | CGFloat v = effectType==RedBallEffectTypeIceing?GameConfigs.redBallSpeedIce:GameConfigs.redBallSpeedNormal; 47 | self.physicsBody.velocity = CGVectorMake(v*dxv, v*dyv); 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /BubbleGame/Actors/SpinnyNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpinnyNode.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SpinnyNode : SKShapeNode 12 | 13 | + (instancetype)spinnyNode; 14 | 15 | @end 16 | 17 | 18 | -------------------------------------------------------------------------------- /BubbleGame/Actors/SpinnyNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpinnyNode.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "SpinnyNode.h" 10 | 11 | @implementation SpinnyNode 12 | 13 | + (instancetype)spinnyNode { 14 | 15 | SpinnyNode *spinnyNode = [SpinnyNode shapeNodeWithRectOfSize:CGSizeMake(50, 50) cornerRadius:15]; 16 | spinnyNode.lineWidth = 2.5; 17 | 18 | [spinnyNode runAction:[SKAction repeatActionForever:[SKAction rotateByAngle:M_PI duration:1]]]; 19 | [spinnyNode runAction:[SKAction sequence:@[ 20 | [SKAction waitForDuration:0.5], 21 | [SKAction fadeOutWithDuration:0.5], 22 | [SKAction removeFromParent], 23 | ]]]; 24 | return spinnyNode; 25 | } 26 | 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BubbleGame/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /BubbleGame/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /BubbleGame/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /BubbleGame/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BubbleGame/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /BubbleGame/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 | -------------------------------------------------------------------------------- /BubbleGame/Configs/GameConfigs.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameConfigs.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "UIColor+ColorExtension.h" 12 | #import "CommUtil.h" 13 | #import 14 | 15 | #define WeakSelf __weak typeof(self) weakSelf = self; 16 | 17 | @interface GameConfigs : NSObject 18 | 19 | + (CGFloat)bubbleGrowingSpeed; 20 | + (CGFloat)maxBubbleScale; 21 | + (NSTimeInterval)growing2MaxDuration; 22 | + (CGFloat)minBubble2Stay; 23 | 24 | + (uint32_t)bubbleCollisionBitMask; 25 | + (uint32_t)redBallCollisionBitMask; 26 | 27 | + (CGFloat)redBallSpeedNormal; 28 | + (CGFloat)redBallSpeedIce; 29 | 30 | + (CGFloat)nomuchTimeRate; 31 | 32 | + (CGFloat)maxBubbleAddForLevel:(NSInteger)level; 33 | + (CGFloat)totalTimeForPass; 34 | 35 | @end 36 | 37 | 38 | -------------------------------------------------------------------------------- /BubbleGame/Configs/GameConfigs.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameConfigs.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameConfigs.h" 10 | 11 | @implementation GameConfigs 12 | 13 | + (CGFloat)bubbleGrowingSpeed { 14 | return 0.025; 15 | } 16 | 17 | + (CGFloat)maxBubbleScale { 18 | return 4.f; 19 | } 20 | 21 | + (CGFloat)minBubble2Stay { 22 | return 1.8f; 23 | } 24 | 25 | + (NSTimeInterval)growing2MaxDuration { 26 | return 2.f; 27 | } 28 | 29 | + (uint32_t)bubbleCollisionBitMask { 30 | return 0x01; 31 | } 32 | 33 | + (uint32_t)redBallCollisionBitMask { 34 | return 0x02; 35 | } 36 | 37 | + (CGFloat)redBallSpeedNormal { 38 | return 300; 39 | } 40 | 41 | + (CGFloat)redBallSpeedIce { 42 | return 150; 43 | } 44 | 45 | + (CGFloat)nomuchTimeRate { 46 | return 0.5; 47 | } 48 | 49 | + (CGFloat)maxBubbleAddForLevel:(NSInteger)level { 50 | return 3000 + 600*level; 51 | } 52 | 53 | + (CGFloat)totalTimeForPass { 54 | return 45.f; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /BubbleGame/GameScenes/MainGameScene.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class MainGameSceneCreatConfig; 12 | 13 | @interface MainGameScene : SKScene 14 | 15 | @property (nonatomic,copy)void(^onGameNeedRestart)(BOOL isWin); 16 | 17 | + (instancetype)sceneWithSize:(CGSize)size config:(MainGameSceneCreatConfig *)configs; 18 | 19 | @property (nonatomic,strong,readonly)MainGameSceneCreatConfig *configs; 20 | 21 | @end 22 | 23 | @interface MainGameSceneCreatConfig : NSObject 24 | 25 | @property (nonatomic)BOOL isWin; 26 | @property (nonatomic)BOOL isFirst; 27 | @property (nonatomic)NSInteger level; 28 | 29 | + (instancetype)firstConfig; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /BubbleGame/GameScenes/MainGameScene.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "MainGameScene.h" 10 | #import "RedBall.h" 11 | #import "Bubble.h" 12 | #import "SpinnyNode.h" 13 | #import "MianSoundManager.h" 14 | #import "GameEmitterManager.h" 15 | #import "GameButton.h" 16 | #import "GameLevelLabel.h" 17 | #import "GameProgressBar.h" 18 | 19 | static UIEdgeInsets const kPhysicsWorldInsert = (UIEdgeInsets){125, 118, 115, 118}; 20 | 21 | @interface MainGameScene() 22 | 23 | @property (nonatomic,strong)Bubble *currentGrowthingBubble; 24 | @property (nonatomic)NSInteger randomFlag; 25 | 26 | @property (nonatomic,strong)MianSoundManager *soundManager; 27 | @property (nonatomic,strong)GameEmitterManager *emitterManager; 28 | 29 | @property (nonatomic,strong)GameProgressBar *bubbleAddBar; 30 | @property (nonatomic,strong)GameProgressBar *timeBar; 31 | 32 | @property (nonatomic)CFTimeInterval gameBeganTime; 33 | @property (nonatomic)CGFloat bubbleProgess;; 34 | 35 | @end 36 | 37 | @implementation MainGameScene 38 | { 39 | BOOL _isGameStart; 40 | } 41 | 42 | + (instancetype)sceneWithSize:(CGSize)size config:(MainGameSceneCreatConfig *)configs { 43 | return [[MainGameScene alloc] initWithSize:size config:configs]; 44 | } 45 | 46 | - (instancetype)initWithSize:(CGSize)size config:(MainGameSceneCreatConfig *)configs { 47 | if (self=[super initWithSize:size]) { 48 | _configs = configs; 49 | [self setupGamePrepareContent]; 50 | } 51 | return self; 52 | } 53 | 54 | #pragma mark - PhysicsContact 55 | - (void)didBeginContact:(SKPhysicsContact *)contact { 56 | 57 | } 58 | 59 | - (void)didEndContact:(SKPhysicsContact *)contact { 60 | 61 | RedBall *redBall = nil; 62 | Bubble *bubble = nil; 63 | 64 | if ([contact.bodyA.node isKindOfClass:RedBall.class]) { 65 | redBall = (RedBall*)contact.bodyA.node; 66 | bubble = (Bubble*)contact.bodyB.node; 67 | }else { 68 | redBall = (RedBall*)contact.bodyB.node; 69 | bubble = (Bubble*)contact.bodyA.node; 70 | } 71 | 72 | if (!([redBall isKindOfClass:RedBall.class]&&[bubble isKindOfClass:Bubble.class])) { 73 | return; 74 | } 75 | 76 | redBall.effectType = bubble.bubbleType==BubbleTypeIce?RedBallEffectTypeIceing:RedBallEffectTypeNone; 77 | 78 | if (bubble.growthing) { 79 | [bubble stopGrowthing]; 80 | [self creatBubbleFaildWithBubble:bubble]; 81 | [self.soundManager playMakeBubbleFaildSoundForByHit]; 82 | [bubble fadeOut]; 83 | self.currentGrowthingBubble = nil; 84 | } 85 | } 86 | 87 | #pragma mark - bubble 88 | - (void)addBubbleInPosition:(CGPoint)pos { 89 | 90 | Bubble *bubble = [Bubble bubbleForIce:self.randomFlag>0&&self.randomFlag%3==0]; 91 | bubble.physicsBody.collisionBitMask = GameConfigs.bubbleCollisionBitMask; 92 | bubble.physicsBody.contactTestBitMask = GameConfigs.redBallCollisionBitMask; 93 | bubble.position = pos; 94 | [self addChild:bubble]; 95 | [bubble beganGrowthingWithTargetScale:GameConfigs.maxBubbleScale duration:GameConfigs.growing2MaxDuration]; 96 | self.currentGrowthingBubble = bubble; 97 | 98 | self.randomFlag ++; 99 | 100 | [self.soundManager controlBubbleGrowingSoundWithPlay:YES]; 101 | } 102 | 103 | - (void)updateBubbleStausWhenTouchOff { 104 | 105 | if (!self.currentGrowthingBubble) { 106 | return; 107 | } 108 | 109 | [self.currentGrowthingBubble stopGrowthing]; 110 | //假如创建的是冰球,我们让他受到重力影响 看下效果 111 | self.currentGrowthingBubble.physicsBody.affectedByGravity = self.currentGrowthingBubble.bubbleType==BubbleTypeIce; 112 | if (self.currentGrowthingBubble.xScale △vx 130 | | . / 131 | | . / 132 | | ./ 133 | | / 垂直方向是角速度,也就是速度的向量 134 | | / 135 | | / 136 | | / 137 | | / 138 | | / 139 | | / 140 | △vy 141 | */ 142 | 143 | NSArray *vectors = @[[NSValue valueWithCGVector:CGVectorMake(speed, speed)], 144 | [NSValue valueWithCGVector:CGVectorMake(speed, -speed)], 145 | [NSValue valueWithCGVector:CGVectorMake(-speed, speed)], 146 | [NSValue valueWithCGVector:CGVectorMake(-speed, -speed)]]; 147 | 148 | for(int i=0;i<3+self.configs.level;i++) { 149 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.5*i + 1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 150 | RedBall *redball = [RedBall redBall]; 151 | redball.physicsBody.collisionBitMask = GameConfigs.redBallCollisionBitMask; 152 | redball.physicsBody.contactTestBitMask = GameConfigs.bubbleCollisionBitMask; 153 | redball.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 154 | [self addChild:redball]; 155 | //可以通过施加一个推力(牛顿)或者j冲量 或者直接赋值速度,直接赋值速度比较方便,推力需要计算 156 | // [redball.physicsBody applyForce:[vectors[i] CGVectorValue]]; 157 | redball.physicsBody.velocity = [vectors[i%vectors.count] CGVectorValue]; 158 | }); 159 | } 160 | } 161 | 162 | #pragma mark - effect 163 | - (void)addSpinnyInPosition:(CGPoint)pos { 164 | SpinnyNode *spinnyNode = [SpinnyNode spinnyNode]; 165 | spinnyNode.position = pos; 166 | spinnyNode.strokeColor = [SKColor randomColor]; 167 | [self addChild:spinnyNode]; 168 | } 169 | 170 | #pragma mark game loop 171 | //每隔0.01秒调用 currentTime 是秒 精确到后三位 例如1.002秒 172 | -(void)update:(CFTimeInterval)currentTime { 173 | 174 | if (_isGameStart) { 175 | if(self.gameBeganTime==0){ 176 | self.gameBeganTime = currentTime; 177 | }else { 178 | [self updateGameDuration:currentTime - self.gameBeganTime]; 179 | } 180 | } 181 | } 182 | 183 | - (void)updateGameDuration:(CFTimeInterval)gameDuration { 184 | if (gameDuration>=GameConfigs.totalTimeForPass) { 185 | if (self.onGameNeedRestart) { 186 | self.onGameNeedRestart(NO); 187 | } 188 | return; 189 | } 190 | CGFloat progress = 1.00f-(gameDuration/GameConfigs.totalTimeForPass); 191 | [self.timeBar updateProgress:progress animation:NO]; 192 | if (progress<=GameConfigs.nomuchTimeRate) { 193 | [self.soundManager playNomuchTimeSound]; 194 | } 195 | } 196 | 197 | #pragma mark - touch control 198 | - (void)touchDownAtPoint:(CGPoint)pos { 199 | if (!_isGameStart) { 200 | return; 201 | } 202 | [self addBubbleInPosition:pos]; 203 | } 204 | 205 | - (void)touchMovedToPoint:(CGPoint)pos { 206 | 207 | [self addSpinnyInPosition:pos]; 208 | 209 | if (!_isGameStart) { 210 | return; 211 | } 212 | } 213 | 214 | - (void)touchUpAtPoint:(CGPoint)pos { 215 | if (!_isGameStart) { 216 | return; 217 | } 218 | [self updateBubbleStausWhenTouchOff]; 219 | } 220 | 221 | #pragma mark - grade 222 | - (void)creatGreatBubbleSucceedWithBubble:(Bubble *)bubble { 223 | [self.soundManager controlBubbleGrowingSoundWithPlay:NO]; 224 | [self.emitterManager runAddBubbleSucceedEmitterWithNode:bubble]; 225 | self.bubbleProgess+=(0.2 * (bubble.xScale/GameConfigs.maxBubbleScale)); 226 | NSLog(@"-------创建泡泡成功-------\n size:%f",bubble.size.width); 227 | } 228 | 229 | - (void)creatBubbleFaildWithBubble:(Bubble *)bubble { 230 | [self.soundManager controlBubbleGrowingSoundWithPlay:NO]; 231 | [self.emitterManager runAddBubbleFaildEmitterWithPosition:bubble.position]; 232 | self.bubbleProgess-=(0.2 * (bubble.xScale/GameConfigs.maxBubbleScale)); 233 | NSLog(@"-------创建泡泡失败-------\n size:%f",bubble.size.width); 234 | } 235 | 236 | - (void)setBubbleProgess:(CGFloat)bubbleProgess { 237 | 238 | bubbleProgess = MAX(0, bubbleProgess); 239 | 240 | _bubbleProgess = bubbleProgess; 241 | if (bubbleProgess>=1) { 242 | if(self.onGameNeedRestart) { 243 | self.onGameNeedRestart(YES); 244 | } 245 | return; 246 | } 247 | [self.bubbleAddBar updateProgress:bubbleProgess animation:YES]; 248 | } 249 | #pragma mark touch delegate 250 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 251 | for (UITouch *t in touches) {[self touchDownAtPoint:[t locationInNode:self]];} 252 | } 253 | 254 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 255 | for (UITouch *t in touches) {[self touchMovedToPoint:[t locationInNode:self]];} 256 | } 257 | 258 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 259 | for (UITouch *t in touches) {[self touchUpAtPoint:[t locationInNode:self]];} 260 | } 261 | 262 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 263 | for (UITouch *t in touches) {[self touchUpAtPoint:[t locationInNode:self]];} 264 | } 265 | 266 | #pragma mark - setup 267 | - (void)setupGamePrepareContent { 268 | 269 | //因为坐标系是从下往上的,和现实世界相反,所以重力是负数 270 | self.physicsWorld.gravity = CGVectorMake(0, -9.8); 271 | 272 | //设置世界的碰撞代理 273 | self.physicsWorld.contactDelegate = self; 274 | 275 | //设置这个世界的边界,任何刚体都无法通过力的作用逃离这个世界的边界 276 | self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(kPhysicsWorldInsert.left, kPhysicsWorldInsert.top, self.size.width-kPhysicsWorldInsert.left-kPhysicsWorldInsert.right, self.size.height-kPhysicsWorldInsert.top-kPhysicsWorldInsert.bottom)]; 277 | 278 | //初始化声音,例子系统 279 | self.soundManager = [[MianSoundManager alloc] initWithScene:self]; 280 | [self.soundManager controlBgMusicWithPlay:YES]; 281 | self.emitterManager = [[GameEmitterManager alloc] initWithScene:self]; 282 | 283 | SKSpriteNode *bgImageNode = [[SKSpriteNode alloc] initWithImageNamed:@"background"]; 284 | bgImageNode.size = self.size; 285 | //position是物体的中间点 286 | bgImageNode.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 287 | //addChild,和addSubView类似 288 | [self addChild:bgImageNode]; 289 | 290 | if (self.configs.isWin) { 291 | [self.soundManager playGameSucceedSound]; 292 | [self.emitterManager runGameSucceed]; 293 | }else if (!self.configs.isFirst) { 294 | [self.soundManager playGameFaildSound]; 295 | } 296 | 297 | [self.emitterManager addSnowWithEdge:kPhysicsWorldInsert]; 298 | 299 | //开始游戏的按钮 300 | GameButton *startGameBtn = [GameButton buttonWithImageNamed:@"startGame"]; 301 | startGameBtn.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 302 | [startGameBtn setScale:3.f]; 303 | [GameEmitterManager addFireForNode:startGameBtn]; 304 | [self addChild:startGameBtn]; 305 | WeakSelf; 306 | [startGameBtn setOnSelectCallback:^(GameButton *button) { 307 | button.userInteractionEnabled = NO; 308 | [weakSelf setUpStartGameContent]; 309 | [button runAction:[SKAction sequence:@[ 310 | [SKAction fadeOutWithDuration:2.f], 311 | [SKAction removeFromParent], 312 | ]]]; 313 | }]; 314 | 315 | GameLevelLabel *levelLabel = [GameLevelLabel levelLabelWithLevel:self.configs.level]; 316 | levelLabel.position =CGPointMake(self.size.width/2.f+95, self.size.height - kPhysicsWorldInsert.top/2.f-16); 317 | [self addChild:levelLabel]; 318 | 319 | [self setupProgressBar]; 320 | 321 | } 322 | 323 | - (void)setUpStartGameContent { 324 | [self.soundManager playReadyGoSound]; 325 | [self setupRedballs]; 326 | [self setupMenu]; 327 | _isGameStart = YES; 328 | } 329 | 330 | - (void)setupMenu { 331 | 332 | GameButton *gameContrlBtn = [GameButton buttonWithImageNamed:@"game_stop"]; 333 | gameContrlBtn.position = CGPointMake(100, 50); 334 | [gameContrlBtn setScale:3]; 335 | WeakSelf; 336 | [gameContrlBtn setOnSelectCallback:^(GameButton *button){ 337 | weakSelf.paused = !weakSelf.paused; 338 | button.texture = [SKTexture textureWithImageNamed:weakSelf.paused?@"game_play":@"game_stop"] ; 339 | }]; 340 | [self addChild:gameContrlBtn]; 341 | 342 | } 343 | 344 | - (void)setupProgressBar { 345 | 346 | CGFloat barHeight = 460; 347 | CGFloat barWidth = 34; 348 | CGFloat barYPos = 277; 349 | 350 | self.bubbleAddBar = [GameProgressBar barWithType:GameProgressBarTypeBubbleAdd frame:CGRectMake(51, barYPos, barWidth, barHeight)]; 351 | [self addChild:self.bubbleAddBar]; 352 | [self.bubbleAddBar updateProgress:0.f animation:NO]; 353 | 354 | 355 | self.timeBar = [GameProgressBar barWithType:GameProgressBarTypeTime frame:CGRectMake(self.size.width - 75, barYPos, barWidth, barHeight)]; 356 | [self addChild:self.timeBar]; 357 | [self.timeBar updateProgress:1.f animation:NO]; 358 | 359 | 360 | } 361 | 362 | 363 | @end 364 | 365 | 366 | @implementation MainGameSceneCreatConfig 367 | 368 | + (instancetype)firstConfig { 369 | MainGameSceneCreatConfig *config = [MainGameSceneCreatConfig new]; 370 | config.isFirst = YES; 371 | return config; 372 | } 373 | 374 | @end 375 | -------------------------------------------------------------------------------- /BubbleGame/GameViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface GameViewController : UIViewController 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /BubbleGame/GameViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameViewController.h" 10 | #import "MainGameScene.h" 11 | #import "GameConfigs.h" 12 | 13 | @interface GameViewController () 14 | 15 | @property (nonatomic,strong)MainGameScene *scene; 16 | @property (nonatomic)NSInteger currentLevel; 17 | 18 | @end 19 | 20 | @implementation GameViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | [self setupGameWithConfig:MainGameSceneCreatConfig.firstConfig]; 25 | } 26 | 27 | - (void)setupGameWithConfig:(MainGameSceneCreatConfig *)config { 28 | 29 | [self.scene removeFromParent]; 30 | self.scene = nil; 31 | 32 | self.scene = [MainGameScene sceneWithSize:self.view.bounds.size config:config]; 33 | self.scene.scaleMode = SKSceneScaleModeAspectFill; 34 | 35 | SKView *skView = (SKView *)self.view; 36 | [skView presentScene:self.scene]; 37 | 38 | //debug 39 | skView.showsFPS = YES; 40 | skView.showsNodeCount = YES; 41 | 42 | WeakSelf 43 | [self.scene setOnGameNeedRestart:^(BOOL isWin) { 44 | if (isWin) { 45 | weakSelf.currentLevel++; 46 | } 47 | MainGameSceneCreatConfig *config = [MainGameSceneCreatConfig new]; 48 | config.isFirst = NO; 49 | config.level = weakSelf.currentLevel; 50 | config.isWin = isWin; 51 | [weakSelf setupGameWithConfig:config]; 52 | }]; 53 | } 54 | 55 | #pragma mark - config 56 | - (BOOL)prefersStatusBarHidden { 57 | return YES; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /BubbleGame/Helper/BubbleFaild.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/BubbleFaild.sks -------------------------------------------------------------------------------- /BubbleGame/Helper/BubbleSucceed.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/BubbleSucceed.sks -------------------------------------------------------------------------------- /BubbleGame/Helper/Fire.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/Fire.sks -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bokeh.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "spark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/GameEmitter.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitterManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameEmitterManager.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface GameEmitterManager : NSObject 13 | 14 | - (instancetype)initWithScene:(SKScene *)scene; 15 | @property (nonatomic,weak,readonly)SKScene *relateScene; 16 | 17 | - (instancetype)init NS_UNAVAILABLE;//using initWithScene: 18 | + (instancetype)new NS_UNAVAILABLE;//using initWithScene: 19 | 20 | - (void)runAddBubbleFaildEmitterWithPosition:(CGPoint)pos; 21 | - (void)runAddBubbleSucceedEmitterWithNode:(SKNode *)node; 22 | - (void)addSnowWithEdge:(UIEdgeInsets)edge; 23 | - (void)removeSnow; 24 | - (void)runGameSucceed; 25 | 26 | + (void)addFireForNode:(SKNode *)node; 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /BubbleGame/Helper/GameEmitterManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameEmitterManager.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameEmitterManager.h" 10 | 11 | @implementation GameEmitterManager 12 | { 13 | SKEmitterNode* _snow; 14 | } 15 | - (instancetype)initWithScene:(id)scene { 16 | if (self=[super init]) { 17 | _relateScene = scene; 18 | } 19 | return self; 20 | } 21 | 22 | //例子系统可以直接通过属性赋值 或者通过可视化的操作 23 | - (void)runAddBubbleFaildEmitterWithPosition:(CGPoint)pos { 24 | 25 | SKEmitterNode* emnode=[SKEmitterNode nodeWithFileNamed:@"BubbleFaild.sks"]; 26 | [self.relateScene addChild:emnode]; 27 | emnode.position = pos; 28 | 29 | //action也可以通过可视化的操作来生成 30 | [emnode runAction:[SKAction sequence:@[[SKAction waitForDuration:2.f], 31 | [SKAction fadeOutWithDuration:0.5], 32 | [SKAction removeFromParent]]]]; 33 | 34 | } 35 | 36 | 37 | - (void)runAddBubbleSucceedEmitterWithNode:(SKNode *)node { 38 | 39 | SKEmitterNode* emnode=[SKEmitterNode nodeWithFileNamed:@"BubbleSucceed.sks"]; 40 | emnode.position = node.position; 41 | [emnode runAction:[SKAction sequence:@[[SKAction waitForDuration:1.f], 42 | [SKAction fadeOutWithDuration:0.5], 43 | [SKAction removeFromParent]]]]; 44 | [self.relateScene addChild:emnode]; 45 | } 46 | 47 | - (void)runGameSucceed { 48 | SKEmitterNode* emnode=[SKEmitterNode nodeWithFileNamed:@"GameSucceed.sks"]; 49 | emnode.position = CGPointMake(self.relateScene.size.width/2.f, self.relateScene.size.height/2.f); 50 | [emnode runAction:[SKAction sequence:@[[SKAction waitForDuration:2.f], 51 | [SKAction fadeOutWithDuration:1.0], 52 | [SKAction removeFromParent]]]]; 53 | [self.relateScene addChild:emnode]; 54 | } 55 | 56 | - (void)addSnowWithEdge:(UIEdgeInsets)edge { 57 | [self removeSnow]; 58 | _snow = [SKEmitterNode nodeWithFileNamed:@"Snow.sks"]; 59 | _snow.position = CGPointMake(self.relateScene.size.width/2.f, self.relateScene.size.height-edge.top); 60 | 61 | [self.relateScene addChild:_snow]; 62 | _snow.particlePositionRange = CGVectorMake(self.relateScene.size.width - edge.left - edge.right, 5); 63 | 64 | } 65 | 66 | - (void)removeSnow { 67 | [_snow removeFromParent]; 68 | _snow = nil; 69 | } 70 | 71 | + (void)addFireForNode:(SKNode *)node { 72 | 73 | SKEmitterNode *fire = [SKEmitterNode nodeWithFileNamed:@"Fire.sks"]; 74 | fire.position = CGPointMake(0, 16); 75 | 76 | [node addChild:fire]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /BubbleGame/Helper/GameSucceed.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/GameSucceed.sks -------------------------------------------------------------------------------- /BubbleGame/Helper/MianSoundManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MianSoundManager.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface MianSoundManager : NSObject 13 | 14 | - (instancetype)initWithScene:(SKScene *)scene; 15 | @property (nonatomic,weak,readonly)SKScene *relateScene; 16 | 17 | - (instancetype)init NS_UNAVAILABLE;//using initWithScene: 18 | + (instancetype)new NS_UNAVAILABLE;//using initWithScene: 19 | 20 | - (void)controlBgMusicWithPlay:(BOOL)play; 21 | - (void)controlBubbleGrowingSoundWithPlay:(BOOL)play; 22 | - (void)playMakeBubbleFaildSoundForTooSmall; 23 | - (void)playMakeBubbleFaildSoundForByHit; 24 | - (void)playReadyGoSound; 25 | - (void)playGameSucceedSound; 26 | - (void)playGameFaildSound; 27 | - (void)playNomuchTimeSound; 28 | 29 | 30 | @end 31 | 32 | -------------------------------------------------------------------------------- /BubbleGame/Helper/MianSoundManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // MianSoundManager.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "MianSoundManager.h" 10 | #import "GameConfigs.h" 11 | 12 | @interface MianSoundManager () 13 | 14 | @property (nonatomic,strong)SKAudioNode *backgroundAudio; 15 | @property (nonatomic,strong)SKAudioNode *bubbleGroweningAudio; 16 | 17 | @end 18 | 19 | @implementation MianSoundManager 20 | { 21 | BOOL __playedNomuchTimeSound; 22 | } 23 | 24 | - (instancetype)initWithScene:(id)scene { 25 | if (self=[super init]) { 26 | _relateScene = scene; 27 | } 28 | return self; 29 | } 30 | 31 | /*播放音视频有多种方法,1种是通过action,例如下面的playReadyGoSound,一种是通过音视频node,哈可以通过AVFundation播放, 32 | 但是使用非SKKit提供的类和方法都不能被自动管理 */ 33 | - (void)controlBgMusicWithPlay:(BOOL)play { 34 | //也可以通过action来循环播放,但是会偶现bug 35 | // [self.relateScene runAction:[SKAction repeatAction:[[[SKAudioNode alloc] initWithFileNamed:@"bubble_growing.wav"]] count:0] 36 | 37 | if (play) { 38 | [self controlBgMusicWithPlay:NO]; 39 | self.backgroundAudio = [[SKAudioNode alloc] initWithFileNamed:@"backmusic.wav"]; 40 | self.backgroundAudio.autoplayLooped = YES; 41 | [self.relateScene addChild:self.backgroundAudio]; 42 | }else { 43 | [self.backgroundAudio removeFromParent]; 44 | self.backgroundAudio = nil; 45 | } 46 | } 47 | 48 | - (void)controlBubbleGrowingSoundWithPlay:(BOOL)play { 49 | 50 | if (play) { 51 | [self controlBubbleGrowingSoundWithPlay:NO]; 52 | self.bubbleGroweningAudio = [[SKAudioNode alloc] initWithFileNamed:@"bubble_growing.wav"]; 53 | self.bubbleGroweningAudio.autoplayLooped = YES; 54 | [self.relateScene addChild:self.bubbleGroweningAudio]; 55 | }else { 56 | [self.bubbleGroweningAudio removeFromParent]; 57 | self.bubbleGroweningAudio = nil; 58 | } 59 | 60 | } 61 | 62 | //如何重复播放呢?通过嵌套repeatAction 63 | - (void)playMakeBubbleFaildSoundForTooSmall { 64 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"bubble_made_faild_toosmall.wav" waitForCompletion:NO]]; 65 | } 66 | 67 | - (void)playMakeBubbleFaildSoundForByHit { 68 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"bubble_made_faild_byhit.wav" waitForCompletion:NO]]; 69 | } 70 | 71 | - (void)playReadyGoSound { 72 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"game_readyGo.wav" waitForCompletion:NO]]; 73 | } 74 | 75 | - (void)playGameSucceedSound { 76 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"game_succeed.wav" waitForCompletion:NO]]; 77 | } 78 | 79 | - (void)playGameFaildSound { 80 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"game_faild.wav" waitForCompletion:NO]]; 81 | } 82 | 83 | - (void)playNomuchTimeSound { 84 | if (!__playedNomuchTimeSound) { 85 | __playedNomuchTimeSound = YES; 86 | SKAudioNode *audio = [[SKAudioNode alloc] initWithFileNamed:@"timer_nomuch.wav"]; 87 | audio.autoplayLooped = YES; 88 | [self.relateScene addChild:audio]; 89 | } 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /BubbleGame/Helper/Snow.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Helper/Snow.sks -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/RedBallStopImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "RedBallStopImage.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/RedBallStopImage.imageset/RedBallStopImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/RedBallStopImage.imageset/RedBallStopImage.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "background.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/background.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/background.imageset/background.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/ball.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "ball.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/ball.imageset/ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/ball.imageset/ball.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/ball_ice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "ball_ice.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/ball_ice.imageset/ball_ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/ball_ice.imageset/ball_ice.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/bubble.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "bubble.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/bubble.imageset/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/bubble.imageset/bubble.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/bubble_ice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "bubble_ice.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/bubble_ice.imageset/bubble_ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/bubble_ice.imageset/bubble_ice.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/game_play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "game_play-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/game_play.imageset/game_play-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/game_play.imageset/game_play-1.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/game_stop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "game_stop-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/game_stop.imageset/game_stop-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/game_stop.imageset/game_stop-1.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/icon_fireworks_flower.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_fireworks_flower@3x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/icon_fireworks_flower.imageset/icon_fireworks_flower@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/icon_fireworks_flower.imageset/icon_fireworks_flower@3x.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/redBallStop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "redBallStop-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/redBallStop.imageset/redBallStop-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/redBallStop.imageset/redBallStop-1.png -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/startGame.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "startGame.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /BubbleGame/ImageReource.xcassets/startGame.imageset/startGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/ImageReource.xcassets/startGame.imageset/startGame.png -------------------------------------------------------------------------------- /BubbleGame/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIRequiresFullScreen 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UIAppFonts 46 | 47 | 04B_25.TTF 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /BubbleGame/Resources/04B_25.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/04B_25.TTF -------------------------------------------------------------------------------- /BubbleGame/Resources/backmusic.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/backmusic.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/bubble_growing.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/bubble_growing.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/bubble_made_faild_byhit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/bubble_made_faild_byhit.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/bubble_made_faild_toosmall.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/bubble_made_faild_toosmall.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/button_click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/button_click.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/game_faild.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/game_faild.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/game_readyGo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/game_readyGo.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/game_succeed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/game_succeed.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/redball_stop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/redball_stop.wav -------------------------------------------------------------------------------- /BubbleGame/Resources/timer_nomuch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imqiuhang/SpriteKit-BubbleGame/e488b0e0358a208d01942bd0069bb5d619e78139/BubbleGame/Resources/timer_nomuch.wav -------------------------------------------------------------------------------- /BubbleGame/UI/GameButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameButton.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GameButton : SKSpriteNode 12 | 13 | + (instancetype)buttonWithImageNamed:(NSString *)imageNamed; 14 | 15 | @property (nonatomic,copy)void(^onSelectCallback)(GameButton *button); 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /BubbleGame/UI/GameButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameButton.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameButton.h" 10 | 11 | @implementation GameButton 12 | 13 | + (instancetype)buttonWithImageNamed:(NSString *)imageNamed { 14 | GameButton *button = [[GameButton alloc] initWithImageNamed:imageNamed]; 15 | button.userInteractionEnabled = YES; 16 | return button; 17 | } 18 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 19 | if (self.onSelectCallback) { 20 | self.onSelectCallback(self); 21 | } 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /BubbleGame/UI/GameLevelLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameLevelLabel.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GameLevelLabel : SKLabelNode 12 | 13 | + (instancetype)levelLabelWithLevel:(NSInteger)level; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /BubbleGame/UI/GameLevelLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameLevelLabel.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameLevelLabel.h" 10 | #import "UIColor+ColorExtension.h" 11 | 12 | @implementation GameLevelLabel 13 | 14 | + (instancetype)levelLabelWithLevel:(NSInteger)level { 15 | GameLevelLabel *label = [[GameLevelLabel alloc] initWithFontNamed:@"04B_25"]; 16 | label.fontColor = [UIColor colorWithHexString:@"#FFFD7C"]; 17 | label.fontSize = 70; 18 | label.text = [NSString stringWithFormat:@"0%li",(long)level+1]; 19 | [label runAction:[SKAction repeatActionForever: 20 | [SKAction sequence:@[[SKAction scaleTo:1.35 duration:1.5], 21 | [SKAction scaleTo:1 duration:1.5], 22 | [SKAction waitForDuration:2] 23 | ]]]]; 24 | return label; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BubbleGame/UI/GameProgressBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // GameProgressBar.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSUInteger, GameProgressBarType) { 12 | GameProgressBarTypeBubbleAdd, 13 | GameProgressBarTypeTime, 14 | }; 15 | 16 | @interface GameProgressBar : SKShapeNode 17 | 18 | + (instancetype)barWithType:(GameProgressBarType)type frame:(CGRect)frame; 19 | 20 | @property (nonatomic,readonly)CGFloat progress; 21 | @property (nonatomic)GameProgressBarType barType; 22 | - (void)updateProgress:(CGFloat)progress animation:(BOOL)animation; 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /BubbleGame/UI/GameProgressBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // GameProgressBar.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "GameProgressBar.h" 10 | #import "UIColor+ColorExtension.h" 11 | #import "GameConfigs.h" 12 | 13 | @implementation GameProgressBar 14 | { 15 | CGSize __originalSize; 16 | CGPoint __originalPosition; 17 | } 18 | 19 | + (instancetype)barWithType:(GameProgressBarType)type frame:(CGRect)frame { 20 | 21 | GameProgressBar *bar = [GameProgressBar shapeNodeWithRect:frame cornerRadius:frame.size.width/2.f]; 22 | [bar __setOriginalSize:bar.frame.size pos:bar.position]; 23 | [bar updateProgress:0 animation:NO]; 24 | bar.barType = type; 25 | 26 | return bar; 27 | } 28 | 29 | - (void)updateProgress:(CGFloat)progress animation:(BOOL)animation { 30 | 31 | CGFloat proDif = fabs((_progress - progress)); 32 | 33 | _progress = progress; 34 | 35 | [self updateBar]; 36 | [self removeAllActions]; 37 | 38 | NSTimeInterval aniDuration = animation?(proDif*5.f):0.f; 39 | 40 | CGFloat y = __originalPosition.y + (1.00-progress)*__originalSize.height; 41 | 42 | 43 | SKAction *scaleAction = [SKAction scaleYTo:progress duration:aniDuration]; 44 | SKAction *moveAction = [SKAction moveToY:y duration:aniDuration]; 45 | [self runAction:[SKAction group:@[ 46 | moveAction, 47 | scaleAction]]]; 48 | } 49 | 50 | - (void)setBarType:(GameProgressBarType)barType { 51 | _barType = barType; 52 | [self updateBar]; 53 | } 54 | 55 | - (void)updateBar { 56 | switch (self.barType) { 57 | case GameProgressBarTypeBubbleAdd: 58 | self.fillColor = [UIColor colorWithHexString:@"#0D2B3D"]; 59 | break; 60 | case GameProgressBarTypeTime: { 61 | self.fillColor = self.progress>GameConfigs.nomuchTimeRate?[UIColor colorWithHexString:@"#887532"]:[UIColor colorWithHexString:@"#480916"]; 62 | break; 63 | } 64 | } 65 | } 66 | 67 | - (void)__setOriginalSize:(CGSize)size pos:(CGPoint)pos { 68 | __originalSize = size; 69 | __originalPosition = pos; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /BubbleGame/Util/CommUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // CommUtil.h 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CommUtil : NSObject 12 | 13 | + (int)randomNumberIncludeFrom:(int)from includeTo:(int)to; 14 | + (BOOL)randomBOOL; 15 | + (BOOL)randomHitWithProb:(float)prob; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /BubbleGame/Util/CommUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // CommUtil.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "CommUtil.h" 10 | 11 | @implementation CommUtil 12 | 13 | + (int)randomNumberIncludeFrom:(int)from includeTo:(int)to { 14 | return (int)(from + (arc4random() % (to - from + 1))); 15 | } 16 | 17 | + (BOOL)randomBOOL { 18 | return [@([self randomNumberIncludeFrom:0 includeTo:1])boolValue]; 19 | } 20 | 21 | + (BOOL)randomHitWithProb:(float)prob { 22 | int max = prob*100; 23 | return [self randomNumberIncludeFrom:0 includeTo:max]==max; 24 | } 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BubbleGame/Util/UIColor+ColorExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+ColorExtension.h 3 | // Framework 4 | // 5 | // Created by imqiuhang on 16/4/3. 6 | // Copyright © 2016年 imqiuhang. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | #import 12 | 13 | FOUNDATION_EXPORT UIColor* ColorWithRGBA(CGFloat r, CGFloat g, CGFloat b,CGFloat a); 14 | FOUNDATION_EXPORT UIColor* ColorWithRGB(CGFloat r, CGFloat g, CGFloat b); 15 | FOUNDATION_EXPORT UIColor* colorWithHexString(NSString *color); 16 | FOUNDATION_EXPORT UIColor* colorWithHexStringAndAlpha(NSString *color ,float alpha); 17 | 18 | @interface UIColor (ColorExtension) 19 | 20 | #pragma mark - UIColor Create 21 | + (UIColor *) colorWithHexString: (NSString *)color; 22 | + (UIColor *) colorWithHexString: (NSString *)color alpha:(float)alpha; 23 | + (UIColor *) colorWithCyan:(CGFloat)cyan 24 | magenta:(CGFloat)magenta 25 | yellow:(CGFloat)yellow 26 | black:(CGFloat)black 27 | alpha:(CGFloat)alpha;///CMYK-A 28 | 29 | + (UIColor *)randomColor; 30 | 31 | #pragma mark - color info get 32 | - (NSString *)hexString;///@"0066cc" 33 | - (NSString *)hexStringWithAlpha;///@"0066ccff". 34 | - (uint32_t)rgbValue;///0x66ccffff 35 | - (uint32_t)rgbaValue;///0x66ccffff 36 | - (NSString *)rgbaDescription;///@return String @"R:100,G100,B200,A:0.3" 37 | 38 | ///RGBA Value 39 | @property (nonatomic, readonly) CGFloat red; 40 | @property (nonatomic, readonly) CGFloat green; 41 | @property (nonatomic, readonly) CGFloat blue; 42 | @property (nonatomic, readonly) CGFloat alpha; 43 | 44 | @end 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /BubbleGame/Util/UIColor+ColorExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+ColorExtension.m 3 | // Framework 4 | // 5 | // Created by imqiuhang on 16/4/3. 6 | // Copyright © 2016年 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import "UIColor+ColorExtension.h" 10 | #import "CommUtil.h" 11 | 12 | #define CLAMP_COLOR_VALUE(v) (v) = (v) < 0 ? 0 : (v) > 1 ? 1 : (v) 13 | 14 | UIColor * ColorWithRGBA(CGFloat r, CGFloat g, CGFloat b,CGFloat a) { 15 | return [UIColor colorWithRed:r/255.f green:g/255.f blue:b/255.f alpha:a]; 16 | } 17 | 18 | UIColor * ColorWithRGB(CGFloat r, CGFloat g, CGFloat b) { 19 | return ColorWithRGBA(r,g,b,1.0f); 20 | } 21 | 22 | UIColor* colorWithHexStringAndAlpha(NSString *color ,float alpha) { 23 | NSString *cString = [[color stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; 24 | 25 | if ([cString length] < 6) 26 | return [UIColor clearColor]; 27 | if ([cString hasPrefix:@"0X"]) 28 | cString = [cString substringFromIndex:2]; 29 | if ([cString hasPrefix:@"#"]) 30 | cString = [cString substringFromIndex:1]; 31 | if ([cString length] != 6) 32 | return [UIColor clearColor]; 33 | 34 | NSRange range; 35 | range.location = 0; 36 | range.length = 2; 37 | NSString *rString = [cString substringWithRange:range]; 38 | range.location = 2; 39 | NSString *gString = [cString substringWithRange:range]; 40 | range.location = 4; 41 | NSString *bString = [cString substringWithRange:range]; 42 | unsigned int r, g, b; 43 | [[NSScanner scannerWithString:rString] scanHexInt:&r]; 44 | [[NSScanner scannerWithString:gString] scanHexInt:&g]; 45 | [[NSScanner scannerWithString:bString] scanHexInt:&b]; 46 | 47 | return [UIColor colorWithRed:((float) r / 255.0f) green:((float) g / 255.0f) blue:((float) b / 255.0f) alpha:alpha]; 48 | } 49 | 50 | UIColor* colorWithHexString(NSString *color) { 51 | return colorWithHexStringAndAlpha(color,1.f); 52 | } 53 | 54 | void CMYK2RGB(CGFloat c, CGFloat m, CGFloat y, CGFloat k, 55 | CGFloat *r, CGFloat *g, CGFloat *b) { 56 | CLAMP_COLOR_VALUE(c); 57 | CLAMP_COLOR_VALUE(m); 58 | CLAMP_COLOR_VALUE(y); 59 | CLAMP_COLOR_VALUE(k); 60 | 61 | *r = (1 - c) * (1 - k); 62 | *g = (1 - m) * (1 - k); 63 | *b = (1 - y) * (1 - k); 64 | } 65 | 66 | 67 | @implementation UIColor (ColorExtension) 68 | 69 | + (UIColor *)colorWithHexString:(NSString *)color { 70 | return [self colorWithHexString:color alpha:1.f]; 71 | } 72 | 73 | +(UIColor *)colorWithHexString:(NSString *)color alpha:(float)alpha { 74 | return colorWithHexStringAndAlpha(color, alpha); 75 | } 76 | 77 | + (UIColor *)colorWithCyan:(CGFloat)cyan 78 | magenta:(CGFloat)magenta 79 | yellow:(CGFloat)yellow 80 | black:(CGFloat)black 81 | alpha:(CGFloat)alpha { 82 | CGFloat r, g, b; 83 | CMYK2RGB(cyan, magenta, yellow, black, &r, &g, &b); 84 | return [UIColor colorWithRed:r green:g blue:b alpha:alpha]; 85 | } 86 | 87 | + (UIColor *)randomColor { 88 | return ColorWithRGB([CommUtil randomNumberIncludeFrom:0 includeTo:255], [CommUtil randomNumberIncludeFrom:0 includeTo:255], [CommUtil randomNumberIncludeFrom:0 includeTo:255]); 89 | } 90 | 91 | - (uint32_t)rgbValue { 92 | CGFloat r = 0, g = 0, b = 0, a = 0; 93 | [self getRed:&r green:&g blue:&b alpha:&a]; 94 | int8_t red = r * 255; 95 | uint8_t green = g * 255; 96 | uint8_t blue = b * 255; 97 | return (red << 16) + (green << 8) + blue; 98 | } 99 | 100 | - (uint32_t)rgbaValue { 101 | CGFloat r = 0, g = 0, b = 0, a = 0; 102 | [self getRed:&r green:&g blue:&b alpha:&a]; 103 | int8_t red = r * 255; 104 | uint8_t green = g * 255; 105 | uint8_t blue = b * 255; 106 | uint8_t alpha = a * 255; 107 | return (red << 24) + (green << 16) + (blue << 8) + alpha; 108 | } 109 | 110 | - (NSString *)rgbaDescription { 111 | return [NSString stringWithFormat:@"R:%.2f,G:%.2f,B:%.2f,A:%.0f",self.red,self.green,self.blue,self.alpha]; 112 | } 113 | 114 | - (NSString *)hexString { 115 | return [self hexStringWithAlpha:NO]; 116 | } 117 | 118 | - (NSString *)hexStringWithAlpha { 119 | return [self hexStringWithAlpha:YES]; 120 | } 121 | 122 | - (NSString *)hexStringWithAlpha:(BOOL)withAlpha { 123 | CGColorRef color = self.CGColor; 124 | size_t count = CGColorGetNumberOfComponents(color); 125 | const CGFloat *components = CGColorGetComponents(color); 126 | static NSString *stringFormat = @"%02x%02x%02x"; 127 | NSString *hex = nil; 128 | if (count == 2) { 129 | NSUInteger white = (NSUInteger)(components[0] * 255.0f); 130 | hex = [NSString stringWithFormat:stringFormat, white, white, white]; 131 | } else if (count == 4) { 132 | hex = [NSString stringWithFormat:stringFormat, 133 | (NSUInteger)(components[0] * 255.0f), 134 | (NSUInteger)(components[1] * 255.0f), 135 | (NSUInteger)(components[2] * 255.0f)]; 136 | } 137 | 138 | if (hex && withAlpha) { 139 | hex = [hex stringByAppendingFormat:@"%02lx", 140 | (unsigned long)(self.alpha * 255.0 + 0.5)]; 141 | } 142 | return hex; 143 | } 144 | 145 | - (CGFloat)red { 146 | CGFloat r = 0, g, b, a; 147 | [self getRed:&r green:&g blue:&b alpha:&a]; 148 | return r; 149 | } 150 | 151 | - (CGFloat)green { 152 | CGFloat r, g = 0, b, a; 153 | [self getRed:&r green:&g blue:&b alpha:&a]; 154 | return g; 155 | } 156 | 157 | - (CGFloat)blue { 158 | CGFloat r, g, b = 0, a; 159 | [self getRed:&r green:&g blue:&b alpha:&a]; 160 | return b; 161 | } 162 | 163 | - (CGFloat)alpha { 164 | return CGColorGetAlpha(self.CGColor); 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /BubbleGame/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BubbleGame 4 | // 5 | // Created by imqiuhang on 2019/3/10. 6 | // Copyright © 2019 imqiuhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### iOS 2D游戏框架-SpriteKit的一些实践过程 3 | ###### @author [imqiuhang](https://github.com/imqiuhang) 4 | 5 | 6 | ### 📣前言 7 | >如果你是iOS开发者,刚好对2D游戏感兴趣,或者想在你的APP中增加一些有趣的场景互动,那么SpriteKit会是一个很好的选择,优秀的物理引擎,熟悉的OC/swift语言配方,和UIKit的无缝对接,使得学习成本非常的低,假如你刚好也了解Cocos2D相关,那么学习成本几乎为0。不过即使之前没有任何物理引擎和2D游戏的了解也没关系,因为我们的Apple已经把生涩难懂的部分封装成了非常简单的API供我们调用即可。 8 | 9 |
10 | 11 | ### 📣说明 12 | 13 | ###### 1. 📎[本文Demo](https://github.com/imqiuhang/BubbleGame) 由于时间问题,目前尺寸没有适配,使用12.9的iPad pro模拟器或者真机进行游戏,模拟器启动后可以在hardware -> rotate left进行旋转方便查看 14 | 15 | 16 | ###### 2. ❓由于才疏学浅,错误在所难免,有错误的地方以及补充欢迎在[issues](https://github.com/imqiuhang/BubbleGame/issues)中提出,第一时间更正,谢谢! 17 | 18 | ###### 3. 本游戏的是本人之前在大二的时候基于XNA制作的Windows游戏的改写的,素材都直接用的之前的素材,所以所有的P图,音效等都显得比较[幼稚]^_^,还请谅解。 19 | 20 | ##### BubbleGame的游戏大致截图 21 | 22 | ![bubbleGame.gif](https://upload-images.jianshu.io/upload_images/3058688-38bc8b8b544c552f.gif?imageMogr2/auto-orient/strip) 23 | 24 | ### 📣游戏介绍说明 25 | ##### 首先,先解释下这个游戏的规则,这个游戏是个关卡游戏,每一关会生成N+3个红球。我们可以点击屏幕创建一个泡泡,泡泡创建完成后会获得分数,左边是对应的进度,进度满了以后进入下一关。当泡泡过小,或者在创建的过程中被红球碰到都会移除并扣分,右边是时间进度,时间结束后分数不足则失败。泡泡有2种类型,普通和冰泡泡。冰泡泡会受到重力影响,创建后下沉,并且红球碰到冰泡泡会减少50%的移动速度,碰到普通泡泡后恢复速度。进入到下一关后红球数量变多,需要的分数变多,时间变少。 26 | 27 | ##### 这样我们这个游戏涉及到精灵(角色),音乐,关卡,物理碰撞,重力,动画等。 28 | 29 |
30 | --- 31 | 32 | `🚪好了,让我们一起进入到SpriteKit的物理世界中吧。` 33 | 34 | --- 35 | 36 | ### 🔍 例行先看下SpriteKit目录 37 | ###### ✅表示本文涉及到,❤️表示重点探讨 38 | 39 | 40 | ```objc 41 | #import ❤️ 42 | #import 43 | #import ❤️ 44 | #import ✅ 45 | #import ✅ 46 | #import ✅ 47 | #import 48 | #import 49 | #import ✅ 50 | #import 51 | #import ✅ 52 | #import 53 | #import 54 | #import 55 | #import 56 | #import 57 | #import 58 | #import ❤️ 59 | #import 60 | #import 61 | #import 62 | #import 63 | #import 64 | #import 65 | 66 | #import 67 | #import 68 | #import 69 | 70 | #import ✅ 71 | #import 72 | #import 73 | 74 | #import 75 | #import 76 | 77 | #import ❤️ 78 | 79 | #import ❤️ 80 | #import 81 | #import ❤️ 82 | 83 | ``` 84 | 85 | ### 什么是SpriteKit 86 | 87 | > 首先要知道什么是Sprite。Sprite的中文译名就是精灵,在游戏开发中,精灵指的是以图像方式呈现在屏幕上的一个图像。这个图像也许可以移动,用户可以与其交互,也有可能仅只是游戏的一个静止的背景图。塔防游戏中敌方源源不断涌来的每个小兵都是一个精灵,我方防御塔发出的炮弹也是精灵。可以说精灵构成了游戏的绝大部分主体视觉内容,而一个2D引擎的主要工作,就是高效地组织,管理和渲染这些精灵。SpriteKit是在iOS7 SDK中Apple新加入的一个2D游戏引擎框架,在SpriteKit出现之前,iOS开发平台上已经出现了像cocos2d这样的比较成熟的2D引擎解决方案。SpriteKit展现出的是Apple将Xcode和iOS/Mac SDK打造成游戏引擎的野心,但是同时也确实与IDE有着更好的集成,减少了开发者的工作。[这段摘录@onevcat WWDC 2013 Session笔记] 88 | 89 | 90 | ### 一些类似框架对比 91 | | 框架 | 一句话 |平台 |语言 |引擎 |特点 | 92 | | :------: | :------: |:------: | :------: | :------: | :------: | 93 | | SpriteKit | Create 2D sprite-based games using an optimized animation |iOS 7.0+
macOS 10.9+
tvOS 9.0+
watchOS 3.0+
|OC/swift |Box2D(C++) |基于UIKit一套实现,实现和对接最为方便 | 94 | | SceneKit | A high-level 3D scene framework |iOS 8.0+
macOS 10.8+
tvOS 9.0+
watchOS 3.0+
|OC/swift |/ |2D 3D两开花 | 95 | | Cocos | a unified package of game development tools |跨平台 |C++/JS/lua |Cocos2d-x(C++) |跨平台 | 96 | | XNA | XNA Runtime |Windows
Xbox
其他(可使用mono跨平台) |C# |/ |微软大法好 | 97 | 98 | 99 | ### 在进入开发之前,首先先聊一些无聊的概念 100 | 在开发之前,我们先了解一下SpriteKit或者说Cocos(两者的概念基本一直,甚至API的名字都大致相同)的一些基本概念,这些概念有别于我们平常的UIKit相关的开发。 101 | 102 | #### SKNode 103 | > SKNode在SpriteKit游戏场景中,就像是UIKit里的UIView,所有能感受到单元的都是一个Node,都是`SKNode`子类,包括可见的(例如各种图片,主角,精灵,技能展现,背景,按钮,人物角色,粒子,视频,甚至整个游戏场景也是一个Node)听见的(背景音乐,特效声音)等,Node和我们的UIView `- (void)addSubview:(UIView *)view;`非常相似,可以通过`- (void)addChild:(SKNode *)node;`来添加一个子Node,其他例如insert remove等操作也有一一对应。,另外SKNode继承UIResponder,也就是说每一个Node都可以处理交互事件。我们先过一次Node的基本属性,以了解他的基本特性。 104 | 105 | | 属性/方法| 说明 | 106 | | :------: | :------: | 107 | |frame | readonly,这点区别于View,frame 无法直接修改| 108 | |position | 需要注意和UIView的一些不同,Node的position是中心位置,不是左上角。另外游戏场景里的坐标系是左下角为0,0而不是左上角 | 109 | | xScale/yScale|x和y方向的缩放,可以直接调用setScale:一起修改 | 110 | | speed| 对这个node所有的action的速度做一次批量修改 | 111 | |alpha | | 112 | | paused| 刚才也说了,游戏场景也继承于node,暂停后这个node和子node所有的事件都会暂停| 113 | |userInteractionEnabled | 和UIView类似,NO表示不接受用户交互 | 114 | |parent/children | 和UIView superView,subViews类似 | 115 | | scene| node所在的场景 | 116 | | physicsBody| node关联的刚体,node只有关联了刚体才能参与物理事件,否则就像是个背景图,对整个世界和其他物体没有任何碰撞等物理作用 | 117 | |addChild:/insertChild:...|和UIView一样| 118 | |runAction:|类似CALayer addAnimation:| 119 | |convertPoint:|坐标系相关| 120 | ||| 121 | 122 | 就像是UIKit提供了button,label,textfield,imageview等UIView的子类。我们看下框架提供的的一些node种类 123 | 124 | | Node| 说明 | 125 | | :------: | :------: | 126 | |SKSpriteNode |类似于UIImageView,最常用的,通过图片纹理图Texture或者颜色生成一个精灵,可以设置纹理,高光等 | 127 | |SKShapeNode |通过形状创建,可以通过frame,CGPath等,和生成一个UIView及其相似,设置背景色,圆角,边框,阴影等。 | 128 | |SKLabelNode| 通过字体名称创建一个Node,和label差不多,可以设置字体字号颜色,文本,富文本等 | 129 | |SKLightNode |/ | 130 | | SKAudioNode,SKVideoNode| 音视频| 131 | |SKCropNode | /| 132 | |SKEmitterNode | 粒子系统,通过速度,生命周期,角度得到范围,其中基本上的属性都是一个随机范围| 133 | 134 | 135 |
136 | #### SKAction 137 | > 知道Node类似于UIView之后我们下一步就是想办法让他动起来。那么让他动起来的就是SKAction ,Node和SKAction就像是CALayer和CAAnimation,是给Node提供了各式各样的动画。上面所述的Node,比如游戏里的主角,可以通过run一个action来执行自己的结构或者内容的变化并设置过程的动画,例如位置变化,大小变化,贴图变化等一系列的改变并伴随一个动画。`[node runAction: withKey:]`,这点和CALayer addAnimatio n:forKey:是不是有点像,一个动画也会对应一个action。和CoreAnimation动画类似,CAAction也能设置`duration` `timingMode` `timingFunction` `speed` ,也可以通过`reversedAction`获取一个相反的action。SKAction比较好用的一点就是能够嵌套使用,比如把一个普通的位移action和一个形变action嵌套到一个顺序执行的action里获得一个总的action,在把这个action嵌套到重复执行的action里,就像这样,就实现了一个每隔2秒进行一次变大变小动画。 138 | 139 |
140 | 141 | ```objc 142 | [node runAction:[SKAction repeatActionForever: 143 | [SKAction sequence:@[[SKAction scaleTo:1.35 duration:1.5], 144 | [SKAction scaleTo:1 duration:1.5], 145 | [SKAction waitForDuration:2]]]]] 146 | 147 | 148 | ``` 149 | 150 | 我们大致看下系统提供的Action种类 151 | ![Action](https://upload-images.jianshu.io/upload_images/1396375-886c5afbec202d57.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/826) 152 | 153 | 154 | 155 | ### SKPhysicsBody刚体,参与物理世界交互的必要条件 156 | 157 | > 那我们刚才说了一个Node只有关联了一个刚体physicsBody才能参与物理世界的交互,其实也很好理解,毕竟一个Node只不过是一张图或者一个形状,而物理世界有质量,摩擦力,速度,动量,密度等等等等。physicsBody是Node的一个属性,默认为nil,如果我们需要让某个node参与到物理世界,比如参与到重力,碰撞等的影响,那么我们需要主动给他赋值一个physicsBody。 158 | 159 | 160 | | 属性/方法| 说明 | 161 | | :------: | :------: | 162 | |mass | 质量,和物体的密度挂钩,质量=密度*size,改变质量会改变密度 | 163 | |density | 密度,和质量挂钩,设置密度会自动获得质量 | 164 | |velocity |线性速度(1000,1000),x,y代表两个方向的分速度 | 165 | | linearDamping| 移动速度衰减系数,类似摩擦力,空气阻力之类的 | 166 | |angularVelocity| 自旋速度 | 167 | | Dynamic| 是否是动态刚体,NO不收任何力的影响 | 168 | |BitMask | 各种BitMask,碰撞掩码,标识自身的掩码和自己能接受的碰撞物体的掩码以及碰撞需要通知的掩码 | 169 | |applyForce: | 对刚体施加一个力| 170 | |applyLinearImpulse | 施加一个冲量(百度百科:在经典力学里,物体所受合外力的冲量等于它的动量的增量(即末动量减去初动量)) | 171 | | restitution| 弹性恢复系数,即这个东西碰到别人后弹性好不好,默认0.2| 172 | | affectedByGravity| 是否受重力影响 | 173 | |SKPhysicsJoint | 非常重要的概念,将两个物体链接,可以想象下割绳子游戏 | 174 | 175 | ### 最后几个比较重要的概念 176 | 177 | | 类| 说明 | 178 | | :------: | :------: | 179 | |SKView| 创建的所有内容都是在SKView中进行的,继承UIView,可以add到具体业务里,也可以作为一个VC的根view视图,通过presentScene 来推入一个场景,显示在最上层| 180 | |SKScene |也是一个Node,不过是一个特殊的Node,是一个游戏的场景,例如游戏的欢迎界面,游戏界面,每一关的界面,失败界面,帮助界面等都是一个个的场景,场景只能运行在SKView中 (void)update:(NSTimeInterval)currentTime; 这个方法会每个0.01秒执行,是计算游戏时间最重要的方法,也通过NSTimer来实现时间的计算,但是不使用SK提供的内容不会被管理,也就是暂停恢复等需要自己处理 | 181 | |physicsWorld | SKScene的一个属性,设置每个独立场景的物理世界的一些参数, 每一个SKScene场景中都对应一个物理世界,所有在场景里运行的刚体都收到物理世界的影响,可以设置最重要的物理世界的重力gravity,默认是(0,9.8),碰撞的代理方法也是在其中。| 182 | 183 |
184 | 185 | ### 了解了这些基本概念后,接下来,我们正式开始开发这个泡泡游戏。(游戏GIF以及规则解释在最上面) 186 | 187 | 188 | > 首先我们新建一个项目,项目类型选择Game,game technology选择 189 | > 我们可以看到系统为我们自动创建的一些文件如图 190 | 191 | ![创建项目](https://upload-images.jianshu.io/upload_images/3058688-e07ec43b042b866d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600) 192 | 193 | > 系统为我们自动创建的GameViewController,对应的XIB中我们可以看到这个View是一个SKView 194 | 195 | 196 | 197 | 删掉系统已有的scene,我们自己建立一个`MainGameScene`来作为我们游戏场景。我们的游戏就在这个scene中进行。 198 | 199 | 我们刚才上面说了,一个scene场景都是运行在skview中的,那么我们在GameViewController中推入我们的scene 200 | 201 | ```objc 202 | 203 | self.scene = [MainGameScene sceneWithSize:self.view.bounds.size config:config]; 204 | self.scene.scaleMode = SKSceneScaleModeAspectFill; 205 | 206 | SKView *skView = (SKView *)self.view; 207 | [skView presentScene:self.scene]; 208 | 209 | ``` 210 | 211 | 这样我们就可以开始在我们的`MainGameScene`中开始我们的表演了。 212 | 213 | 214 | > 首先刚才我们说游戏都是在scene中进行的,那么我们现在GameViewController中推入我们自己的MainGameScene 215 | 216 | 217 | 218 | ```objc 219 | 220 | @interface GameViewController () 221 | 222 | @property (nonatomic,strong)MainGameScene *scene; 223 | @property (nonatomic)NSInteger currentLevel; 224 | 225 | @end 226 | 227 | @implementation GameViewController 228 | 229 | - (void)viewDidLoad { 230 | [super viewDidLoad]; 231 | [self setupGameWithConfig:MainGameSceneCreatConfig.firstConfig]; 232 | } 233 | 234 | - (void)setupGameWithConfig:(MainGameSceneCreatConfig *)config { 235 | 236 | [self.scene removeFromParent]; 237 | self.scene = nil; 238 | 239 | self.scene = [MainGameScene sceneWithSize:self.view.bounds.size config:config]; 240 | self.scene.scaleMode = SKSceneScaleModeAspectFill; 241 | 242 | SKView *skView = (SKView *)self.view; 243 | [skView presentScene:self.scene]; 244 | 245 | //debug 246 | skView.showsFPS = YES; 247 | skView.showsNodeCount = YES; 248 | 249 | //简单关卡控制 250 | WeakSelf 251 | [self.scene setOnGameNeedRestart:^(BOOL isWin) { 252 | if (isWin) { 253 | weakSelf.currentLevel++; 254 | } 255 | MainGameSceneCreatConfig *config = [MainGameSceneCreatConfig new]; 256 | config.isFirst = NO; 257 | config.level = weakSelf.currentLevel; 258 | config.isWin = isWin; 259 | [weakSelf setupGameWithConfig:config]; 260 | }]; 261 | } 262 | 263 | @end 264 | 265 | ``` 266 | 267 | > 细节我们可以先忽略,这里主要是在viewDidLoad中setupGameWithConfig初始化一次我们的Main Scene,然后通过SKView presentScene来将我们的scene推到上层显示,然后我们监听了Main Scene中闯关成功和失败的回调,重新通过setupGameWithConfig重新生成新的scene并present。 268 | 269 | 270 | ##### 因此,我们完成了我们的第一步,成功推入我们的游戏界面,下一步我们便开始游戏的开发。 271 | 272 | > 首先,我们进入到MainGameScene的开发中,首先我们也需要构建一下我们的游戏场景,根据我们的规则,我们的泡泡或者红球都是不能超出边界范围的,碰撞会反弹,切冰球会受到重力的影响,这一切说明我们的游戏背景将会是一个封闭的,带有重力的矩形,那么我们根据需求在MainGameScene初始化一下我们的scene的物理世界。 273 | 274 | 275 | 276 | ```objc 277 | //因为坐标系是从下往上的,和现实世界相反,所以重力是负数 278 | self.physicsWorld.gravity = CGVectorMake(0, -9.8); 279 | 280 | //设置世界的碰撞代理 281 | self.physicsWorld.contactDelegate = self; 282 | 283 | //设置这个世界的边界,任何刚体都无法通过力的作用逃离这个世界的边界 284 | self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(kPhysicsWorldInsert.left, kPhysicsWorldInsert.top, self.size.width-kPhysicsWorldInsert.left-kPhysicsWorldInsert.right, self.size.height-kPhysicsWorldInsert.top-kPhysicsWorldInsert.bottom)]; 285 | 286 | 287 | ``` 288 | > 可以看到,一个Scene中都对应着一个physicsWorld,我们设置了physicsWorld的重力,以及碰撞代理和物理世界的边界,这里我们选择是矩形边界。 289 | 290 | ##### 现在,世界有了,是不是差点背景音乐? 291 | 292 | > 在上面我们也说到了声音的3种播放方式,那么一般像背景音乐这种需要较多控制和持续播放的我们选择add 声音node的方式,一般像一次的音效我们使用run一个action的方法,集体我们可以在MianSoundManager这个类中查看,这里列举一下这两种的播放 293 | 294 | 295 | ```objc 296 | 297 | //我们在MainGameScene刚才初始化的地方增加声音的初始化 298 | 299 | //初始化声音,例子系统 300 | self.soundManager = [[MianSoundManager alloc] initWithScene:self]; 301 | [self.soundManager controlBgMusicWithPlay:YES]; 302 | 303 | 304 | //以下是MianSoundManager的方法 305 | - (void)controlBgMusicWithPlay:(BOOL)play { 306 | 307 | if (play) { 308 | [self controlBgMusicWithPlay:NO]; 309 | self.backgroundAudio = [[SKAudioNode alloc] initWithFileNamed:@"backmusic.wav"]; 310 | self.backgroundAudio.autoplayLooped = YES; 311 | [self.relateScene addChild:self.backgroundAudio]; 312 | }else { 313 | [self.backgroundAudio removeFromParent]; 314 | self.backgroundAudio = nil; 315 | } 316 | } 317 | 318 | //播放进入游戏 ready go的音效 319 | - (void)playReadyGoSound { 320 | [self.relateScene runAction:[SKAction playSoundFileNamed:@"game_readyGo.wav" waitForCompletion:NO]]; 321 | } 322 | 323 | ``` 324 | > 上面列举了播放背景声音和音效的两种不同方法。其中self.relateScene使我们调用方也就是MainGameScene传进去的,也就是方法1播放背景声音其实是通过向MainGameScene中add了一个声音node,方法2种则是通过MainGameScene run了有个Sound action。 325 | 326 | 327 | ##### 至此,物理世界和音效我们都已经完成了,那么我们在增加一个背景图以及开始游戏的按钮 328 | 329 | ```objc 330 | 331 | 332 | SKSpriteNode *bgImageNode = [[SKSpriteNode alloc] initWithImageNamed:@"background"]; 333 | bgImageNode.size = self.size; 334 | //position是物体的中间点 335 | bgImageNode.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 336 | //addChild,和addSubView类似 337 | [self addChild:bgImageNode]; 338 | 339 | GameButton *startGameBtn = [GameButton buttonWithImageNamed:@"startGame"]; 340 | startGameBtn.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 341 | [startGameBtn setScale:3.f]; 342 | [GameEmitterManager addFireForNode:startGameBtn]; 343 | [self addChild:startGameBtn]; 344 | WeakSelf; 345 | [startGameBtn setOnSelectCallback:^(GameButton *button) { 346 | button.userInteractionEnabled = NO; 347 | [weakSelf setUpStartGameContent]; 348 | [button runAction:[SKAction sequence:@[ 349 | [SKAction fadeOutWithDuration:2.f], 350 | [SKAction removeFromParent], 351 | ]]]; 352 | }]; 353 | 354 | ``` 355 | 356 | > 至于背景图我们没有特别的,只是add了一个图片node而已,而游戏button我们则需要自己稍加处理一下,因为游戏中没有现成的按钮可以提供给我们使用,但是一个node是继承自UIResponder,因为我们可以继承一个图片node,然后重新touch相关的UIResponder方法,来模拟一个按钮,具体可以看GameButton中,这里我只是在touchesBegan 方法里直接回调,如果想要更好的交互,可以参考UIButton的api,增加各种手势以及状态的回调。 357 | 358 | 359 | ##### 细心的你一定会发现背景在下雪,已经开始按钮在冒火,这其实是框架给我们提供了非常好用的粒子系统,新建一个例子系统,我们可以非常方便的通过可视化界面随意调节我们的粒子效果 360 | 361 | > 我们file - new - file,拉到比较下面的位置 选择sprite particle file,选择例如下雨下雪烟雾等的其中一个,新建一个粒子文件。选择刚才的粒子文件,我们可以看到xcode给我们提供了比较丰富的可视化操作,当然这些都有对应的属性通过代码设置,在粒子系统里,我们通过position range,lifetime speed range等的作用设置密度和范围的发小。 362 | 363 | ![](https://upload-images.jianshu.io/upload_images/3058688-fcda9c522344ae53.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 364 | 365 | ```objc 366 | 367 | //我们通过在我们的按钮node上add一个粒子node 368 | + (void)addFireForNode:(SKNode *)node { 369 | 370 | SKEmitterNode *fire = [SKEmitterNode nodeWithFileNamed:@"Fire.sks"]; 371 | fire.position = CGPointMake(0, 16); 372 | 373 | [node addChild:fire]; 374 | } 375 | 376 | 377 | ``` 378 | 379 | ##### 以上都是一些游戏的附属相关的开始,那么现在我们的主角应该登场,也就是我们的红球redball和泡泡bubble,那么我们抽象一下主角的模型,并且在初始化的赋值一些物理属性,首先我们看红球。 380 | 381 | ```objc 382 | #import 383 | #import "CommUtil.h" 384 | #import "GameConfigs.h" 385 | 386 | typedef NS_ENUM(NSUInteger, RedBallEffectType) { 387 | RedBallEffectTypeNone, 388 | RedBallEffectTypeIceing, 389 | }; 390 | 391 | @interface RedBall : SKSpriteNode 392 | 393 | @property (nonatomic,readwrite)RedBallEffectType effectType; 394 | 395 | //random 396 | + (instancetype)redBall; 397 | 398 | @end 399 | 400 | 401 | #import "RedBall.h" 402 | 403 | @implementation RedBall 404 | 405 | + (instancetype)redBall { 406 | 407 | RedBall *redBall = [RedBall spriteNodeWithImageNamed:@"ball"]; 408 | [redBall setScale:5]; 409 | redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2.f]; 410 | 411 | //------------------------init configs--------------------------------- 412 | redBall.physicsBody.affectedByGravity = NO;//不受重力影响 413 | redBall.physicsBody.mass = 10;//质量 10kg 414 | redBall.physicsBody.angularDamping = 0.f;//角动量摩擦力 415 | redBall.physicsBody.linearDamping = 0.f;//线性摩擦力 416 | redBall.physicsBody.restitution = 1.f;//反弹的动量 1即无限反弹,不丢失动量,相当于一个永动机=。= 417 | redBall.physicsBody.friction = 0.f;//摩擦力 418 | redBall.physicsBody.allowsRotation = YES;//允许受到角动量 419 | redBall.physicsBody.usesPreciseCollisionDetection = YES;//独立计算碰撞 420 | 421 | return redBall; 422 | } 423 | 424 | - (void)setEffectType:(RedBallEffectType)effectType { 425 | if (effectType==_effectType) { 426 | return; 427 | } 428 | _effectType = effectType; 429 | 430 | //也可以通过action动画b换图 431 | self.texture = [SKTexture textureWithImageNamed:effectType==RedBallEffectTypeIceing?@"ball_ice":@"ball"]; 432 | 433 | //碰撞后拿出原有的角速度的方向 赋值到新速度,方向不变 d速度减半或者恢复 434 | //当然也可以角速度直接操作 angularVelocity 435 | CGVector originalVelocity = self.physicsBody.velocity ; 436 | CGFloat dxv = originalVelocity.dx>=0?1:-1; 437 | CGFloat dyv = originalVelocity.dy>=0?1:-1; 438 | CGFloat v = effectType==RedBallEffectTypeIceing?GameConfigs.redBallSpeedIce:GameConfigs.redBallSpeedNormal; 439 | self.physicsBody.velocity = CGVectorMake(v*dxv, v*dyv); 440 | } 441 | 442 | @end 443 | 444 | 445 | ``` 446 | > 以上我们定义了红球的类型,普通已经减速效果,我们先看红球的初始化,红球其实也是一个图片node,和我们的背景图或者按钮没有什么差别而physicsBody才是红球加入到物理世界的最重要的条件,按照上面我们说的,我们给红球赋值一个.physicsBody,设置质量等,然后我们设置physicsBody.restitution = 1.f,也就是没有衰减的弹性,这样红球碰到泡泡或者朋友我们游戏边界的时候就能原速度返回了,这里红球的质量是10。 在下面setEffectType的方法中,我们判定红球是否受到了减速,如果是我们则取出他的速度,减少一半。但是方向不变(这个是碰撞后调用的,所以方向什么的碰撞引擎已经给我们做好了,我们只需要将速度减半) 447 | 448 | 449 | ##### 再来看下bubble 450 | 451 | ```objc 452 | 453 | + (instancetype)bubbleWithType:(BubbleType)bubbleType { 454 | Bubble *bubble = bubbleType==BubbleTypeNormal?[self spriteNodeWithImageNamed:@"bubble"]:[self spriteNodeWithImageNamed:@"bubble_ice"]; 455 | 456 | bubble.bubbleType = bubbleType; 457 | bubble.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble.size.width/2.f]; 458 | [bubble setScale:0.f]; 459 | bubble.physicsBody.affectedByGravity = NO; 460 | bubble.physicsBody.mass = 500;//质量是红球的50倍 461 | bubble.physicsBody.restitution = 0.2;//动量传递衰减80%(即看着弹性很小,设置为1碰撞后可以一直弹) 462 | bubble.physicsBody.angularDamping = 0.4; 463 | bubble.physicsBody.linearDamping = 0.3f; 464 | 465 | return bubble; 466 | } 467 | 468 | 469 | - (void)fadeOut { 470 | [self runAction:[SKAction sequence:@[ 471 | [SKAction fadeOutWithDuration:0.5], 472 | [SKAction removeFromParent], 473 | ]]]; 474 | } 475 | 476 | - (void)beganGrowthingWithTargetScale:(CGFloat)scale duration:(NSTimeInterval)duration{ 477 | [self stopGrowthing]; 478 | _growthing = YES; 479 | [self runAction:[SKAction scaleTo:scale duration:duration] withKey:kGrowthingAnimationName]; 480 | } 481 | 482 | - (void)stopGrowthing { 483 | _growthing = NO; 484 | [self removeActionForKey:kGrowthingAnimationName]; 485 | } 486 | 487 | ``` 488 | > 我们先忽略其他无关的代码,首先创建bubble,我们可以看到bubble的质量为500,而红球我们刚才设置的是10,所以bubble被红球碰撞后会有微量的偏移(其实都设置密度会比较好,这样红球撞到不同大小的bubble会出现不同的位移变化更加逼真,这里我们为了方便),在来看下`beganGrowthingWithTargetScale`方法,这个方法其实是在mianScene中点击创建bubble调用的,也就是bubble的增大我们是通过给bubble run一个scale的action来达到,这样也达到了很多我们想要的效果,1是变大,而是有个5秒的过程,3是因为scale是指定的,所以当达到最大的时候手指还按着就不会在变大了,省去了我们人工判断的过程。再看下`fadeOut `方法,其实就是bubble在生成过程被红球碰到后的消失过程,可以看到我们通过嵌套了几个action,线性执行。 489 | 490 | 491 | ##### bubble和redball我们都已经完成,那么就是在开始游戏后生成红球开始运动,按钮在点击屏幕的时候创建红球。并且开始倒计时。细节demo里都有,我们只讲几个重要的方法。 492 | 493 | ##### 1是红球的运动,我们上面也说了很多种使刚体动起来的方法,其中我们可以通过直接赋值速度的方法最快达到 494 | 495 | ```objc 496 | 497 | #pragma Mark- redball 498 | - (void)setupRedballs { 499 | 500 | CGFloat speed = GameConfigs.redBallSpeedNormal; 501 | 502 | /* -------------> △vx 503 | | . / 504 | | . / 505 | | ./ 506 | | / 垂直方向是角速度,也就是速度的向量 507 | | / 508 | | / 509 | | / 510 | | / 511 | | / 512 | | / 513 | △vy 514 | */ 515 | 516 | NSArray *vectors = @[[NSValue valueWithCGVector:CGVectorMake(speed, speed)], 517 | [NSValue valueWithCGVector:CGVectorMake(speed, -speed)], 518 | [NSValue valueWithCGVector:CGVectorMake(-speed, speed)], 519 | [NSValue valueWithCGVector:CGVectorMake(-speed, -speed)]]; 520 | 521 | for(int i=0;i<3+self.configs.level;i++) { 522 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.5*i + 1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 523 | RedBall *redball = [RedBall redBall]; 524 | redball.physicsBody.collisionBitMask = GameConfigs.redBallCollisionBitMask; 525 | redball.physicsBody.contactTestBitMask = GameConfigs.bubbleCollisionBitMask; 526 | redball.position = CGPointMake(self.size.width/2.f, self.size.height/2.f); 527 | [self addChild:redball]; 528 | //可以通过施加一个推力(牛顿)或者j冲量 或者直接赋值速度,直接赋值速度比较方便,推力需要计算 529 | // [redball.physicsBody applyForce:[vectors[i] CGVectorValue]]; 530 | redball.physicsBody.velocity = [vectors[i%vectors.count] CGVectorValue]; 531 | }); 532 | } 533 | } 534 | 535 | 536 | ``` 537 | 538 | ##### 第二是碰撞的检测 539 | 540 | > 在上面初始化scene物理世界的时候,有一段`self.physicsWorld.contactDelegate = self;`其实就是设置了物理设置的碰撞代理为scene,那我们就可以在碰撞代理回调里拿到红球和bubble的碰撞了,另外一点,碰撞必须设置collisionBitMask和contactTestBitMask,也就是谁,以及碰撞了谁需要通知,这里设置为对方的掩码就行。 541 | 542 | ```objc 543 | 544 | #pragma mark - PhysicsContact 545 | - (void)didBeginContact:(SKPhysicsContact *)contact { 546 | 547 | } 548 | 549 | - (void)didEndContact:(SKPhysicsContact *)contact { 550 | 551 | RedBall *redBall = nil; 552 | Bubble *bubble = nil; 553 | 554 | if ([contact.bodyA.node isKindOfClass:RedBall.class]) { 555 | redBall = (RedBall*)contact.bodyA.node; 556 | bubble = (Bubble*)contact.bodyB.node; 557 | }else { 558 | redBall = (RedBall*)contact.bodyB.node; 559 | bubble = (Bubble*)contact.bodyA.node; 560 | } 561 | 562 | if (!([redBall isKindOfClass:RedBall.class]&&[bubble isKindOfClass:Bubble.class])) { 563 | return; 564 | } 565 | 566 | redBall.effectType = bubble.bubbleType==BubbleTypeIce?RedBallEffectTypeIceing:RedBallEffectTypeNone; 567 | 568 | if (bubble.growthing) { 569 | [bubble stopGrowthing]; 570 | [self creatBubbleFaildWithBubble:bubble]; 571 | [self.soundManager playMakeBubbleFaildSoundForByHit]; 572 | [bubble fadeOut]; 573 | self.currentGrowthingBubble = nil; 574 | } 575 | } 576 | 577 | ``` 578 | 579 | > 以上就是我们可以分别在碰撞前和碰撞后的代码里实现我们的需求,碰撞后的话速度的方向之类的已经受到了改变。 580 | 581 | 582 | ##### 再就是倒计时的实现,之前我们也说可以通过NSTimer实现,但是NSTimer不会受到SK的管理。也就是暂停等需要我们自己的维护,非常不方便,而我们其实可以通过重写scene的update:方法来实现毫秒级的时间监测,来实现倒计时之类的实现。 583 | 584 | ```objc 585 | 586 | #pragma mark game loop 587 | //每隔0.01秒调用 currentTime 是秒 精确到后三位 例如1.002秒 588 | -(void)update:(CFTimeInterval)currentTime { 589 | 590 | if (_isGameStart) { 591 | if(self.gameBeganTime==0){ 592 | self.gameBeganTime = currentTime; 593 | }else { 594 | [self updateGameDuration:currentTime - self.gameBeganTime]; 595 | } 596 | } 597 | } 598 | 599 | - (void)updateGameDuration:(CFTimeInterval)gameDuration { 600 | if (gameDuration>=GameConfigs.totalTimeForPass) { 601 | if (self.onGameNeedRestart) { 602 | self.onGameNeedRestart(NO); 603 | } 604 | return; 605 | } 606 | CGFloat progress = 1.00f-(gameDuration/GameConfigs.totalTimeForPass); 607 | [self.timeBar updateProgress:progress animation:NO]; 608 | if (progress<=GameConfigs.nomuchTimeRate) { 609 | [self.soundManager playNomuchTimeSound]; 610 | } 611 | } 612 | 613 | 614 | ``` 615 | 616 | ##### 以上是对我们这个泡泡游戏的一些实现过程的解释,其中的细节在demo里也有非常详细的注释,其实我们只是用到了SK里面非常少的功能,起还有其他强大之处可以给我们使用,也期待会有更多的场景能在我们APP中用到SK这个强大的框架。 617 | 618 | 619 |
620 | 621 | [↑↑↑↑回到顶部↑↑↑↑](#readme) 622 | 623 | [↑↑↑↑回到顶部↑↑↑↑](#readme) 624 | 625 | [↑↑↑↑回到顶部↑↑↑↑](#readme) 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | --------------------------------------------------------------------------------