├── .gitignore
├── Base.lproj
└── LaunchScreen.storyboard
├── README.md
├── art.scnassets
├── fire.scnp
├── grass.png
├── ringTexture.png
├── ship.scn
├── skybox.png
├── smoke.png
├── smoke.scnp
├── spark.png
└── texture.png
├── assets.xcassets
└── AppIcon.appiconset
│ ├── AppIcon-29@2x.png
│ ├── AppIcon-29@3x.png
│ ├── AppIcon-40@2x.png
│ ├── AppIcon-40@3x.png
│ ├── AppIcon-60@2x.png
│ ├── AppIcon-60@3x.png
│ └── Contents.json
├── partI
├── GameLevel.swift
├── GameViewController.swift
├── Info.plist
└── Player.swift
├── partII
├── GameLevel.swift
├── GameViewController.swift
├── Info.plist
└── Player.swift
├── partIII
├── GameLevel.swift
├── GameSettings.swift
├── GameViewController.swift
├── HUD.swift
├── Info.plist
├── Player.swift
└── Ring.swift
├── partIV
├── GameLevel.swift
├── GameObject.swift
├── GameSettings.swift
├── GameViewController.swift
├── HUD.swift
├── Handicap.swift
├── Info.plist
├── Player.swift
└── Ring.swift
├── partV
├── GameLevel.swift
├── GameObject.swift
├── GameSettings.swift
├── GameViewController.swift
├── HUD.swift
├── Handicap.swift
├── Info.plist
├── Player.swift
└── Ring.swift
├── partVI
├── Bullet.swift
├── Enemy.swift
├── GameLevel.swift
├── GameObject.swift
├── GameSettings.swift
├── GameSound.swift
├── GameViewController.swift
├── HUD.swift
├── Handicap.swift
├── Info.plist
├── Plane.swift
├── Player.swift
└── Ring.swift
├── screenshots
├── screenshot0.png
├── screenshot1.png
├── screenshot2.png
├── screenshot3.png
├── screenshot4.png
├── screenshot5.png
└── screenshot6.png
├── shared
├── AppDelegate.swift
├── RB+CGPoint.swift
├── RB+SCNVector.swift
├── RB+SKAction.swift
├── RB+UIColor.swift
├── RBLog.swift
├── RBPerlinNoiseGenerator.swift
├── RBRandom.swift
├── RBTerrain.swift
└── RBUtility.swift
├── sounds
├── bonus.wav
├── explosion.wav
└── fire.wav
└── tutorial.xcodeproj
├── project.pbxproj
└── project.xcworkspace
└── contents.xcworkspacedata
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/swift,macos
3 |
4 | ### macOS ###
5 | *.DS_Store
6 | .AppleDouble
7 | .LSOverride
8 |
9 | # Icon must end with two \r
10 | Icon
11 |
12 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear in the root of a volume
16 | .DocumentRevisions-V100
17 | .fseventsd
18 | .Spotlight-V100
19 | .TemporaryItems
20 | .Trashes
21 | .VolumeIcon.icns
22 | .com.apple.timemachine.donotpresent
23 |
24 | # Directories potentially created on remote AFP share
25 | .AppleDB
26 | .AppleDesktop
27 | Network Trash Folder
28 | Temporary Items
29 | .apdisk
30 |
31 | ### Swift ###
32 | # Xcode
33 | #
34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
35 |
36 | ## Build generated
37 | build/
38 | DerivedData/
39 |
40 | ## Various settings
41 | *.pbxuser
42 | !default.pbxuser
43 | *.mode1v3
44 | !default.mode1v3
45 | *.mode2v3
46 | !default.mode2v3
47 | *.perspectivev3
48 | !default.perspectivev3
49 | xcuserdata/
50 |
51 | ## Other
52 | *.moved-aside
53 | *.xccheckout
54 | *.xcscmblueprint
55 |
56 | ## Obj-C/Swift specific
57 | *.hmap
58 | *.ipa
59 | *.dSYM.zip
60 | *.dSYM
61 |
62 | ## Playgrounds
63 | timeline.xctimeline
64 | playground.xcworkspace
65 |
66 | # Swift Package Manager
67 | #
68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
69 | # Packages/
70 | # Package.pins
71 | .build/
72 |
73 | # CocoaPods - Refactored to standalone file
74 |
75 | # Carthage - Refactored to standalone file
76 |
77 | # fastlane
78 | #
79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
80 | # screenshots whenever they are needed.
81 | # For more information about the recommended setup visit:
82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
83 |
84 | fastlane/report.xml
85 | fastlane/Preview.html
86 | fastlane/screenshots
87 | fastlane/test_output
88 |
89 | # End of https://www.gitignore.io/api/swift,macos
90 |
--------------------------------------------------------------------------------
/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Update February 2024**:: I wrote the first part of this tutorial almost eight years ago.
2 | SceneKit is available on Vision OS but is no longer used for immersive 3D rendering.
3 | Instead, you have to use RealityKit. But since this information is still very useful,
4 | I have created a little [E-Book](https://visionos.substack.com) that contains the entire SceneKit Tutorial.
5 |
6 |
7 | ### The Tutorial
8 |
9 | The code is part of my SceneKit Tutorial Serie: From Zero to Hero.
10 |
11 | 
12 |
13 |
14 | ### Tutorial
15 | - Chapter 1 - Building a Terrain
16 | - Chapter 2 - Create a real player game object
17 | - Chapter 3 - Add life to your terrain
18 | - Chapter 4 - Implement a Game Loop
19 | - Chapter 5 - Fly smoothly with CoreMotion
20 | - Chapter 6 - Finish the game (The missing parts)
21 |
22 | *Remarks*:
23 | - *Code is now updated to be Swift 4 compliant*
24 | - *It's imortant to know that the code is not intented to learn Swift, but to understand learning game development concepts*
25 | - *Therefore some things (like unwrapping optionals) are just here to make things simpler*
26 |
27 |
28 | ### The Game
29 | This tutorial is a companion work I do parallel on the development of my next upcoming 3D Game which uses just SceneKit and it's written entirely with Swift 4.
30 |
31 |
32 | ### For who is this Tutorial?
33 | It's not for the absolute beginner or novice, because I assume things like Swift notation, trigonometric know-how etc.
34 | On the other hand you don't have to learn all at the begin. Like the terrain class **RBTerrain**.
35 | You can use it just like it is and read the code later again when you have more know-how.
36 | The most important is that you understand the principles.
37 |
38 |
39 | ### Introduction
40 | It's important for me to start directly with a good and solid base which can be used for real games later.
41 | So please don't create a project using the Xcode Project wizard but start directly with the Source on GitHub.
42 | Also I don't want to build a step by step tutorial which shows each line, but always cover a chapter of the Game and introduce
43 | a new component and show the how and why :)
44 |
45 |
46 | ### Screenshots
47 |
48 | Tutorial Part 5
49 | 
50 |
51 | Tutorial Part 4
52 | 
53 |
54 | Tutorial Part 3
55 | 
56 |
57 | Tutorial Part 2
58 | 
59 |
60 | Tutorial Part 1
61 | 
62 |
63 | Follow me on [Substack](https://visionos.substack.com) to get the next tutorial:
64 | [Vision OS: Create an Immersive App with RealityKit](https://visionos.substack.com)
65 |
66 |
67 | **Important**:
68 | *Feel free to use this code in every way you want, but please consider also
69 | to give something back to the community.*
70 |
71 | *I don't own the license rights for the assets used in this tutorials,
72 | so before you use them for something else then self-learning, please check by yourself the license behind
73 | or even better replace it with your own art. Thank you!*
74 |
--------------------------------------------------------------------------------
/art.scnassets/fire.scnp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/fire.scnp
--------------------------------------------------------------------------------
/art.scnassets/grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/grass.png
--------------------------------------------------------------------------------
/art.scnassets/ringTexture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/ringTexture.png
--------------------------------------------------------------------------------
/art.scnassets/ship.scn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/ship.scn
--------------------------------------------------------------------------------
/art.scnassets/skybox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/skybox.png
--------------------------------------------------------------------------------
/art.scnassets/smoke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/smoke.png
--------------------------------------------------------------------------------
/art.scnassets/smoke.scnp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/smoke.scnp
--------------------------------------------------------------------------------
/art.scnassets/spark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/spark.png
--------------------------------------------------------------------------------
/art.scnassets/texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/art.scnassets/texture.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-60@2x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/assets.xcassets/AppIcon.appiconset/AppIcon-60@3x.png
--------------------------------------------------------------------------------
/assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "size" : "29x29",
15 | "idiom" : "iphone",
16 | "filename" : "AppIcon-29@2x.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "size" : "29x29",
21 | "idiom" : "iphone",
22 | "filename" : "AppIcon-29@3x.png",
23 | "scale" : "3x"
24 | },
25 | {
26 | "size" : "40x40",
27 | "idiom" : "iphone",
28 | "filename" : "AppIcon-40@2x.png",
29 | "scale" : "2x"
30 | },
31 | {
32 | "size" : "40x40",
33 | "idiom" : "iphone",
34 | "filename" : "AppIcon-40@3x.png",
35 | "scale" : "3x"
36 | },
37 | {
38 | "size" : "60x60",
39 | "idiom" : "iphone",
40 | "filename" : "AppIcon-60@2x.png",
41 | "scale" : "2x"
42 | },
43 | {
44 | "size" : "60x60",
45 | "idiom" : "iphone",
46 | "filename" : "AppIcon-60@3x.png",
47 | "scale" : "3x"
48 | },
49 | {
50 | "idiom" : "ios-marketing",
51 | "size" : "1024x1024",
52 | "scale" : "1x"
53 | }
54 | ],
55 | "info" : {
56 | "version" : 1,
57 | "author" : "xcode"
58 | }
59 | }
--------------------------------------------------------------------------------
/partI/GameLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLevel.swift
3 | //
4 | // Part 1 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | // -----------------------------------------------------------------------------
15 |
16 | class GameLevel: SCNScene, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {
17 | private let levelWidth = 320
18 | private let levelLength = 320
19 |
20 | private var _terrain: RBTerrain?
21 | private var _player: Player?
22 |
23 | // -------------------------------------------------------------------------
24 | // MARK: - Place objects
25 |
26 | private func addPlayer() {
27 | _player = Player()
28 | _player!.position = SCNVector3(160, 3, 0)
29 | self.rootNode.addChildNode(_player!)
30 |
31 | let moveAction = SCNAction.moveBy(x: 0, y: 0, z: 200, duration: 20)
32 | _player!.runAction(moveAction)
33 | }
34 |
35 | // -------------------------------------------------------------------------
36 |
37 | private func addTerrain() {
38 | // Create terrain
39 | _terrain = RBTerrain(width: levelWidth, length: levelLength, scale: 128)
40 |
41 | let generator = RBPerlinNoiseGenerator(seed: nil)
42 | _terrain?.formula = {(x: Int32, y: Int32) in
43 | return generator.valueFor(x: x, y: y)
44 | }
45 |
46 | _terrain!.create(withColor: UIColor.green)
47 | _terrain!.position = SCNVector3Make(0, 0, 0)
48 | self.rootNode.addChildNode(_terrain!)
49 | }
50 |
51 | // -------------------------------------------------------------------------
52 | // MARK: - Initialisation
53 |
54 | func create() {
55 | addTerrain()
56 | addPlayer()
57 | }
58 |
59 | // -------------------------------------------------------------------------
60 |
61 | override init() {
62 | super.init()
63 | }
64 |
65 | // -------------------------------------------------------------------------
66 |
67 | required init(coder: NSCoder) {
68 | fatalError("Not yet implemented")
69 | }
70 |
71 | // -------------------------------------------------------------------------
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/partI/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 1 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | class GameViewController: UIViewController {
15 |
16 | private var _sceneView: SCNView!
17 | private var _level: GameLevel!
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - ViewController life cycle
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | _level = GameLevel()
26 | _level.create()
27 |
28 | _sceneView = SCNView()
29 | _sceneView.scene = _level
30 | _sceneView.allowsCameraControl = false
31 | _sceneView.showsStatistics = true
32 | _sceneView.backgroundColor = UIColor.black
33 | _sceneView!.debugOptions = .showWireframe
34 | self.view = _sceneView
35 | }
36 |
37 | // -------------------------------------------------------------------------
38 |
39 | override func didReceiveMemoryWarning() {
40 | super.didReceiveMemoryWarning()
41 | }
42 |
43 | // -------------------------------------------------------------------------
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/partI/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Game!
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UIStatusBarHidden
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationLandscapeLeft
34 | UIInterfaceOrientationLandscapeRight
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 |
39 |
40 |
--------------------------------------------------------------------------------
/partI/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 1 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Player : SCNNode {
16 | private let lookAtForwardPosition = SCNVector3Make(0.0, 0.0, 1.0)
17 | private let cameraFowardPosition = SCNVector3(x: 0.8, y: 1, z: -0.5)
18 |
19 | private var _lookAtNode: SCNNode?
20 | private var _cameraNode: SCNNode?
21 | private var _playerNode: SCNNode?
22 |
23 | // -------------------------------------------------------------------------
24 | // MARK: - Initialisation
25 |
26 | override init() {
27 | super.init()
28 |
29 | // Create player node
30 | let cubeGeometry = SCNBox(width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0.0)
31 | _playerNode = SCNNode(geometry: cubeGeometry)
32 | _playerNode?.isHidden = true
33 | addChildNode(_playerNode!)
34 |
35 | let colorMaterial = SCNMaterial()
36 | cubeGeometry.materials = [colorMaterial]
37 |
38 | // Look at Node
39 | _lookAtNode = SCNNode()
40 | _lookAtNode!.position = lookAtForwardPosition
41 | addChildNode(_lookAtNode!)
42 |
43 | // Camera Node
44 | _cameraNode = SCNNode()
45 | _cameraNode!.camera = SCNCamera()
46 | _cameraNode!.position = cameraFowardPosition
47 | _cameraNode!.camera!.zNear = 0.1
48 | _cameraNode!.camera!.zFar = 200
49 | self.addChildNode(_cameraNode!)
50 |
51 | // Link them
52 | let constraint1 = SCNLookAtConstraint(target: _lookAtNode)
53 | constraint1.isGimbalLockEnabled = true
54 | _cameraNode!.constraints = [constraint1]
55 |
56 | // Create a spotlight at the player
57 | let spotLight = SCNLight()
58 | spotLight.type = SCNLight.LightType.spot
59 | spotLight.spotInnerAngle = 40.0
60 | spotLight.spotOuterAngle = 80.0
61 | spotLight.castsShadow = true
62 | spotLight.color = UIColor.white
63 | let spotLightNode = SCNNode()
64 | spotLightNode.light = spotLight
65 | spotLightNode.position = SCNVector3(x: 1.0, y: 5.0, z: -2.0)
66 | self.addChildNode(spotLightNode)
67 |
68 | // Linnk it
69 | let constraint2 = SCNLookAtConstraint(target: self)
70 | constraint2.isGimbalLockEnabled = true
71 | spotLightNode.constraints = [constraint2]
72 |
73 | // Create additional omni light
74 | let lightNode = SCNNode()
75 | lightNode.light = SCNLight()
76 | lightNode.light!.type = SCNLight.LightType.omni
77 | lightNode.light!.color = UIColor.darkGray
78 | lightNode.position = SCNVector3(x: 0, y: 10.00, z: -2)
79 | self.addChildNode(lightNode)
80 | }
81 |
82 | // -------------------------------------------------------------------------
83 |
84 | required init(coder: NSCoder) {
85 | fatalError("Not yet implemented")
86 | }
87 |
88 | // -------------------------------------------------------------------------
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/partII/GameLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLevel.swift
3 | //
4 | // Part 2 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | // -----------------------------------------------------------------------------
15 |
16 | class GameLevel: SCNScene, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {
17 | private let levelWidth = 320
18 | private let levelLength = 320
19 |
20 | private var _terrain: RBTerrain?
21 | private var _player: Player?
22 |
23 | // -------------------------------------------------------------------------
24 | // MARK: - Input handling
25 |
26 | func swipeLeft() {
27 | _player!.moveLeft()
28 | }
29 |
30 | // -------------------------------------------------------------------------
31 |
32 | func swipeRight() {
33 | _player!.moveRight()
34 | }
35 |
36 | // -------------------------------------------------------------------------
37 | // MARK: - Place objects
38 |
39 | private func addPlayer() {
40 | _player = Player()
41 | _player!.position = SCNVector3(160, 3, 0)
42 | self.rootNode.addChildNode(_player!)
43 |
44 | let moveAction = SCNAction.moveBy(x: 0, y: 0, z: 200, duration: 20)
45 | _player!.runAction(moveAction)
46 | }
47 |
48 | // -------------------------------------------------------------------------
49 |
50 | private func addTerrain() {
51 | // Create terrain
52 | _terrain = RBTerrain(width: levelWidth, length: levelLength, scale: 128)
53 |
54 | let generator = RBPerlinNoiseGenerator(seed: nil)
55 | _terrain?.formula = {(x: Int32, y: Int32) in
56 | return generator.valueFor(x: x, y: y)
57 | }
58 |
59 | _terrain!.create(withColor: UIColor.green)
60 | _terrain!.position = SCNVector3Make(0, 0, 0)
61 | self.rootNode.addChildNode(_terrain!)
62 | }
63 |
64 | // -------------------------------------------------------------------------
65 | // MARK: - Initialisation
66 |
67 | func create() {
68 | addTerrain()
69 | addPlayer()
70 | }
71 |
72 | // -------------------------------------------------------------------------
73 |
74 | override init() {
75 | super.init()
76 | }
77 |
78 | // -------------------------------------------------------------------------
79 |
80 | required init(coder: NSCoder) {
81 | fatalError("Not yet implemented")
82 | }
83 |
84 | // -------------------------------------------------------------------------
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/partII/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 2 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | class GameViewController: UIViewController {
15 |
16 | private var _sceneView: SCNView!
17 | private var _level: GameLevel!
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - Swipe gestures
21 |
22 | @objc private func handleSwipe(_ gestureRecognize: UISwipeGestureRecognizer) {
23 | if (gestureRecognize.direction == .left) {
24 | _level!.swipeLeft()
25 | }
26 | else if (gestureRecognize.direction == .right) {
27 | _level!.swipeRight()
28 | }
29 | }
30 |
31 | // -------------------------------------------------------------------------
32 | // MARK: - ViewController life cycle
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | _level = GameLevel()
38 | _level.create()
39 |
40 | _sceneView = SCNView()
41 | _sceneView.scene = _level
42 | _sceneView.allowsCameraControl = false
43 | _sceneView.showsStatistics = true
44 | _sceneView.backgroundColor = UIColor.black
45 | self.view = _sceneView
46 |
47 | let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
48 | swipeLeftGesture.direction = .left
49 | _sceneView!.addGestureRecognizer(swipeLeftGesture)
50 |
51 | let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
52 | swipeRightGesture.direction = .right
53 | _sceneView!.addGestureRecognizer(swipeRightGesture)
54 | }
55 |
56 | // -------------------------------------------------------------------------
57 |
58 | override func didReceiveMemoryWarning() {
59 | super.didReceiveMemoryWarning()
60 | }
61 |
62 | // -------------------------------------------------------------------------
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/partII/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIRequiredDeviceCapabilities
24 |
25 | armv7
26 |
27 | UIStatusBarHidden
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationLandscapeLeft
32 | UIInterfaceOrientationLandscapeRight
33 |
34 | UILaunchStoryboardName
35 | LaunchScreen
36 |
37 |
38 |
--------------------------------------------------------------------------------
/partII/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 2 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Player : SCNNode {
16 | private let lookAtForwardPosition = SCNVector3Make(0.0, -1.0, 6.0)
17 | private let cameraFowardPosition = SCNVector3(x: 5, y: 1.0, z: -5)
18 |
19 | private var _lookAtNode: SCNNode?
20 | private var _cameraNode: SCNNode?
21 | private var _playerNode: SCNNode?
22 |
23 |
24 | // -------------------------------------------------------------------------
25 | // MARK: - Camera adjustment
26 |
27 | private func toggleCamera() {
28 | var position = _cameraNode!.position
29 |
30 | if position.x < 0 {
31 | position.x = 5.0
32 | }
33 | else {
34 | position.x = -5.0
35 | }
36 |
37 | SCNTransaction.begin()
38 | SCNTransaction.animationDuration = 1.0
39 |
40 | _cameraNode?.position = position
41 |
42 | SCNTransaction.commit()
43 | }
44 |
45 | // -------------------------------------------------------------------------
46 | // MARK: - Plane movements
47 |
48 | func moveLeft() {
49 | let moveAction = SCNAction.moveBy(x: 2.0, y: 0.0, z: 0, duration: 0.5)
50 | self.runAction(moveAction, forKey: "moveLeftRight")
51 |
52 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
53 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
54 |
55 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
56 |
57 | toggleCamera()
58 | }
59 |
60 | // -------------------------------------------------------------------------
61 |
62 | func moveRight() {
63 | let moveAction = SCNAction.moveBy(x: -2.0, y: 0.0, z: 0, duration: 0.5)
64 | self.runAction(moveAction, forKey: "moveLeftRight")
65 |
66 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
67 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
68 |
69 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
70 |
71 | toggleCamera()
72 | }
73 |
74 | // -------------------------------------------------------------------------
75 | // MARK: - Initialisation
76 |
77 | override init() {
78 | super.init()
79 |
80 | // Create player node
81 | let scene = SCNScene(named: "art.scnassets/ship.scn")
82 | if (scene == nil) {
83 | fatalError("Scene not loaded")
84 | }
85 |
86 | _playerNode = scene!.rootNode.childNode(withName: "ship", recursively: true)
87 | if (_playerNode == nil) {
88 | fatalError("Ship node not found")
89 | }
90 |
91 | _playerNode!.scale = SCNVector3(x: 0.25, y: 0.25, z: 0.25)
92 | self.addChildNode(_playerNode!)
93 |
94 | // Look at Node
95 | _lookAtNode = SCNNode()
96 | _lookAtNode!.position = lookAtForwardPosition
97 | addChildNode(_lookAtNode!)
98 |
99 | // Camera Node
100 | _cameraNode = SCNNode()
101 | _cameraNode!.camera = SCNCamera()
102 | _cameraNode!.position = cameraFowardPosition
103 | _cameraNode!.camera!.zNear = 0.1
104 | _cameraNode!.camera!.zFar = 200
105 | self.addChildNode(_cameraNode!)
106 |
107 | // Link them
108 | let constraint1 = SCNLookAtConstraint(target: _lookAtNode)
109 | constraint1.isGimbalLockEnabled = true
110 | _cameraNode!.constraints = [constraint1]
111 |
112 | // Create a spotlight at the player
113 | let spotLight = SCNLight()
114 | spotLight.type = SCNLight.LightType.spot
115 | spotLight.spotInnerAngle = 40.0
116 | spotLight.spotOuterAngle = 80.0
117 | spotLight.castsShadow = true
118 | spotLight.color = UIColor.white
119 | let spotLightNode = SCNNode()
120 | spotLightNode.light = spotLight
121 | spotLightNode.position = SCNVector3(x: 1.0, y: 5.0, z: -2.0)
122 | self.addChildNode(spotLightNode)
123 |
124 | // Linnk it
125 | let constraint2 = SCNLookAtConstraint(target: self)
126 | constraint2.isGimbalLockEnabled = true
127 | spotLightNode.constraints = [constraint2]
128 |
129 | // Create additional omni light
130 | let lightNode = SCNNode()
131 | lightNode.light = SCNLight()
132 | lightNode.light!.type = SCNLight.LightType.omni
133 | lightNode.light!.color = UIColor.darkGray
134 | lightNode.position = SCNVector3(x: 0, y: 10.00, z: -2)
135 | self.addChildNode(lightNode)
136 | }
137 |
138 | // -------------------------------------------------------------------------
139 |
140 | required init(coder: NSCoder) {
141 | fatalError("Not yet implemented")
142 | }
143 |
144 | // -------------------------------------------------------------------------
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/partIII/GameLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLevel.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | // -----------------------------------------------------------------------------
15 |
16 | class GameLevel: SCNScene, SCNSceneRendererDelegate, SCNPhysicsContactDelegate {
17 | private let levelWidth = 320
18 | private let levelLength = 640
19 |
20 | private var _terrain: RBTerrain?
21 | private var _player: Player?
22 |
23 | // Part 3: Number od rings and touched rings saved here
24 | private let numberOfRings = 10
25 | private var touchedRings = 0
26 |
27 | // Part 3: Reference to the HUD
28 | private var _hud: HUD?
29 |
30 | // -------------------------------------------------------------------------
31 | // MARK: - Properties
32 |
33 | var hud: HUD? {
34 | get {
35 | return _hud
36 | }
37 | set(value) {
38 | _hud = value
39 | }
40 | }
41 |
42 | // -------------------------------------------------------------------------
43 | // MARK: - Input handling
44 |
45 | func swipeLeft() {
46 | _player!.moveLeft()
47 | }
48 |
49 | // -------------------------------------------------------------------------
50 |
51 | func swipeRight() {
52 | _player!.moveRight()
53 | }
54 |
55 | // -------------------------------------------------------------------------
56 | // MARK: - Physics delegate
57 |
58 | func collision(withRing ring: Ring) {
59 | // Part 3: Collision handling based on physics
60 | if ring.isHidden {
61 | return
62 | }
63 |
64 | debugPrint("Collision width \(ring)")
65 |
66 | ring.isHidden = true
67 | _player!.roll()
68 |
69 | touchedRings += 1
70 |
71 | _hud?.points = touchedRings
72 | }
73 |
74 | // -------------------------------------------------------------------------
75 |
76 | func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
77 | // Part 3: Physics delegate get called when objects collide
78 | if let ring = contact.nodeB.parent as? Ring {
79 | collision(withRing: ring)
80 | }
81 | }
82 |
83 | // -------------------------------------------------------------------------
84 | // MARK: - Place objects
85 |
86 | private func addRings() {
87 | // Part 3: Add rings to the game level
88 | let space = levelLength / (numberOfRings+1)
89 |
90 | for i in 1...numberOfRings {
91 | let ring = Ring()
92 |
93 | var x: CGFloat = 160
94 | let rnd = RBRandom.integer(1, 3)
95 | if rnd == 1 {
96 | x = x - Player.moveOffset
97 | }
98 | else if rnd == 3 {
99 | x = x + Player.moveOffset
100 | }
101 |
102 | ring.position = SCNVector3(Int(x), 3, (i*space))
103 | self.rootNode.addChildNode(ring)
104 | }
105 | }
106 |
107 | // -------------------------------------------------------------------------
108 |
109 | private func addPlayer() {
110 | _player = Player()
111 | _player!.position = SCNVector3(160, 4, 0)
112 | self.rootNode.addChildNode(_player!)
113 |
114 | let moveAction = SCNAction.moveBy(x: 0, y: 0, z: CGFloat(levelLength)-10, duration: 60)
115 | _player!.runAction(moveAction)
116 | }
117 |
118 | // -------------------------------------------------------------------------
119 |
120 | private func addTerrain() {
121 | // Create terrain
122 | _terrain = RBTerrain(width: levelWidth, length: levelLength, scale: 128)
123 |
124 | let generator = RBPerlinNoiseGenerator(seed: nil)
125 | _terrain?.formula = {(x: Int32, y: Int32) in
126 | return generator.valueFor(x: x, y: y)
127 | }
128 |
129 | _terrain!.create(withColor: UIColor.green)
130 | _terrain!.position = SCNVector3Make(0, 0, 0)
131 | self.rootNode.addChildNode(_terrain!)
132 | }
133 |
134 | // -------------------------------------------------------------------------
135 | // MARK: - Initialisation
136 |
137 | func create() {
138 | addTerrain()
139 | addPlayer()
140 | addRings()
141 | }
142 |
143 | // -------------------------------------------------------------------------
144 |
145 | override init() {
146 | super.init()
147 |
148 | self.physicsWorld.contactDelegate = self
149 | }
150 |
151 | // -------------------------------------------------------------------------
152 |
153 | required init(coder: NSCoder) {
154 | fatalError("Not yet implemented")
155 | }
156 |
157 | // -------------------------------------------------------------------------
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/partIII/GameSettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSettings.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // Structs are a very good way to structure the different application settings.
11 | // Like that they are easy to read and changeable at one place
12 | //
13 |
14 | import Foundation
15 |
16 | struct Game {
17 |
18 | struct Physics {
19 |
20 | // Category bits used for physics handling
21 | struct Categories {
22 | static let player: Int = 0b00000001
23 | static let ring: Int = 0b00000010
24 | }
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/partIII/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | class GameViewController: UIViewController {
15 |
16 | private var _sceneView: SCNView!
17 | private var _level: GameLevel!
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - Swipe gestures
21 |
22 | @objc private func handleSwipe(_ gestureRecognize: UISwipeGestureRecognizer) {
23 | if (gestureRecognize.direction == .left) {
24 | _level!.swipeLeft()
25 | }
26 | else if (gestureRecognize.direction == .right) {
27 | _level!.swipeRight()
28 | }
29 | }
30 |
31 | // -------------------------------------------------------------------------
32 | // MARK: - ViewController life cycle
33 |
34 | override func viewWillAppear(_ animated: Bool) {
35 | super.viewWillAppear(animated)
36 |
37 | // Part 3: HUD is created and assigned to view and game level
38 | let hud = HUD(size: self.view.bounds.size)
39 | _level.hud = hud
40 | _sceneView.overlaySKScene = hud.scene
41 | }
42 |
43 | // -------------------------------------------------------------------------
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 |
48 | _level = GameLevel()
49 | _level.create()
50 |
51 | _sceneView = SCNView()
52 | _sceneView.scene = _level
53 | _sceneView.allowsCameraControl = false
54 | _sceneView.showsStatistics = true
55 | _sceneView.backgroundColor = UIColor.black
56 | self.view = _sceneView
57 |
58 | let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
59 | swipeLeftGesture.direction = .left
60 | _sceneView!.addGestureRecognizer(swipeLeftGesture)
61 |
62 | let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
63 | swipeRightGesture.direction = .right
64 | _sceneView!.addGestureRecognizer(swipeRightGesture)
65 | }
66 |
67 | // -------------------------------------------------------------------------
68 |
69 | override func didReceiveMemoryWarning() {
70 | super.didReceiveMemoryWarning()
71 | }
72 |
73 | // -------------------------------------------------------------------------
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/partIII/HUD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HUD.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 11.11.17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 | // This class I have newly introduced in Part 3. It's purpose
11 | // is to show information, like points, time etc. to the user.
12 | // At the moment the number of catched rings.
13 | //
14 |
15 | import SpriteKit
16 |
17 | class HUD {
18 | private var _scene: SKScene!
19 | private let _points = SKLabelNode(text: "0 RINGS")
20 |
21 | // -------------------------------------------------------------------------
22 | // MARK: - Properties
23 |
24 | var scene: SKScene {
25 | get {
26 | return _scene
27 | }
28 | }
29 |
30 | // -------------------------------------------------------------------------
31 |
32 | var points: Int {
33 | get {
34 | return 0
35 | }
36 | set(value) {
37 | _points.text = String(format: "%d RINGS", value)
38 | }
39 | }
40 |
41 | // -------------------------------------------------------------------------
42 | // MARK: - Initialisation
43 |
44 | init(size: CGSize) {
45 | _scene = SKScene(size: size)
46 |
47 | _points.position = CGPoint(x: size.width/2, y: size.height-50)
48 | _points.horizontalAlignmentMode = .center
49 | _points.fontName = "MarkerFelt-Wide"
50 | _points.fontSize = 30
51 | _points.fontColor = UIColor.white
52 | _scene.addChild(_points)
53 | }
54 |
55 | // -------------------------------------------------------------------------
56 |
57 | required init?(coder aDecoder: NSCoder) {
58 | fatalError("init?(coder aDecoder: NSCoder) not implemented")
59 | }
60 |
61 | // -------------------------------------------------------------------------
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/partIII/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UIStatusBarHidden
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationLandscapeLeft
34 | UIInterfaceOrientationLandscapeRight
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/partIII/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Player : SCNNode {
16 | public static let moveOffset: CGFloat = 10
17 |
18 | private let lookAtForwardPosition = SCNVector3Make(0.0, -1.0, 6.0)
19 | private let cameraFowardPosition = SCNVector3(x: 0, y: 1.0, z: -5)
20 |
21 | private var _lookAtNode: SCNNode?
22 | private var _cameraNode: SCNNode?
23 | private var _playerNode: SCNNode?
24 |
25 | // -------------------------------------------------------------------------
26 | // MARK: - Effects
27 |
28 | func roll() {
29 | // Part 3: An easy effect we use, whenever we fly trough a ring
30 | let rotateAction = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 360.0), duration: 0.5)
31 | _playerNode!.runAction(rotateAction)
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 | // MARK: - Camera adjustment
36 |
37 | private func toggleCamera() {
38 | var position = _cameraNode!.position
39 |
40 | if position.x < 0 {
41 | position.x = 0.5
42 | }
43 | else {
44 | position.x = -0.5
45 | }
46 |
47 | SCNTransaction.begin()
48 | SCNTransaction.animationDuration = 1.0
49 |
50 | _cameraNode?.position = position
51 |
52 | SCNTransaction.commit()
53 | }
54 |
55 | // -------------------------------------------------------------------------
56 | // MARK: - Plane movements
57 |
58 | func moveLeft() {
59 | let moveAction = SCNAction.moveBy(x: Player.moveOffset, y: 0.0, z: 0, duration: 0.5)
60 | self.runAction(moveAction, forKey: "moveLeftRight")
61 |
62 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
63 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
64 |
65 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
66 |
67 | toggleCamera()
68 | }
69 |
70 | // -------------------------------------------------------------------------
71 |
72 | func moveRight() {
73 | let moveAction = SCNAction.moveBy(x: -Player.moveOffset, y: 0.0, z: 0, duration: 0.5)
74 | self.runAction(moveAction, forKey: "moveLeftRight")
75 |
76 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
77 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
78 |
79 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
80 |
81 | toggleCamera()
82 | }
83 |
84 | // -------------------------------------------------------------------------
85 | // MARK: - Initialisation
86 |
87 | override init() {
88 | super.init()
89 |
90 | // Create player node
91 | let scene = SCNScene(named: "art.scnassets/ship.scn")
92 | if (scene == nil) {
93 | fatalError("Scene not loaded")
94 | }
95 |
96 | _playerNode = scene!.rootNode.childNode(withName: "ship", recursively: true)
97 | _playerNode?.name = "player"
98 |
99 | if (_playerNode == nil) {
100 | fatalError("Ship node not found")
101 | }
102 |
103 | _playerNode!.scale = SCNVector3(x: 0.25, y: 0.25, z: 0.25)
104 | self.addChildNode(_playerNode!)
105 |
106 | // Contact box
107 | // Part 3: Instead of use the plane itself we add a collision node to the player object
108 | let boxMaterial = SCNMaterial()
109 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
110 |
111 | let box = SCNBox(width: 2.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
112 | box.materials = [boxMaterial]
113 | let contactBox = SCNNode(geometry: box)
114 | contactBox.name = "player"
115 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
116 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.player
117 | contactBox.physicsBody!.contactTestBitMask = Game.Physics.Categories.ring
118 | self.addChildNode(contactBox)
119 |
120 | // Look at Node
121 | _lookAtNode = SCNNode()
122 | _lookAtNode!.position = lookAtForwardPosition
123 | addChildNode(_lookAtNode!)
124 |
125 | // Camera Node
126 | _cameraNode = SCNNode()
127 | _cameraNode!.camera = SCNCamera()
128 | _cameraNode!.position = cameraFowardPosition
129 | _cameraNode!.camera!.zNear = 0.1
130 | _cameraNode!.camera!.zFar = 200
131 | self.addChildNode(_cameraNode!)
132 |
133 | // Link them
134 | let constraint1 = SCNLookAtConstraint(target: _lookAtNode)
135 | constraint1.isGimbalLockEnabled = true
136 | _cameraNode!.constraints = [constraint1]
137 |
138 | // Create a spotlight at the player
139 | let spotLight = SCNLight()
140 | spotLight.type = SCNLight.LightType.spot
141 | spotLight.spotInnerAngle = 40.0
142 | spotLight.spotOuterAngle = 80.0
143 | spotLight.castsShadow = true
144 | spotLight.color = UIColor.white
145 | let spotLightNode = SCNNode()
146 | spotLightNode.light = spotLight
147 | spotLightNode.position = SCNVector3(x: 1.0, y: 5.0, z: -2.0)
148 | self.addChildNode(spotLightNode)
149 |
150 | // Link it
151 | let constraint2 = SCNLookAtConstraint(target: self)
152 | constraint2.isGimbalLockEnabled = true
153 | spotLightNode.constraints = [constraint2]
154 |
155 | // Create additional omni light
156 | let lightNode = SCNNode()
157 | lightNode.light = SCNLight()
158 | lightNode.light!.type = SCNLight.LightType.omni
159 | lightNode.light!.color = UIColor.darkGray
160 | lightNode.position = SCNVector3(x: 0, y: 10.00, z: -2)
161 | self.addChildNode(lightNode)
162 | }
163 |
164 | // -------------------------------------------------------------------------
165 |
166 | required init(coder: NSCoder) {
167 | fatalError("Not yet implemented")
168 | }
169 |
170 | // -------------------------------------------------------------------------
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/partIII/Ring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ring.swift
3 | //
4 | // Part 3 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 | // Rings are the first game elements (besides the player) introduced in this tutorial.
11 | // The goal is simple: Th eplayer must fly trough this rings and get one point for each.
12 | //
13 |
14 | import SceneKit
15 |
16 | // -----------------------------------------------------------------------------
17 |
18 | class Ring : SCNNode {
19 |
20 | // -------------------------------------------------------------------------
21 | // MARK: - Initialisation
22 |
23 | override init() {
24 | super.init()
25 |
26 | let ringMaterial = SCNMaterial()
27 | ringMaterial.diffuse.contents = UIImage(named: "art.scnassets/ringTexture")
28 | ringMaterial.diffuse.wrapS = .repeat
29 | ringMaterial.diffuse.wrapT = .repeat
30 | ringMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(3, 1, 1)
31 |
32 | let ring = SCNTorus(ringRadius: 5.0, pipeRadius: 0.5)
33 | ring.materials = [ringMaterial]
34 | let ringNode = SCNNode(geometry: ring)
35 | self.addChildNode(ringNode)
36 |
37 | ringNode.eulerAngles = SCNVector3(degreesToRadians(value: 90), 0, 0)
38 |
39 | let action = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 360), duration: 3.0)
40 | ringNode.runAction(SCNAction.repeatForever(action))
41 |
42 | // Contact box
43 | let boxMaterial = SCNMaterial()
44 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
45 |
46 | let box = SCNBox(width: 10.0, height: 10.0, length: 1.0, chamferRadius: 0.0)
47 | box.materials = [boxMaterial]
48 | let contactBox = SCNNode(geometry: box)
49 | contactBox.name = "ring"
50 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
51 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.ring
52 | self.addChildNode(contactBox)
53 | }
54 |
55 | // -------------------------------------------------------------------------
56 |
57 | required init(coder: NSCoder) {
58 | fatalError("Not yet implemented")
59 | }
60 |
61 | // -------------------------------------------------------------------------
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/partIV/GameLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLevel.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // New in Part 4:
11 | // The game has become a game loop. A main concept used in any pro game
12 | // While you can create SceneKit games without this, it open's some b iug
13 | // advantages which are covered in more detail in the tutorial
14 |
15 | import UIKit
16 | import SceneKit
17 |
18 | enum GameState {
19 | case initialized, play, win, loose, stopped
20 | }
21 |
22 | // -----------------------------------------------------------------------------
23 |
24 | class GameLevel: SCNScene, SCNPhysicsContactDelegate {
25 | private let levelWidth = 320
26 | private let levelLength = 640
27 |
28 | // New in Part 4: A list of all game objects
29 | private var _gameObjects = Array()
30 |
31 | private var _terrain: RBTerrain?
32 | private var _player: Player?
33 | private var _hud: HUD?
34 |
35 | private let _numberOfRings = 10
36 | private var _touchedRings = 0
37 |
38 | // New in Part 4: We catch now also the number of missed rings
39 | private var _missedRings = 0
40 |
41 | // New in Part 4: The game becomes differenr states
42 | private var _state = GameState.initialized
43 |
44 | // -------------------------------------------------------------------------
45 | // MARK: - Properties
46 |
47 | var hud: HUD? {
48 | get {
49 | return _hud
50 | }
51 | set(value) {
52 | _hud = value
53 | }
54 | }
55 |
56 | // -------------------------------------------------------------------------
57 |
58 | var state: GameState {
59 | get {
60 | return _state
61 | }
62 | set(value) {
63 | rbDebug("State of level changed from \(_state) to \(value)")
64 | _state = value
65 | }
66 | }
67 |
68 | // -------------------------------------------------------------------------
69 | // MARK: - Input handling
70 |
71 | func swipeLeft() {
72 | _player!.moveLeft()
73 | }
74 |
75 | // -------------------------------------------------------------------------
76 |
77 | func swipeRight() {
78 | _player!.moveRight()
79 | }
80 |
81 | // -------------------------------------------------------------------------
82 | // MARK: - Actions
83 |
84 | func flyTrough(_ ring: Ring) {
85 | _touchedRings += 1
86 | _hud?.rings = _touchedRings
87 | }
88 |
89 | // -------------------------------------------------------------------------
90 |
91 | func touchedHandicap(_ handicap: Handicap) {
92 | _hud?.message("GAME OVER", information: "- Touch to restart - ")
93 |
94 | self.state = .loose
95 | }
96 |
97 | // -------------------------------------------------------------------------
98 | // MARK: - Game loop
99 |
100 | func update(atTime time: TimeInterval) {
101 | // New in Part 4: The game loop (see tutorial)
102 | if self.state != .play {
103 | return
104 | }
105 |
106 | var missedRings = 0
107 |
108 | for object in _gameObjects {
109 | object.update(atTime: time, level: self)
110 |
111 | // Check which rings are behind the player but still 'alive'
112 | if let ring = object as? Ring {
113 | if (ring.presentation.position.z + 5.0) < _player!.presentation.position.z {
114 | if ring.state == .alive {
115 | missedRings += 1
116 | }
117 | }
118 | }
119 | }
120 |
121 | if missedRings > _missedRings {
122 | _missedRings = missedRings
123 | _hud?.missedRings = _missedRings
124 | }
125 |
126 | // Test for end of game
127 | if _missedRings + _touchedRings == _numberOfRings {
128 | if _missedRings < 3 {
129 | _hud?.message("YOU WIN", information: "- Touch to restart - ")
130 | }
131 | else {
132 | _hud?.message("TRY TO IMPROVE", information: "- Touch to restart - ")
133 | }
134 |
135 | self.state = .win
136 | }
137 | }
138 |
139 | // -------------------------------------------------------------------------
140 | // MARK: - Physics delegate
141 |
142 | func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
143 | // Part 4: Call collision handler of the object (see GameObject)
144 | if let gameObjectA = contact.nodeA.parent as? GameObject, let gameObjectB = contact.nodeB.parent as? GameObject {
145 | gameObjectA.collision(with: gameObjectB, level: self)
146 | gameObjectB.collision(with: gameObjectA, level: self)
147 | }
148 | }
149 |
150 | // -------------------------------------------------------------------------
151 | // MARK: - Place objects
152 |
153 | private func addRings() {
154 | // Part 3: Add rings to the game level
155 | let space = levelLength / (_numberOfRings+1)
156 |
157 | for i in 1..._numberOfRings {
158 | let ring = Ring()
159 |
160 | var x: CGFloat = 160
161 | let rnd = RBRandom.integer(1, 3)
162 | if rnd == 1 {
163 | x = x - Game.Player.moveOffset
164 | }
165 | else if rnd == 3 {
166 | x = x + Game.Player.moveOffset
167 | }
168 |
169 | ring.position = SCNVector3(Int(x), 3, (i*space))
170 | self.rootNode.addChildNode(ring)
171 |
172 | _gameObjects.append(ring)
173 | }
174 | }
175 |
176 | // -------------------------------------------------------------------------
177 |
178 | private func addHandicap(x: CGFloat, z: CGFloat) {
179 | let handicap = Handicap()
180 | handicap.position = SCNVector3(x, handicap.height/2, z)
181 | self.rootNode.addChildNode(handicap)
182 |
183 | _gameObjects.append(handicap)
184 | }
185 |
186 | // -------------------------------------------------------------------------
187 |
188 | private func addHandicaps() {
189 | // Part 4: Add handicaps to the game level
190 | let space: CGFloat = CGFloat(levelLength / (_numberOfRings+1))
191 |
192 | for i in 1..._numberOfRings-1 {
193 | var x: CGFloat = 160
194 | let rnd = RBRandom.integer(1, 3)
195 |
196 | if rnd == 1 {
197 | x = x - Game.Player.moveOffset
198 |
199 | addHandicap(x: x-RBRandom.cgFloat(10, 50), z: CGFloat(i)*space + space/2.0)
200 | addHandicap(x: x+Game.Player.moveOffset+RBRandom.cgFloat(10, 50), z: CGFloat(i)*space + space/2.0)
201 | }
202 | else if rnd == 3 {
203 | x = x + Game.Player.moveOffset
204 |
205 | addHandicap(x: x+RBRandom.cgFloat(10, 50), z: CGFloat(i)*space + space/2.0)
206 | addHandicap(x: x-Game.Player.moveOffset-RBRandom.cgFloat(10, 50), z: CGFloat(i)*space + space/2.0)
207 | }
208 |
209 | addHandicap(x: x, z: CGFloat(i)*space + space/2.0)
210 | }
211 | }
212 |
213 | // -------------------------------------------------------------------------
214 |
215 | private func addPlayer() {
216 | _player = Player()
217 | _player!.state = .alive
218 |
219 | _player!.position = SCNVector3(160, 4, 0)
220 | self.rootNode.addChildNode(_player!)
221 |
222 | let moveAction = SCNAction.moveBy(x: 0, y: 0, z: CGFloat(levelLength)-10, duration: 60)
223 | _player!.runAction(moveAction)
224 |
225 | _gameObjects.append(_player!)
226 | }
227 |
228 | // -------------------------------------------------------------------------
229 |
230 | private func addTerrain() {
231 | // Create terrain
232 | _terrain = RBTerrain(width: levelWidth, length: levelLength, scale: 128)
233 |
234 | let generator = RBPerlinNoiseGenerator(seed: nil)
235 | _terrain?.formula = {(x: Int32, y: Int32) in
236 | return generator.valueFor(x: x, y: y)
237 | }
238 |
239 | _terrain!.create(withColor: UIColor.green)
240 | _terrain!.position = SCNVector3Make(0, 0, 0)
241 | self.rootNode.addChildNode(_terrain!)
242 | }
243 |
244 | // -------------------------------------------------------------------------
245 | // MARK: - Stop
246 |
247 | func stop() {
248 | // New in Part 4: Stop all!
249 | for object in _gameObjects {
250 | object.stop()
251 | }
252 |
253 | self.physicsWorld.contactDelegate = nil
254 | self.hud = nil
255 | self.state = .stopped
256 | }
257 |
258 | // -------------------------------------------------------------------------
259 | // MARK: - Initialisation
260 |
261 | func create() {
262 | // New in Part 4: A skybox is used to show a game's background
263 | self.background.contents = UIImage(named: "art.scnassets/skybox")
264 |
265 | addTerrain()
266 | addRings()
267 | addHandicaps()
268 | addPlayer()
269 |
270 | self.state = .play
271 | }
272 |
273 | // -------------------------------------------------------------------------
274 |
275 | override init() {
276 | super.init()
277 |
278 | self.physicsWorld.contactDelegate = self
279 | }
280 |
281 | // -------------------------------------------------------------------------
282 |
283 | required init(coder: NSCoder) {
284 | fatalError("Not yet implemented")
285 | }
286 |
287 | // -------------------------------------------------------------------------
288 |
289 | }
290 |
--------------------------------------------------------------------------------
/partIV/GameObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameObject.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 31.10.17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 | // New in Part 4: This is a bigger change. While the game becomes more and more
11 | // various game objects, it's common to encapsulate general things
12 | // in a base class.
13 | // The most important is the state which is described in detail in the tutorial.
14 |
15 | import SceneKit
16 |
17 | enum GameObjecState {
18 | case initialized, alive, died, stopped
19 | }
20 |
21 | class GameObject : SCNNode {
22 | private static var count: Int = 0
23 | static var level: GameLevel?
24 |
25 | private var _internalID: Int = 0
26 | private var _tag = 0
27 | private var _state = GameObjecState.initialized
28 |
29 | // -------------------------------------------------------------------------
30 | // MARK: - Propertiues
31 |
32 | override var description: String {
33 | get {
34 | return "game object \(self.id)"
35 | }
36 | }
37 |
38 | // -------------------------------------------------------------------------
39 |
40 | var id: Int {
41 | return _internalID
42 | }
43 |
44 | // -------------------------------------------------------------------------
45 |
46 | var tag: Int {
47 | get {
48 | return _tag
49 | }
50 | set(value) {
51 | _tag = value
52 | }
53 | }
54 |
55 | // -------------------------------------------------------------------------
56 |
57 | var state: GameObjecState {
58 | get {
59 | return _state
60 | }
61 | set(value) {
62 | rbDebug("State of \(self) changed from \(_state) to \(value)")
63 | _state = value
64 | }
65 | }
66 |
67 | // -------------------------------------------------------------------------
68 | // MARK: - Actions
69 |
70 | func hit() {} // Object get hit by another object
71 |
72 | // -------------------------------------------------------------------------
73 |
74 | func stop() {
75 | // Stop object (release all)
76 | self.state = .stopped
77 | stopAllActions(self)
78 | }
79 |
80 | // -------------------------------------------------------------------------
81 | // MARK: - Game loop
82 |
83 | func update(atTime time: TimeInterval, level: GameLevel) {}
84 |
85 | // -------------------------------------------------------------------------
86 | // MARK: - Collision handling
87 |
88 | func collision(with object: GameObject, level: GameLevel) {}
89 |
90 | // -------------------------------------------------------------------------
91 | // MARK: - Helper methods
92 |
93 | func stopAllActions(_ node: SCNNode) {
94 | // It's important to stop all actions before we remove a game object
95 | // Otherwise they continue to run and result in difficult side effects.
96 | node.removeAllActions()
97 |
98 | for child in node.childNodes {
99 | child.removeAllActions()
100 | stopAllActions(child)
101 | }
102 | }
103 |
104 | // -------------------------------------------------------------------------
105 | // MARK: - Initialisation
106 |
107 | override init() {
108 | super.init()
109 |
110 | GameObject.count += 1
111 | _internalID = GameObject.count
112 | }
113 |
114 | // -------------------------------------------------------------------------
115 |
116 | required init?(coder aDecoder: NSCoder) {
117 | fatalError("init(coder aDecoder: NSCoder) is not implemented")
118 | }
119 |
120 | // -------------------------------------------------------------------------
121 | }
122 |
123 |
124 |
--------------------------------------------------------------------------------
/partIV/GameSettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSettings.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // Structs are a very good way to structure the different application settings.
11 | // Like that they are easy to read and changeable at one place
12 | //
13 | // New in Part 4: Move all constants here
14 |
15 | import UIKit
16 |
17 | struct Game {
18 |
19 | struct Player {
20 | static let moveOffset: CGFloat = 15
21 | }
22 |
23 | struct Physics {
24 |
25 | // Category bits used for physics handling
26 | struct Categories {
27 | static let player: Int = 0b00000001
28 | static let ring: Int = 0b00000010
29 | static let enemy: Int = 0b00000100
30 | }
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/partIV/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 | import SpriteKit
14 |
15 | class GameViewController: UIViewController, SCNSceneRendererDelegate {
16 | private var _sceneView: SCNView!
17 | private var _level: GameLevel!
18 | private var _hud: HUD!
19 |
20 | // -------------------------------------------------------------------------
21 | // MARK: - Properties
22 |
23 | var sceneView: SCNView {
24 | return _sceneView
25 | }
26 |
27 | // -------------------------------------------------------------------------
28 |
29 | var hud: HUD {
30 | return _hud
31 | }
32 |
33 | // -------------------------------------------------------------------------
34 | // MARK: - Render delegate (New in Part 4)
35 |
36 | func renderer(_ renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval) {
37 | if _level != nil {
38 | _level.update(atTime: time)
39 | }
40 |
41 | renderer.loops = true
42 | }
43 |
44 | // -------------------------------------------------------------------------
45 | // MARK: - Gesture recognoizers
46 |
47 | @objc private func handleTap(_ gestureRecognize: UITapGestureRecognizer) {
48 | // New in Part 4: A tap is used to restart the level (see tutorial)
49 | if _level.state == .loose || _level.state == .win {
50 | _level.stop()
51 | _level = nil
52 |
53 | DispatchQueue.main.async {
54 | // Create things in main thread
55 |
56 | let level = GameLevel()
57 | level.create()
58 |
59 | level.hud = self.hud
60 | self.hud.reset()
61 |
62 | self.sceneView.scene = level
63 | self._level = level
64 | }
65 |
66 | }
67 | }
68 |
69 | // -------------------------------------------------------------------------
70 |
71 | @objc private func handleSwipe(_ gestureRecognize: UISwipeGestureRecognizer) {
72 | if (gestureRecognize.direction == .left) {
73 | _level!.swipeLeft()
74 | }
75 | else if (gestureRecognize.direction == .right) {
76 | _level!.swipeRight()
77 | }
78 | }
79 |
80 | // -------------------------------------------------------------------------
81 | // MARK: - ViewController life cycle
82 |
83 | override func viewWillAppear(_ animated: Bool) {
84 | super.viewWillAppear(animated)
85 |
86 | // Part 3: HUD is created and assigned to view and game level
87 | _hud = HUD(size: self.view.bounds.size)
88 | _level.hud = _hud
89 | _sceneView.overlaySKScene = _hud.scene
90 | }
91 |
92 | // -------------------------------------------------------------------------
93 |
94 | override func viewDidLoad() {
95 | super.viewDidLoad()
96 |
97 | _level = GameLevel()
98 | _level.create()
99 |
100 | _sceneView = SCNView()
101 | _sceneView.scene = _level
102 | _sceneView.allowsCameraControl = false
103 | _sceneView.showsStatistics = false
104 | _sceneView.backgroundColor = UIColor.black
105 | _sceneView.delegate = self
106 |
107 | self.view = _sceneView
108 |
109 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
110 | _sceneView!.addGestureRecognizer(tapGesture)
111 |
112 | let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
113 | swipeLeftGesture.direction = .left
114 | _sceneView!.addGestureRecognizer(swipeLeftGesture)
115 |
116 | let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
117 | swipeRightGesture.direction = .right
118 | _sceneView!.addGestureRecognizer(swipeRightGesture)
119 | }
120 |
121 | // -------------------------------------------------------------------------
122 |
123 | override func didReceiveMemoryWarning() {
124 | super.didReceiveMemoryWarning()
125 | }
126 |
127 | // -------------------------------------------------------------------------
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/partIV/HUD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HUD.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 11.11.17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SpriteKit
12 |
13 | class HUD {
14 | private var _scene: SKScene!
15 | private let _rings = SKLabelNode(text: "")
16 | private let _missedRings = SKLabelNode(text: "")
17 | private let _message = SKLabelNode(text: "")
18 | private let _info = SKLabelNode(text: "")
19 |
20 | // -------------------------------------------------------------------------
21 | // MARK: - Properties
22 |
23 | var scene: SKScene {
24 | get {
25 | return _scene
26 | }
27 | }
28 |
29 | // -------------------------------------------------------------------------
30 |
31 | var rings: Int {
32 | get {
33 | return 0
34 | }
35 | set(value) {
36 | if value == 1 {
37 | _rings.text = String(format: "%d RING", value)
38 | }
39 | else {
40 | _rings.text = String(format: "%d RINGS", value)
41 | }
42 |
43 | // New in Part 4: Animated HUD informations (check RB+SKAction.swift for details)
44 | let scaling: CGFloat = 3
45 | let action = SKAction.zoomWithNode(_rings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
46 | _rings.run(action)
47 | }
48 | }
49 |
50 | // -------------------------------------------------------------------------
51 |
52 | var missedRings: Int {
53 | get {
54 | return 0
55 | }
56 | set(value) {
57 | _missedRings.text = String(format: "%d MISSED", value)
58 |
59 | if value > 0 {
60 | _missedRings.fontColor = UIColor.red
61 | }
62 | else {
63 | _missedRings.fontColor = UIColor.white
64 | }
65 |
66 | let scaling: CGFloat = 3
67 | let action = SKAction.zoomWithNode(_missedRings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
68 | _missedRings.run(action)
69 | }
70 | }
71 |
72 | // -------------------------------------------------------------------------
73 |
74 | func message(_ str: String, information: String? = nil) {
75 | // New in Part 4: Used for game over and win messages
76 | _message.text = str
77 | _message.isHidden = false
78 |
79 | let scaling: CGFloat = 10
80 | let action = SKAction.zoomWithNode(_message, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
81 | _message.run(action)
82 |
83 | if information != nil {
84 | info(information!)
85 | }
86 | }
87 |
88 | // -------------------------------------------------------------------------
89 |
90 | func info(_ str: String) {
91 | // New in Part 4: Uses for additional info when show messages
92 |
93 | _info.text = str
94 | _info.isHidden = false
95 |
96 | let scaling: CGFloat = 2
97 | let action = SKAction.zoomWithNode(_info, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
98 | _info.run(action)
99 | }
100 |
101 | // -------------------------------------------------------------------------
102 |
103 | func reset() {
104 | // New in Part 4: Reset is needed whenever start the level
105 |
106 | _message.text = ""
107 | _message.isHidden = true
108 |
109 | _info.text = ""
110 | _info.isHidden = true
111 |
112 | _rings.text = "0 RINGS"
113 | _missedRings.text = "0 MISSED"
114 | }
115 |
116 | // -------------------------------------------------------------------------
117 | // MARK: - Initialisation
118 |
119 | init(size: CGSize) {
120 | _scene = SKScene(size: size)
121 |
122 | _rings.position = CGPoint(x: 40, y: size.height-50)
123 | _rings.horizontalAlignmentMode = .left
124 | _rings.fontName = "MarkerFelt-Wide"
125 | _rings.fontSize = 30
126 | _rings.fontColor = UIColor.white
127 | _scene.addChild(_rings)
128 |
129 | _missedRings.position = CGPoint(x: size.width-40, y: size.height-50)
130 | _missedRings.horizontalAlignmentMode = .right
131 | _missedRings.fontName = "MarkerFelt-Wide"
132 | _missedRings.fontSize = 30
133 | _missedRings.fontColor = UIColor.white
134 | _scene.addChild(_missedRings)
135 |
136 | _message.position = CGPoint(x: size.width/2, y: size.height/2)
137 | _message.horizontalAlignmentMode = .center
138 | _message.fontName = "MarkerFelt-Wide"
139 | _message.fontSize = 60
140 | _message.fontColor = UIColor.white
141 | _message.isHidden = true
142 | _scene.addChild(_message)
143 |
144 | _info.position = CGPoint(x: size.width/2, y: size.height/2-40)
145 | _info.horizontalAlignmentMode = .center
146 | _info.fontName = "MarkerFelt-Wide"
147 | _info.fontSize = 20
148 | _info.fontColor = UIColor.white
149 | _info.isHidden = true
150 | _scene.addChild(_info)
151 |
152 | reset()
153 | }
154 |
155 | // -------------------------------------------------------------------------
156 |
157 | required init?(coder aDecoder: NSCoder) {
158 | fatalError("init?(coder aDecoder: NSCoder) not implemented")
159 | }
160 |
161 | // -------------------------------------------------------------------------
162 | }
163 |
--------------------------------------------------------------------------------
/partIV/Handicap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Handicap.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 | // New in Part 4: So far we just had rings to fly trough. Now we introduce
11 | // handicaps which the plane should not touch. Otherwise it crashes and the
12 | // game is over.
13 |
14 | import SceneKit
15 |
16 | // -----------------------------------------------------------------------------
17 |
18 | class Handicap : GameObject {
19 | private var _node: SCNNode!
20 | private var _height: CGFloat = 0
21 |
22 | // -------------------------------------------------------------------------
23 | // MARK: - Propertiues
24 |
25 | override var description: String {
26 | get {
27 | return "handicap \(self.id)"
28 | }
29 | }
30 |
31 | // -------------------------------------------------------------------------
32 |
33 | var height: CGFloat {
34 | get {
35 | return _height
36 | }
37 | }
38 |
39 | // -------------------------------------------------------------------------
40 | // MARK: - Actions
41 |
42 | override func hit() {
43 | if self.state != .alive {
44 | return
45 | }
46 |
47 | self.state = .died
48 |
49 | let action1 = SCNAction.moveBy(x: 0, y: -3, z: 0, duration: 0.15)
50 | _node.runAction(action1)
51 |
52 | let action2 = SCNAction.rotateBy(x: degreesToRadians(value: 30), y: 0, z: degreesToRadians(value: 15), duration: 0.3)
53 | _node.runAction(action2)
54 |
55 | if let emitter = SCNParticleSystem(named: "art.scnassets/fire.scnp", inDirectory: nil) {
56 | self.addParticleSystem(emitter)
57 | }
58 | }
59 |
60 | // -------------------------------------------------------------------------
61 | // MARK: - Initialisation
62 |
63 | override init() {
64 | super.init()
65 |
66 | // Use some randomness in height, width and color
67 | let material = SCNMaterial()
68 | material.diffuse.contents = UIColor.random(list: UIGreenColorList)
69 |
70 | let width = RBRandom.cgFloat(4.0, 9.0)
71 | _height = RBRandom.cgFloat(15.0, 25)
72 |
73 | var geometry: SCNGeometry!
74 | let rnd = RBRandom.integer(1, 3)
75 | if rnd == 1 {
76 | geometry = SCNBox(width: width, height: _height, length: 2.0, chamferRadius: 0.0)
77 | }
78 | else if rnd == 2 {
79 | geometry = SCNCylinder(radius: width, height: _height)
80 | }
81 | else {
82 | geometry = SCNCone(topRadius: 0.0, bottomRadius: width, height: _height)
83 | }
84 |
85 | geometry.materials = [material]
86 |
87 | _node = SCNNode(geometry: geometry)
88 | _node.name = "handicap"
89 | _node.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
90 | _node.physicsBody?.categoryBitMask = Game.Physics.Categories.enemy
91 | self.addChildNode(_node)
92 |
93 | self.state = .alive
94 | }
95 |
96 | // -------------------------------------------------------------------------
97 |
98 | required init(coder: NSCoder) {
99 | fatalError("Not yet implemented")
100 | }
101 |
102 | // -------------------------------------------------------------------------
103 |
104 | }
105 |
106 |
107 |
--------------------------------------------------------------------------------
/partIV/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UIStatusBarHidden
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationLandscapeLeft
34 | UIInterfaceOrientationLandscapeRight
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/partIV/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // Part 4: Now the player objects is derrived from GameObject instead of SCNNode
11 | //
12 |
13 | import SceneKit
14 |
15 | // -----------------------------------------------------------------------------
16 |
17 | class Player : GameObject {
18 | private let lookAtForwardPosition = SCNVector3Make(0.0, -1.0, 6.0)
19 | private let cameraFowardPosition = SCNVector3(x: 0, y: 1.0, z: -5)
20 |
21 | private var _lookAtNode: SCNNode?
22 | private var _cameraNode: SCNNode?
23 | private var _playerNode: SCNNode?
24 |
25 | // -------------------------------------------------------------------------
26 | // MARK: - Propertiues
27 |
28 | override var description: String {
29 | get {
30 | return "player \(self.id)"
31 | }
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 | // MARK: - Collision handling
36 |
37 | override func collision(with object: GameObject, level: GameLevel) {
38 | if let ring = object as? Ring {
39 | if ring.state != .alive {
40 | return
41 | }
42 |
43 | level.flyTrough(ring)
44 | ring.hit()
45 |
46 | self.roll()
47 | }
48 | else if let handicap = object as? Handicap {
49 | level.touchedHandicap(handicap)
50 | handicap.hit()
51 |
52 | self.die()
53 | }
54 | }
55 |
56 | // -------------------------------------------------------------------------
57 | // MARK: - Effects
58 |
59 | func die() {
60 | if self.state != .alive {
61 | return
62 | }
63 |
64 | self.state = .died
65 | _playerNode?.isHidden = true
66 |
67 | self.removeAllActions()
68 | _playerNode?.removeAllActions()
69 | }
70 |
71 | // -------------------------------------------------------------------------
72 |
73 | func roll() {
74 | // Part 3: An easy effect we use, whenever we fly trough a ring
75 | let rotateAction = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 360.0), duration: 0.5)
76 | _playerNode!.runAction(rotateAction)
77 | }
78 |
79 | // -------------------------------------------------------------------------
80 | // MARK: - Camera adjustment
81 |
82 | private func toggleCamera() {
83 | var position = _cameraNode!.position
84 |
85 | if position.x < 0 {
86 | position.x = 0.5
87 | }
88 | else {
89 | position.x = -0.5
90 | }
91 |
92 | SCNTransaction.begin()
93 | SCNTransaction.animationDuration = 1.0
94 |
95 | _cameraNode?.position = position
96 |
97 | SCNTransaction.commit()
98 | }
99 |
100 | // -------------------------------------------------------------------------
101 | // MARK: - Plane movements
102 |
103 | func moveLeft() {
104 | let moveAction = SCNAction.moveBy(x: Game.Player.moveOffset, y: 0.0, z: 0, duration: 0.5)
105 | self.runAction(moveAction, forKey: "moveLeftRight")
106 |
107 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
108 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
109 |
110 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
111 |
112 | toggleCamera()
113 | }
114 |
115 | // -------------------------------------------------------------------------
116 |
117 | func moveRight() {
118 | let moveAction = SCNAction.moveBy(x: -Game.Player.moveOffset, y: 0.0, z: 0, duration: 0.5)
119 | self.runAction(moveAction, forKey: "moveLeftRight")
120 |
121 | let rotateAction1 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 15.0), duration: 0.25)
122 | let rotateAction2 = SCNAction.rotateBy(x: 0, y: 0, z: -degreesToRadians(value: 15.0), duration: 0.25)
123 |
124 | _playerNode!.runAction(SCNAction.sequence([rotateAction1, rotateAction2]))
125 |
126 | toggleCamera()
127 | }
128 |
129 | // -------------------------------------------------------------------------
130 | // MARK: - Initialisation
131 |
132 | override init() {
133 | super.init()
134 |
135 | // Create player node
136 | let scene = SCNScene(named: "art.scnassets/ship.scn")
137 | if (scene == nil) {
138 | fatalError("Scene not loaded")
139 | }
140 |
141 | _playerNode = scene!.rootNode.childNode(withName: "ship", recursively: true)
142 | _playerNode?.name = "player"
143 |
144 | if (_playerNode == nil) {
145 | fatalError("Ship node not found")
146 | }
147 |
148 | _playerNode!.scale = SCNVector3(x: 0.25, y: 0.25, z: 0.25)
149 | self.addChildNode(_playerNode!)
150 |
151 | // Contact box
152 | // Part 3: Instead of use the plane itself we add a collision node to the player object
153 | let boxMaterial = SCNMaterial()
154 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
155 |
156 | let box = SCNBox(width: 2.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
157 | box.materials = [boxMaterial]
158 | let contactBox = SCNNode(geometry: box)
159 | contactBox.name = "player"
160 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
161 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.player
162 | contactBox.physicsBody!.contactTestBitMask = Game.Physics.Categories.ring | Game.Physics.Categories.enemy
163 | self.addChildNode(contactBox)
164 |
165 | // Look at Node
166 | _lookAtNode = SCNNode()
167 | _lookAtNode!.position = lookAtForwardPosition
168 | addChildNode(_lookAtNode!)
169 |
170 | // Camera Node
171 | _cameraNode = SCNNode()
172 | _cameraNode!.camera = SCNCamera()
173 | _cameraNode!.position = cameraFowardPosition
174 | _cameraNode!.camera!.zNear = 0.1
175 | _cameraNode!.camera!.zFar = 200
176 | self.addChildNode(_cameraNode!)
177 |
178 | // Link them
179 | let constraint1 = SCNLookAtConstraint(target: _lookAtNode)
180 | constraint1.isGimbalLockEnabled = true
181 | _cameraNode!.constraints = [constraint1]
182 |
183 | // Create a spotlight at the player
184 | let spotLight = SCNLight()
185 | spotLight.type = SCNLight.LightType.spot
186 | spotLight.spotInnerAngle = 40.0
187 | spotLight.spotOuterAngle = 80.0
188 | spotLight.castsShadow = true
189 | spotLight.color = UIColor.white
190 | let spotLightNode = SCNNode()
191 | spotLightNode.light = spotLight
192 | spotLightNode.position = SCNVector3(x: 1.0, y: 5.0, z: -2.0)
193 | self.addChildNode(spotLightNode)
194 |
195 | // Link it
196 | let constraint2 = SCNLookAtConstraint(target: self)
197 | constraint2.isGimbalLockEnabled = true
198 | spotLightNode.constraints = [constraint2]
199 |
200 | // Create additional omni light
201 | let lightNode = SCNNode()
202 | lightNode.light = SCNLight()
203 | lightNode.light!.type = SCNLight.LightType.omni
204 | lightNode.light!.color = UIColor.darkGray
205 | lightNode.position = SCNVector3(x: 0, y: 10.00, z: -2)
206 | self.addChildNode(lightNode)
207 | }
208 |
209 | // -------------------------------------------------------------------------
210 |
211 | required init(coder: NSCoder) {
212 | fatalError("Not yet implemented")
213 | }
214 |
215 | // -------------------------------------------------------------------------
216 |
217 | }
218 |
--------------------------------------------------------------------------------
/partIV/Ring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ring.swift
3 | //
4 | // Part 4 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 | // Part 4: Now the ring object is derrived from GameObject instead of SCNNode
11 | //
12 | import SceneKit
13 |
14 | // -----------------------------------------------------------------------------
15 |
16 | class Ring : GameObject {
17 |
18 | // -------------------------------------------------------------------------
19 | // MARK: - Propertiues
20 |
21 | override var description: String {
22 | get {
23 | return "ring \(self.id)"
24 | }
25 | }
26 |
27 | // -------------------------------------------------------------------------
28 | // MARK: - Actions
29 |
30 | override func hit() {
31 | self.state = .died
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 | // MARK: - Initialisation
36 |
37 | override init() {
38 | super.init()
39 |
40 | let ringMaterial = SCNMaterial()
41 | ringMaterial.diffuse.contents = UIImage(named: "art.scnassets/ringTexture")
42 | ringMaterial.diffuse.wrapS = .repeat
43 | ringMaterial.diffuse.wrapT = .repeat
44 | ringMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(3, 1, 1)
45 |
46 | let ring = SCNTorus(ringRadius: 5.0, pipeRadius: 0.5)
47 | ring.materials = [ringMaterial]
48 | let ringNode = SCNNode(geometry: ring)
49 | self.addChildNode(ringNode)
50 |
51 | ringNode.eulerAngles = SCNVector3(degreesToRadians(value: 90), 0, 0)
52 |
53 | let action = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 360), duration: 3.0)
54 | ringNode.runAction(SCNAction.repeatForever(action))
55 |
56 | // Contact box
57 | let boxMaterial = SCNMaterial()
58 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
59 |
60 | let box = SCNBox(width: 10.0, height: 10.0, length: 1.0, chamferRadius: 0.0)
61 | box.materials = [boxMaterial]
62 | let contactBox = SCNNode(geometry: box)
63 | contactBox.name = "ring"
64 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
65 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.ring
66 | self.addChildNode(contactBox)
67 |
68 | self.state = .alive
69 | }
70 |
71 | // -------------------------------------------------------------------------
72 |
73 | required init(coder: NSCoder) {
74 | fatalError("Not yet implemented")
75 | }
76 |
77 | // -------------------------------------------------------------------------
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/partV/GameObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameObject.swift
3 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
4 | // https://rogerboesch.github.io/
5 | //
6 | // Created by Roger Boesch on 31.10.17.
7 | // Copyright © 2017 Roger Boesch. All rights reserved.
8 | //
9 | // New in Part 4: This is a bigger change. While the game becomes more and more
10 | // various game objects, it's common to encapsulate general things
11 | // in a base class.
12 | // The most important is the state which is described in detail in the tutorial.
13 |
14 | import SceneKit
15 |
16 | enum GameObjecState {
17 | case initialized, alive, died, stopped
18 | }
19 |
20 | class GameObject : SCNNode {
21 | private static var count: Int = 0
22 | static var level: GameLevel?
23 |
24 | private var _internalID: Int = 0
25 | private var _tag = 0
26 | private var _state = GameObjecState.initialized
27 |
28 | // -------------------------------------------------------------------------
29 | // MARK: - Propertiues
30 |
31 | override var description: String {
32 | get {
33 | return "game object \(self.id)"
34 | }
35 | }
36 |
37 | // -------------------------------------------------------------------------
38 |
39 | var id: Int {
40 | return _internalID
41 | }
42 |
43 | // -------------------------------------------------------------------------
44 |
45 | var tag: Int {
46 | get {
47 | return _tag
48 | }
49 | set(value) {
50 | _tag = value
51 | }
52 | }
53 |
54 | // -------------------------------------------------------------------------
55 |
56 | var state: GameObjecState {
57 | get {
58 | return _state
59 | }
60 | set(value) {
61 | rbDebug("State of \(self) changed from \(_state) to \(value)")
62 | _state = value
63 | }
64 | }
65 |
66 | // -------------------------------------------------------------------------
67 | // MARK: - Actions
68 |
69 | func hit() {} // Object get hit by another object
70 |
71 | // -------------------------------------------------------------------------
72 |
73 | func stop() {
74 | // Stop object (release all)
75 | self.state = .stopped
76 | stopAllActions(self)
77 | }
78 |
79 | // -------------------------------------------------------------------------
80 | // MARK: - Game loop
81 |
82 | func update(atTime time: TimeInterval, level: GameLevel) {}
83 |
84 | // -------------------------------------------------------------------------
85 | // MARK: - Collision handling
86 |
87 | func collision(with object: GameObject, level: GameLevel) {}
88 |
89 | // -------------------------------------------------------------------------
90 | // MARK: - Helper methods
91 |
92 | func stopAllActions(_ node: SCNNode) {
93 | // It's important to stop all actions before we remove a game object
94 | // Otherwise they continue to run and result in difficult side effects.
95 | node.removeAllActions()
96 |
97 | for child in node.childNodes {
98 | child.removeAllActions()
99 | stopAllActions(child)
100 | }
101 | }
102 |
103 | // -------------------------------------------------------------------------
104 | // MARK: - Initialisation
105 |
106 | override init() {
107 | super.init()
108 |
109 | GameObject.count += 1
110 | _internalID = GameObject.count
111 | }
112 |
113 | // -------------------------------------------------------------------------
114 |
115 | required init?(coder aDecoder: NSCoder) {
116 | fatalError("init(coder aDecoder: NSCoder) is not implemented")
117 | }
118 |
119 | // -------------------------------------------------------------------------
120 | }
121 |
122 |
123 |
--------------------------------------------------------------------------------
/partV/GameSettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSettings.swift
3 | //
4 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // Structs are a very good way to structure the different application settings.
11 | // Like that they are easy to read and changeable at one place
12 | //
13 |
14 | import UIKit
15 |
16 | struct Game {
17 |
18 | struct Level {
19 | static let numberOfRings: Int = 10 // Number of rings in level
20 |
21 | static let width: CGFloat = 320 // Terrain width
22 | static let length: CGFloat = 840 // Terraiun length
23 | static let start: CGFloat = 100 // Start of player
24 |
25 | struct Fog {
26 | static let start: CGFloat = 20 // Begin of fog
27 | static let end: CGFloat = 300 // End of fog
28 | }
29 | }
30 |
31 | struct Player {
32 | static let actionTime: TimeInterval = 10.0 // Time used for move actions
33 | static let leftRightMoveDistance: CGFloat = 6.0 // Left/Right distance take in time
34 | static let upDownMoveDistance: CGFloat = 4.0 // Up/Down distance take in time
35 | static let speedDistance: CGFloat = 100.0 // Forward distance take in time
36 |
37 | static let upDownAngle: CGFloat = 5.0 // Angle when fly up or down
38 | static let leftRightAngle: CGFloat = 30.0 // Angle when fly left or right
39 |
40 | static let minimumHeight: Float = 4.0 // Minimum height
41 | static let maximumHeight: Float = 30.0 // Maximum height
42 | static let maximumLeft: Float = 360.0 // Maximum left
43 | static let maximumRight: Float = 30.0 // Maximum right
44 | }
45 |
46 | struct Objects {
47 | static let offset: CGFloat = 15 // Space between objects
48 | }
49 |
50 | struct Physics {
51 |
52 | // Category bits used for physics handling
53 | struct Categories {
54 | static let player: Int = 0b00000001
55 | static let ring: Int = 0b00000010
56 | static let enemy: Int = 0b00000100
57 | }
58 | }
59 |
60 | struct Motion {
61 | static let threshold: Double = 0.2 // Minium threshold of accelerometer
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/partV/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 | import SpriteKit
14 | import CoreMotion
15 | import GameController
16 |
17 | class GameViewController: UIViewController, SCNSceneRendererDelegate {
18 | private var _sceneView: SCNView!
19 | private var _level: GameLevel!
20 | private var _hud: HUD!
21 |
22 | // New in Part 5: Use CoreMotion to fly the plane
23 | private var _motionManager = CMMotionManager()
24 | private var _startAttitude: CMAttitude? // Start attitude
25 | private var _currentAttitude: CMAttitude? // Current attitude
26 |
27 | // -------------------------------------------------------------------------
28 | // MARK: - Properties
29 |
30 | var sceneView: SCNView {
31 | return _sceneView
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 |
36 | var hud: HUD {
37 | return _hud
38 | }
39 |
40 | // -------------------------------------------------------------------------
41 | // MARK: - Render delegate (New in Part 4)
42 |
43 | func renderer(_ renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval) {
44 | guard _level != nil else { return }
45 |
46 | _level.update(atTime: time)
47 | renderer.loops = true
48 | }
49 |
50 | // -------------------------------------------------------------------------
51 | // MARK: - Gesture recognoizers
52 |
53 | @objc private func handleTap(_ gestureRecognize: UITapGestureRecognizer) {
54 | // New in Part 4: A tap is used to restart the level (see tutorial)
55 | if _level.state == .loose || _level.state == .win {
56 | _level.stop()
57 | _level = nil
58 |
59 | DispatchQueue.main.async {
60 | // Create things in main thread
61 |
62 | let level = GameLevel()
63 | level.create()
64 |
65 | level.hud = self.hud
66 | self.hud.reset()
67 |
68 | self.sceneView.scene = level
69 | self._level = level
70 |
71 | self.hud.message("READY?", information: "- Touch screen to start -")
72 | }
73 | }
74 | // New in Part 5: A tap is used to start the level (see tutorial)
75 | else if _level.state == .ready {
76 | _startAttitude = _currentAttitude
77 | _level.start()
78 | }
79 | }
80 |
81 | // -------------------------------------------------------------------------
82 |
83 | @objc private func handleSwipe(_ gestureRecognize: UISwipeGestureRecognizer) {
84 | if _level.state != .play {
85 | return
86 | }
87 |
88 | if (gestureRecognize.direction == .left) {
89 | _level!.swipeLeft()
90 | }
91 | else if (gestureRecognize.direction == .right) {
92 | _level!.swipeRight()
93 | }
94 | else if (gestureRecognize.direction == .down) {
95 | _level!.swipeDown()
96 | }
97 | else if (gestureRecognize.direction == .up) {
98 | _level!.swipeUp()
99 | }
100 | }
101 |
102 | // -------------------------------------------------------------------------
103 | // MARK: - Motion handling
104 |
105 | private func motionDidChange(data: CMDeviceMotion) {
106 | _currentAttitude = data.attitude
107 |
108 | guard _level != nil, _level?.state == .play else { return }
109 |
110 | // Up/Down
111 | let diff1 = _startAttitude!.roll - _currentAttitude!.roll
112 |
113 | if (diff1 >= Game.Motion.threshold) {
114 | _level!.motionMoveUp()
115 | }
116 | else if (diff1 <= -Game.Motion.threshold) {
117 | _level!.motionMoveDown()
118 | }
119 | else {
120 | _level!.motionStopMovingUpDown()
121 | }
122 |
123 | let diff2 = _startAttitude!.pitch - _currentAttitude!.pitch
124 |
125 | if (diff2 >= Game.Motion.threshold) {
126 | _level!.motionMoveLeft()
127 | }
128 | else if (diff2 <= -Game.Motion.threshold) {
129 | _level!.motionMoveRight()
130 | }
131 | else {
132 | _level!.motionStopMovingLeftRight()
133 | }
134 | }
135 |
136 | // -------------------------------------------------------------------------
137 |
138 | private func setupMotionHandler() {
139 | if (GCController.controllers().count == 0 && _motionManager.isAccelerometerAvailable) {
140 | _motionManager.accelerometerUpdateInterval = 1/60.0
141 |
142 | _motionManager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: {(data, error) in
143 | self.motionDidChange(data: data!)
144 | })
145 | }
146 | }
147 |
148 | // -------------------------------------------------------------------------
149 | // MARK: - ViewController life cycle
150 |
151 | override func viewWillAppear(_ animated: Bool) {
152 | super.viewWillAppear(animated)
153 |
154 | // Part 3: HUD is created and assigned to view and game level
155 | _hud = HUD(size: self.view.bounds.size)
156 | _level.hud = _hud
157 | _sceneView.overlaySKScene = _hud.scene
158 |
159 | self.hud.message("READY?", information: "- Touch screen to start -")
160 | }
161 |
162 | // -------------------------------------------------------------------------
163 |
164 | override func viewDidLoad() {
165 | super.viewDidLoad()
166 |
167 | _level = GameLevel()
168 | _level.create()
169 |
170 | _sceneView = SCNView()
171 | _sceneView.scene = _level
172 | _sceneView.allowsCameraControl = false
173 | _sceneView.showsStatistics = false
174 | _sceneView.backgroundColor = UIColor.black
175 | _sceneView.delegate = self
176 |
177 | self.view = _sceneView
178 |
179 | setupMotionHandler()
180 |
181 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
182 | _sceneView!.addGestureRecognizer(tapGesture)
183 |
184 | let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
185 | swipeLeftGesture.direction = .left
186 | _sceneView!.addGestureRecognizer(swipeLeftGesture)
187 |
188 | let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
189 | swipeRightGesture.direction = .right
190 | _sceneView!.addGestureRecognizer(swipeRightGesture)
191 |
192 | let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
193 | swipeDownGesture.direction = .down
194 | _sceneView!.addGestureRecognizer(swipeDownGesture)
195 |
196 | let swipeUpGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
197 | swipeUpGesture.direction = .up
198 | _sceneView!.addGestureRecognizer(swipeUpGesture)
199 | }
200 |
201 | // -------------------------------------------------------------------------
202 |
203 | override func didReceiveMemoryWarning() {
204 | super.didReceiveMemoryWarning()
205 | }
206 |
207 | // -------------------------------------------------------------------------
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/partV/HUD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HUD.swift
3 | //
4 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 11.11.17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SpriteKit
12 |
13 | class HUD {
14 | private var _scene: SKScene!
15 | private let _rings = SKLabelNode(text: "")
16 | private let _missedRings = SKLabelNode(text: "")
17 | private let _message = SKLabelNode(text: "")
18 | private let _info = SKLabelNode(text: "")
19 |
20 | // -------------------------------------------------------------------------
21 | // MARK: - Properties
22 |
23 | var scene: SKScene {
24 | get {
25 | return _scene
26 | }
27 | }
28 |
29 | // -------------------------------------------------------------------------
30 |
31 | var rings: Int {
32 | get {
33 | return 0
34 | }
35 | set(value) {
36 | if value == 1 {
37 | _rings.text = String(format: "%d RING", value)
38 | }
39 | else {
40 | _rings.text = String(format: "%d RINGS", value)
41 | }
42 |
43 | // New in Part 4: Animated HUD informations (check RB+SKAction.swift for details)
44 | let scaling: CGFloat = 3
45 | let action = SKAction.zoomWithNode(_rings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
46 | _rings.run(action)
47 | }
48 | }
49 |
50 | // -------------------------------------------------------------------------
51 |
52 | var missedRings: Int {
53 | get {
54 | return 0
55 | }
56 | set(value) {
57 | _missedRings.text = String(format: "%d MISSED", value)
58 |
59 | if value > 0 {
60 | _missedRings.isHidden = false
61 | _missedRings.fontColor = UIColor.red
62 | }
63 | else {
64 | _missedRings.isHidden = true
65 | }
66 |
67 | let scaling: CGFloat = 3
68 | let action = SKAction.zoomWithNode(_missedRings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
69 | _missedRings.run(action)
70 | }
71 | }
72 |
73 | // -------------------------------------------------------------------------
74 |
75 | func message(_ str: String, information: String? = nil) {
76 | // New in Part 4: Used for game over and win messages
77 | _message.text = str
78 | _message.isHidden = false
79 |
80 | let scaling: CGFloat = 10
81 | let action = SKAction.zoomWithNode(_message, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
82 | _message.run(action)
83 |
84 | if information != nil {
85 | info(information!)
86 | }
87 | }
88 |
89 | // -------------------------------------------------------------------------
90 |
91 | func info(_ str: String) {
92 | // New in Part 4: Uses for additional info when show messages
93 |
94 | _info.text = str
95 | _info.isHidden = false
96 |
97 | let scaling: CGFloat = 2
98 | let action = SKAction.zoomWithNode(_info, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
99 | _info.run(action)
100 | }
101 |
102 | // -------------------------------------------------------------------------
103 |
104 | func reset() {
105 | // New in Part 4: Reset is needed whenever start the level
106 |
107 | _message.text = ""
108 | _message.isHidden = true
109 |
110 | _info.text = ""
111 | _info.isHidden = true
112 |
113 | rings = 0
114 | missedRings = 0
115 | }
116 |
117 | // -------------------------------------------------------------------------
118 | // MARK: - Initialisation
119 |
120 | init(size: CGSize) {
121 | _scene = SKScene(size: size)
122 |
123 | _rings.position = CGPoint(x: 40, y: size.height-50)
124 | _rings.horizontalAlignmentMode = .left
125 | _rings.fontName = "MarkerFelt-Wide"
126 | _rings.fontSize = 30
127 | _rings.fontColor = UIColor.white
128 | _scene.addChild(_rings)
129 |
130 | _missedRings.position = CGPoint(x: size.width-40, y: size.height-50)
131 | _missedRings.horizontalAlignmentMode = .right
132 | _missedRings.fontName = "MarkerFelt-Wide"
133 | _missedRings.fontSize = 30
134 | _missedRings.fontColor = UIColor.white
135 | _scene.addChild(_missedRings)
136 |
137 | _message.position = CGPoint(x: size.width/2, y: size.height/2)
138 | _message.horizontalAlignmentMode = .center
139 | _message.fontName = "MarkerFelt-Wide"
140 | _message.fontSize = 60
141 | _message.fontColor = UIColor.white
142 | _message.isHidden = true
143 | _scene.addChild(_message)
144 |
145 | _info.position = CGPoint(x: size.width/2, y: size.height/2-40)
146 | _info.horizontalAlignmentMode = .center
147 | _info.fontName = "MarkerFelt-Wide"
148 | _info.fontSize = 20
149 | _info.fontColor = UIColor.white
150 | _info.isHidden = true
151 | _scene.addChild(_info)
152 |
153 | reset()
154 | }
155 |
156 | // -------------------------------------------------------------------------
157 |
158 | required init?(coder aDecoder: NSCoder) {
159 | fatalError("init?(coder aDecoder: NSCoder) not implemented")
160 | }
161 |
162 | // -------------------------------------------------------------------------
163 | }
164 |
--------------------------------------------------------------------------------
/partV/Handicap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Handicap.swift
3 | //
4 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Handicap : GameObject {
16 | private var _node: SCNNode!
17 | private var _height: CGFloat = 0
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - Propertiues
21 |
22 | override var description: String {
23 | get {
24 | return "handicap \(self.id)"
25 | }
26 | }
27 |
28 | // -------------------------------------------------------------------------
29 |
30 | var height: CGFloat {
31 | get {
32 | return _height
33 | }
34 | }
35 |
36 | // -------------------------------------------------------------------------
37 | // MARK: - Actions
38 |
39 | override func hit() {
40 | if self.state != .alive {
41 | return
42 | }
43 |
44 | self.state = .died
45 |
46 | let action1 = SCNAction.moveBy(x: 0, y: -3, z: 0, duration: 0.15)
47 | _node.runAction(action1)
48 |
49 | let action2 = SCNAction.rotateBy(x: degreesToRadians(value: 30), y: 0, z: degreesToRadians(value: 15), duration: 0.3)
50 | _node.runAction(action2)
51 |
52 | if let emitter = SCNParticleSystem(named: "art.scnassets/fire.scnp", inDirectory: nil) {
53 | self.addParticleSystem(emitter)
54 | }
55 | }
56 |
57 | // -------------------------------------------------------------------------
58 | // MARK: - Initialisation
59 |
60 | override init() {
61 | super.init()
62 |
63 | // Use some randomness in height, width and color
64 | let material = SCNMaterial()
65 | material.diffuse.contents = UIColor.random(list: UIGreenColorList)
66 |
67 | let width = RBRandom.cgFloat(4.0, 9.0)
68 | _height = RBRandom.cgFloat(15.0, 25)
69 |
70 | var geometry: SCNGeometry!
71 | let rnd = RBRandom.integer(1, 3)
72 | if rnd == 1 {
73 | geometry = SCNBox(width: width, height: _height, length: 2.0, chamferRadius: 0.0)
74 | }
75 | else if rnd == 2 {
76 | geometry = SCNCylinder(radius: width, height: _height)
77 | }
78 | else {
79 | geometry = SCNCone(topRadius: 0.0, bottomRadius: width, height: _height)
80 | }
81 |
82 | geometry.materials = [material]
83 |
84 | _node = SCNNode(geometry: geometry)
85 | _node.name = "handicap"
86 | _node.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
87 | _node.physicsBody?.categoryBitMask = Game.Physics.Categories.enemy
88 | self.addChildNode(_node)
89 |
90 | self.state = .alive
91 | }
92 |
93 | // -------------------------------------------------------------------------
94 |
95 | required init(coder: NSCoder) {
96 | fatalError("Not yet implemented")
97 | }
98 |
99 | // -------------------------------------------------------------------------
100 |
101 | }
102 |
103 |
104 |
--------------------------------------------------------------------------------
/partV/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UIStatusBarHidden
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationLandscapeLeft
34 | UIInterfaceOrientationLandscapeRight
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/partV/Ring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ring.swift
3 | //
4 | // Part 5 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Ring : GameObject {
16 | private var _number: SCNNode!
17 |
18 | // -------------------------------------------------------------------------
19 | // MARK: - Propertiues
20 |
21 | override var description: String {
22 | get {
23 | return "ring \(self.id)"
24 | }
25 | }
26 |
27 | // -------------------------------------------------------------------------
28 | // MARK: - Actions
29 |
30 | override func hit() {
31 | self.state = .died
32 |
33 | _number.isHidden = false
34 | _number.removeAllActions()
35 | }
36 |
37 | // -------------------------------------------------------------------------
38 | // MARK: - Initialisation
39 |
40 | init(number: Int) {
41 | super.init()
42 |
43 | let ringMaterial = SCNMaterial()
44 | ringMaterial.diffuse.contents = UIImage(named: "art.scnassets/ringTexture")
45 | ringMaterial.diffuse.wrapS = .repeat
46 | ringMaterial.diffuse.wrapT = .repeat
47 | ringMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(3, 1, 1)
48 |
49 | let ring = SCNTorus(ringRadius: 5.0, pipeRadius: 0.5)
50 | ring.materials = [ringMaterial]
51 | let ringNode = SCNNode(geometry: ring)
52 | self.addChildNode(ringNode)
53 |
54 | ringNode.eulerAngles = SCNVector3(degreesToRadians(value: 90), 0, 0)
55 |
56 | let action1 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 360), duration: 3.0)
57 | ringNode.runAction(SCNAction.repeatForever(action1))
58 |
59 | // Contact box
60 | let boxMaterial = SCNMaterial()
61 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
62 |
63 | let box = SCNBox(width: 10.0, height: 10.0, length: 1.0, chamferRadius: 0.0)
64 | box.materials = [boxMaterial]
65 | let contactBox = SCNNode(geometry: box)
66 | contactBox.name = "ring"
67 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
68 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.ring
69 | self.addChildNode(contactBox)
70 |
71 | // New in Part 5: Add a number to the ring
72 | let numberMaterial = SCNMaterial()
73 | numberMaterial.diffuse.contents = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
74 |
75 | let text = SCNText(string: "\(number)", extrusionDepth: 0.01)
76 | text.materials = [numberMaterial]
77 | _number = SCNNode(geometry: text)
78 | _number.eulerAngles = SCNVector3(0, degreesToRadians(value: 180), 0)
79 | _number.scale = SCNVector3(0.7, 0.7, 0.7)
80 | _number.position = SCNVector3(2.0, 4.5, -0.7)
81 | self.addChildNode(_number)
82 |
83 | let action2 = SCNAction.scale(to: 0.7, duration: 1.0)
84 | let action3 = SCNAction.scale(to: 1.0, duration: 1.0)
85 | let action4 = SCNAction.sequence([action2, action3])
86 | _number.runAction(SCNAction.repeatForever(action4))
87 |
88 | self.state = .alive
89 | }
90 |
91 | // -------------------------------------------------------------------------
92 |
93 | required init(coder: NSCoder) {
94 | fatalError("Not yet implemented")
95 | }
96 |
97 | // -------------------------------------------------------------------------
98 |
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/partVI/Bullet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bullet.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 |
14 | class Bullet : GameObject {
15 | private static let speedDistance: CGFloat = 80.0 // Distance taken in time
16 | private static let lifeTime: TimeInterval = 3.0 // Life time of a bullet in s
17 |
18 | private var _enemy = false // Bullet show by player/enemy
19 | private var _speed: CGFloat = 2.0 // Speed of th ebullet
20 |
21 | private var _bulletNode: SCNNode!
22 |
23 | // -------------------------------------------------------------------------
24 | // MARK: - Getter/Setter
25 |
26 | var enemy: Bool {
27 | get {
28 | return _enemy
29 | }
30 | set(value) {
31 | _enemy = value
32 |
33 | if (_enemy) {
34 | _bulletNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
35 | _bulletNode.physicsBody!.categoryBitMask = Game.Physics.Categories.bullet
36 | _bulletNode.physicsBody!.contactTestBitMask = Game.Physics.Categories.player
37 | }
38 | else {
39 | _bulletNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
40 | _bulletNode.physicsBody!.categoryBitMask = Game.Physics.Categories.bullet
41 | _bulletNode.physicsBody!.contactTestBitMask = Game.Physics.Categories.enemy
42 | }
43 | }
44 | }
45 |
46 | // -------------------------------------------------------------------------
47 |
48 | override var description: String {
49 | get {
50 | if (self.enemy) {
51 | return "enemy bullet \(self.id)"
52 | }
53 | else {
54 | return "player bullet \(self.id)"
55 | }
56 | }
57 | }
58 |
59 | // -------------------------------------------------------------------------
60 | // MARK: - Fire bullet
61 |
62 | func fire(direction: PlaneDirection, sideDistance: CGFloat, fallDistance: CGFloat, speed: CGFloat = 2.0) {
63 | var moveAction: SCNAction?
64 |
65 | self.state = .alive
66 |
67 | _speed = speed
68 |
69 | switch (direction) {
70 | case .up:
71 | moveAction = SCNAction.moveBy(x: sideDistance, y: fallDistance, z: Bullet.speedDistance, duration: TimeInterval(10/_speed))
72 | case .down:
73 | moveAction = SCNAction.moveBy(x: -sideDistance, y: fallDistance, z: -Bullet.speedDistance, duration: TimeInterval(10/_speed))
74 | case .left:
75 | moveAction = SCNAction.moveBy(x: Bullet.speedDistance, y: fallDistance, z: 0, duration: TimeInterval(10/_speed))
76 | case .right:
77 | moveAction = SCNAction.moveBy(x: -Bullet.speedDistance, y: fallDistance, z: 0, duration: TimeInterval(10/_speed))
78 |
79 | default:
80 | return
81 | }
82 |
83 | self.runAction(SCNAction.repeatForever(moveAction!), forKey: "fire")
84 |
85 | let delay = SCNAction.wait(duration: Bullet.lifeTime)
86 | let action2 = SCNAction.run({ _ in
87 | rbDebug("Lifetime is over for \(self)")
88 |
89 | self.isHidden = true
90 | self.state = .died
91 | self.removeAllActions()
92 | })
93 |
94 | self.runAction(SCNAction.sequence([delay, action2]), forKey: "lifetime")
95 | }
96 |
97 | // -------------------------------------------------------------------------
98 | // MARK: - Collision handling
99 |
100 | override func collision(with object: GameObject, level: GameLevel) {
101 | // Collision with player and enemies are handled in their classes
102 | // Here the bullets are set themself to destroyed
103 | if (self.state == .died) {
104 | return
105 | }
106 |
107 | if let enemy = object as? Enemy {
108 | // Check for player bullet
109 | if !self.enemy {
110 | rbDebug("\(self) collided with \(object)")
111 |
112 | GameSound.explosion(enemy)
113 | enemy.hit()
114 |
115 | level.addPoints(enemy.points)
116 |
117 | }
118 | }
119 | else if let player = object as? Player {
120 | // Check for enemy bullet
121 | if self.enemy {
122 | rbDebug("\(self) collided with \(object)")
123 |
124 | GameSound.explosion(player)
125 | player.hit()
126 | }
127 | }
128 | else if let handicap = object as? Handicap {
129 | // Check for player bullet
130 | if !self.enemy {
131 | rbDebug("\(self) collided with \(object)")
132 |
133 | GameSound.explosion(handicap)
134 | handicap.hit()
135 |
136 | level.addPoints(handicap.points)
137 | }
138 | }
139 |
140 | // Destroy myself
141 | self.isHidden = true
142 | self.state = .died
143 |
144 | self.removeAllActions()
145 | }
146 |
147 | // -------------------------------------------------------------------------
148 | // MARK: - Initialisation
149 |
150 | init(enemy: Bool) {
151 | super.init()
152 |
153 | let material = SCNMaterial()
154 | material.diffuse.contents = UIColor.white
155 | let geometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0.0)
156 |
157 | _bulletNode = SCNNode(geometry: geometry)
158 |
159 | self.addChildNode(_bulletNode)
160 |
161 | self.enemy = enemy
162 |
163 | rbDebug("Create \(self)")
164 | }
165 |
166 | // -------------------------------------------------------------------------
167 |
168 | required init(coder: NSCoder) {
169 | fatalError("Not yet implemented")
170 | }
171 |
172 | // -------------------------------------------------------------------------
173 |
174 | }
175 |
176 |
--------------------------------------------------------------------------------
/partVI/Enemy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 | // Part 6: Created a enemy plane class
11 | //
12 |
13 | import SceneKit
14 |
15 | // -----------------------------------------------------------------------------
16 |
17 | class Enemy : Plane {
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - Propertiues
21 |
22 | override var description: String {
23 | get {
24 | return "enemy \(self.id)"
25 | }
26 | }
27 |
28 | // -------------------------------------------------------------------------
29 | // MARK: - Actions
30 |
31 | override func hit() {
32 | if let emitter = SCNParticleSystem(named: "art.scnassets/smoke.scnp", inDirectory: nil) {
33 | self.addParticleSystem(emitter)
34 | }
35 |
36 | moveDown()
37 | moveDown()
38 |
39 | SCNTransaction.begin()
40 | SCNTransaction.animationDuration = 1.0
41 |
42 | self.modelNode?.eulerAngles = SCNVector3(degreesToRadians(value: 40.0), degreesToRadians(value: 0.0), 0)
43 |
44 | SCNTransaction.commit()
45 | }
46 |
47 | // -------------------------------------------------------------------------
48 |
49 | override func start() {
50 | super.start()
51 | moveUp()
52 | }
53 |
54 | // -------------------------------------------------------------------------
55 |
56 | func fire(_ level: GameLevel) {
57 | if self.numberOfBullets == 0 {
58 | rbDebug("\(self) has no bullets anymore")
59 | return
60 | }
61 |
62 | var position = self.position
63 | position.z = position.z + 1.0
64 |
65 | GameSound.fire(self)
66 |
67 | // Create bullet and fire
68 | self.numberOfBullets -= 1
69 | let bullet = level.fireBullet(enemy: true, position: position, sideDistance: 0, fallDistance: 0)
70 |
71 | rbDebug("\(self) has fired bullet \(bullet)")
72 | }
73 |
74 | // -------------------------------------------------------------------------
75 | // MARK: - Game loop
76 |
77 | override func update(atTime time: TimeInterval, level: GameLevel) {
78 | super.update(atTime: time, level: level)
79 |
80 | if self.playerIsInDistance(level.player) && self.numberOfBullets > 0 {
81 | self.fire(level)
82 | }
83 | }
84 |
85 | // -------------------------------------------------------------------------
86 | // MARK: - Helper methods
87 |
88 | private func playerIsInDistance(_ player: Player?) -> Bool {
89 | if player == nil {
90 | return false
91 | }
92 |
93 | if self.position.distance(to: player!.position) <= Game.Plane.fireDistance {
94 | return true
95 | }
96 |
97 | return false
98 | }
99 |
100 |
101 | // -------------------------------------------------------------------------
102 | // MARK: - Initialisation
103 |
104 | override init() {
105 | super.init()
106 |
107 | // Set physics
108 | self.collissionNode!.name = "enemy-plane"
109 | self.collissionNode!.physicsBody?.categoryBitMask = Game.Physics.Categories.enemy
110 |
111 | self.flip = true
112 |
113 | self.state = .alive
114 | self.points = Game.Points.enemy
115 | self.numberOfBullets = Game.Bullets.enemy
116 | }
117 |
118 | // -------------------------------------------------------------------------
119 |
120 | required init(coder: NSCoder) {
121 | fatalError("Not yet implemented")
122 | }
123 |
124 | // -------------------------------------------------------------------------
125 |
126 | }
127 |
128 |
--------------------------------------------------------------------------------
/partVI/GameObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameObject.swift
3 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
4 | // https://rogerboesch.github.io/
5 | //
6 | // Created by Roger Boesch on 31.10.17.
7 | // Copyright © 2017 Roger Boesch. All rights reserved.
8 | //
9 |
10 | import SceneKit
11 |
12 | enum GameObjecState {
13 | case initialized, alive, died, stopped
14 | }
15 |
16 | class GameObject : SCNNode {
17 | private static var count: Int = 0
18 | static var level: GameLevel?
19 |
20 | private var _internalID: Int = 0
21 | private var _tag = 0
22 | private var _state = GameObjecState.initialized
23 |
24 | private var _points = 0
25 |
26 | // -------------------------------------------------------------------------
27 | // MARK: - Propertiues
28 |
29 | override var description: String {
30 | get {
31 | return "game object \(self.id)"
32 | }
33 | }
34 |
35 | // -------------------------------------------------------------------------
36 |
37 | var id: Int {
38 | return _internalID
39 | }
40 |
41 | // -------------------------------------------------------------------------
42 |
43 | var tag: Int {
44 | get {
45 | return _tag
46 | }
47 | set(value) {
48 | _tag = value
49 | }
50 | }
51 |
52 | // -------------------------------------------------------------------------
53 |
54 | var state: GameObjecState {
55 | get {
56 | return _state
57 | }
58 | set(value) {
59 | rbDebug("State of \(self) changed from \(_state) to \(value)")
60 | _state = value
61 | }
62 | }
63 |
64 | // -------------------------------------------------------------------------
65 |
66 | var points: Int {
67 | get {
68 | return _points
69 | }
70 | set(value) {
71 | _points = value
72 | }
73 | }
74 |
75 | // -------------------------------------------------------------------------
76 | // MARK: - Actions
77 |
78 | func hit() {} // Object get hit by another object
79 |
80 | // -------------------------------------------------------------------------
81 |
82 | func start() { }
83 |
84 | func stop() {
85 | // Stop object (release all)
86 | self.state = .stopped
87 | stopAllActions(self)
88 | }
89 |
90 | // -------------------------------------------------------------------------
91 | // MARK: - Game loop
92 |
93 | func update(atTime time: TimeInterval, level: GameLevel) {}
94 |
95 | // -------------------------------------------------------------------------
96 | // MARK: - Collision handling
97 |
98 | func collision(with object: GameObject, level: GameLevel) {}
99 |
100 | // -------------------------------------------------------------------------
101 | // MARK: - Helper methods
102 |
103 | func stopAllActions(_ node: SCNNode) {
104 | // It's important to stop all actions before we remove a game object
105 | // Otherwise they continue to run and result in difficult side effects.
106 | node.removeAllActions()
107 |
108 | for child in node.childNodes {
109 | child.removeAllActions()
110 | stopAllActions(child)
111 | }
112 | }
113 |
114 | // -------------------------------------------------------------------------
115 | // MARK: - Initialisation
116 |
117 | override init() {
118 | super.init()
119 |
120 | GameObject.count += 1
121 | _internalID = GameObject.count
122 | }
123 |
124 | // -------------------------------------------------------------------------
125 |
126 | required init?(coder aDecoder: NSCoder) {
127 | fatalError("init(coder aDecoder: NSCoder) is not implemented")
128 | }
129 |
130 | // -------------------------------------------------------------------------
131 | }
132 |
133 |
134 |
--------------------------------------------------------------------------------
/partVI/GameSettings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSettings.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/10/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 |
13 | struct Game {
14 |
15 | struct Points {
16 | static let ring: Int = 200 // Points get when fly trough a ring
17 | static let enemy: Int = 100 // Points get when shoot a enemy plane
18 | static let handicap: Int = 50 // Points get when shoot a handicap
19 | }
20 |
21 | struct Bullets {
22 | static let player: Int = Int.max // Number of bullets player has initially
23 | static let enemy: Int = 1 // Number of bullets an enemy plane has initially
24 | }
25 |
26 | struct Level {
27 | static let numberOfRings: Int = 10 // Number of rings in level
28 | static let numberOfEnemies: Int = 20 // Number of enemy planes in level
29 |
30 | static let width: CGFloat = 640 // Terrain width
31 | static let length: CGFloat = 960 // Terraiun length
32 | static let start: CGFloat = 100 // Start of player
33 |
34 | struct Fog {
35 | static let start: CGFloat = 20 // Begin of fog
36 | static let end: CGFloat = 300 // End of fog
37 | }
38 | }
39 |
40 | struct Plane {
41 | static let fireDistance: CGFloat = 70.0 // Fire distance (to player) for enemy
42 |
43 | static let actionTime: TimeInterval = 10.0 // Time used for move actions
44 | static let leftRightMoveDistance: CGFloat = 6.0 // Left/Right distance take in time
45 | static let upDownMoveDistance: CGFloat = 4.0 // Up/Down distance take in time
46 | static let speedDistance: CGFloat = 100.0 // Forward distance take in time
47 |
48 | static let upDownAngle: CGFloat = 5.0 // Angle when fly up or down
49 | static let leftRightAngle: CGFloat = 30.0 // Angle when fly left or right
50 |
51 | static let crashHeight: Float = 1.5 // Minimum height when crashed
52 | static let minimumHeight: Float = 4.0 // Minimum height
53 | static let maximumHeight: Float = 30.0 // Maximum height
54 | static let maximumLeft: Float = 360.0 // Maximum left
55 | static let maximumRight: Float = 30.0 // Maximum right
56 | }
57 |
58 | struct Objects {
59 | static let offset: CGFloat = 15 // Space between objects
60 | }
61 |
62 | struct Physics {
63 |
64 | // Category bits used for physics handling
65 | struct Categories {
66 | static let player: Int = 0b00000001
67 | static let ring: Int = 0b00000010
68 | static let enemy: Int = 0b00000100
69 | static let bullet: Int = 0b00001000
70 | }
71 | }
72 |
73 | struct Motion {
74 | static let threshold: Double = 0.2 // Minium threshold of accelerometer
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/partVI/GameSound.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSound.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import Foundation
12 | import SceneKit
13 | import AudioToolbox.AudioServices
14 |
15 | class GameSound {
16 | private static let explosion = SCNAudioSource(fileNamed: "sounds/explosion.wav")
17 | private static let fire = SCNAudioSource(fileNamed: "sounds/fire.wav")
18 | private static let bonus = SCNAudioSource(fileNamed: "sounds/bonus.wav")
19 |
20 | // -------------------------------------------------------------------------
21 | // MARK: - Vibrate
22 |
23 | static func vibrate() {
24 | rbDebug("GameSound: vibrate")
25 | AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
26 | }
27 |
28 | // -------------------------------------------------------------------------
29 |
30 | private static func play(_ name: String, source: SCNAudioSource, node: SCNNode) {
31 | let _ = GameAudioPlayer(name: name, source: source, node: node)
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 | // MARK: - Sound effects
36 |
37 | static func explosion(_ node: SCNNode) {
38 | guard explosion != nil else { return }
39 |
40 | GameSound.play("explosion", source: GameSound.explosion!, node: node)
41 | GameSound.vibrate()
42 | }
43 |
44 | // -------------------------------------------------------------------------
45 |
46 | static func fire(_ node: SCNNode) {
47 | guard fire != nil else { return }
48 |
49 | GameSound.play("fire", source: GameSound.fire!, node: node)
50 | GameSound.vibrate()
51 | }
52 |
53 | // -------------------------------------------------------------------------
54 |
55 | static func bonus(_ node: SCNNode) {
56 | guard bonus != nil else { return }
57 |
58 | GameSound.play("bonus", source: GameSound.bonus!, node: node)
59 | }
60 |
61 | // -------------------------------------------------------------------------
62 |
63 | }
64 |
65 | class GameAudioPlayer : SCNAudioPlayer {
66 | private var _node: SCNNode!
67 |
68 | init(name: String, source: SCNAudioSource, node: SCNNode) {
69 | super.init(source: source)
70 |
71 | node.addAudioPlayer(self)
72 | _node = node
73 |
74 | rbDebug("GameAudioPlayer: play \(name)")
75 |
76 | self.didFinishPlayback = {
77 | rbDebug("GameAudioPlayer: stopped \(name)")
78 |
79 | self._node.removeAudioPlayer(self)
80 | }
81 | }
82 |
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/partVI/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import UIKit
12 | import SceneKit
13 | import SpriteKit
14 | import CoreMotion
15 | import GameController
16 |
17 | class GameViewController: UIViewController, SCNSceneRendererDelegate {
18 | private var _sceneView: SCNView!
19 | private var _level: GameLevel!
20 | private var _hud: HUD!
21 |
22 | // New in Part 5: Use CoreMotion to fly the plane
23 | private var _motionManager = CMMotionManager()
24 | private var _startAttitude: CMAttitude? // Start attitude
25 | private var _currentAttitude: CMAttitude? // Current attitude
26 |
27 | // -------------------------------------------------------------------------
28 | // MARK: - Properties
29 |
30 | var sceneView: SCNView {
31 | return _sceneView
32 | }
33 |
34 | // -------------------------------------------------------------------------
35 |
36 | var hud: HUD {
37 | return _hud
38 | }
39 |
40 | // -------------------------------------------------------------------------
41 | // MARK: - Render delegate (New in Part 4)
42 |
43 | func renderer(_ renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval) {
44 | guard _level != nil else { return }
45 |
46 | _level.update(atTime: time)
47 | renderer.loops = true
48 | }
49 |
50 | // -------------------------------------------------------------------------
51 | // MARK: - Gesture recognoizers
52 |
53 | @objc private func handleTap(_ gestureRecognize: UITapGestureRecognizer) {
54 | // New in Part 4: A tap is used to restart the level (see tutorial)
55 | if _level.state == .loose || _level.state == .win {
56 | _level.stop()
57 | _level = nil
58 |
59 | DispatchQueue.main.async {
60 | // Create things in main thread
61 |
62 | let level = GameLevel()
63 | level.create()
64 |
65 | level.hud = self.hud
66 | self.hud.reset()
67 |
68 | self.sceneView.scene = level
69 | self._level = level
70 |
71 | self.hud.message("READY?", information: "- Touch screen to start -")
72 | }
73 | }
74 | // New in Part 5: A tap is used to start the level (see tutorial)
75 | else if _level.state == .ready {
76 | _startAttitude = _currentAttitude
77 | _level.start()
78 | }
79 | // New in Part 6: A tap is used to shot fire (see tutorial)
80 | else if _level.state == .play {
81 | _level.fire()
82 | }
83 | }
84 |
85 | // -------------------------------------------------------------------------
86 |
87 | @objc private func handleSwipe(_ gestureRecognize: UISwipeGestureRecognizer) {
88 | if _level.state != .play {
89 | return
90 | }
91 |
92 | if (gestureRecognize.direction == .left) {
93 | _level!.swipeLeft()
94 | }
95 | else if (gestureRecognize.direction == .right) {
96 | _level!.swipeRight()
97 | }
98 | else if (gestureRecognize.direction == .down) {
99 | _level!.swipeDown()
100 | }
101 | else if (gestureRecognize.direction == .up) {
102 | _level!.swipeUp()
103 | }
104 | }
105 |
106 | // -------------------------------------------------------------------------
107 | // MARK: - Motion handling
108 |
109 | private func motionDidChange(data: CMDeviceMotion) {
110 | _currentAttitude = data.attitude
111 |
112 | guard _level != nil, _level?.state == .play else { return }
113 |
114 | // Up/Down
115 | let diff1 = _startAttitude!.roll - _currentAttitude!.roll
116 |
117 | if (diff1 >= Game.Motion.threshold) {
118 | _level!.motionMoveUp()
119 | }
120 | else if (diff1 <= -Game.Motion.threshold) {
121 | _level!.motionMoveDown()
122 | }
123 | else {
124 | _level!.motionStopMovingUpDown()
125 | }
126 |
127 | let diff2 = _startAttitude!.pitch - _currentAttitude!.pitch
128 |
129 | if (diff2 >= Game.Motion.threshold) {
130 | _level!.motionMoveLeft()
131 | }
132 | else if (diff2 <= -Game.Motion.threshold) {
133 | _level!.motionMoveRight()
134 | }
135 | else {
136 | _level!.motionStopMovingLeftRight()
137 | }
138 | }
139 |
140 | // -------------------------------------------------------------------------
141 |
142 | private func setupMotionHandler() {
143 | if (GCController.controllers().count == 0 && _motionManager.isAccelerometerAvailable) {
144 | _motionManager.accelerometerUpdateInterval = 1/60.0
145 |
146 | _motionManager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: {(data, error) in
147 | self.motionDidChange(data: data!)
148 | })
149 | }
150 | }
151 |
152 | // -------------------------------------------------------------------------
153 | // MARK: - ViewController life cycle
154 |
155 | override func viewWillAppear(_ animated: Bool) {
156 | super.viewWillAppear(animated)
157 |
158 | // Part 3: HUD is created and assigned to view and game level
159 | _hud = HUD(size: self.view.bounds.size)
160 | _level.hud = _hud
161 | _sceneView.overlaySKScene = _hud.scene
162 |
163 | self.hud.message("READY?", information: "- Touch screen to start -")
164 | }
165 |
166 | // -------------------------------------------------------------------------
167 |
168 | override func viewDidLoad() {
169 | super.viewDidLoad()
170 |
171 | _level = GameLevel()
172 | _level.create()
173 |
174 | _sceneView = SCNView()
175 | _sceneView.scene = _level
176 | _sceneView.allowsCameraControl = false
177 | _sceneView.showsStatistics = false
178 | _sceneView.backgroundColor = UIColor.black
179 | _sceneView.delegate = self
180 |
181 | self.view = _sceneView
182 |
183 | setupMotionHandler()
184 |
185 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
186 | _sceneView!.addGestureRecognizer(tapGesture)
187 |
188 | let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
189 | swipeLeftGesture.direction = .left
190 | _sceneView!.addGestureRecognizer(swipeLeftGesture)
191 |
192 | let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
193 | swipeRightGesture.direction = .right
194 | _sceneView!.addGestureRecognizer(swipeRightGesture)
195 |
196 | let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
197 | swipeDownGesture.direction = .down
198 | _sceneView!.addGestureRecognizer(swipeDownGesture)
199 |
200 | let swipeUpGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
201 | swipeUpGesture.direction = .up
202 | _sceneView!.addGestureRecognizer(swipeUpGesture)
203 | }
204 |
205 | // -------------------------------------------------------------------------
206 |
207 | override func didReceiveMemoryWarning() {
208 | super.didReceiveMemoryWarning()
209 | }
210 |
211 | // -------------------------------------------------------------------------
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/partVI/HUD.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HUD.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 11.11.17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SpriteKit
12 |
13 | class HUD {
14 | private var _scene: SKScene!
15 | private let _points = SKLabelNode(text: "")
16 | private let _rings = SKLabelNode(text: "")
17 | private let _missedRings = SKLabelNode(text: "")
18 | private let _message = SKLabelNode(text: "")
19 | private let _info = SKLabelNode(text: "")
20 |
21 | private var _totalPoints = 0
22 |
23 | // -------------------------------------------------------------------------
24 | // MARK: - Properties
25 |
26 | var scene: SKScene {
27 | get {
28 | return _scene
29 | }
30 | }
31 |
32 | // -------------------------------------------------------------------------
33 |
34 | var points: Int {
35 | get {
36 | return _totalPoints
37 | }
38 | set(value) {
39 | _totalPoints = value
40 | _points.text = String(format: "%04d", _totalPoints)
41 | }
42 | }
43 |
44 | // -------------------------------------------------------------------------
45 |
46 | var rings: Int {
47 | get {
48 | return 0
49 | }
50 | set(value) {
51 | if value == 1 {
52 | _rings.text = String(format: "%d RING", value)
53 | }
54 | else {
55 | _rings.text = String(format: "%d RINGS", value)
56 | }
57 |
58 | // New in Part 4: Animated HUD informations (check RB+SKAction.swift for details)
59 | let scaling: CGFloat = 3
60 | let action = SKAction.zoomWithNode(_rings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
61 | _rings.run(action)
62 | }
63 | }
64 |
65 | // -------------------------------------------------------------------------
66 |
67 | var missedRings: Int {
68 | get {
69 | return 0
70 | }
71 | set(value) {
72 | _missedRings.text = String(format: "%d MISSED", value)
73 |
74 | if value > 0 {
75 | _missedRings.isHidden = false
76 | _missedRings.fontColor = UIColor.red
77 | }
78 | else {
79 | _missedRings.isHidden = true
80 | }
81 |
82 | let scaling: CGFloat = 3
83 | let action = SKAction.zoomWithNode(_missedRings, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
84 | _missedRings.run(action)
85 | }
86 | }
87 |
88 | // -------------------------------------------------------------------------
89 | // MARK: - Points handling
90 |
91 | private func changePoints(_ pointsToAdd: Int, total: Int, count: Int) {
92 | _totalPoints += pointsToAdd
93 | points = _totalPoints
94 |
95 | let scaling: CGFloat = 1.5
96 |
97 | let action = SKAction.zoomWithNode(_points, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.01)
98 | _points.run(action)
99 |
100 | if count < total {
101 | Run.after(0.01, {
102 | self.changePoints(1, total: total, count: count+1)
103 | })
104 | }
105 | }
106 |
107 | // -------------------------------------------------------------------------
108 |
109 | func addPoints(_ points: Int) {
110 | self.changePoints(1, total: points, count: 1)
111 | }
112 |
113 | // -------------------------------------------------------------------------
114 | // MARK: - Message handling
115 |
116 | func message(_ str: String, information: String? = nil) {
117 | // New in Part 4: Used for game over and win messages
118 | _message.text = str
119 | _message.isHidden = false
120 |
121 | let scaling: CGFloat = 10
122 | let action = SKAction.zoomWithNode(_message, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
123 | _message.run(action)
124 |
125 | if information != nil {
126 | info(information!)
127 | }
128 | }
129 |
130 | // -------------------------------------------------------------------------
131 |
132 | func info(_ str: String) {
133 | // New in Part 4: Uses for additional info when show messages
134 |
135 | _info.text = str
136 | _info.isHidden = false
137 |
138 | let scaling: CGFloat = 2
139 | let action = SKAction.zoomWithNode(_info, amount: CGPoint.make(scaling, scaling), oscillations: 1, duration: 0.5)
140 | _info.run(action)
141 | }
142 |
143 | // -------------------------------------------------------------------------
144 |
145 | func reset() {
146 | // New in Part 4: Reset is needed whenever start the level
147 |
148 | _message.text = ""
149 | _message.isHidden = true
150 |
151 | _info.text = ""
152 | _info.isHidden = true
153 |
154 | rings = 0
155 | missedRings = 0
156 | points = 0
157 | }
158 |
159 | // -------------------------------------------------------------------------
160 | // MARK: - Initialisation
161 |
162 | init(size: CGSize) {
163 | _scene = SKScene(size: size)
164 |
165 | _points.position = CGPoint(x: size.width/2, y: size.height-50)
166 | _points.horizontalAlignmentMode = .center
167 | _points.fontName = "MarkerFelt-Wide"
168 | _points.fontSize = 30
169 | _points.fontColor = UIColor.white
170 | _scene.addChild(_points)
171 |
172 | _rings.position = CGPoint(x: 40, y: size.height-50)
173 | _rings.horizontalAlignmentMode = .left
174 | _rings.fontName = "MarkerFelt-Wide"
175 | _rings.fontSize = 30
176 | _rings.fontColor = UIColor.white
177 | _scene.addChild(_rings)
178 |
179 | _missedRings.position = CGPoint(x: size.width-40, y: size.height-50)
180 | _missedRings.horizontalAlignmentMode = .right
181 | _missedRings.fontName = "MarkerFelt-Wide"
182 | _missedRings.fontSize = 30
183 | _missedRings.fontColor = UIColor.white
184 | _scene.addChild(_missedRings)
185 |
186 | _message.position = CGPoint(x: size.width/2, y: size.height/2)
187 | _message.horizontalAlignmentMode = .center
188 | _message.fontName = "MarkerFelt-Wide"
189 | _message.fontSize = 60
190 | _message.fontColor = UIColor.white
191 | _message.isHidden = true
192 | _scene.addChild(_message)
193 |
194 | _info.position = CGPoint(x: size.width/2, y: size.height/2-40)
195 | _info.horizontalAlignmentMode = .center
196 | _info.fontName = "MarkerFelt-Wide"
197 | _info.fontSize = 20
198 | _info.fontColor = UIColor.white
199 | _info.isHidden = true
200 | _scene.addChild(_info)
201 |
202 | reset()
203 | }
204 |
205 | // -------------------------------------------------------------------------
206 |
207 | required init?(coder aDecoder: NSCoder) {
208 | fatalError("init?(coder aDecoder: NSCoder) not implemented")
209 | }
210 |
211 | // -------------------------------------------------------------------------
212 | }
213 |
--------------------------------------------------------------------------------
/partVI/Handicap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Handicap.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Handicap : GameObject {
16 | private var _node: SCNNode!
17 | private var _height: CGFloat = 0
18 |
19 | // -------------------------------------------------------------------------
20 | // MARK: - Propertiues
21 |
22 | override var description: String {
23 | get {
24 | return "handicap \(self.id)"
25 | }
26 | }
27 |
28 | // -------------------------------------------------------------------------
29 |
30 | var height: CGFloat {
31 | get {
32 | return _height
33 | }
34 | }
35 |
36 | // -------------------------------------------------------------------------
37 | // MARK: - Actions
38 |
39 | override func hit() {
40 | if self.state != .alive {
41 | return
42 | }
43 |
44 | self.state = .died
45 |
46 | let action1 = SCNAction.moveBy(x: 0, y: -4, z: 0, duration: 0.15)
47 | _node.runAction(action1)
48 |
49 | let action2 = SCNAction.rotateBy(x: degreesToRadians(value: 30), y: 0, z: degreesToRadians(value: 15), duration: 0.3)
50 | _node.runAction(action2)
51 |
52 | if let emitter = SCNParticleSystem(named: "art.scnassets/fire.scnp", inDirectory: nil) {
53 | self.addParticleSystem(emitter)
54 | }
55 | }
56 |
57 | // -------------------------------------------------------------------------
58 | // MARK: - Initialisation
59 |
60 | override init() {
61 | super.init()
62 |
63 | let material = SCNMaterial()
64 | material.diffuse.contents = UIColor.random(list: UIGreenColorList)
65 | material.specular.contents = UIColor.white
66 | material.shininess = 1.0
67 |
68 | // Use some randomness in height, width and color
69 | let width = RBRandom.cgFloat(4.0, 9.0)
70 | _height = RBRandom.cgFloat(15.0, 25)
71 |
72 | var geometry: SCNGeometry!
73 | let rnd = RBRandom.integer(1, 3)
74 | if rnd == 1 {
75 | geometry = SCNBox(width: width, height: _height, length: 2.0, chamferRadius: 0.0)
76 | }
77 | else if rnd == 2 {
78 | geometry = SCNCylinder(radius: width, height: _height)
79 | }
80 | else {
81 | geometry = SCNCone(topRadius: 0.0, bottomRadius: width, height: _height)
82 | }
83 |
84 | geometry.materials = [material]
85 |
86 | _node = SCNNode(geometry: geometry)
87 | _node.name = "handicap"
88 | _node.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
89 | _node.physicsBody?.categoryBitMask = Game.Physics.Categories.enemy
90 | self.addChildNode(_node)
91 |
92 | self.state = .alive
93 | self.points = Game.Points.handicap
94 | }
95 |
96 | // -------------------------------------------------------------------------
97 |
98 | required init(coder: NSCoder) {
99 | fatalError("Not yet implemented")
100 | }
101 |
102 | // -------------------------------------------------------------------------
103 |
104 | }
105 |
106 |
107 |
--------------------------------------------------------------------------------
/partVI/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UIStatusBarHidden
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationLandscapeLeft
34 | UIInterfaceOrientationLandscapeRight
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/partVI/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 12/07/16.
8 | // Copyright © 2016 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Player : Plane {
16 | private let lookAtForwardPosition = SCNVector3Make(0.0, -1.0, 6.0)
17 | private let cameraFowardPosition = SCNVector3(x: 0, y: 1.0, z: -5)
18 |
19 | private var _lookAtNode: SCNNode?
20 | private var _cameraNode: SCNNode?
21 |
22 | private var _crashed = false
23 |
24 | // -------------------------------------------------------------------------
25 | // MARK: - Propertiues
26 |
27 | override var description: String {
28 | get {
29 | return "player \(self.id)"
30 | }
31 | }
32 |
33 | // -------------------------------------------------------------------------
34 | // MARK: - Actions
35 |
36 | func fire(_ level: GameLevel) {
37 | if self.numberOfBullets == 0 {
38 | rbDebug("\(self) has no bullets anymore")
39 | return
40 | }
41 |
42 | var position = self.position
43 | position.z = position.z + 1.0
44 |
45 | GameSound.fire(self)
46 |
47 | var y: CGFloat = 0
48 | if self.upDownDirection == .up {
49 | y = 20
50 | }
51 | else if self.upDownDirection == .down {
52 | y = -20
53 | }
54 |
55 | var x: CGFloat = 0
56 | if self.leftRightDirection == .left {
57 | x = 40
58 | }
59 | else if self.leftRightDirection == .right {
60 | x = -40
61 | }
62 |
63 | // Create bullet and fire
64 | self.numberOfBullets -= 1
65 | let bullet = level.fireBullet(enemy: false, position: position, sideDistance: x, fallDistance: y)
66 |
67 | rbDebug("\(self) has fired bullet \(bullet)")
68 | }
69 |
70 | // -------------------------------------------------------------------------
71 |
72 | override func hit() {
73 | if _crashed {
74 | return
75 | }
76 |
77 | if let emitter = SCNParticleSystem(named: "art.scnassets/smoke.scnp", inDirectory: nil) {
78 | self.addParticleSystem(emitter)
79 | }
80 |
81 | _crashed = true
82 |
83 | moveDown()
84 | moveDown()
85 |
86 | SCNTransaction.begin()
87 | SCNTransaction.animationDuration = 1.0
88 |
89 | self.modelNode?.eulerAngles = SCNVector3(degreesToRadians(value: 40.0), degreesToRadians(value: 0.0), 0)
90 |
91 | SCNTransaction.commit()
92 | }
93 |
94 | // -------------------------------------------------------------------------
95 | // MARK: - Game loop
96 |
97 | override func update(atTime time: TimeInterval, level: GameLevel) {
98 | if !_crashed {
99 | super.update(atTime: time, level: level)
100 | return
101 | }
102 |
103 | if (self.position.y <= Game.Plane.crashHeight) {
104 | self.die()
105 | self.modelNode?.isHidden = false
106 |
107 | GameSound.explosion(self)
108 |
109 | level.crashed()
110 | }
111 | }
112 |
113 | // -------------------------------------------------------------------------
114 | // MARK: - Collision handling
115 |
116 | override func collision(with object: GameObject, level: GameLevel) {
117 | if self.state != .alive {
118 | return
119 | }
120 |
121 | if let ring = object as? Ring {
122 | rbDebug("\(self) collided with \(object)")
123 |
124 | if ring.state != .alive {
125 | return
126 | }
127 |
128 | GameSound.bonus(self)
129 |
130 | level.flyTrough(ring)
131 | ring.hit()
132 | }
133 | else if let handicap = object as? Handicap {
134 | if self.state != .alive {
135 | return
136 | }
137 |
138 | rbDebug("\(self) collided with \(object)")
139 |
140 | GameSound.explosion(self)
141 |
142 | level.touchedHandicap(handicap)
143 | handicap.hit()
144 |
145 | self.die()
146 | }
147 | else if let enemy = object as? Enemy {
148 | if enemy.state != .alive {
149 | return
150 | }
151 |
152 | rbDebug("\(self) collided with \(object)")
153 |
154 | GameSound.explosion(self)
155 |
156 | enemy.hit()
157 | self.hit()
158 | }
159 | }
160 |
161 | // -------------------------------------------------------------------------
162 | // MARK: - Camera adjustment
163 |
164 | private func adjustCamera() {
165 | // New in Part 5: move the camera according to the fly direction
166 | var position = _cameraNode!.position
167 |
168 | if (self.leftRightDirection == .left) {
169 | position.x = 1.0
170 | }
171 | else if (self.leftRightDirection == .right) {
172 | position.x = -1.0
173 | }
174 | else if (self.leftRightDirection == .none) {
175 | position.x = 0.1
176 | }
177 |
178 | SCNTransaction.begin()
179 | SCNTransaction.animationDuration = 1.0
180 |
181 | _cameraNode?.position = position
182 |
183 | SCNTransaction.commit()
184 | }
185 |
186 | // -------------------------------------------------------------------------
187 | // MARK: - New in Part 5: Move Actions
188 |
189 | override func moveUp() {
190 | if _crashed {
191 | return
192 | }
193 |
194 | let oldDirection = self.upDownDirection
195 |
196 | super.moveUp()
197 |
198 | if oldDirection != self.upDownDirection {
199 | adjustCamera()
200 | }
201 | }
202 |
203 | // -------------------------------------------------------------------------
204 |
205 | override func moveDown() {
206 | let oldDirection = self.upDownDirection
207 |
208 | super.moveDown()
209 |
210 | if oldDirection != self.upDownDirection {
211 | adjustCamera()
212 | }
213 | }
214 |
215 | // -------------------------------------------------------------------------
216 |
217 | override func stopMovingUpDown() {
218 | if _crashed {
219 | return
220 | }
221 |
222 | let oldDirection = self.upDownDirection
223 |
224 | super.stopMovingUpDown()
225 |
226 | if oldDirection != self.upDownDirection {
227 | adjustCamera()
228 | }
229 | }
230 |
231 | // -------------------------------------------------------------------------
232 |
233 | override func moveLeft() {
234 | let oldDirection = self.leftRightDirection
235 |
236 | super.moveLeft()
237 |
238 | if oldDirection != self.leftRightDirection {
239 | adjustCamera()
240 | }
241 | }
242 |
243 | // -------------------------------------------------------------------------
244 |
245 | override func moveRight() {
246 | let oldDirection = self.leftRightDirection
247 |
248 | super.moveRight()
249 |
250 | if oldDirection != self.leftRightDirection {
251 | adjustCamera()
252 | }
253 | }
254 |
255 | // -------------------------------------------------------------------------
256 |
257 | override func stopMovingLeftRight() {
258 | let oldDirection = self.leftRightDirection
259 |
260 | super.stopMovingLeftRight()
261 |
262 | if oldDirection != self.leftRightDirection {
263 | adjustCamera()
264 | }
265 | }
266 |
267 | // -------------------------------------------------------------------------
268 | // MARK: - Initialisation
269 |
270 | override init() {
271 | super.init()
272 |
273 | // Set physics
274 | self.collissionNode!.name = "player"
275 | self.collissionNode!.physicsBody?.categoryBitMask = Game.Physics.Categories.player
276 | self.collissionNode!.physicsBody!.contactTestBitMask = Game.Physics.Categories.ring | Game.Physics.Categories.enemy
277 |
278 | // Look at Node
279 | _lookAtNode = SCNNode()
280 | _lookAtNode!.position = lookAtForwardPosition
281 | addChildNode(_lookAtNode!)
282 |
283 | // Camera
284 | let camera = SCNCamera()
285 | camera.zNear = 0.1
286 | camera.zFar = 600
287 |
288 | _cameraNode = SCNNode()
289 | _cameraNode!.camera = camera
290 | _cameraNode!.position = cameraFowardPosition
291 | self.addChildNode(_cameraNode!)
292 |
293 | // Link them
294 | let constraint1 = SCNLookAtConstraint(target: _lookAtNode)
295 | constraint1.isGimbalLockEnabled = true
296 | _cameraNode!.constraints = [constraint1]
297 |
298 | // Create a spotlight at the player
299 | let spotLight = SCNLight()
300 | spotLight.type = SCNLight.LightType.spot
301 | spotLight.spotInnerAngle = 40.0
302 | spotLight.spotOuterAngle = 80.0
303 | spotLight.castsShadow = true
304 | spotLight.color = UIColor.white
305 | let spotLightNode = SCNNode()
306 | spotLightNode.light = spotLight
307 | spotLightNode.position = SCNVector3(x: 0.0, y: 25.0, z: -1.0)
308 | self.addChildNode(spotLightNode)
309 |
310 | // Link it
311 | let constraint2 = SCNLookAtConstraint(target: self)
312 | constraint2.isGimbalLockEnabled = true
313 | spotLightNode.constraints = [constraint2]
314 |
315 | // Create additional omni light
316 | let lightNode = SCNNode()
317 | lightNode.light = SCNLight()
318 | lightNode.light!.type = SCNLight.LightType.omni
319 | lightNode.light!.color = UIColor.darkGray
320 | lightNode.position = SCNVector3(x: 0, y: 100.00, z: -2)
321 | self.addChildNode(lightNode)
322 |
323 | self.state = .alive
324 | self.numberOfBullets = Game.Bullets.player
325 | }
326 |
327 | // -------------------------------------------------------------------------
328 |
329 | required init(coder: NSCoder) {
330 | fatalError("Not yet implemented")
331 | }
332 |
333 | // -------------------------------------------------------------------------
334 |
335 | }
336 |
--------------------------------------------------------------------------------
/partVI/Ring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ring.swift
3 | //
4 | // Part 6 of the SceneKit Tutorial Series 'From Zero to Hero' at:
5 | // https://rogerboesch.github.io/
6 | //
7 | // Created by Roger Boesch on 13/12/17.
8 | // Copyright © 2017 Roger Boesch. All rights reserved.
9 | //
10 |
11 | import SceneKit
12 |
13 | // -----------------------------------------------------------------------------
14 |
15 | class Ring : GameObject {
16 | private var _number: SCNNode!
17 |
18 | // -------------------------------------------------------------------------
19 | // MARK: - Propertiues
20 |
21 | override var description: String {
22 | get {
23 | return "ring \(self.id)"
24 | }
25 | }
26 |
27 | // -------------------------------------------------------------------------
28 | // MARK: - Actions
29 |
30 | override func hit() {
31 | self.state = .died
32 |
33 | _number.isHidden = false
34 | _number.removeAllActions()
35 | }
36 |
37 | // -------------------------------------------------------------------------
38 | // MARK: - Initialisation
39 |
40 | init(number: Int) {
41 | super.init()
42 |
43 | let ringMaterial = SCNMaterial()
44 | ringMaterial.diffuse.contents = UIImage(named: "art.scnassets/ringTexture")
45 | ringMaterial.diffuse.wrapS = .repeat
46 | ringMaterial.diffuse.wrapT = .repeat
47 | ringMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(3, 1, 1)
48 |
49 | let ring = SCNTorus(ringRadius: 5.0, pipeRadius: 0.5)
50 | ring.materials = [ringMaterial]
51 | let ringNode = SCNNode(geometry: ring)
52 | self.addChildNode(ringNode)
53 |
54 | ringNode.eulerAngles = SCNVector3(degreesToRadians(value: 90), 0, 0)
55 |
56 | let action1 = SCNAction.rotateBy(x: 0, y: 0, z: degreesToRadians(value: 360), duration: 3.0)
57 | ringNode.runAction(SCNAction.repeatForever(action1))
58 |
59 | // Contact box
60 | let boxMaterial = SCNMaterial()
61 | boxMaterial.diffuse.contents = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
62 |
63 | let box = SCNBox(width: 10.0, height: 10.0, length: 1.0, chamferRadius: 0.0)
64 | box.materials = [boxMaterial]
65 | let contactBox = SCNNode(geometry: box)
66 | contactBox.name = "ring"
67 | contactBox.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
68 | contactBox.physicsBody?.categoryBitMask = Game.Physics.Categories.ring
69 | self.addChildNode(contactBox)
70 |
71 | // New in Part 5: Add a number to the ring
72 | let numberMaterial = SCNMaterial()
73 | numberMaterial.diffuse.contents = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
74 |
75 | let text = SCNText(string: "\(number)", extrusionDepth: 0.01)
76 | text.materials = [numberMaterial]
77 | _number = SCNNode(geometry: text)
78 | _number.eulerAngles = SCNVector3(0, degreesToRadians(value: 180), 0)
79 | _number.scale = SCNVector3(0.7, 0.7, 0.7)
80 | _number.position = SCNVector3(2.0, 4.5, -0.7)
81 | self.addChildNode(_number)
82 |
83 | let action2 = SCNAction.scale(to: 0.7, duration: 1.0)
84 | let action3 = SCNAction.scale(to: 1.0, duration: 1.0)
85 | let action4 = SCNAction.sequence([action2, action3])
86 | _number.runAction(SCNAction.repeatForever(action4))
87 |
88 | self.state = .alive
89 | self.points = Game.Points.ring
90 | }
91 |
92 | // -------------------------------------------------------------------------
93 |
94 | required init(coder: NSCoder) {
95 | fatalError("Not yet implemented")
96 | }
97 |
98 | // -------------------------------------------------------------------------
99 |
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/screenshots/screenshot0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot0.png
--------------------------------------------------------------------------------
/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/screenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot3.png
--------------------------------------------------------------------------------
/screenshots/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot4.png
--------------------------------------------------------------------------------
/screenshots/screenshot5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot5.png
--------------------------------------------------------------------------------
/screenshots/screenshot6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/screenshots/screenshot6.png
--------------------------------------------------------------------------------
/shared/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | //
4 | // Created by Roger Boesch on 12/07/16.
5 | // Copyright © 2016 Roger Boesch. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | private var _window: UIWindow?
14 | private var _gameViewController: GameViewController?
15 |
16 | // ------------------------------------------------------------------------------
17 |
18 | func applicationDidFinishLaunching(_ application: UIApplication) {
19 | _gameViewController = GameViewController()
20 |
21 | _window = UIWindow(frame: UIScreen.main.bounds)
22 | _window?.rootViewController = _gameViewController
23 | _window?.makeKeyAndVisible()
24 | }
25 |
26 | // ------------------------------------------------------------------------------
27 |
28 | func applicationWillResignActive(_ application: UIApplication) {}
29 | func applicationDidEnterBackground(_ application: UIApplication) {}
30 | func applicationWillEnterForeground(_ application: UIApplication) {}
31 | func applicationDidBecomeActive(_ application: UIApplication) {}
32 | func applicationWillTerminate(_ application: UIApplication) {}
33 |
34 | // ------------------------------------------------------------------------------
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/shared/RB+CGPoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RB+CGPoint.swift
3 | // CGRect extensions
4 | //
5 | // Created by Roger Boesch on 01/01/16.
6 | // Copyright © 2016 Roger Boesch All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import AppKit
11 | #else
12 | import UIKit
13 | #endif
14 |
15 | // -----------------------------------------------------------------------------
16 | // MARK: - Make a CGPoint
17 |
18 | extension CGPoint {
19 |
20 | static func make(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
21 | return CGPoint(x: x, y: y)
22 | }
23 |
24 | }
25 |
26 | // -----------------------------------------------------------------------------
27 |
28 | public extension CGPoint {
29 |
30 | // ------------------------------------------------------------------------------
31 |
32 | public init(vector: CGVector) {
33 | self.init(x: vector.dx, y: vector.dy)
34 | }
35 |
36 | // ------------------------------------------------------------------------------
37 |
38 | public init(angle: CGFloat) {
39 | self.init(x: cos(angle), y: sin(angle))
40 | }
41 |
42 | // ------------------------------------------------------------------------------
43 |
44 | public mutating func offset(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
45 | x += dx
46 | y += dy
47 |
48 | return self
49 | }
50 |
51 | // ------------------------------------------------------------------------------
52 |
53 | public func length() -> CGFloat {
54 | return sqrt(x*x + y*y)
55 | }
56 |
57 | // ------------------------------------------------------------------------------
58 |
59 | public func lengthSquared() -> CGFloat {
60 | return x*x + y*y
61 | }
62 |
63 | // ------------------------------------------------------------------------------
64 |
65 | func normalized() -> CGPoint {
66 | let len = length()
67 |
68 | return len>0 ? self / len : CGPoint.zero
69 | }
70 |
71 | // ------------------------------------------------------------------------------
72 |
73 | public mutating func normalize() -> CGPoint {
74 | self = normalized()
75 |
76 | return self
77 | }
78 |
79 | // ------------------------------------------------------------------------------
80 |
81 | public func distanceTo(_ point: CGPoint) -> CGFloat {
82 | return (self - point).length()
83 | }
84 |
85 | // ------------------------------------------------------------------------------
86 |
87 | public var angle: CGFloat {
88 | return atan2(y, x)
89 | }
90 |
91 | // ------------------------------------------------------------------------------
92 |
93 | }
94 |
95 | // ------------------------------------------------------------------------------
96 |
97 | public func + (left: CGPoint, right: CGPoint) -> CGPoint {
98 | return CGPoint(x: left.x + right.x, y: left.y + right.y)
99 | }
100 |
101 | // ------------------------------------------------------------------------------
102 |
103 | public func += (left: inout CGPoint, right: CGPoint) {
104 | left = left + right
105 | }
106 |
107 | // ------------------------------------------------------------------------------
108 |
109 | public func + (left: CGPoint, right: CGVector) -> CGPoint {
110 | return CGPoint(x: left.x + right.dx, y: left.y + right.dy)
111 | }
112 |
113 | // ------------------------------------------------------------------------------
114 |
115 | public func += (left: inout CGPoint, right: CGVector) {
116 | left = left + right
117 | }
118 |
119 | // ------------------------------------------------------------------------------
120 |
121 | public func - (left: CGPoint, right: CGPoint) -> CGPoint {
122 | return CGPoint(x: left.x - right.x, y: left.y - right.y)
123 | }
124 |
125 | // ------------------------------------------------------------------------------
126 |
127 | public func -= (left: inout CGPoint, right: CGPoint) {
128 | left = left - right
129 | }
130 |
131 | // ------------------------------------------------------------------------------
132 |
133 | public func - (left: CGPoint, right: CGVector) -> CGPoint {
134 | return CGPoint(x: left.x - right.dx, y: left.y - right.dy)
135 | }
136 |
137 | // ------------------------------------------------------------------------------
138 |
139 | public func -= (left: inout CGPoint, right: CGVector) {
140 | left = left - right
141 | }
142 |
143 | // ------------------------------------------------------------------------------
144 |
145 | public func * (left: CGPoint, right: CGPoint) -> CGPoint {
146 | return CGPoint(x: left.x * right.x, y: left.y * right.y)
147 | }
148 |
149 | // ------------------------------------------------------------------------------
150 |
151 | public func *= (left: inout CGPoint, right: CGPoint) {
152 | left = left * right
153 | }
154 |
155 | // ------------------------------------------------------------------------------
156 |
157 | public func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
158 | return CGPoint(x: point.x * scalar, y: point.y * scalar)
159 | }
160 |
161 | // ------------------------------------------------------------------------------
162 |
163 | public func *= (point: inout CGPoint, scalar: CGFloat) {
164 | point = point * scalar
165 | }
166 |
167 | // ------------------------------------------------------------------------------
168 |
169 | public func * (left: CGPoint, right: CGVector) -> CGPoint {
170 | return CGPoint(x: left.x * right.dx, y: left.y * right.dy)
171 | }
172 |
173 | // ------------------------------------------------------------------------------
174 |
175 | public func *= (left: inout CGPoint, right: CGVector) {
176 | left = left * right
177 | }
178 |
179 | // ------------------------------------------------------------------------------
180 |
181 | public func / (left: CGPoint, right: CGPoint) -> CGPoint {
182 | return CGPoint(x: left.x / right.x, y: left.y / right.y)
183 | }
184 |
185 | // ------------------------------------------------------------------------------
186 |
187 | public func /= (left: inout CGPoint, right: CGPoint) {
188 | left = left / right
189 | }
190 |
191 | // ------------------------------------------------------------------------------
192 |
193 | public func / (point: CGPoint, scalar: CGFloat) -> CGPoint {
194 | return CGPoint(x: point.x / scalar, y: point.y / scalar)
195 | }
196 |
197 | // ------------------------------------------------------------------------------
198 |
199 | public func /= (point: inout CGPoint, scalar: CGFloat) {
200 | point = point / scalar
201 | }
202 |
203 | // ------------------------------------------------------------------------------
204 |
205 | public func / (left: CGPoint, right: CGVector) -> CGPoint {
206 | return CGPoint(x: left.x / right.dx, y: left.y / right.dy)
207 | }
208 |
209 | // ------------------------------------------------------------------------------
210 |
211 | public func /= (left: inout CGPoint, right: CGVector) {
212 | left = left / right
213 | }
214 |
215 | // ------------------------------------------------------------------------------
216 |
217 | public func lerp(_ start: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint {
218 | return CGPoint(x: start.x + (end.x - start.x)*t, y: start.y + (end.y - start.y)*t)
219 | }
220 |
221 | // ------------------------------------------------------------------------------
222 |
--------------------------------------------------------------------------------
/shared/RB+SCNVector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RB+SCNVector.swift
3 | // SCNVector3 extension
4 | //
5 | // Created by Roger Boesch on 1/09/17.
6 | // Copyright © 2017 Roger Boesch. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | extension SCNVector3 {
12 |
13 | func distance(to destination: SCNVector3) -> CGFloat {
14 | let dx = destination.x - x
15 | let dy = destination.y - y
16 | let dz = destination.z - z
17 |
18 | return CGFloat(sqrt(dx*dx + dy*dy + dz*dz))
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/shared/RB+SKAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKAction+Extensions.swift
3 | // SpriteKit action extensions
4 | //
5 | // Created by Roger Boesch on 19/09/13.
6 | // Copyright © 2015 Roger Boesch. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | // -----------------------------------------------------------------------------
12 | // MARK: - Timing functions
13 |
14 | func SKTTimingFunctionLinear(_ t: CGFloat) -> CGFloat {
15 | return t
16 | }
17 |
18 | func SKTCreateShakeFunction(_ oscillations: Int) -> (CGFloat) -> CGFloat {
19 | return { t in -pow(2.0, -10.0 * t) * sin(t * pi * CGFloat(oscillations) * 2.0) + 1.0 }
20 | }
21 |
22 | // -----------------------------------------------------------------------------
23 | // MARK: - Effects
24 |
25 | class SKTEffect {
26 |
27 | unowned var node: SKNode
28 | var duration: TimeInterval
29 | open var timingFunction: ((CGFloat) -> CGFloat)?
30 |
31 | init(node: SKNode, duration: TimeInterval) {
32 | self.node = node
33 | self.duration = duration
34 |
35 | timingFunction = SKTTimingFunctionLinear
36 | }
37 |
38 | func update(_ t: CGFloat) { }
39 | }
40 |
41 | class SKTScaleEffect: SKTEffect {
42 | var startScale: CGPoint
43 | var delta: CGPoint
44 | var previousScale: CGPoint
45 |
46 | init(node: SKNode, duration: TimeInterval, startScale: CGPoint, endScale: CGPoint) {
47 | previousScale = CGPoint(x: node.xScale, y: node.yScale)
48 | self.startScale = startScale
49 | delta = endScale - startScale
50 |
51 | super.init(node: node, duration: duration)
52 | }
53 |
54 | override func update(_ t: CGFloat) {
55 | let newScale = startScale + delta*t
56 | let diff = newScale / previousScale
57 | previousScale = newScale
58 | node.xScale *= diff.x
59 | node.yScale *= diff.y
60 | }
61 | }
62 |
63 | // -----------------------------------------------------------------------------
64 | // MARK: - Actions
65 |
66 | extension SKAction {
67 |
68 | class func actionWithEffect(_ effect: SKTEffect) -> SKAction {
69 | return SKAction.customAction(withDuration: effect.duration) { node, elapsedTime in
70 | var t = elapsedTime / CGFloat(effect.duration)
71 |
72 | if let timingFunction = effect.timingFunction {
73 | t = timingFunction(t)
74 | }
75 |
76 | effect.update(t)
77 | }
78 | }
79 |
80 |
81 | class func zoomWithNode(_ node: SKNode, amount: CGPoint, oscillations: Int, duration: TimeInterval) -> SKAction {
82 | let oldScale = CGPoint(x: node.xScale, y: node.yScale)
83 | let newScale = oldScale * amount
84 |
85 | let effect = SKTScaleEffect(node: node, duration: duration, startScale: newScale, endScale: oldScale)
86 | effect.timingFunction = SKTCreateShakeFunction(oscillations)
87 |
88 | return SKAction.actionWithEffect(effect)
89 | }
90 |
91 | // -------------------------------------------------------------------------
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/shared/RB+UIColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RB+UIColor.swift
3 | // UIColor extensions
4 | //
5 | // Created by Roger Boesch on 01/11/15.
6 | // Copyright © 2016 Roger Boesch All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | // -----------------------------------------------------------------------------
12 |
13 | let UIRedColorList:[UIColor] = [
14 | UIColor(hex: "#800000"),
15 | UIColor(hex: "#AA0000"),
16 | UIColor(hex: "#D40000"),
17 | UIColor(hex: "#FF0000"),
18 | UIColor(hex: "#FF2A2A")
19 | ]
20 |
21 | // -----------------------------------------------------------------------------
22 |
23 | let UIPurpleColorList:[UIColor] = [
24 | UIColor(hex: "#660067"),
25 | UIColor(hex: "#810080"),
26 | UIColor(hex: "#BE2AED"),
27 | UIColor(hex: "#D997FF"),
28 | UIColor(hex: "#F0BCFF")
29 | ]
30 |
31 | // -----------------------------------------------------------------------------
32 |
33 | let UINeonColorList:[UIColor] = [
34 | UIColor(hex: "#FF15AC"),
35 | UIColor(hex: "#FF6900"),
36 | UIColor(hex: "#FFFF01"),
37 | UIColor(hex: "#00FF01"),
38 | UIColor(hex: "#01E6f0")
39 | ]
40 |
41 | // -----------------------------------------------------------------------------
42 |
43 | let UIGreenColorList:[UIColor] = [
44 | UIColor(hex: "#075907"),
45 | UIColor(hex: "#097609"),
46 | UIColor(hex: "#70AF1A"),
47 | UIColor(hex: "#B9D40B"),
48 | UIColor(hex: "#E5EB0B")
49 | ]
50 |
51 | // -----------------------------------------------------------------------------
52 |
53 | let UIGrayColorList:[UIColor] = [
54 | UIColor(hex: "#C0CBCB"),
55 | UIColor(hex: "#CAC6BF"),
56 | UIColor(hex: "#B4ACA1"),
57 | UIColor(hex: "#E5E4dE"),
58 | UIColor(hex: "#C5C1B7"),
59 | UIColor(hex: "#78797D")
60 | ]
61 |
62 | // -----------------------------------------------------------------------------
63 | // MARK: - Hex support
64 |
65 | extension UIColor {
66 |
67 | public convenience init(hex: String) {
68 | var str = hex
69 |
70 | if str.hasPrefix("#") {
71 | let index = str.index(str.startIndex, offsetBy: 1)
72 | str = String(str[index...])
73 | }
74 |
75 | if (str.count == 6) {
76 | str = "\(str)ff"
77 | }
78 |
79 | if str.count == 8 {
80 | let scanner = Scanner(string: String(str))
81 | var hexNumber: UInt64 = 0
82 |
83 | if scanner.scanHexInt64(&hexNumber) {
84 | let r = (hexNumber & 0xff000000) >> 24
85 | let g = (hexNumber & 0x00ff0000) >> 16
86 | let b = (hexNumber & 0x0000ff00) >> 8
87 | let a = (hexNumber & 0x000000ff)
88 |
89 | self.init(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(a)/255.0)
90 | return
91 | }
92 | }
93 |
94 | self.init(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
95 | }
96 | }
97 |
98 | // -----------------------------------------------------------------------------
99 | // MARK: - Random colors
100 |
101 | extension UIColor {
102 |
103 | public static func random(list: [UIColor]) -> UIColor {
104 | let maxValue = list.count
105 | let rand = RBRandom.integer(0, maxValue-1)
106 |
107 | return list[rand]
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/shared/RBLog.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RBLog.swift
3 | // (Simple-) Logging functionality
4 | //
5 | // Created by Roger Boesch on 02/04/16.
6 | // Copyright © 2016 Roger Boesch All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SceneKit
11 |
12 | enum RBLogSeverity : Int {
13 | case debug = 0
14 | case info = 1
15 | case warning = 2
16 | case error = 3
17 | case none = 4
18 | }
19 |
20 | public class RBLog: NSObject {
21 | static var _severity = RBLogSeverity.debug
22 |
23 | // -----------------------------------------------------------------------------
24 | // MARK: - Properties
25 |
26 | static var severity: RBLogSeverity {
27 | get {
28 | return _severity
29 | }
30 | set(value) {
31 | _severity = value
32 | }
33 | }
34 |
35 | // -------------------------------------------------------------------------
36 | // MARK: - Logging severity
37 |
38 | static func error(message: String) {
39 | if (RBLogSeverity.error.rawValue >= RBLog.severity.rawValue) {
40 | RBLog.log(message: message, severity: "⛔️")
41 | }
42 | }
43 |
44 | // -------------------------------------------------------------------------
45 |
46 | static func warning(message: String) {
47 | if (RBLogSeverity.warning.rawValue >= RBLog.severity.rawValue) {
48 | RBLog.log(message: message, severity: "⚠️")
49 | }
50 | }
51 |
52 | // -------------------------------------------------------------------------
53 |
54 | static func info(message: String) {
55 | if (RBLogSeverity.info.rawValue >= RBLog.severity.rawValue) {
56 | RBLog.log(message: message, severity: "▷")
57 | }
58 | }
59 |
60 | // -------------------------------------------------------------------------
61 |
62 | static func debug(message: String) {
63 | if (RBLogSeverity.debug.rawValue >= RBLog.severity.rawValue) {
64 | RBLog.log(message: message, severity: "→")
65 | }
66 | }
67 |
68 | // -------------------------------------------------------------------------
69 | // MARK: - Write logs
70 |
71 | private static func log(message: String, severity: String) {
72 | RBLog.write(message: "\(severity) \(message)")
73 | }
74 |
75 | // -------------------------------------------------------------------------
76 |
77 | public static func write(message: String) {
78 | print(message)
79 | }
80 |
81 | // -------------------------------------------------------------------------
82 |
83 | }
84 |
85 | // -----------------------------------------------------------------------------
86 | // MARK: - Short functions
87 |
88 | func rbError(_ message: String, _ args: CVarArg...) {
89 | let str = String(format: message, arguments: args)
90 | RBLog.error(message: str)
91 | }
92 |
93 | // -----------------------------------------------------------------------------
94 |
95 | func rbWarning(_ message: String, _ args: CVarArg...) {
96 | let str = String(format: message, arguments: args)
97 | RBLog.warning(message: str)
98 | }
99 |
100 | // -----------------------------------------------------------------------------
101 |
102 | func rbInfo(_ message: String, _ args: CVarArg...) {
103 | let str = String(format: message, arguments: args)
104 | RBLog.info(message: str)
105 | }
106 |
107 | // -----------------------------------------------------------------------------
108 |
109 | func rbDebug(_ message: String, _ args: CVarArg...) {
110 | let str = String(format: message, arguments: args)
111 | RBLog.debug(message: str)
112 | }
113 |
114 | // -----------------------------------------------------------------------------
115 |
--------------------------------------------------------------------------------
/shared/RBPerlinNoiseGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RBPerlinNoiseGenerator.swift
3 | // Perlin noise generator (used for terrain class)
4 | //
5 | // Created by Roger Boesch on 12/07/16.
6 | // Based on Obj-C code created by Steven Troughton-Smith on 24/12/11.
7 | //
8 |
9 | import UIKit
10 |
11 | class RBPerlinNoiseGenerator {
12 | private static let noiseX = 1619
13 | private static let noiseY = 31337
14 | private static let noiseSeed = 1013
15 |
16 | private var _seed: Int = 1
17 |
18 | // -------------------------------------------------------------------------
19 |
20 | private func interpolate(a: Double, b: Double, x: Double) ->Double {
21 | let ft: Double = x * Double.pi
22 | let f: Double = (1.0-cos(ft)) * 0.5
23 |
24 | return a*(1.0-f)+b*f
25 | }
26 |
27 | // -------------------------------------------------------------------------
28 |
29 | private func findNoise(x: Double, y: Double) ->Double {
30 | var n = (RBPerlinNoiseGenerator.noiseX*Int(x) +
31 | RBPerlinNoiseGenerator.noiseY*Int(y) +
32 | RBPerlinNoiseGenerator.noiseSeed * _seed) & 0x7fffffff
33 |
34 | n = (n >> 13) ^ n
35 | n = (n &* (n &* n &* 60493 + 19990303) + 1376312589) & 0x7fffffff
36 |
37 | return 1.0 - Double(n)/1073741824
38 | }
39 |
40 | // -------------------------------------------------------------------------
41 |
42 | private func noise(x: Double, y: Double) ->Double {
43 | let floorX: Double = Double(Int(x))
44 | let floorY: Double = Double(Int(y))
45 |
46 | let s = findNoise(x:floorX, y:floorY)
47 | let t = findNoise(x:floorX+1, y:floorY)
48 | let u = findNoise(x:floorX, y:floorY+1)
49 | let v = findNoise(x:floorX+1, y:floorY+1)
50 |
51 | let i1 = interpolate(a:s, b:t, x:x-floorX)
52 | let i2 = interpolate(a:u, b:v, x:x-floorX)
53 |
54 | return interpolate(a:i1, b:i2, x:y-floorY)
55 | }
56 |
57 | // -------------------------------------------------------------------------
58 | // MARK: - Calculate a noise value for x,y
59 |
60 | func valueFor(x: Int32, y: Int32) ->Double {
61 | let octaves = 2
62 | let p: Double = 1/2
63 | let zoom: Double = 6
64 | var getnoise: Double = 0
65 |
66 | for a in 0.. 255) {
76 | value = 255
77 | }
78 | else if (value < 0) {
79 | value = 0
80 | }
81 |
82 | return value
83 | }
84 |
85 | // -------------------------------------------------------------------------
86 | // MARK: - Initialisation
87 |
88 | init(seed: Int? = nil) {
89 | if (seed == nil) {
90 | _seed = Int(arc4random()) % Int(INT32_MAX)
91 | }
92 | else {
93 | _seed = seed!
94 | }
95 | }
96 |
97 | // -------------------------------------------------------------------------
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/shared/RBRandom.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RBRandom.swift
3 | // Random number extensions
4 | //
5 | // Created by Roger Boesch on 13/01/17.
6 | // Copyright © 2017 Roger Boesch. All rights reserved.
7 | //
8 | // This class I have newly introduced in Part 3. It's mainly and abstraction
9 | // class for the classes and methods available in GameKit. It allows a better
10 | // random handling then those available in system library.
11 |
12 | import Foundation
13 | import GameKit
14 |
15 | extension Int {
16 | static func random(maxValue: Int) -> Int {
17 | let rand = Int(arc4random_uniform(UInt32(maxValue)))
18 | return rand
19 | }
20 | }
21 |
22 | class RBRandom {
23 | private let source = GKMersenneTwisterRandomSource()
24 |
25 | // -------------------------------------------------------------------------
26 | // MARK: - Get random numbers
27 |
28 | class func boolean() -> Bool {
29 | if RBRandom.sharedInstance.integer(0, 1) == 1 {
30 | return true
31 | }
32 |
33 | return false
34 | }
35 |
36 | // -------------------------------------------------------------------------
37 |
38 | class func integer(_ from: Int, _ to: Int) -> Int {
39 | return RBRandom.sharedInstance.integer(from, to)
40 | }
41 |
42 | // -------------------------------------------------------------------------
43 |
44 | class func timeInterval(_ from: Int, _ to: Int) -> TimeInterval {
45 | return TimeInterval(RBRandom.sharedInstance.integer(from, to))
46 | }
47 |
48 | // -------------------------------------------------------------------------
49 |
50 | class func cgFloat(_ from: CGFloat, _ to: CGFloat) -> CGFloat {
51 | return CGFloat(RBRandom.sharedInstance.integer(Int(from), Int(to)))
52 | }
53 |
54 | // -------------------------------------------------------------------------
55 |
56 | private func integer(_ from: Int, _ to: Int) -> Int {
57 | let rd = GKRandomDistribution(randomSource: self.source, lowestValue: from, highestValue: to)
58 | let number = rd.nextInt()
59 |
60 | return number
61 | }
62 |
63 | // -------------------------------------------------------------------------
64 | // MARK: - Initialisation
65 |
66 | init() {
67 | source.seed = UInt64(CFAbsoluteTimeGetCurrent())
68 | }
69 |
70 | // -------------------------------------------------------------------------
71 |
72 | private static let sharedInstance : RBRandom = {
73 | let instance = RBRandom()
74 | return instance
75 | }()
76 |
77 | // -------------------------------------------------------------------------
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/shared/RBTerrain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RBTerrain.swift
3 | // Terrain creation
4 | //
5 | // Created by Roger Boesch on 12/07/16.
6 | // Inspired by Obj-C code created by Steven Troughton-Smith on 24/12/11.
7 | //
8 |
9 | import Foundation
10 | import SceneKit
11 |
12 | typealias RBTerrainFormula = ((Int32, Int32) -> (Double))
13 |
14 | // -----------------------------------------------------------------------------
15 |
16 | class RBTerrain: SCNNode {
17 | private var _heightScale = 256
18 | private var _terrainWidth = 32
19 | private var _terrainLength = 32
20 | private var _terrainGeometry: SCNGeometry?
21 | private var _texture: UIImage?
22 | private var _color = UIColor.white
23 |
24 | var formula: RBTerrainFormula?
25 |
26 | // -------------------------------------------------------------------------
27 | // MARK: - Properties
28 |
29 | var length: Int {
30 | get {
31 | return _terrainLength
32 | }
33 | }
34 |
35 | // -------------------------------------------------------------------------
36 |
37 | var width: Int {
38 | get {
39 | return _terrainLength
40 | }
41 | }
42 |
43 | // -------------------------------------------------------------------------
44 |
45 | var texture: UIImage? {
46 | get {
47 | return _texture
48 | }
49 | set(value) {
50 | _texture = value
51 |
52 | if (_terrainGeometry != nil && _texture != nil) {
53 | let material = SCNMaterial()
54 | material.diffuse.contents = _texture!
55 | material.isLitPerPixel = true
56 | material.diffuse.magnificationFilter = .none
57 | material.diffuse.wrapS = .repeat
58 | material.diffuse.wrapT = .repeat
59 | material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(_terrainWidth*2), Float(_terrainLength*2), 1)
60 |
61 | _terrainGeometry!.firstMaterial = material
62 | _terrainGeometry!.firstMaterial!.isDoubleSided = true
63 | }
64 | }
65 | }
66 |
67 | // -------------------------------------------------------------------------
68 |
69 | var color: UIColor {
70 | get {
71 | return _color
72 | }
73 | set(value) {
74 | _color = value
75 |
76 | if (_terrainGeometry != nil) {
77 | let material = SCNMaterial()
78 | material.diffuse.contents = _color
79 | material.isLitPerPixel = true
80 |
81 | _terrainGeometry!.firstMaterial = material
82 | _terrainGeometry!.firstMaterial!.isDoubleSided = true
83 | }
84 | }
85 | }
86 |
87 | // -------------------------------------------------------------------------
88 | // MARK: - Terrain formula
89 |
90 | func valueFor(x: Int32, y: Int32) ->Double {
91 | if (formula == nil) {
92 | return 0.0
93 | }
94 |
95 | return formula!(x, y)
96 | }
97 |
98 | // -------------------------------------------------------------------------
99 | // MARK: - Geometry creation
100 |
101 | private func createGeometry() ->SCNGeometry {
102 | let cint: CInt = 0
103 | let sizeOfCInt = MemoryLayout.size(ofValue: cint)
104 | let float: Float = 0.0
105 | let sizeOfFloat = MemoryLayout.size(ofValue: float)
106 | let vec2: vector_float2 = vector2(0, 0)
107 | let sizeOfVecFloat = MemoryLayout.size(ofValue: vec2)
108 |
109 | let w: CGFloat = CGFloat(_terrainWidth)
110 | let h: CGFloat = CGFloat(_terrainLength)
111 | let scale: Double = Double(_heightScale)
112 |
113 | var sources = [SCNGeometrySource]()
114 | var elements = [SCNGeometryElement]()
115 |
116 | let maxElements: Int = _terrainWidth * _terrainLength * 4
117 | var vertices = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
118 | var normals = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
119 | var uvList: [vector_float2] = []
120 |
121 | var vertexCount = 0
122 | let factor: CGFloat = 0.5
123 |
124 | for y in 0...Int(h-2) {
125 | for x in 0...Int(w-1) {
126 | let topLeftZ = valueFor(x: Int32(x), y: Int32(y+1)) / scale
127 | let topRightZ = valueFor(x: Int32(x+1), y: Int32(y+1)) / scale
128 | let bottomLeftZ = valueFor(x: Int32(x), y: Int32(y)) / scale
129 | let bottomRightZ = valueFor(x: Int32(x+1), y: Int32(y)) / scale
130 |
131 | let topLeft = SCNVector3Make(Float(x)-Float(factor), Float(topLeftZ), Float(y)+Float(factor))
132 | let topRight = SCNVector3Make(Float(x)+Float(factor), Float(topRightZ), Float(y)+Float(factor))
133 | let bottomLeft = SCNVector3Make(Float(x)-Float(factor), Float(bottomLeftZ), Float(y)-Float(factor))
134 | let bottomRight = SCNVector3Make(Float(x)+Float(factor), Float(bottomRightZ), Float(y)-Float(factor))
135 |
136 | vertices[vertexCount] = bottomLeft
137 | vertices[vertexCount+1] = topLeft
138 | vertices[vertexCount+2] = topRight
139 | vertices[vertexCount+3] = bottomRight
140 |
141 | let xf = CGFloat(x)
142 | let yf = CGFloat(y)
143 |
144 | uvList.append(vector_float2(Float(xf/w), Float(yf/h)))
145 | uvList.append(vector_float2(Float(xf/w), Float((yf+factor)/h)))
146 | uvList.append(vector_float2(Float((xf+factor)/w), Float((yf+factor)/h)))
147 | uvList.append(vector_float2(Float((xf+factor)/w), Float(yf/h)))
148 |
149 | vertexCount += 4
150 | }
151 | }
152 |
153 | let source = SCNGeometrySource(vertices: vertices)
154 | sources.append(source)
155 |
156 | let geometryData = NSMutableData()
157 |
158 | var geometry: CInt = 0
159 | while (geometry < CInt(vertexCount)) {
160 | let bytes: [CInt] = [geometry, geometry+2, geometry+3, geometry, geometry+1, geometry+2]
161 | geometryData.append(bytes, length: sizeOfCInt*6)
162 | geometry += 4
163 | }
164 |
165 | let element = SCNGeometryElement(data: geometryData as Data, primitiveType: .triangles, primitiveCount: vertexCount/2, bytesPerIndex: sizeOfCInt)
166 | elements.append(element)
167 |
168 | for normalIndex in 0...vertexCount-1 {
169 | normals[normalIndex] = SCNVector3Make(0, 0, -1)
170 | }
171 | sources.append(SCNGeometrySource(normals: normals))
172 |
173 | let uvData = NSData(bytes: uvList, length: uvList.count * sizeOfVecFloat)
174 | let uvSource = SCNGeometrySource(data: uvData as Data, semantic: SCNGeometrySource.Semantic.texcoord, vectorCount: uvList.count, usesFloatComponents: true, componentsPerVector: 2, bytesPerComponent: sizeOfFloat, dataOffset: 0, dataStride: sizeOfVecFloat)
175 | sources.append(uvSource)
176 |
177 | _terrainGeometry = SCNGeometry(sources: sources, elements: elements)
178 |
179 | let material = SCNMaterial()
180 | material.isLitPerPixel = true
181 | material.diffuse.magnificationFilter = .none
182 | material.diffuse.wrapS = .repeat
183 | material.diffuse.wrapT = .repeat
184 | material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(_terrainWidth*2), Float(_terrainLength*2), 1)
185 | material.diffuse.intensity = 1.0
186 |
187 | _terrainGeometry!.firstMaterial = material
188 | _terrainGeometry!.firstMaterial!.isDoubleSided = true
189 |
190 | return _terrainGeometry!
191 | }
192 |
193 | // -------------------------------------------------------------------------
194 | // MARK: - Create terrain
195 |
196 | func create(withImage image: UIImage?) {
197 | let terrainNode = SCNNode(geometry: createGeometry())
198 | self.addChildNode(terrainNode)
199 |
200 | if (image != nil) {
201 | self.texture = image
202 | }
203 | else {
204 | self.color = UIColor.green
205 | }
206 | }
207 |
208 | // -------------------------------------------------------------------------
209 |
210 | func create(withColor color: UIColor) {
211 | let terrainNode = SCNNode(geometry: createGeometry())
212 | self.addChildNode(terrainNode)
213 |
214 | self.color = color
215 | }
216 |
217 | // -------------------------------------------------------------------------
218 | // MARK: - Initialisation
219 |
220 | init(width: Int, length: Int, scale: Int) {
221 | super.init()
222 |
223 | _terrainWidth = width
224 | _terrainLength = length
225 | _heightScale = scale
226 | }
227 |
228 | // -------------------------------------------------------------------------
229 |
230 | required init(coder: NSCoder) {
231 | fatalError("Not yet implemented")
232 | }
233 |
234 | // -------------------------------------------------------------------------
235 | }
236 |
--------------------------------------------------------------------------------
/shared/RBUtility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RBUtility.swift
3 | // General utilities
4 | //
5 | // Created by Roger Boesch on 19/09/15.
6 | // Copyright © 2015 Roger Boesch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | let pi = CGFloat(Double.pi)
12 |
13 | // -----------------------------------------------------------------------------
14 | // MARK: - Deg/Rad
15 |
16 | func degreesToRadians(value: CGFloat) -> CGFloat {
17 | return value * pi / 180.0
18 | }
19 |
20 | // -----------------------------------------------------------------------------
21 |
22 | func radiansToDegrees(value: CGFloat) -> CGFloat {
23 | return value * 180.0 / pi
24 | }
25 |
26 | // -----------------------------------------------------------------------------
27 |
28 | typealias RunClosure = () -> ()
29 |
30 | public class Run {
31 |
32 | static func after(_ timeInterval: TimeInterval, _ closure: @escaping RunClosure) {
33 | let when = DispatchTime.now() + timeInterval
34 | DispatchQueue.main.asyncAfter(deadline: when) {
35 | closure()
36 | }
37 | }
38 |
39 | }
40 |
41 | // -----------------------------------------------------------------------------
42 |
--------------------------------------------------------------------------------
/sounds/bonus.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/sounds/bonus.wav
--------------------------------------------------------------------------------
/sounds/explosion.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/sounds/explosion.wav
--------------------------------------------------------------------------------
/sounds/fire.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rogerboesch/SceneKitTutorial/7fbe31110fc6a4bba43dbdde79cf26844e63ed11/sounds/fire.wav
--------------------------------------------------------------------------------
/tutorial.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------