├── 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 |
--------------------------------------------------------------------------------