├── MySecondGame ├── spark.png ├── Explosion.wav ├── ExplosionParticle.sks ├── Images.xcassets │ ├── Spaceship.imageset │ │ ├── Ship.png │ │ └── Contents.json │ ├── StarParticle.imageset │ │ ├── Star.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── AppIcon29x1.png │ │ ├── AppIcon29x2.png │ │ ├── AppIcon29x3.png │ │ ├── AppIcon40x1.png │ │ ├── AppIcon40x2.png │ │ ├── AppIcon40x3.png │ │ ├── AppIcon60x2.png │ │ ├── AppIcon60x3.png │ │ ├── AppIcon76x1.png │ │ ├── AppIcon76x2.png │ │ ├── AppIcon29x2-1.png │ │ ├── AppIcon40x2-1.png │ │ └── Contents.json ├── License.txt ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.xib ├── README.md ├── Info.plist ├── AppDelegate.swift ├── EnemySpriteController.swift ├── GameViewController.swift └── GameScene.swift ├── .gitignore ├── README.md └── MySecondGame.xcodeproj └── project.pbxproj /MySecondGame/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/spark.png -------------------------------------------------------------------------------- /MySecondGame/Explosion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Explosion.wav -------------------------------------------------------------------------------- /MySecondGame/ExplosionParticle.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/ExplosionParticle.sks -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/Spaceship.imageset/Ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/Spaceship.imageset/Ship.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/StarParticle.imageset/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/StarParticle.imageset/Star.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x1.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x2.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x3.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x1.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x2.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x3.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon60x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon60x2.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon60x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon60x3.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon76x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon76x1.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon76x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon76x2.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon29x2-1.png -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfnjstn/MySecondGame/HEAD/MySecondGame/Images.xcassets/AppIcon.appiconset/AppIcon40x2-1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xcworkspacedata 3 | 4 | *.xcuserstate 5 | 6 | *.xcsettings 7 | 8 | *.xcbkptlist 9 | 10 | *.xcscheme 11 | MySecondGame.xcodeproj/xcuserdata/stefanjosten.xcuserdatad/xcschemes/xcschememanagement.plist 12 | 13 | MySecondGameTests/Info.plist 14 | 15 | MySecondGameTests/MySecondGameTests.swift 16 | 17 | *.xccheckout 18 | 19 | -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/Spaceship.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Ship.png" 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 | } -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/StarParticle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Star.png" 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 | } -------------------------------------------------------------------------------- /MySecondGame/License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stefan Josten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MySecondGame/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MySecondGame/README.md: -------------------------------------------------------------------------------- 1 | MySecondGame 2 | =========== 3 | 4 | Sample code related to my blog 5 | 6 | http://developerplayground.net/?p=5 7 | 8 | The videos are available in my YouTube channel: https://www.youtube.com/channel/UCwC_QHjZ-COEQpLVAjaDyfQ 9 | 10 | You can download my prototyping App for creating this tutorial here: https://itunes.apple.com/us/app/yet-another-spaceshooter/id949662362?mt=8 11 | 12 | 13 | 14 | 15 | License 16 | ======= 17 | 18 | This code is published under the MIT licence: http://opensource.org/licenses/MIT 19 | 20 | The MIT License (MIT) 21 | [OSI Approved License] 22 | The MIT License (MIT) 23 | 24 | Copyright (c) 2015 by Stefan Josten 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in 34 | all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 42 | THE SOFTWARE. 43 | -------------------------------------------------------------------------------- /MySecondGame/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MyGameTutorial 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.9 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | gamekit 35 | 36 | UIStatusBarHidden 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MySecondGame 2 | =========== 3 | 4 | Sample code related to my blog: 5 | http://developerplayground.net 6 | 7 | Tutorial: 8 | http://developerplayground.net/?p=16 9 | 10 | The videos are available in my YouTube channel: https://www.youtube.com/channel/UCwC_QHjZ-COEQpLVAjaDyfQ 11 | 12 | This version in AppStore: 13 | https://itunes.apple.com/us/app/mysecondgame/id956647245?ls=1&mt=8 14 | 15 | More sophisticated version. Also using the code fragments of my tutorials: 16 | https://itunes.apple.com/us/app/yet-another-spaceshooter/id949662362?mt=8 17 | 18 | 19 | 20 | License 21 | ======= 22 | 23 | This code is published under the MIT licence: http://opensource.org/licenses/MIT 24 | 25 | The MIT License (MIT) 26 | [OSI Approved License] 27 | The MIT License (MIT) 28 | 29 | Copyright (c) 2014 by Stefan Josten 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | 49 | -------------------------------------------------------------------------------- /MySecondGame/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon29x2-1.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon29x3.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon40x2-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon40x3.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon60x2.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon60x3.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "AppIcon29x1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "AppIcon29x2.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon40x1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "AppIcon40x2.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "AppIcon76x1.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "AppIcon76x2.png", 73 | "scale" : "2x" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "xcode" 79 | } 80 | } -------------------------------------------------------------------------------- /MySecondGame/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MySecondGame 4 | // 5 | // Created by STEFAN on 23.10.14. 6 | // Copyright (c) 2014 Stefan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /MySecondGame/EnemySpriteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnemySpriteController.swift 3 | // MySecondGame 4 | // 5 | // Created by STEFAN on 09.11.14. 6 | // Copyright (c) 2014 Stefan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | 12 | // Controller class for: 13 | // - creating/destroying enemies, 14 | // - shooting 15 | // - animitaions 16 | class EnemySpriteController { 17 | var enemySprites: [SKSpriteNode] = [] 18 | 19 | // Return a new enemy sprite which follows the targetSprite node 20 | func spawnEnemy(targetSprite: SKNode) -> SKSpriteNode { 21 | 22 | // create a new enemy sprite 23 | let newEnemy = SKSpriteNode(imageNamed:"Spaceship") 24 | enemySprites.append(newEnemy) 25 | newEnemy.xScale = 0.08 26 | newEnemy.yScale = 0.08 27 | newEnemy.color = UIColor.redColor() 28 | newEnemy.colorBlendFactor=0.6 29 | 30 | // position new sprite at a random position on the screen 31 | let sizeRect = UIScreen.mainScreen().applicationFrame; 32 | let posX = arc4random_uniform(UInt32(sizeRect.size.width)) 33 | let posY = arc4random_uniform(UInt32(sizeRect.size.height)) 34 | newEnemy.position = CGPoint(x: CGFloat(posX), y: CGFloat(posY)) 35 | 36 | // Define Constraints for orientation/targeting behavior 37 | let i = enemySprites.count-1 38 | let rangeForOrientation = SKRange(constantValue:CGFloat(M_2_PI*7)) 39 | let orientConstraint = SKConstraint.orientToNode(targetSprite, offset: rangeForOrientation) 40 | let rangeToSprite = SKRange(lowerLimit: 80, upperLimit: 90) 41 | var distanceConstraint: SKConstraint 42 | 43 | // First enemy has to follow spriteToFollow, second enemy has to follow first enemy, ... 44 | if enemySprites.count-1 == 0 { 45 | distanceConstraint = SKConstraint.distance(rangeToSprite, toNode: targetSprite) 46 | } else { 47 | distanceConstraint = SKConstraint.distance(rangeToSprite, toNode: enemySprites[i-1]) 48 | } 49 | newEnemy.constraints = [orientConstraint, distanceConstraint] 50 | 51 | return newEnemy 52 | } 53 | 54 | // Shoot in direction of spriteToShoot 55 | func shoot(targetSprite: SKNode) { 56 | 57 | for enemy in enemySprites { 58 | 59 | // Create the bullet sprite 60 | let bullet = SKSpriteNode() 61 | bullet.color = UIColor.greenColor() 62 | bullet.size = CGSize(width: 5,height: 5) 63 | bullet.position = CGPointMake(enemy.position.x, enemy.position.y) 64 | targetSprite.parent?.addChild(bullet) 65 | 66 | // Add physics body for collision detection 67 | bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size) 68 | bullet.physicsBody?.dynamic = true 69 | bullet.physicsBody?.affectedByGravity = false 70 | bullet.physicsBody?.categoryBitMask = collisionBulletCategory 71 | bullet.physicsBody?.contactTestBitMask = collisionHeroCategory 72 | bullet.physicsBody?.collisionBitMask = 0x0; 73 | 74 | // Determine vector to targetSprite 75 | let vector = CGVectorMake((targetSprite.position.x-enemy.position.x), targetSprite.position.y-enemy.position.y) 76 | 77 | // Create the action to move the bullet. Don't forget to remove the bullet! 78 | let bulletAction = SKAction.sequence([SKAction.repeatAction(SKAction.moveBy(vector, duration: 1), count: 10) , SKAction.waitForDuration(30.0/60.0), SKAction.removeFromParent()]) 79 | bullet.runAction(bulletAction) 80 | 81 | } 82 | } 83 | 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /MySecondGame/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /MySecondGame/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // MySecondGame 4 | // 5 | // Created by STEFAN on 23.10.14. 6 | // Copyright (c) 2014 Stefan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import GameKit 12 | import iAd 13 | 14 | class GameViewController: UIViewController, ADBannerViewDelegate, GKGameCenterControllerDelegate, GameSceneDelegate { 15 | 16 | var scene : GameScene? 17 | 18 | // Properties for Banner Ad 19 | var iAdBanner = ADBannerView() 20 | var bannerVisible = false 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | // Initialize game center 26 | self.initGameCenter() 27 | } 28 | 29 | override func viewDidAppear(animated: Bool) { 30 | super.viewDidAppear(animated) 31 | 32 | // Detect the screensize 33 | let sizeRect = UIScreen.mainScreen().applicationFrame 34 | let width = sizeRect.size.width * UIScreen.mainScreen().scale 35 | let height = sizeRect.size.height * UIScreen.mainScreen().scale 36 | 37 | // Create a fullscreen Scene object 38 | scene = GameScene(size: CGSizeMake(width, height)) 39 | scene!.scaleMode = .AspectFill 40 | scene!.gameCenterDelegate = self 41 | 42 | // Configure the view. 43 | let skView = self.view as! SKView 44 | //skView.showsFPS = true 45 | //skView.showsNodeCount = true 46 | 47 | /* Sprite Kit applies additional optimizations to improve rendering performance */ 48 | skView.ignoresSiblingOrder = true 49 | 50 | skView.presentScene(scene) 51 | 52 | // Prepare banner Ad 53 | iAdBanner.frame = CGRectMake(0, self.view.frame.size.height, self.view.frame.width, 50) 54 | iAdBanner.delegate = self 55 | bannerVisible = false 56 | 57 | // Prepare fullscreen Ad 58 | UIViewController.prepareInterstitialAds() 59 | } 60 | 61 | override func shouldAutorotate() -> Bool { 62 | return true 63 | } 64 | 65 | override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { 66 | return UIInterfaceOrientationMask.LandscapeLeft 67 | } 68 | 69 | override func didReceiveMemoryWarning() { 70 | super.didReceiveMemoryWarning() 71 | // Release any cached data, images, etc that aren't in use. 72 | } 73 | 74 | override func prefersStatusBarHidden() -> Bool { 75 | return true 76 | } 77 | 78 | // Initialize Game Center 79 | func initGameCenter() { 80 | 81 | // Check if user is already authenticated in game center 82 | if GKLocalPlayer.localPlayer().authenticated == false { 83 | 84 | // Show the Login Prompt for Game Center 85 | GKLocalPlayer.localPlayer().authenticateHandler = {(viewController, error) -> Void in 86 | if viewController != nil { 87 | //self.scene!.gamePaused = true 88 | self.presentViewController(viewController!, animated: true, completion: nil) 89 | 90 | // Add an observer which calls 'gameCenterStateChanged' to handle a changed game center state 91 | let notificationCenter = NSNotificationCenter.defaultCenter() 92 | notificationCenter.addObserver(self, selector:"gameCenterStateChanged", name: "GKPlayerAuthenticationDidChangeNotificationName", object: nil) 93 | } 94 | } 95 | } 96 | } 97 | 98 | // Continue the Game, if GameCenter Authentication state 99 | // has been changed (login dialog is closed) 100 | func gameCenterStateChanged() { 101 | self.scene!.gamePaused = false 102 | } 103 | 104 | // Show game center leaderboard 105 | func gameOver() { 106 | 107 | let gcViewController = GKGameCenterViewController() 108 | gcViewController.gameCenterDelegate = self 109 | gcViewController.viewState = GKGameCenterViewControllerState.Leaderboards 110 | gcViewController.leaderboardIdentifier = "MySecondGameLeaderboard" 111 | 112 | // Show leaderboard 113 | if GKLocalPlayer.localPlayer().authenticated == true { 114 | self.presentViewController(gcViewController, animated: true, completion: { 115 | }) 116 | } else { 117 | // Show fullscreen Ad 118 | self.openAds(self) 119 | } 120 | } 121 | 122 | // Continue the game after GameCenter is closed 123 | func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) { 124 | gameCenterViewController.dismissViewControllerAnimated(true, completion: nil) 125 | //scene!.gameOver = false 126 | 127 | // Show fullscreen Ad 128 | openAds(self) 129 | } 130 | 131 | // Show banner, if Ad is successfully loaded. 132 | func bannerViewDidLoadAd(banner: ADBannerView!) { 133 | if(bannerVisible == false) { 134 | 135 | // Add banner Ad to the view 136 | if(iAdBanner.superview == nil) { 137 | self.view.addSubview(iAdBanner) 138 | } 139 | 140 | // Move banner into visible screen frame: 141 | UIView.beginAnimations("iAdBannerShow", context: nil) 142 | banner.frame = CGRectOffset(banner.frame, 0, -banner.frame.size.height) 143 | UIView.commitAnimations() 144 | 145 | bannerVisible = true 146 | } 147 | } 148 | 149 | // Hide banner, if Ad is not loaded. 150 | func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) { 151 | if(bannerVisible == true) { 152 | // Move banner below screen frame: 153 | UIView.beginAnimations("iAdBannerHide", context: nil) 154 | banner.frame = CGRectOffset(banner.frame, 0, banner.frame.size.height) 155 | UIView.commitAnimations() 156 | bannerVisible = false 157 | } 158 | } 159 | 160 | // Open a fullscreen Ad 161 | func openAds(sender: AnyObject) { 162 | // Create an alert 163 | let alert = UIAlertController(title: "", message: "Play again?", preferredStyle: UIAlertControllerStyle.Alert) 164 | 165 | // Play again option 166 | alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default) { _ in 167 | self.scene!.gameOver = false 168 | }) 169 | 170 | // Show fullscreen Ad option 171 | alert.addAction(UIAlertAction(title: "Watch Ad", style: UIAlertActionStyle.Default) { _ in 172 | self.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Manual 173 | self.requestInterstitialAdPresentation() 174 | self.scene!.gameOver = false 175 | }) 176 | 177 | self.presentViewController(alert, animated: true, completion: nil) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /MySecondGame.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2C12B2A21A5F36B400931AEF /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C12B2A11A5F36B400931AEF /* GameKit.framework */; }; 11 | 2C422B161A3F752F00E2DDCD /* Explosion.wav in Resources */ = {isa = PBXBuildFile; fileRef = 2C422B151A3F752F00E2DDCD /* Explosion.wav */; }; 12 | 2C422B5B1A3F892A00E2DDCD /* spark.png in Resources */ = {isa = PBXBuildFile; fileRef = 2C422B591A3F892A00E2DDCD /* spark.png */; }; 13 | 2C422B611A3F90C000E2DDCD /* ExplosionParticle.sks in Resources */ = {isa = PBXBuildFile; fileRef = 2C422B601A3F90C000E2DDCD /* ExplosionParticle.sks */; }; 14 | 2C5516DE1A0974B800F55C08 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 2C5516DD1A0974B800F55C08 /* README.md */; }; 15 | 2C5516E01A0974C900F55C08 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2C5516DF1A0974C900F55C08 /* License.txt */; }; 16 | 2C5516E21A1026A300F55C08 /* EnemySpriteController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5516E11A1026A300F55C08 /* EnemySpriteController.swift */; }; 17 | 2C6E24B11B1E2FE4003C0BB9 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C6E24B01B1E2FE4003C0BB9 /* StoreKit.framework */; }; 18 | 2C9D0FC119F9AC320028E429 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9D0FC019F9AC320028E429 /* AppDelegate.swift */; }; 19 | 2C9D0FC519F9AC320028E429 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9D0FC419F9AC320028E429 /* GameScene.swift */; }; 20 | 2C9D0FC719F9AC320028E429 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9D0FC619F9AC320028E429 /* GameViewController.swift */; }; 21 | 2C9D0FCA19F9AC320028E429 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C9D0FC819F9AC320028E429 /* Main.storyboard */; }; 22 | 2C9D0FCC19F9AC320028E429 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C9D0FCB19F9AC320028E429 /* Images.xcassets */; }; 23 | 2C9D0FCF19F9AC320028E429 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C9D0FCD19F9AC320028E429 /* LaunchScreen.xib */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 2C12B2A11A5F36B400931AEF /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; }; 28 | 2C422B151A3F752F00E2DDCD /* Explosion.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Explosion.wav; sourceTree = ""; }; 29 | 2C422B591A3F892A00E2DDCD /* spark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spark.png; sourceTree = ""; }; 30 | 2C422B601A3F90C000E2DDCD /* ExplosionParticle.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = ExplosionParticle.sks; sourceTree = ""; }; 31 | 2C5516DD1A0974B800F55C08 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = MySecondGame/README.md; sourceTree = ""; }; 32 | 2C5516DF1A0974C900F55C08 /* License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; 33 | 2C5516E11A1026A300F55C08 /* EnemySpriteController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnemySpriteController.swift; sourceTree = ""; }; 34 | 2C6E24B01B1E2FE4003C0BB9 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 35 | 2C9D0FBB19F9AC320028E429 /* MySecondGame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MySecondGame.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 2C9D0FBF19F9AC320028E429 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 2C9D0FC019F9AC320028E429 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | 2C9D0FC419F9AC320028E429 /* GameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; 39 | 2C9D0FC619F9AC320028E429 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; }; 40 | 2C9D0FC919F9AC320028E429 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | 2C9D0FCB19F9AC320028E429 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 42 | 2C9D0FCE19F9AC320028E429 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 2C9D0FB819F9AC320028E429 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 2C12B2A21A5F36B400931AEF /* GameKit.framework in Frameworks */, 51 | 2C6E24B11B1E2FE4003C0BB9 /* StoreKit.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 2C422B171A3F7B3A00E2DDCD /* Resources */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 2C422B151A3F752F00E2DDCD /* Explosion.wav */, 62 | 2C422B601A3F90C000E2DDCD /* ExplosionParticle.sks */, 63 | 2C422B591A3F892A00E2DDCD /* spark.png */, 64 | ); 65 | name = Resources; 66 | sourceTree = ""; 67 | }; 68 | 2C9D0FB219F9AC320028E429 = { 69 | isa = PBXGroup; 70 | children = ( 71 | 2C6E24B01B1E2FE4003C0BB9 /* StoreKit.framework */, 72 | 2C12B2A11A5F36B400931AEF /* GameKit.framework */, 73 | 2C5516DD1A0974B800F55C08 /* README.md */, 74 | 2C9D0FBD19F9AC320028E429 /* MySecondGame */, 75 | 2C9D0FBC19F9AC320028E429 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 2C9D0FBC19F9AC320028E429 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 2C9D0FBB19F9AC320028E429 /* MySecondGame.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 2C9D0FBD19F9AC320028E429 /* MySecondGame */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 2C422B171A3F7B3A00E2DDCD /* Resources */, 91 | 2C9D0FC019F9AC320028E429 /* AppDelegate.swift */, 92 | 2C9D0FC419F9AC320028E429 /* GameScene.swift */, 93 | 2C5516E11A1026A300F55C08 /* EnemySpriteController.swift */, 94 | 2C9D0FC619F9AC320028E429 /* GameViewController.swift */, 95 | 2C9D0FC819F9AC320028E429 /* Main.storyboard */, 96 | 2C9D0FCB19F9AC320028E429 /* Images.xcassets */, 97 | 2C9D0FCD19F9AC320028E429 /* LaunchScreen.xib */, 98 | 2C5516DF1A0974C900F55C08 /* License.txt */, 99 | 2C9D0FBE19F9AC320028E429 /* Supporting Files */, 100 | ); 101 | path = MySecondGame; 102 | sourceTree = ""; 103 | }; 104 | 2C9D0FBE19F9AC320028E429 /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 2C9D0FBF19F9AC320028E429 /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | 2C9D0FBA19F9AC320028E429 /* MySecondGame */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = 2C9D0FDE19F9AC330028E429 /* Build configuration list for PBXNativeTarget "MySecondGame" */; 118 | buildPhases = ( 119 | 2C9D0FB719F9AC320028E429 /* Sources */, 120 | 2C9D0FB819F9AC320028E429 /* Frameworks */, 121 | 2C9D0FB919F9AC320028E429 /* Resources */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = MySecondGame; 128 | productName = MySecondGame; 129 | productReference = 2C9D0FBB19F9AC320028E429 /* MySecondGame.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 2C9D0FB319F9AC320028E429 /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastSwiftMigration = 0710; 139 | LastSwiftUpdateCheck = 0710; 140 | LastUpgradeCheck = 0710; 141 | ORGANIZATIONNAME = Stefan; 142 | TargetAttributes = { 143 | 2C9D0FBA19F9AC320028E429 = { 144 | CreatedOnToolsVersion = 6.1; 145 | DevelopmentTeam = XU67R53NEB; 146 | SystemCapabilities = { 147 | com.apple.GameCenter = { 148 | enabled = 1; 149 | }; 150 | com.apple.InAppPurchase = { 151 | enabled = 1; 152 | }; 153 | }; 154 | }; 155 | }; 156 | }; 157 | buildConfigurationList = 2C9D0FB619F9AC320028E429 /* Build configuration list for PBXProject "MySecondGame" */; 158 | compatibilityVersion = "Xcode 3.2"; 159 | developmentRegion = English; 160 | hasScannedForEncodings = 0; 161 | knownRegions = ( 162 | en, 163 | Base, 164 | ); 165 | mainGroup = 2C9D0FB219F9AC320028E429; 166 | productRefGroup = 2C9D0FBC19F9AC320028E429 /* Products */; 167 | projectDirPath = ""; 168 | projectRoot = ""; 169 | targets = ( 170 | 2C9D0FBA19F9AC320028E429 /* MySecondGame */, 171 | ); 172 | }; 173 | /* End PBXProject section */ 174 | 175 | /* Begin PBXResourcesBuildPhase section */ 176 | 2C9D0FB919F9AC320028E429 /* Resources */ = { 177 | isa = PBXResourcesBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | 2C5516E01A0974C900F55C08 /* License.txt in Resources */, 181 | 2C422B5B1A3F892A00E2DDCD /* spark.png in Resources */, 182 | 2C9D0FCF19F9AC320028E429 /* LaunchScreen.xib in Resources */, 183 | 2C9D0FCC19F9AC320028E429 /* Images.xcassets in Resources */, 184 | 2C422B611A3F90C000E2DDCD /* ExplosionParticle.sks in Resources */, 185 | 2C9D0FCA19F9AC320028E429 /* Main.storyboard in Resources */, 186 | 2C422B161A3F752F00E2DDCD /* Explosion.wav in Resources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXResourcesBuildPhase section */ 191 | 192 | /* Begin PBXSourcesBuildPhase section */ 193 | 2C9D0FB719F9AC320028E429 /* Sources */ = { 194 | isa = PBXSourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 2C5516E21A1026A300F55C08 /* EnemySpriteController.swift in Sources */, 198 | 2C9D0FC519F9AC320028E429 /* GameScene.swift in Sources */, 199 | 2C9D0FC719F9AC320028E429 /* GameViewController.swift in Sources */, 200 | 2C5516DE1A0974B800F55C08 /* README.md in Sources */, 201 | 2C9D0FC119F9AC320028E429 /* AppDelegate.swift in Sources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXSourcesBuildPhase section */ 206 | 207 | /* Begin PBXVariantGroup section */ 208 | 2C9D0FC819F9AC320028E429 /* Main.storyboard */ = { 209 | isa = PBXVariantGroup; 210 | children = ( 211 | 2C9D0FC919F9AC320028E429 /* Base */, 212 | ); 213 | name = Main.storyboard; 214 | sourceTree = ""; 215 | }; 216 | 2C9D0FCD19F9AC320028E429 /* LaunchScreen.xib */ = { 217 | isa = PBXVariantGroup; 218 | children = ( 219 | 2C9D0FCE19F9AC320028E429 /* Base */, 220 | ); 221 | name = LaunchScreen.xib; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXVariantGroup section */ 225 | 226 | /* Begin XCBuildConfiguration section */ 227 | 2C9D0FDC19F9AC330028E429 /* Debug */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 232 | CLANG_CXX_LIBRARY = "libc++"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_CONSTANT_CONVERSION = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_UNREACHABLE_CODE = YES; 243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 244 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 245 | COPY_PHASE_STRIP = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | ENABLE_TESTABILITY = YES; 248 | GCC_C_LANGUAGE_STANDARD = gnu99; 249 | GCC_DYNAMIC_NO_PIC = NO; 250 | GCC_OPTIMIZATION_LEVEL = 0; 251 | GCC_PREPROCESSOR_DEFINITIONS = ( 252 | "DEBUG=1", 253 | "$(inherited)", 254 | ); 255 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 258 | GCC_WARN_UNDECLARED_SELECTOR = YES; 259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 260 | GCC_WARN_UNUSED_FUNCTION = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 263 | MTL_ENABLE_DEBUG_INFO = YES; 264 | ONLY_ACTIVE_ARCH = YES; 265 | SDKROOT = iphoneos; 266 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 267 | TARGETED_DEVICE_FAMILY = "1,2"; 268 | }; 269 | name = Debug; 270 | }; 271 | 2C9D0FDD19F9AC330028E429 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 289 | COPY_PHASE_STRIP = YES; 290 | ENABLE_NS_ASSERTIONS = NO; 291 | ENABLE_STRICT_OBJC_MSGSEND = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu99; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 300 | MTL_ENABLE_DEBUG_INFO = NO; 301 | SDKROOT = iphoneos; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | VALIDATE_PRODUCT = YES; 304 | }; 305 | name = Release; 306 | }; 307 | 2C9D0FDF19F9AC330028E429 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | CODE_SIGN_IDENTITY = "iPhone Developer"; 312 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 313 | INFOPLIST_FILE = MySecondGame/Info.plist; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | PRODUCT_BUNDLE_IDENTIFIER = "Stefan.$(PRODUCT_NAME:rfc1034identifier)"; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | PROVISIONING_PROFILE = ""; 318 | }; 319 | name = Debug; 320 | }; 321 | 2C9D0FE019F9AC330028E429 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | CODE_SIGN_IDENTITY = "iPhone Developer"; 326 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 327 | INFOPLIST_FILE = MySecondGame/Info.plist; 328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 329 | PRODUCT_BUNDLE_IDENTIFIER = "Stefan.$(PRODUCT_NAME:rfc1034identifier)"; 330 | PRODUCT_NAME = "$(TARGET_NAME)"; 331 | PROVISIONING_PROFILE = ""; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | 2C9D0FB619F9AC320028E429 /* Build configuration list for PBXProject "MySecondGame" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 2C9D0FDC19F9AC330028E429 /* Debug */, 342 | 2C9D0FDD19F9AC330028E429 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | 2C9D0FDE19F9AC330028E429 /* Build configuration list for PBXNativeTarget "MySecondGame" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | 2C9D0FDF19F9AC330028E429 /* Debug */, 351 | 2C9D0FE019F9AC330028E429 /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = 2C9D0FB319F9AC320028E429 /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /MySecondGame/GameScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene.swift 3 | // MySecondGame 4 | // 5 | // Created by STEFAN on 23.10.14. 6 | // Copyright (c) 2014 Stefan. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | import GameKit 11 | import StoreKit 12 | 13 | // protocol to inform the delegate (GameViewController) about a game over situation 14 | protocol GameSceneDelegate { 15 | func gameOver() 16 | } 17 | 18 | let collisionBulletCategory: UInt32 = 0x1 << 0 19 | let collisionHeroCategory: UInt32 = 0x1 << 1 20 | 21 | class GameScene: SKScene, SKPhysicsContactDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate { 22 | 23 | let soundAction = SKAction.playSoundFileNamed("Explosion.wav", waitForCompletion: false) 24 | 25 | // Global sprite properties 26 | var heroSprite = SKSpriteNode(imageNamed:"Spaceship") 27 | var invisibleControllerSprite = SKSpriteNode() 28 | var enemySprites = EnemySpriteController() 29 | 30 | // HUD global properties 31 | var lifeNodes : [SKSpriteNode] = [] 32 | var remainingLifes = 3 33 | var scoreNode = SKLabelNode() 34 | var score : Int64 = 0 35 | var gamePaused = false 36 | 37 | // GameCenter 38 | var gameCenterDelegate : GameSceneDelegate? 39 | var gameOver = false 40 | 41 | override func didMoveToView(view: SKView) { 42 | /* Setup your scene here */ 43 | 44 | self.backgroundColor = UIColor.blackColor() 45 | 46 | // Create the hero sprite and place it in the middle of the screen 47 | heroSprite.xScale = 0.10 48 | heroSprite.yScale = 0.10 49 | heroSprite.position = CGPointMake(self.frame.width/2, self.frame.height/2) 50 | 51 | // Add physics body for collision detection 52 | heroSprite.physicsBody?.dynamic = true 53 | heroSprite.physicsBody = SKPhysicsBody(texture: heroSprite.texture!, size: heroSprite.size) 54 | heroSprite.physicsBody?.affectedByGravity = false 55 | heroSprite.physicsBody?.categoryBitMask = collisionHeroCategory 56 | heroSprite.physicsBody?.contactTestBitMask = collisionBulletCategory 57 | heroSprite.physicsBody?.collisionBitMask = 0x0 58 | 59 | 60 | self.addChild(heroSprite) 61 | 62 | // Define invisible sprite for rotating and steering behavior without trigonometry 63 | invisibleControllerSprite.size = CGSizeMake(0, 0) 64 | self.addChild(invisibleControllerSprite) 65 | 66 | // Define Constraint for the orientation behavior 67 | let rangeForOrientation = SKRange(constantValue: CGFloat(M_2_PI*7)) 68 | heroSprite.constraints = [SKConstraint.orientToNode(invisibleControllerSprite, offset: rangeForOrientation)] 69 | 70 | // Add enemy sprites 71 | for(var i=0; i<3;i++){ 72 | self.addChild(enemySprites.spawnEnemy(heroSprite)) 73 | } 74 | 75 | // Add HUD 76 | createHUD() 77 | 78 | // Handle collisions 79 | self.physicsWorld.contactDelegate = self 80 | 81 | // Add Starfield with 3 emitterNodes for a parallax effect 82 | // - Stars in top layer: light, fast, big 83 | // - ... 84 | // - Stars in back layer: dark, slow, small 85 | var emitterNode = starfieldEmitter(SKColor.lightGrayColor(), starSpeedY: 50, starsPerSecond: 1, starScaleFactor: 0.2) 86 | emitterNode.zPosition = -10 87 | self.addChild(emitterNode) 88 | 89 | emitterNode = starfieldEmitter(SKColor.grayColor(), starSpeedY: 30, starsPerSecond: 2, starScaleFactor: 0.1) 90 | emitterNode.zPosition = -11 91 | self.addChild(emitterNode) 92 | 93 | emitterNode = starfieldEmitter(SKColor.darkGrayColor(), starSpeedY: 15, starsPerSecond: 4, starScaleFactor: 0.05) 94 | emitterNode.zPosition = -12 95 | self.addChild(emitterNode) 96 | 97 | // In-App Purchase 98 | initInAppPurchases() 99 | checkAndActivateGreenShip() 100 | 101 | } 102 | 103 | // -------------------------- 104 | // ---- particle effects ---- 105 | // -------------------------- 106 | func starfieldEmitter(color: SKColor, starSpeedY: CGFloat, starsPerSecond: CGFloat, starScaleFactor: CGFloat) -> SKEmitterNode { 107 | 108 | // Determine the time a star is visible on screen 109 | let lifetime = frame.size.height * UIScreen.mainScreen().scale / starSpeedY 110 | 111 | // Create the emitter node 112 | let emitterNode = SKEmitterNode() 113 | emitterNode.particleTexture = SKTexture(imageNamed: "StarParticle") 114 | emitterNode.particleBirthRate = starsPerSecond 115 | emitterNode.particleColor = SKColor.lightGrayColor() 116 | emitterNode.particleSpeed = starSpeedY * -1 117 | emitterNode.particleScale = starScaleFactor 118 | emitterNode.particleColorBlendFactor = 1 119 | emitterNode.particleLifetime = lifetime 120 | 121 | // Position in the middle at top of the screen 122 | emitterNode.position = CGPoint(x: frame.size.width/2, y: frame.size.height) 123 | emitterNode.particlePositionRange = CGVector(dx: frame.size.width, dy: 0) 124 | 125 | // Fast forward the effect to start with a filled screen 126 | emitterNode.advanceSimulationTime(NSTimeInterval(lifetime)) 127 | 128 | return emitterNode 129 | } 130 | 131 | func explosion(pos: CGPoint) { 132 | let emitterNode = SKEmitterNode(fileNamed: "ExplosionParticle.sks") 133 | emitterNode!.particlePosition = pos 134 | self.addChild(emitterNode!) 135 | self.runAction(SKAction.waitForDuration(2), completion: { emitterNode!.removeFromParent() }) 136 | } 137 | 138 | // Handle touch events 139 | override func touchesBegan(touches: Set, withEvent event: UIEvent?) { 140 | /* Called when a touch begins */ 141 | 142 | for touch in (touches ) { 143 | let location = touch.locationInNode(self) 144 | let node = self.nodeAtPoint(location) 145 | if (node.name == "PauseButton") || (node.name == "PauseButtonContainer") { 146 | showPauseAlert() 147 | } else if (node.name == "PurchaseButton") { 148 | inAppPurchase() 149 | } else if (node.name == "InfoButton") { 150 | UIApplication.sharedApplication().openURL(NSURL(string: "http://stefansdevplayground.blogspot.com/p/tutorials.html")!) 151 | } else { 152 | 153 | // Determine the new position for the invisible sprite: 154 | // The calculation is needed to ensure the positions of both sprites 155 | // are nearly the same, but different. Otherwise the hero sprite rotates 156 | // back to it's original orientation after reaching the location of 157 | // the invisible sprite 158 | var xOffset:CGFloat = 1.0 159 | var yOffset:CGFloat = 1.0 160 | if location.x>heroSprite.position.x { 161 | xOffset = -1.0 162 | } 163 | if location.y>heroSprite.position.y { 164 | yOffset = -1.0 165 | } 166 | 167 | // Create an action to move the invisibleControllerSprite. 168 | // This will cause automatic orientation changes for the hero sprite 169 | let actionMoveInvisibleNode = SKAction.moveTo(CGPointMake(location.x - xOffset, location.y - yOffset), duration: 0.2) 170 | invisibleControllerSprite.runAction(actionMoveInvisibleNode) 171 | 172 | // Create an action to move the hero sprite to the touch location 173 | let actionMove = SKAction.moveTo(location, duration: 1) 174 | heroSprite.runAction(actionMove) 175 | } 176 | } 177 | } 178 | 179 | // Show Pause Alert 180 | func showPauseAlert() { 181 | self.gamePaused = true 182 | let alert = UIAlertController(title: "Pause", message: "", preferredStyle: UIAlertControllerStyle.Alert) 183 | alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default) { _ in 184 | self.gamePaused = false 185 | }) 186 | self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil) 187 | } 188 | 189 | func createHUD() { 190 | 191 | // Create a root node with black background to position and group the HUD elemets 192 | // HUD size is relative to the screen resolution to handle iPad and iPhone screens 193 | let hud = SKSpriteNode(color: UIColor.blackColor(), size: CGSizeMake(self.size.width, self.size.height*0.05)) 194 | hud.anchorPoint=CGPointMake(0, 0) 195 | hud.position = CGPointMake(0, self.size.height-hud.size.height) 196 | self.addChild(hud) 197 | 198 | // Display the remaining lifes 199 | // Add icons to display the remaining lifes 200 | // Reuse the Spaceship image: Scale and position releative to the HUD size 201 | let lifeSize = CGSizeMake(hud.size.height-10, hud.size.height-10) 202 | for(var i = 0; i0 { 265 | self.lifeNodes[remainingLifes-1].alpha=0.0 266 | self.remainingLifes--; 267 | } 268 | 269 | // check if remaining lifes exists 270 | if (self.remainingLifes==0) { 271 | showGameOverAlert() 272 | } 273 | 274 | // Stop movement, fade out, move to center, fade in 275 | heroSprite.removeAllActions() 276 | self.heroSprite.runAction(SKAction.fadeOutWithDuration(1) , completion: { 277 | self.heroSprite.position = CGPointMake(self.size.width/2, self.size.height/2) 278 | self.heroSprite.runAction(SKAction.fadeInWithDuration(1), completion: { 279 | self.gamePaused = false 280 | }) 281 | }) 282 | } 283 | 284 | // Game Over 285 | func showGameOverAlert() { 286 | self.gameOver = true 287 | let alert = UIAlertController(title: "Game Over", message: "", preferredStyle: UIAlertControllerStyle.Alert) 288 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { _ in 289 | 290 | // restore lifes in HUD 291 | self.remainingLifes=3 292 | for(var i = 0; i<3; i++) { 293 | self.lifeNodes[i].alpha=1.0 294 | } 295 | // reset score 296 | self.addLeaderboardScore(self.score) 297 | self.score=0 298 | self.scoreNode.text = String(0) 299 | 300 | }) 301 | 302 | // show alert 303 | self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil) 304 | } 305 | 306 | // Game Center integration 307 | func addLeaderboardScore(score: Int64) { 308 | let newGCScore = GKScore(leaderboardIdentifier: "MySecondGameLeaderboard") 309 | newGCScore.value = score 310 | GKScore.reportScores([newGCScore], withCompletionHandler: {(error) -> Void in 311 | if error != nil { 312 | print("Score not submitted") 313 | // Continue 314 | self.gameOver = false 315 | } else { 316 | // Notify the delegate to show the game center leaderboard 317 | self.gameCenterDelegate!.gameOver() 318 | } 319 | }) 320 | } 321 | 322 | // Handle collisions 323 | func didBeginContact(contact: SKPhysicsContact) { 324 | if !self.gamePaused { 325 | lifeLost() 326 | } 327 | } 328 | 329 | // Game Loop 330 | var _dLastShootTime: CFTimeInterval = 1 331 | override func update(currentTime: CFTimeInterval) { 332 | 333 | if !self.gamePaused && !self.gameOver { 334 | 335 | if currentTime - _dLastShootTime >= 1 { 336 | enemySprites.shoot(heroSprite) 337 | _dLastShootTime=currentTime 338 | 339 | // Increase score 340 | self.score++ 341 | self.scoreNode.text = String(score) 342 | } 343 | } 344 | } 345 | 346 | // --------------------------------- 347 | // ---- Handle In-App Purchases ---- 348 | // --------------------------------- 349 | 350 | private var request : SKProductsRequest! 351 | private var products : [SKProduct] = [] // List of available purchases 352 | private var greenShipPurchased = false // Used to enable/disable the 'green ship' feature 353 | 354 | // Open a menu with the available purchases 355 | func inAppPurchase() { 356 | 357 | let alert = UIAlertController(title: "In App Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert) 358 | 359 | self.gamePaused = true 360 | 361 | // Add an alert action for each available product 362 | for (var i = 0; i < products.count; i++) { 363 | 364 | let currentProduct = products[i] 365 | if !(currentProduct.productIdentifier == "MySecondGameGreenShip" && greenShipPurchased) { 366 | 367 | // Get the localized price 368 | let numberFormatter = NSNumberFormatter() 369 | numberFormatter.numberStyle = .CurrencyStyle 370 | numberFormatter.locale = currentProduct.priceLocale 371 | 372 | // Add the alert action 373 | alert.addAction(UIAlertAction(title: currentProduct.localizedTitle + " " + numberFormatter.stringFromNumber(currentProduct.price)!, style: UIAlertActionStyle.Default) { _ in 374 | 375 | // Perform the purchase 376 | self.buyProduct(currentProduct) 377 | self.gamePaused = false 378 | }) 379 | } 380 | } 381 | 382 | // Offer the restore option only if purchase info is not available 383 | if(greenShipPurchased == false) { 384 | alert.addAction(UIAlertAction(title: "Restore", style: UIAlertActionStyle.Default) { _ in 385 | self.restorePurchasedProducts() 386 | self.gamePaused = false 387 | }) 388 | } 389 | 390 | alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in 391 | self.gamePaused = false 392 | }) 393 | 394 | // Show the alert 395 | self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil) 396 | } 397 | 398 | // Initialize the App Purchases 399 | func initInAppPurchases() { 400 | SKPaymentQueue.defaultQueue().addTransactionObserver(self) 401 | 402 | // Get the list of possible purchases 403 | if self.request == nil { 404 | self.request = SKProductsRequest(productIdentifiers: Set(["MySecondGameGreenShip","MySecondGameDonate"])) 405 | self.request.delegate = self 406 | self.request.start() 407 | } 408 | } 409 | 410 | // Request a purchase 411 | func buyProduct(product: SKProduct) { 412 | let payment = SKPayment(product: product) 413 | SKPaymentQueue.defaultQueue().addPayment(payment) 414 | } 415 | 416 | // Restore purchases 417 | func restorePurchasedProducts() { 418 | SKPaymentQueue.defaultQueue().restoreCompletedTransactions() 419 | } 420 | 421 | // StoreKit protocoll method. Called when the AppStore responds 422 | func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) { 423 | self.products = response.products 424 | self.request = nil 425 | } 426 | 427 | // StoreKit protocoll method. Called when an error happens in the communication with the AppStore 428 | func request(request: SKRequest, didFailWithError error: NSError) { 429 | print(error) 430 | self.request = nil 431 | } 432 | 433 | // StoreKit protocoll method. Called after the purchase 434 | func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 435 | 436 | for transaction in transactions { 437 | switch (transaction.transactionState) { 438 | 439 | case .Purchased: 440 | if transaction.payment.productIdentifier == "MySecondGameGreenShip" { 441 | handleGreenShipPurchased() 442 | } 443 | queue.finishTransaction(transaction) 444 | 445 | case .Restored: 446 | if transaction.payment.productIdentifier == "MySecondGameGreenShip" { 447 | handleGreenShipPurchased() 448 | } 449 | queue.finishTransaction(transaction) 450 | 451 | case .Failed: 452 | print("Payment Error: %@", transaction.error) 453 | queue.finishTransaction(transaction) 454 | default: 455 | print("Transaction State: %@", transaction.transactionState) 456 | } 457 | } 458 | } 459 | 460 | // Called after the purchase to provide the 'green ship' feature 461 | func handleGreenShipPurchased() { 462 | greenShipPurchased = true 463 | checkAndActivateGreenShip() 464 | // persist the purchase locally 465 | NSUserDefaults.standardUserDefaults().setBool(true, forKey: "MySecondGameGreenShip") 466 | } 467 | 468 | // Called after applicattion start to check if the 'green ship' feature was purchased 469 | func checkAndActivateGreenShip() { 470 | if NSUserDefaults.standardUserDefaults().boolForKey("MySecondGameGreenShip") { 471 | greenShipPurchased = true 472 | heroSprite.color = UIColor.greenColor() 473 | heroSprite.colorBlendFactor=0.8 474 | } 475 | } 476 | 477 | 478 | 479 | 480 | } 481 | --------------------------------------------------------------------------------