DemoBots: Version 2.3, 2016-09-13

Updated to Swift 3
This commit is contained in:
Liu Lantao 2016-10-09 10:24:17 +08:00
parent ab88a73bc7
commit 4dd3ea358f
100 changed files with 1375 additions and 1497 deletions

View File

@ -10,7 +10,7 @@ import Cocoa
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true return true
} }
} }

View File

@ -27,6 +27,6 @@ class GameViewController: NSViewController {
let skView = view as! SKView let skView = view as! SKView
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput) sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
sceneManager.transitionToSceneWithSceneIdentifier(.Home) sceneManager.transitionToScene(identifier: .home)
} }
} }

View File

@ -24,18 +24,17 @@ class GameWindowController: NSWindowController, NSWindowDelegate {
} }
// MARK: NSWindowDelegate // MARK: NSWindowDelegate
func windowWillStartLiveResize(_ notification: Notification) {
func windowWillStartLiveResize(notification: NSNotification) {
// Pause the scene while the window resizes if the game is active. // Pause the scene while the window resizes if the game is active.
if let levelScene = view.scene as? LevelScene where levelScene.stateMachine.currentState is LevelSceneActiveState { if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
levelScene.paused = true levelScene.isPaused = true
} }
} }
func windowDidEndLiveResize(notification: NSNotification) { func windowDidEndLiveResize(_ notification: Notification) {
// Un-pause the scene when the window stops resizing if the game is active. // Un-pause the scene when the window stops resizing if the game is active.
if let levelScene = view.scene as? LevelScene where levelScene.stateMachine.currentState is LevelSceneActiveState { if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
levelScene.paused = false levelScene.isPaused = false
} }
} }

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178t" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="ilw-vW-taU"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.11" systemVersion="16A201m" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ilw-vW-taU">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/> <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--View Controller--> <!--View Controller-->
@ -16,25 +18,20 @@
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/> <viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
</layoutGuides> </layoutGuides>
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView"> <view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
<rect key="frame" x="20" y="0.0" width="560" height="115"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA"> <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
<rect key="frame" x="49" y="115" width="502" height="322"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/> <constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
</constraints> </constraints>
</imageView> </imageView>
</subviews> </subviews>
<animations/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/> <constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/> <constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
@ -52,6 +49,6 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="DemoBotsLogo" width="387" height="248"/> <image name="DemoBotsLogo" width="904" height="579"/>
</resources> </resources>
</document> </document>

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178t" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="ilw-vW-taU"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.11" systemVersion="16A201m" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ilw-vW-taU">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/> <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Game View Controller--> <!--Game View Controller-->
@ -16,25 +18,20 @@
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/> <viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
</layoutGuides> </layoutGuides>
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView"> <view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
<rect key="frame" x="20" y="0.0" width="560" height="115"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA"> <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
<rect key="frame" x="49" y="115" width="502" height="322"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/> <constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
</constraints> </constraints>
</imageView> </imageView>
</subviews> </subviews>
<animations/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/> <constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/> <constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
@ -55,6 +52,6 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="DemoBotsLogo" width="387" height="248"/> <image name="DemoBotsLogo" width="904" height="579"/>
</resources> </resources>
</document> </document>

View File

@ -39,23 +39,22 @@ class GameViewController: UIViewController, SceneManagerDelegate {
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput) sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
sceneManager.delegate = self sceneManager.delegate = self
sceneManager.transitionToSceneWithSceneIdentifier(.Home) sceneManager.transitionToScene(identifier: .home)
} }
// Hide status bar during game play. // Hide status bar during game play.
override func prefersStatusBarHidden() -> Bool { override var prefersStatusBarHidden: Bool {
return true return true
} }
// MARK: SceneManagerDelegate // MARK: SceneManagerDelegate
func sceneManagerDidTransitionToScene(scene: SKScene) { func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene) {
// Fade out the app's initial loading `logoView` if it is visible. // Fade out the app's initial loading `logoView` if it is visible.
UIView.animateWithDuration(0.2, delay: 0.0, options: [], animations: { UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
self.logoView.alpha = 0.0 self.logoView.alpha = 0.0
}, completion: { _ in }, completion: { _ in
self.logoView.hidden = true self.logoView.isHidden = true
}) })
} }
} }

View File

@ -28,12 +28,12 @@ class GameViewController: GCEventViewController, SceneManagerDelegate {
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput) sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
sceneManager.delegate = self sceneManager.delegate = self
sceneManager.transitionToSceneWithSceneIdentifier(.Home) sceneManager.transitionToScene(identifier: .home)
} }
// MARK: SceneManagerDelegate // MARK: SceneManagerDelegate
func sceneManagerDidTransitionToScene(scene: SKScene) { func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene) {
/* /*
When transitioning to the `HomeEndScene` set When transitioning to the `HomeEndScene` set
`controllerUserInteractionEnabled` to `true` to allow the `controllerUserInteractionEnabled` to `true` to allow the

View File

@ -73,7 +73,6 @@
0A38AC181B73BE0000AE9C43 /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEEA9B61AE1E3C300C519E7 /* GeometryExtensions.swift */; }; 0A38AC181B73BE0000AE9C43 /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEEA9B61AE1E3C300C519E7 /* GeometryExtensions.swift */; };
0A38AC191B73BE0500AE9C43 /* BeamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */; }; 0A38AC191B73BE0500AE9C43 /* BeamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */; };
0A38AC1B1B73BE0500AE9C43 /* ChargeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168482F61AA66D2400EAB4C0 /* ChargeBar.swift */; }; 0A38AC1B1B73BE0500AE9C43 /* ChargeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168482F61AA66D2400EAB4C0 /* ChargeBar.swift */; };
0A38AC1C1B73BE0500AE9C43 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
0A38AC1E1B73BE0A00AE9C43 /* ColliderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16950F6E1A9C08240074F3EC /* ColliderType.swift */; }; 0A38AC1E1B73BE0A00AE9C43 /* ColliderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16950F6E1A9C08240074F3EC /* ColliderType.swift */; };
0A38AC1F1B73BE0A00AE9C43 /* ContactNotifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5FB611B1B7E3D007D0C86 /* ContactNotifiableType.swift */; }; 0A38AC1F1B73BE0A00AE9C43 /* ContactNotifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5FB611B1B7E3D007D0C86 /* ContactNotifiableType.swift */; };
0A38AC201B73BE1400AE9C43 /* GameInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169B38AC1A73279B00B850B8 /* GameInput.swift */; }; 0A38AC201B73BE1400AE9C43 /* GameInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169B38AC1A73279B00B850B8 /* GameInput.swift */; };
@ -89,7 +88,7 @@
0A38AC2A1B73BE2400AE9C43 /* SceneLoaderPreparingResourcesState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2619A81B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift */; }; 0A38AC2A1B73BE2400AE9C43 /* SceneLoaderPreparingResourcesState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2619A81B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift */; };
0A38AC2B1B73BE2400AE9C43 /* SceneLoaderResourcesReadyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */; }; 0A38AC2B1B73BE2400AE9C43 /* SceneLoaderResourcesReadyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */; };
0A38AC2C1B73BE2400AE9C43 /* ResourceLoadableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */; }; 0A38AC2C1B73BE2400AE9C43 /* ResourceLoadableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */; };
0A38AC2D1B73BE2400AE9C43 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; }; 0A38AC2D1B73BE2400AE9C43 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
0A38AC2E1B73BE2400AE9C43 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; }; 0A38AC2E1B73BE2400AE9C43 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
0A38AC2F1B73BE2400AE9C43 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; }; 0A38AC2F1B73BE2400AE9C43 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
0A38AC321B73BE3200AE9C43 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16A8FCA41A7062CC006FC06F /* Images.xcassets */; }; 0A38AC321B73BE3200AE9C43 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16A8FCA41A7062CC006FC06F /* Images.xcassets */; };
@ -132,8 +131,8 @@
0A73CB411B1CDEB80030BB13 /* TaskBotAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */; }; 0A73CB411B1CDEB80030BB13 /* TaskBotAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */; };
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; }; 0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
0A73CB441B1CE91C0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; }; 0A73CB441B1CE91C0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
0A788CD21B6AC873006B3799 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; }; 0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; }; 0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
0A788CD51B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; }; 0A788CD51B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
0A788CD61B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; }; 0A788CD61B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
0A9F5F201B1150D8002D0238 /* BaseScene+Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A58DD331B114ACD000BEEB3 /* BaseScene+Buttons.swift */; }; 0A9F5F201B1150D8002D0238 /* BaseScene+Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A58DD331B114ACD000BEEB3 /* BaseScene+Buttons.swift */; };
@ -192,8 +191,6 @@
16D800131AF4254F00979C5F /* TaskBotBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */; }; 16D800131AF4254F00979C5F /* TaskBotBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */; };
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; }; 16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
16E3D95B1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; }; 16E3D95B1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
16F685FC1A9FF32800B28C12 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
16F685FD1A9FF32800B28C12 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */; }; 3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */; };
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */; }; 3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */; };
3E8513021AA0BDB400AF1B97 /* ControlInputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */; }; 3E8513021AA0BDB400AF1B97 /* ControlInputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */; };
@ -353,7 +350,7 @@
0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneLoaderResourcesReadyState.swift; sourceTree = "<group>"; }; 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneLoaderResourcesReadyState.swift; sourceTree = "<group>"; };
0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgent.swift; sourceTree = "<group>"; }; 0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgent.swift; sourceTree = "<group>"; };
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadResourcesOperation.swift; sourceTree = "<group>"; }; 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadResourcesOperation.swift; sourceTree = "<group>"; };
0A788CD11B6AC873006B3799 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; }; 0A788CD11B6AC873006B3799 /* SceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneOperation.swift; sourceTree = "<group>"; };
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadSceneOperation.swift; sourceTree = "<group>"; }; 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadSceneOperation.swift; sourceTree = "<group>"; };
0AC33EAE1B7E91D600791074 /* ButtonFocusActions.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = ButtonFocusActions.sks; sourceTree = "<group>"; }; 0AC33EAE1B7E91D600791074 /* ButtonFocusActions.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = ButtonFocusActions.sks; sourceTree = "<group>"; };
0ADAB8C71B1660E700F98E5A /* SceneMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneMetadata.swift; sourceTree = "<group>"; }; 0ADAB8C71B1660E700F98E5A /* SceneMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneMetadata.swift; sourceTree = "<group>"; };
@ -385,7 +382,6 @@
16D8000B1AF4000000979C5F /* TaskBotAgentControlledState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgentControlledState.swift; sourceTree = "<group>"; }; 16D8000B1AF4000000979C5F /* TaskBotAgentControlledState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgentControlledState.swift; sourceTree = "<group>"; };
16D800111AF4254F00979C5F /* TaskBotBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskBotBehavior.swift; path = Components/TaskBotBehavior.swift; sourceTree = "<group>"; }; 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskBotBehavior.swift; path = Components/TaskBotBehavior.swift; sourceTree = "<group>"; };
16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsComponent.swift; sourceTree = "<group>"; }; 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsComponent.swift; sourceTree = "<group>"; };
16F685FB1A9FF32800B28C12 /* EntityNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityNode.swift; sourceTree = "<group>"; };
3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+KeyboardEventForwarding.swift"; sourceTree = "<group>"; }; 3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+KeyboardEventForwarding.swift"; sourceTree = "<group>"; };
3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+TouchEventForwarding.swift"; sourceTree = "<group>"; }; 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+TouchEventForwarding.swift"; sourceTree = "<group>"; };
3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlInputSource.swift; sourceTree = "<group>"; }; 3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlInputSource.swift; sourceTree = "<group>"; };
@ -539,7 +535,7 @@
0A724CDF1B17949200F64132 /* SceneLoader.swift */, 0A724CDF1B17949200F64132 /* SceneLoader.swift */,
0A724CE51B17C53000F64132 /* SceneLoader States */, 0A724CE51B17C53000F64132 /* SceneLoader States */,
0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */, 0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */,
0A788CD11B6AC873006B3799 /* Operation.swift */, 0A788CD11B6AC873006B3799 /* SceneOperation.swift */,
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */, 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */,
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */, 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */,
); );
@ -608,7 +604,6 @@
B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */, B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */,
B5B2DA681BE0DD1A00DF201F /* ButtonNode.swift */, B5B2DA681BE0DD1A00DF201F /* ButtonNode.swift */,
168482F61AA66D2400EAB4C0 /* ChargeBar.swift */, 168482F61AA66D2400EAB4C0 /* ChargeBar.swift */,
16F685FB1A9FF32800B28C12 /* EntityNode.swift */,
B5B2DA6C1BE0DD4200DF201F /* ThumbStickNode.swift */, B5B2DA6C1BE0DD4200DF201F /* ThumbStickNode.swift */,
); );
path = Nodes; path = Nodes;
@ -951,8 +946,9 @@
Level2, Level2,
Level3, Level3,
); );
LastSwiftMigration = 0800;
LastSwiftUpdateCheck = 0700; LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700; LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Apple, Inc."; ORGANIZATIONNAME = "Apple, Inc.";
TargetAttributes = { TargetAttributes = {
0A38ABAF1B73013F00AE9C43 = { 0A38ABAF1B73013F00AE9C43 = {
@ -1131,7 +1127,6 @@
B5B2DA671BE0DD0400DF201F /* ProgressScene.swift in Sources */, B5B2DA671BE0DD0400DF201F /* ProgressScene.swift in Sources */,
0A38AC0F1B73BDF800AE9C43 /* CompassDirection.swift in Sources */, 0A38AC0F1B73BDF800AE9C43 /* CompassDirection.swift in Sources */,
0A38AC121B73BDF800AE9C43 /* RulesComponent.swift in Sources */, 0A38AC121B73BDF800AE9C43 /* RulesComponent.swift in Sources */,
0A38AC1C1B73BE0500AE9C43 /* EntityNode.swift in Sources */,
0A38AC231B73BE1D00AE9C43 /* SceneManager.swift in Sources */, 0A38AC231B73BE1D00AE9C43 /* SceneManager.swift in Sources */,
B5B2DA6E1BE0DD4900DF201F /* ThumbStickNode.swift in Sources */, B5B2DA6E1BE0DD4900DF201F /* ThumbStickNode.swift in Sources */,
0A38AC031B73BDF300AE9C43 /* PlayerBotHitState.swift in Sources */, 0A38AC031B73BDF300AE9C43 /* PlayerBotHitState.swift in Sources */,
@ -1148,7 +1143,7 @@
0A38ABFB1B73BDEB00AE9C43 /* BeamComponent.swift in Sources */, 0A38ABFB1B73BDEB00AE9C43 /* BeamComponent.swift in Sources */,
0A18365B1B82B11900177830 /* LevelScene+Pause.swift in Sources */, 0A18365B1B82B11900177830 /* LevelScene+Pause.swift in Sources */,
0A38AC211B73BE1400AE9C43 /* ControlInputSource.swift in Sources */, 0A38AC211B73BE1400AE9C43 /* ControlInputSource.swift in Sources */,
0A38AC2D1B73BE2400AE9C43 /* Operation.swift in Sources */, 0A38AC2D1B73BE2400AE9C43 /* SceneOperation.swift in Sources */,
0A38AC101B73BDF800AE9C43 /* PhysicsComponent.swift in Sources */, 0A38AC101B73BDF800AE9C43 /* PhysicsComponent.swift in Sources */,
0A38AC111B73BDF800AE9C43 /* RenderComponent.swift in Sources */, 0A38AC111B73BDF800AE9C43 /* RenderComponent.swift in Sources */,
0A38AC0C1B73BDF800AE9C43 /* TaskBotZappedState.swift in Sources */, 0A38AC0C1B73BDF800AE9C43 /* TaskBotZappedState.swift in Sources */,
@ -1221,7 +1216,7 @@
B5B2DA831BE0DDF200DF201F /* GameWindowController.swift in Sources */, B5B2DA831BE0DDF200DF201F /* GameWindowController.swift in Sources */,
B545AF1E1AF0F422002BC931 /* BeamComponent.swift in Sources */, B545AF1E1AF0F422002BC931 /* BeamComponent.swift in Sources */,
0A6F773E1B02EA060091C645 /* SceneManager.swift in Sources */, 0A6F773E1B02EA060091C645 /* SceneManager.swift in Sources */,
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */, 0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */,
B5CAD52D1AEBAE9D00D9B7A9 /* FlyingBotBlastState.swift in Sources */, B5CAD52D1AEBAE9D00D9B7A9 /* FlyingBotBlastState.swift in Sources */,
16873A0C1A9E6DC2003FB425 /* RenderComponent.swift in Sources */, 16873A0C1A9E6DC2003FB425 /* RenderComponent.swift in Sources */,
B52D2DFB1AFD11E000469E3B /* PlayerBotAppearState.swift in Sources */, B52D2DFB1AFD11E000469E3B /* PlayerBotAppearState.swift in Sources */,
@ -1245,7 +1240,6 @@
0ADCE3381B1BE3F200E85CEB /* ResourceLoadableType.swift in Sources */, 0ADCE3381B1BE3F200E85CEB /* ResourceLoadableType.swift in Sources */,
16109B641AF54D3800A780AE /* FuzzyTaskBotRule.swift in Sources */, 16109B641AF54D3800A780AE /* FuzzyTaskBotRule.swift in Sources */,
0A255E2B1B5EB9EF0085D218 /* BaseScene+Focus.swift in Sources */, 0A255E2B1B5EB9EF0085D218 /* BaseScene+Focus.swift in Sources */,
16F685FD1A9FF32800B28C12 /* EntityNode.swift in Sources */,
16D8000D1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */, 16D8000D1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
0ADAB8C91B1660E700F98E5A /* SceneMetadata.swift in Sources */, 0ADAB8C91B1660E700F98E5A /* SceneMetadata.swift in Sources */,
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */, 3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */,
@ -1284,7 +1278,6 @@
3E91BAC41A89755500CE240A /* LevelScene.swift in Sources */, 3E91BAC41A89755500CE240A /* LevelScene.swift in Sources */,
B5CAD5251AEBA48E00D9B7A9 /* FlyingBot.swift in Sources */, B5CAD5251AEBA48E00D9B7A9 /* FlyingBot.swift in Sources */,
B545AF1D1AF0F418002BC931 /* BeamComponent.swift in Sources */, B545AF1D1AF0F418002BC931 /* BeamComponent.swift in Sources */,
16F685FC1A9FF32800B28C12 /* EntityNode.swift in Sources */,
16D8000C1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */, 16D8000C1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */, 3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */,
55A24EF41AF033C70039A2DB /* LevelConfiguration.swift in Sources */, 55A24EF41AF033C70039A2DB /* LevelConfiguration.swift in Sources */,
@ -1316,7 +1309,7 @@
B54A81591B15140A00D33DF8 /* SceneOverlay.swift in Sources */, B54A81591B15140A00D33DF8 /* SceneOverlay.swift in Sources */,
B543D7DD1B1B976000F0AA9D /* OrientationComponent.swift in Sources */, B543D7DD1B1B976000F0AA9D /* OrientationComponent.swift in Sources */,
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */, 16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */,
0A788CD21B6AC873006B3799 /* Operation.swift in Sources */, 0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */,
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */, 0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */,
0A2619A91B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift in Sources */, 0A2619A91B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift in Sources */,
3E8513071AA0BEA800AF1B97 /* TouchControlInputNode.swift in Sources */, 3E8513071AA0BEA800AF1B97 /* TouchControlInputNode.swift in Sources */,
@ -1387,7 +1380,10 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon"; ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist"; INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1396,8 +1392,9 @@
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
PRODUCT_NAME = DemoBots; PRODUCT_NAME = DemoBots;
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = 3; TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0; TVOS_DEPLOYMENT_TARGET = 10.0;
}; };
name = Debug; name = Debug;
}; };
@ -1406,6 +1403,9 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon"; ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist"; INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1414,8 +1414,9 @@
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
PRODUCT_NAME = DemoBots; PRODUCT_NAME = DemoBots;
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = 3; TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0; TVOS_DEPLOYMENT_TARGET = 10.0;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -1424,6 +1425,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPRESSION = lossless;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
@ -1455,14 +1457,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = ""; OTHER_SWIFT_FLAGS = "";
PRODUCT_NAME = DemoBots; PRODUCT_NAME = DemoBots;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TOOLCHAINS = default;
}; };
name = Debug; name = Debug;
}; };
@ -1470,6 +1472,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPRESSION = "respect-asset-catalog";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
@ -1495,11 +1498,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_NAME = DemoBots; PRODUCT_NAME = DemoBots;
SDKROOT = macosx; SDKROOT = macosx;
TOOLCHAINS = default;
}; };
name = Release; name = Release;
}; };
@ -1508,20 +1511,22 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(BUILD_DIR)", "$(BUILD_DIR)",
"$(inherited)", "$(inherited)",
); );
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist"; INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3"; ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
OTHER_SWIFT_FLAGS = "-D DEBUG"; OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
}; };
name = Debug; name = Debug;
}; };
@ -1530,18 +1535,20 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(BUILD_DIR)", "$(BUILD_DIR)",
"$(inherited)", "$(inherited)",
); );
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist"; INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3"; ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 3.0;
}; };
name = Release; name = Release;
}; };
@ -1558,21 +1565,18 @@
"$(BUILD_DIR)", "$(BUILD_DIR)",
"$(inherited)", "$(inherited)",
); );
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist"; INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1"; ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
OTHER_SWIFT_FLAGS = "-D DEBUG"; OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@ -1590,14 +1594,15 @@
"$(inherited)", "$(inherited)",
); );
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist"; INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1"; ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots; PRODUCT_MODULE_NAME = DemoBots;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "FlyingBotGoodWalk@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "GroundBotBadWalk@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "PlayerBotWalk@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "resistor_horizontal@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -12,6 +12,12 @@
"filename" : "App Icon - Small.imagestack", "filename" : "App Icon - Small.imagestack",
"role" : "primary-app-icon" "role" : "primary-app-icon"
}, },
{
"size" : "2320x720",
"idiom" : "tv",
"filename" : "Top Shelf Image Wide.imageset",
"role" : "top-shelf-image-wide"
},
{ {
"size" : "1920x720", "size" : "1920x720",
"idiom" : "tv", "idiom" : "tv",

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -30,4 +30,4 @@
"version" : 1, "version" : 1,
"author" : "xcode" "author" : "xcode"
} }
} }

View File

@ -14,7 +14,7 @@ extension BaseScene: ButtonNodeResponderType {
/// Searches the scene for all `ButtonNode`s. /// Searches the scene for all `ButtonNode`s.
func findAllButtonsInScene() -> [ButtonNode] { func findAllButtonsInScene() -> [ButtonNode] {
return ButtonIdentifier.allButtonIdentifiers.flatMap { buttonIdentifier in return ButtonIdentifier.allButtonIdentifiers.flatMap { buttonIdentifier in
childNodeWithName("//\(buttonIdentifier.rawValue)") as? ButtonNode childNode(withName: "//\(buttonIdentifier.rawValue)") as? ButtonNode
} }
} }
@ -22,27 +22,27 @@ extension BaseScene: ButtonNodeResponderType {
func buttonTriggered(button: ButtonNode) { func buttonTriggered(button: ButtonNode) {
switch button.buttonIdentifier! { switch button.buttonIdentifier! {
case .Home: case .home:
sceneManager.transitionToSceneWithSceneIdentifier(.Home) sceneManager.transitionToScene(identifier: .home)
case .ProceedToNextScene: case .proceedToNextScene:
sceneManager.transitionToSceneWithSceneIdentifier(.NextLevel) sceneManager.transitionToScene(identifier: .nextLevel)
case .Replay: case .replay:
sceneManager.transitionToSceneWithSceneIdentifier(.CurrentLevel) sceneManager.transitionToScene(identifier: .currentLevel)
case .ScreenRecorderToggle: case .screenRecorderToggle:
#if os(iOS) #if os(iOS)
toggleScreenRecording(button) toggleScreenRecording(button: button)
#endif #endif
case .ViewRecordedContent: case .viewRecordedContent:
#if os(iOS) #if os(iOS)
displayRecordedContent() displayRecordedContent()
#endif #endif
default: default:
fatalError("Unsupported ButtonNode type in Scene.") fatalError("Unsupported ButtonNode type in Scene.")
} }
} }
} }

View File

@ -27,7 +27,7 @@ extension BaseScene {
/// A computed property to determine which buttons are focusable. /// A computed property to determine which buttons are focusable.
var currentlyFocusableButtons: [ButtonNode] { var currentlyFocusableButtons: [ButtonNode] {
return buttons.filter { !$0.hidden && $0.userInteractionEnabled } return buttons.filter { !$0.isHidden && $0.isUserInteractionEnabled }
} }
/** /**
@ -40,14 +40,14 @@ extension BaseScene {
*/ */
private var buttonIdentifiersOrderedByInitialFocusPriority: [ButtonIdentifier] { private var buttonIdentifiersOrderedByInitialFocusPriority: [ButtonIdentifier] {
return [ return [
.Resume, .resume,
.ProceedToNextScene, .proceedToNextScene,
.Replay, .replay,
.Retry, .retry,
.Home, .home,
.Cancel, .cancel,
.ViewRecordedContent, .viewRecordedContent,
.ScreenRecorderToggle .screenRecorderToggle
] ]
} }
@ -60,7 +60,7 @@ extension BaseScene {
could be expanded to include horizontal navigation if necessary. could be expanded to include horizontal navigation if necessary.
*/ */
func createButtonFocusGraph() { func createButtonFocusGraph() {
let sortedFocusableButtons = currentlyFocusableButtons.sort { $0.position.y > $1.position.y } let sortedFocusableButtons = currentlyFocusableButtons.sorted { $0.position.y > $1.position.y }
// Clear any existing connections. // Clear any existing connections.
sortedFocusableButtons.forEach { $0.focusableNeighbors.removeAll() } sortedFocusableButtons.forEach { $0.focusableNeighbors.removeAll() }
@ -71,8 +71,8 @@ extension BaseScene {
let nextNode = sortedFocusableButtons[i + 1] let nextNode = sortedFocusableButtons[i + 1]
// Create a bidirectional connection between the nodes. // Create a bidirectional connection between the nodes.
node.focusableNeighbors[.Down] = nextNode node.focusableNeighbors[.down] = nextNode
nextNode.focusableNeighbors[.Up] = node nextNode.focusableNeighbors[.up] = node
} }
} }
@ -87,14 +87,14 @@ extension BaseScene {
// On iOS, ensure a game controller is connected otherwise return without providing focus. // On iOS, ensure a game controller is connected otherwise return without providing focus.
guard sceneManager.gameInput.isGameControllerConnected else { return } guard sceneManager.gameInput.isGameControllerConnected else { return }
#endif #endif
// Reset focus to the `buttonNode` with the maximum initial focus priority. // Reset focus to the `buttonNode` with the maximum initial focus priority.
focusedButton = currentlyFocusableButtons.maxElement { lhsButton, rhsButton in focusedButton = currentlyFocusableButtons.max { lhsButton, rhsButton in
// The initial focus priority is the index within the `buttonIdentifiersOrderedByInitialFocusPriority` array. // The initial focus priority is the index within the `buttonIdentifiersOrderedByInitialFocusPriority` array.
let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(lhsButton.buttonIdentifier)! let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: lhsButton.buttonIdentifier)!
let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(rhsButton.buttonIdentifier)! let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: rhsButton.buttonIdentifier)!
return lhsPriority > rhsPriority return lhsPriority > rhsPriority
} }
} }
} }

View File

@ -21,27 +21,27 @@ extension BaseScene {
// MARK: NSResponder // MARK: NSResponder
override func mouseDown(event: NSEvent) { override func mouseDown(with event: NSEvent) {
keyboardControlInputSource.handleMouseDownEvent() keyboardControlInputSource.handleMouseDownEvent()
} }
override func mouseUp(theEvent: NSEvent) { override func mouseUp(with event: NSEvent) {
keyboardControlInputSource.handleMouseUpEvent() keyboardControlInputSource.handleMouseUpEvent()
} }
override func keyDown(event: NSEvent) { override func keyDown(with event: NSEvent) {
guard let characters = event.charactersIgnoringModifiers?.characters else { return } guard let characters = event.charactersIgnoringModifiers?.characters else { return }
for character in characters { for character in characters {
keyboardControlInputSource.handleKeyDownForCharacter(character) keyboardControlInputSource.handleKeyDown(forCharacter: character)
} }
} }
override func keyUp(event: NSEvent) { override func keyUp(with event: NSEvent) {
guard let characters = event.charactersIgnoringModifiers?.characters else { return } guard let characters = event.charactersIgnoringModifiers?.characters else { return }
for character in characters { for character in characters {
keyboardControlInputSource.handleKeyUpForCharacter(character) keyboardControlInputSource.handleKeyUp(forCharacter: character)
} }
} }
} }

View File

@ -12,7 +12,7 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// MARK: Computed Properties // MARK: Computed Properties
var screenRecordingToggleEnabled: Bool { var screenRecordingToggleEnabled: Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(screenRecorderEnabledKey) return UserDefaults.standard.bool(forKey: screenRecorderEnabledKey)
} }
// MARK: Start/Stop Screen Recording // MARK: Start/Stop Screen Recording
@ -21,25 +21,25 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// Do nothing if screen recording hasn't been enabled. // Do nothing if screen recording hasn't been enabled.
guard screenRecordingToggleEnabled else { return } guard screenRecordingToggleEnabled else { return }
let sharedRecorder = RPScreenRecorder.sharedRecorder() let sharedRecorder = RPScreenRecorder.shared()
// Register as the recorder's delegate to handle errors. // Register as the recorder's delegate to handle errors.
sharedRecorder.delegate = self sharedRecorder.delegate = self
sharedRecorder.startRecordingWithMicrophoneEnabled(true) { error in sharedRecorder.startRecording() { error in
if let error = error { if let error = error {
self.showScreenRecordingAlert(error.localizedDescription) self.showScreenRecordingAlert(message: error.localizedDescription)
} }
} }
} }
func stopScreenRecordingWithHandler(handler:(() -> Void)) { func stopScreenRecording(withHandler handler:@escaping (() -> Void)) {
let sharedRecorder = RPScreenRecorder.sharedRecorder() let sharedRecorder = RPScreenRecorder.shared()
sharedRecorder.stopRecordingWithHandler { (previewViewController: RPPreviewViewController?, error: NSError?) in sharedRecorder.stopRecording { previewViewController, error in
if let error = error { if let error = error {
// If an error has occurred, display an alert to the user. // If an error has occurred, display an alert to the user.
self.showScreenRecordingAlert(error.localizedDescription) self.showScreenRecordingAlert(message: error.localizedDescription)
return return
} }
@ -60,13 +60,13 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
func showScreenRecordingAlert(message: String) { func showScreenRecordingAlert(message: String) {
// Pause the scene and un-pause after the alert returns. // Pause the scene and un-pause after the alert returns.
paused = true isPaused = true
// Show an alert notifying the user that there was an issue with starting or stopping the recorder. // Show an alert notifying the user that there was an issue with starting or stopping the recorder.
let alertController = UIAlertController(title: "ReplayKit Error", message: message, preferredStyle: .Alert) let alertController = UIAlertController(title: "ReplayKit Error", message: message, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { _ in let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.`default`) { _ in
self.paused = false self.isPaused = false
} }
alertController.addAction(alertAction) alertController.addAction(alertAction)
@ -74,23 +74,23 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
`ReplayKit` event handlers may be called on a background queue. Ensure `ReplayKit` event handlers may be called on a background queue. Ensure
this alert is presented on the main queue. this alert is presented on the main queue.
*/ */
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async() {
self.view?.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil) self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
} }
} }
func discardRecording() { func discardRecording() {
// When we no longer need the `previewViewController`, tell `ReplayKit` to discard the recording and nil out our reference // When we no longer need the `previewViewController`, tell `ReplayKit` to discard the recording and nil out our reference
RPScreenRecorder.sharedRecorder().discardRecordingWithHandler { RPScreenRecorder.shared().discardRecording {
self.previewViewController = nil self.previewViewController = nil
} }
} }
// MARK: RPScreenRecorderDelegate // MARK: RPScreenRecorderDelegate
func screenRecorder(screenRecorder: RPScreenRecorder, didStopRecordingWithError error: NSError, previewViewController: RPPreviewViewController?) { func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWithError error: Error, previewViewController: RPPreviewViewController?) {
// Display the error the user to alert them that the recording failed. // Display the error the user to alert them that the recording failed.
showScreenRecordingAlert(error.localizedDescription) showScreenRecordingAlert(message: error.localizedDescription)
/// Hold onto a reference of the `previewViewController` if not nil. /// Hold onto a reference of the `previewViewController` if not nil.
if previewViewController != nil { if previewViewController != nil {
@ -101,6 +101,6 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// MARK: RPPreviewViewControllerDelegate // MARK: RPPreviewViewControllerDelegate
func previewControllerDidFinish(previewController: RPPreviewViewController) { func previewControllerDidFinish(previewController: RPPreviewViewController) {
previewViewController?.dismissViewControllerAnimated(true, completion: nil) previewViewController?.dismiss(animated: true, completion: nil)
} }
} }

View File

@ -33,13 +33,13 @@ extension BaseScene {
touchControlInputNode.size = size touchControlInputNode.size = size
// Center the control node on the camera. // Center the control node on the camera.
touchControlInputNode.position = CGPointZero touchControlInputNode.position = CGPoint.zero
/* /*
Assign a `zPosition` that is above in-game elements, but below the top Assign a `zPosition` that is above in-game elements, but below the top
layer where buttons are added. layer where buttons are added.
*/ */
touchControlInputNode.zPosition = WorldLayer.Top.rawValue - CGFloat(1.0) touchControlInputNode.zPosition = WorldLayer.top.rawValue - CGFloat(1.0)
// Add the control node to the camera node so the controls remain stationary as the camera moves. // Add the control node to the camera node so the controls remain stationary as the camera moves.
camera.addChild(touchControlInputNode) camera.addChild(touchControlInputNode)
@ -48,4 +48,4 @@ extension BaseScene {
touchControlInputNode.hideThumbStickNodes = false touchControlInputNode.hideThumbStickNodes = false
} }
} }
} }

View File

@ -54,13 +54,13 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
// Clear the `buttons` in preparation for new buttons in the overlay. // Clear the `buttons` in preparation for new buttons in the overlay.
buttons = [] buttons = []
if let overlay = overlay, camera = camera { if let overlay = overlay, let camera = camera {
overlay.backgroundNode.removeFromParent() overlay.backgroundNode.removeFromParent()
camera.addChild(overlay.backgroundNode) camera.addChild(overlay.backgroundNode)
// Animate the overlay in. // Animate the overlay in.
overlay.backgroundNode.alpha = 0.0 overlay.backgroundNode.alpha = 0.0
overlay.backgroundNode.runAction(SKAction.fadeInWithDuration(0.25)) overlay.backgroundNode.run(SKAction.fadeIn(withDuration: 0.25))
overlay.updateScale() overlay.updateScale()
buttons = findAllButtonsInScene() buttons = findAllButtonsInScene()
@ -70,7 +70,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
} }
// Animate the old overlay out. // Animate the old overlay out.
oldValue?.backgroundNode.runAction(SKAction.fadeOutWithDuration(0.25)) { oldValue?.backgroundNode.run(SKAction.fadeOut(withDuration: 0.25)) {
oldValue?.backgroundNode.removeFromParent() oldValue?.backgroundNode.removeFromParent()
} }
} }
@ -81,8 +81,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
// MARK: SKScene Life Cycle // MARK: SKScene Life Cycle
override func didMoveToView(view: SKView) { override func didMove(to view: SKView) {
super.didMoveToView(view) super.didMove(to: view)
updateCameraScale() updateCameraScale()
overlay?.updateScale() overlay?.updateScale()
@ -95,7 +95,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
resetFocus() resetFocus()
} }
override func didChangeSize(oldSize: CGSize) { override func didChangeSize(_ oldSize: CGSize) {
super.didChangeSize(oldSize) super.didChangeSize(oldSize)
updateCameraScale() updateCameraScale()
@ -122,11 +122,11 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
// MARK: ControlInputSourceGameStateDelegate // MARK: ControlInputSourceGameStateDelegate
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType) { func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType) {
focusedButton?.buttonTriggered() focusedButton?.buttonTriggered()
} }
func controlInputSource(controlInputSource: ControlInputSourceType, didSpecifyDirection direction: ControlInputDirection) { func controlInputSource(_ controlInputSource: ControlInputSourceType, didSpecifyDirection direction: ControlInputDirection) {
// Check that this scene has focus changes enabled, otherwise ignore. // Check that this scene has focus changes enabled, otherwise ignore.
guard focusChangesEnabled else { return } guard focusChangesEnabled else { return }
@ -155,7 +155,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
constant input. constant input.
*/ */
focusChangesEnabled = false focusChangesEnabled = false
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(200 * NSEC_PER_MSEC)), dispatch_get_main_queue()) { let deadline = DispatchTime.now() + DispatchTimeInterval.microseconds(200)
DispatchQueue.main.asyncAfter(deadline: deadline) {
self.focusChangesEnabled = true self.focusChangesEnabled = true
/* /*
@ -167,7 +168,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
} }
else { else {
// Indicate that a neighboring button does not exist for the requested direction. // Indicate that a neighboring button does not exist for the requested direction.
currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction) currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction: direction)
} }
} }
else { else {
@ -176,20 +177,20 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
} }
} }
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) { func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
// Subclasses implement to toggle pause state. // Subclasses implement to toggle pause state.
} }
#if DEBUG #if DEBUG
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) { func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
// Subclasses implement if necessary, to display useful debug info. // Subclasses implement if necessary, to display useful debug info.
} }
func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType) { func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType) {
// Implemented by subclasses to switch to next level while debugging. // Implemented by subclasses to switch to next level while debugging.
} }
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) { func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
// Implemented by subclasses to force failing the level while debugging. // Implemented by subclasses to force failing the level while debugging.
} }
#endif #endif

View File

@ -1,272 +0,0 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
`ButtonNode` is a custom `SKSpriteNode` that provides button-like behavior in a SpriteKit scene. It is supported by `ButtonNodeResponderType` (a protocol for classes that can respond to button presses) and `ButtonIdentifier` (an enumeration that defines all of the kinds of buttons that are supported in the game).
*/
import SpriteKit
/// A type that can respond to `ButtonNode` button press events.
protocol ButtonNodeResponderType: class {
/// Responds to a button press.
func buttonTriggered(button: ButtonNode)
}
/// The complete set of button identifiers supported in the app.
enum ButtonIdentifier: String {
case Resume
case Home
case ProceedToNextScene
case Replay
case Retry
case Cancel
case ScreenRecorderToggle
case ViewRecordedContent
/// Convenience array of all available button identifiers.
static let allButtonIdentifiers: [ButtonIdentifier] = [
.Resume, .Home, .ProceedToNextScene, .Replay, .Retry, .Cancel, .ScreenRecorderToggle, .ViewRecordedContent
]
/// The name of the texture to use for a button when the button is selected.
var selectedTextureName: String? {
switch self {
case .ScreenRecorderToggle:
return "ButtonAutoRecordOn"
default:
return nil
}
}
}
/// A custom sprite node that represents a press able and selectable button in a scene.
class ButtonNode: SKSpriteNode {
// MARK: Properties
/// The identifier for this button, deduced from its name in the scene.
var buttonIdentifier: ButtonIdentifier!
/**
The scene that contains a `ButtonNode` must be a `ButtonNodeResponderType`
so that touch events can be forwarded along through `buttonPressed()`.
*/
var responder: ButtonNodeResponderType {
guard let responder = scene as? ButtonNodeResponderType else {
fatalError("ButtonNode may only be used within a `ButtonNodeResponderType` scene.")
}
return responder
}
/// Indicates whether the button is currently highlighted (pressed).
var isHighlighted = false {
// Animate to a pressed / unpressed state when the highlight state changes.
didSet {
// Guard against repeating the same action.
guard oldValue != isHighlighted else { return }
// Remove any existing animations that may be in progress.
removeAllActions()
// Create a scale action to make the button look like it is slightly depressed.
let newScale: CGFloat = isHighlighted ? 0.99 : 1.01
let scaleAction = SKAction.scaleBy(newScale, duration: 0.15)
// Create a color blend action to darken the button slightly when it is depressed.
let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0
let colorBlendAction = SKAction.colorizeWithColorBlendFactor(newColorBlendFactor, duration: 0.15)
// Run the two actions at the same time.
runAction(SKAction.group([scaleAction, colorBlendAction]))
}
}
/**
Indicates whether the button is currently selected (on or off).
Most buttons do not support or require selection. In DemoBots,
selection is used by the screen recorder buttons to indicate whether
screen recording is turned on or off.
*/
var isSelected = false {
didSet {
// Change the texture based on the current selection state.
texture = isSelected ? selectedTexture : defaultTexture
}
}
/// The texture to use when the button is not selected.
var defaultTexture: SKTexture?
/// The texture to use when the button is selected.
var selectedTexture: SKTexture?
/// A mapping of neighboring `ButtonNode`s keyed by the `ControlInputDirection` to reach the node.
var focusableNeighbors = [ControlInputDirection: ButtonNode]()
/**
Input focus shows which button will be triggered when the action
button is pressed on indirect input devices such as game controllers
and keyboards.
*/
var isFocused = false {
didSet {
if isFocused {
runAction(SKAction.scaleTo(1.08, duration: 0.20))
focusRing.alpha = 0.0
focusRing.hidden = false
focusRing.runAction(SKAction.fadeInWithDuration(0.2))
}
else {
runAction(SKAction.scaleTo(1.0, duration: 0.20))
focusRing.hidden = true
}
}
}
/// A node to indicate when a button has the input focus.
lazy var focusRing: SKNode = self.childNodeWithName("focusRing")!
// MARK: Initializers
/// Overridden to support `copyWithZone(_:)`.
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Ensure that the node has a supported button identifier as its name.
guard let nodeName = name, buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else {
fatalError("Unsupported button name found.")
}
self.buttonIdentifier = buttonIdentifier
// Remember the button's default texture (taken from its texture in the scene).
defaultTexture = texture
if let textureName = buttonIdentifier.selectedTextureName {
// Use a specific selected texture if one is specified for this identifier.
selectedTexture = SKTexture(imageNamed: textureName)
}
else {
// Otherwise, use the default `texture`.
selectedTexture = texture
}
// The focus ring should be hidden until the button is given the input focus.
focusRing.hidden = true
// Enable user interaction on the button node to detect tap and click events.
userInteractionEnabled = true
}
override func copyWithZone(zone: NSZone) -> AnyObject {
let newButton = super.copyWithZone(zone) as! ButtonNode
// Copy the `ButtonNode` specific properties.
newButton.buttonIdentifier = buttonIdentifier
newButton.defaultTexture = defaultTexture?.copy() as? SKTexture
newButton.selectedTexture = selectedTexture?.copy() as? SKTexture
return newButton
}
func buttonTriggered() {
if userInteractionEnabled {
// Forward the button press event through to the responder.
responder.buttonTriggered(self)
}
}
/**
Performs an animation to indicate when a user is trying to navigate
away but no other focusable buttons are available in the requested
direction.
*/
func performInvalidFocusChangeAnimationForDirection(direction: ControlInputDirection) {
let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey"
guard actionForKey(animationKey) == nil else { return }
// Find the reference action from `ButtonFocusActions.sks`.
let action: SKAction
switch direction {
case .Up: action = SKAction(named: "InvalidFocusChange_Up")!
case .Down: action = SKAction(named: "InvalidFocusChange_Down")!
case .Left: action = SKAction(named: "InvalidFocusChange_Left")!
case .Right: action = SKAction(named: "InvalidFocusChange_Right")!
}
runAction(action, withKey: animationKey)
}
// MARK: Responder
#if os(iOS)
/// UIResponder touch handling.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
isHighlighted = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
isHighlighted = false
// Touch up inside behavior.
if containsTouches(touches) {
buttonTriggered()
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
isHighlighted = false
}
/// Determine if any of the touches are within the `ButtonNode`.
private func containsTouches(touches: Set<UITouch>) -> Bool {
guard let scene = scene else { fatalError("Button must be used within a scene.") }
return touches.contains { touch in
let touchPoint = touch.locationInNode(scene)
let touchedNode = scene.nodeAtPoint(touchPoint)
return touchedNode === self || touchedNode.inParentHierarchy(self)
}
}
#elseif os(OSX)
/// NSResponder mouse handling.
override func mouseDown(event: NSEvent) {
super.mouseDown(event)
isHighlighted = true
}
override func mouseUp(event: NSEvent) {
super.mouseUp(event)
isHighlighted = false
// Touch up inside behavior.
if containsLocationForEvent(event) {
buttonTriggered()
}
}
/// Determine if the event location is within the `ButtonNode`.
private func containsLocationForEvent(event: NSEvent) -> Bool {
guard let scene = scene else { fatalError("Button must be used within a scene.") }
let location = event.locationInNode(scene)
let clickedNode = scene.nodeAtPoint(location)
return clickedNode === self || clickedNode.inParentHierarchy(self)
}
#endif
}

View File

@ -22,7 +22,7 @@ extension ButtonNodeResponderType where Self: BaseScene {
button.isSelected = !button.isSelected button.isSelected = !button.isSelected
NSUserDefaults.standardUserDefaults().setBool(button.isSelected, forKey: screenRecorderEnabledKey) UserDefaults.standard.set(button.isSelected, forKey: screenRecorderEnabledKey)
} }
func displayRecordedContent() { func displayRecordedContent() {
@ -30,8 +30,8 @@ extension ButtonNodeResponderType where Self: BaseScene {
guard let rootViewController = view?.window?.rootViewController else { fatalError("The scene must be contained in a window with a root view controller.") } guard let rootViewController = view?.window?.rootViewController else { fatalError("The scene must be contained in a window with a root view controller.") }
// `RPPreviewViewController` only supports full screen modal presentation. // `RPPreviewViewController` only supports full screen modal presentation.
previewViewController.modalPresentationStyle = UIModalPresentationStyle.FullScreen previewViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
rootViewController.presentViewController(previewViewController, animated: true, completion:nil) rootViewController.present(previewViewController, animated: true, completion:nil)
} }
} }

View File

@ -11,14 +11,14 @@ import GameplayKit
/// The different animation states that an animated character can be in. /// The different animation states that an animated character can be in.
enum AnimationState: String { enum AnimationState: String {
case Idle = "Idle" case idle = "Idle"
case WalkForward = "WalkForward" case walkForward = "WalkForward"
case WalkBackward = "WalkBackward" case walkBackward = "WalkBackward"
case PreAttack = "PreAttack" case preAttack = "PreAttack"
case Attack = "Attack" case attack = "Attack"
case Zapped = "Zapped" case zapped = "Zapped"
case Hit = "Hit" case hit = "Hit"
case Inactive = "Inactive" case inactive = "Inactive"
} }
/** /**
@ -86,7 +86,7 @@ class AnimationComponent: GKComponent {
static let textureActionKey = "textureAction" static let textureActionKey = "textureAction"
/// The time to display each frame of a texture animation. /// The time to display each frame of a texture animation.
static let timePerFrame = NSTimeInterval(1.0 / 10.0) static let timePerFrame = TimeInterval(1.0 / 10.0)
// MARK: Properties // MARK: Properties
@ -109,18 +109,23 @@ class AnimationComponent: GKComponent {
private(set) var currentAnimation: Animation? private(set) var currentAnimation: Animation?
/// The length of time spent in the current animation state and direction. /// The length of time spent in the current animation state and direction.
private var elapsedAnimationDuration: NSTimeInterval = 0.0 private var elapsedAnimationDuration: TimeInterval = 0.0
// MARK: Initializers // MARK: Initializers
init(textureSize: CGSize, animations: [AnimationState: [CompassDirection: Animation]]) { init(textureSize: CGSize, animations: [AnimationState: [CompassDirection: Animation]]) {
node = SKSpriteNode(texture: nil, size: textureSize) node = SKSpriteNode(texture: nil, size: textureSize)
self.animations = animations self.animations = animations
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
// MARK: Character Animation // MARK: Character Animation
private func runAnimationForAnimationState(animationState: AnimationState, compassDirection: CompassDirection, deltaTime: NSTimeInterval) { private func runAnimationForAnimationState(animationState: AnimationState, compassDirection: CompassDirection, deltaTime: TimeInterval) {
// Update the tracking of how long we have been animating. // Update the tracking of how long we have been animating.
elapsedAnimationDuration += deltaTime elapsedAnimationDuration += deltaTime
@ -145,21 +150,21 @@ class AnimationComponent: GKComponent {
// Check if the action for the body node has changed. // Check if the action for the body node has changed.
if currentAnimation?.bodyActionName != animation.bodyActionName { if currentAnimation?.bodyActionName != animation.bodyActionName {
// Remove the existing body action if it exists. // Remove the existing body action if it exists.
node.removeActionForKey(AnimationComponent.bodyActionKey) node.removeAction(forKey: AnimationComponent.bodyActionKey)
// Reset the node's position in its parent (it may have been animating with a move action). // Reset the node's position in its parent (it may have been animating with a move action).
node.position = CGPoint.zero node.position = CGPoint.zero
// Add the new body action to the node if an action exists. // Add the new body action to the node if an action exists.
if let bodyAction = animation.bodyAction { if let bodyAction = animation.bodyAction {
node.runAction(SKAction.repeatActionForever(bodyAction), withKey: AnimationComponent.bodyActionKey) node.run(SKAction.repeatForever(bodyAction), withKey: AnimationComponent.bodyActionKey)
} }
} }
// Check if the action for the shadow node has changed. // Check if the action for the shadow node has changed.
if currentAnimation?.shadowActionName != animation.shadowActionName { if currentAnimation?.shadowActionName != animation.shadowActionName {
// Remove the existing shadow action if it exists. // Remove the existing shadow action if it exists.
shadowNode?.removeActionForKey(AnimationComponent.shadowActionKey) shadowNode?.removeAction(forKey: AnimationComponent.shadowActionKey)
// Reset the node's position in its parent (it may have been animating with a move action). // Reset the node's position in its parent (it may have been animating with a move action).
shadowNode?.position = CGPoint.zero shadowNode?.position = CGPoint.zero
@ -170,12 +175,12 @@ class AnimationComponent: GKComponent {
// Add the new shadow action to the shadow node if an action exists. // Add the new shadow action to the shadow node if an action exists.
if let shadowAction = animation.shadowAction { if let shadowAction = animation.shadowAction {
shadowNode?.runAction(SKAction.repeatActionForever(shadowAction), withKey: AnimationComponent.shadowActionKey) shadowNode?.run(SKAction.repeatForever(shadowAction), withKey: AnimationComponent.shadowActionKey)
} }
} }
// Remove the existing texture animation action if it exists. // Remove the existing texture animation action if it exists.
node.removeActionForKey(AnimationComponent.textureActionKey) node.removeAction(forKey: AnimationComponent.textureActionKey)
// Create a new action to display the appropriate animation textures. // Create a new action to display the appropriate animation textures.
let texturesAction: SKAction let texturesAction: SKAction
@ -210,15 +215,15 @@ class AnimationComponent: GKComponent {
// Create an appropriate action from the (possibly offset) animation frames. // Create an appropriate action from the (possibly offset) animation frames.
if animation.repeatTexturesForever { if animation.repeatTexturesForever {
texturesAction = SKAction.repeatActionForever(SKAction.animateWithTextures(animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame)) texturesAction = SKAction.repeatForever(SKAction.animate(with: animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame))
} }
else { else {
texturesAction = SKAction.animateWithTextures(animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame) texturesAction = SKAction.animate(with: animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame)
} }
} }
// Add the textures animation to the body node. // Add the textures animation to the body node.
node.runAction(texturesAction, withKey: AnimationComponent.textureActionKey) node.run(texturesAction, withKey: AnimationComponent.textureActionKey)
// Remember the animation we are currently running. // Remember the animation we are currently running.
currentAnimation = animation currentAnimation = animation
@ -229,14 +234,14 @@ class AnimationComponent: GKComponent {
// MARK: GKComponent Life Cycle // MARK: GKComponent Life Cycle
override func updateWithDeltaTime(deltaTime: NSTimeInterval) { override func update(deltaTime: TimeInterval) {
super.updateWithDeltaTime(deltaTime) super.update(deltaTime: deltaTime)
// If an animation has been requested, run the animation. // If an animation has been requested, run the animation.
if let animationState = requestedAnimationState { if let animationState = requestedAnimationState {
guard let orientationComponent = entity?.componentForClass(OrientationComponent.self) else { fatalError("An AnimationComponent's entity must have an OrientationComponent.") } guard let orientationComponent = entity?.component(ofType: OrientationComponent.self) else { fatalError("An AnimationComponent's entity must have an OrientationComponent.") }
runAnimationForAnimationState(animationState, compassDirection: orientationComponent.compassDirection, deltaTime: deltaTime) runAnimationForAnimationState(animationState: animationState, compassDirection: orientationComponent.compassDirection, deltaTime: deltaTime)
requestedAnimationState = nil requestedAnimationState = nil
} }
} }
@ -248,7 +253,7 @@ class AnimationComponent: GKComponent {
// Filter for this facing direction, and sort the resulting texture names alphabetically. // Filter for this facing direction, and sort the resulting texture names alphabetically.
let textureNames = atlas.textureNames.filter { let textureNames = atlas.textureNames.filter {
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_") $0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
}.sort() }.sorted()
// Find and return the first texture for this direction. // Find and return the first texture for this direction.
return atlas.textureNamed(textureNames.first!) return atlas.textureNamed(textureNames.first!)
@ -257,7 +262,7 @@ class AnimationComponent: GKComponent {
/// Creates a texture action from all textures in an atlas. /// Creates a texture action from all textures in an atlas.
class func actionForAllTexturesInAtlas(atlas: SKTextureAtlas) -> SKAction { class func actionForAllTexturesInAtlas(atlas: SKTextureAtlas) -> SKAction {
// Sort the texture names alphabetically, and map them to an array of actual textures. // Sort the texture names alphabetically, and map them to an array of actual textures.
let textures = atlas.textureNames.sort().map { let textures = atlas.textureNames.sorted().map {
atlas.textureNamed($0) atlas.textureNamed($0)
} }
@ -266,8 +271,8 @@ class AnimationComponent: GKComponent {
return SKAction.setTexture(textures.first!) return SKAction.setTexture(textures.first!)
} }
else { else {
let texturesAction = SKAction.animateWithTextures(textures, timePerFrame: AnimationComponent.timePerFrame) let texturesAction = SKAction.animate(with: textures, timePerFrame: AnimationComponent.timePerFrame)
return SKAction.repeatActionForever(texturesAction) return SKAction.repeatForever(texturesAction)
} }
} }
@ -299,7 +304,7 @@ class AnimationComponent: GKComponent {
// Find all matching texture names, sorted alphabetically, and map them to an array of actual textures. // Find all matching texture names, sorted alphabetically, and map them to an array of actual textures.
let textures = atlas.textureNames.filter { let textures = atlas.textureNames.filter {
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_") $0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
}.sort { }.sorted {
playBackwards ? $0 > $1 : $0 < $1 playBackwards ? $0 > $1 : $0 < $1
}.map { }.map {
atlas.textureNamed($0) atlas.textureNamed($0)

View File

@ -20,8 +20,8 @@ class BeamComponent: GKComponent {
let rotation: Float let rotation: Float
init(entity: GKEntity, antennaOffset: CGPoint) { init(entity: GKEntity, antennaOffset: CGPoint) {
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has a RenderComponent") } guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has a RenderComponent") }
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has an OrientationComponent") } guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has an OrientationComponent") }
position = CGPoint(x: renderComponent.node.position.x + antennaOffset.x, y: renderComponent.node.position.y + antennaOffset.y) position = CGPoint(x: renderComponent.node.position.x + antennaOffset.x, y: renderComponent.node.position.y + antennaOffset.y)
rotation = Float(orientationComponent.zRotation) rotation = Float(orientationComponent.zRotation)
@ -71,7 +71,7 @@ class BeamComponent: GKComponent {
/// The `RenderComponent' for this component's 'entity'. /// The `RenderComponent' for this component's 'entity'.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = entity?.componentForClass(RenderComponent.self) else { fatalError("A BeamComponent's entity must have a RenderComponent") } guard let renderComponent = entity?.component(ofType: RenderComponent.self) else { fatalError("A BeamComponent's entity must have a RenderComponent") }
return renderComponent return renderComponent
} }
@ -86,7 +86,11 @@ class BeamComponent: GKComponent {
BeamCoolingState(beamComponent: self) BeamCoolingState(beamComponent: self)
]) ])
stateMachine.enterState(BeamIdleState.self) stateMachine.enter(BeamIdleState.self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
deinit { deinit {
@ -96,8 +100,8 @@ class BeamComponent: GKComponent {
// MARK: GKComponent Life Cycle // MARK: GKComponent Life Cycle
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
stateMachine.updateWithDeltaTime(seconds) stateMachine.update(deltaTime: seconds)
} }
// MARK: Convenience // MARK: Convenience
@ -106,18 +110,18 @@ class BeamComponent: GKComponent {
Finds the nearest "bad" `TaskBot` that lies within the beam's arc. Finds the nearest "bad" `TaskBot` that lies within the beam's arc.
Returns `nil` if no `TaskBot`s are within targeting range. Returns `nil` if no `TaskBot`s are within targeting range.
*/ */
func findTargetInBeamArcWithCurrentTarget(currentTarget: TaskBot?) -> TaskBot? { func findTargetInBeamArc(withCurrentTarget currentTarget: TaskBot?) -> TaskBot? {
let playerBotNode = renderComponent.node let playerBotNode = renderComponent.node
// Use the player's `EntitySnapshot` to build an array of targetable `TaskBot`s who's antennas are within the beam's arc. // Use the player's `EntitySnapshot` to build an array of targetable `TaskBot`s who's antennas are within the beam's arc.
guard let level = playerBotNode.scene as? LevelScene else { return nil } guard let level = playerBotNode.scene as? LevelScene else { return nil }
guard let snapshot = level.entitySnapshotForEntity(playerBot) else { return nil } guard let snapshot = level.entitySnapshotForEntity(entity: playerBot) else { return nil }
let botsInArc = snapshot.entityDistances.filter { entityDistance in let botsInArc = snapshot.entityDistances.filter { entityDistance in
guard let taskBot = entityDistance.target as? TaskBot else { return false } guard let taskBot = entityDistance.target as? TaskBot else { return false }
// Filter out entities that aren't "bad" `TaskBot`s with a `RenderComponent`. // Filter out entities that aren't "bad" `TaskBot`s with a `RenderComponent`.
guard let taskBotNode = taskBot.componentForClass(RenderComponent.self)?.node else { return false } guard let taskBotNode = taskBot.component(ofType: RenderComponent.self)?.node else { return false }
if taskBot.isGood { if taskBot.isGood {
return false return false
} }
@ -139,16 +143,16 @@ class BeamComponent: GKComponent {
This adjustment allows for easier aiming as the `PlayerBot` and `TaskBot` This adjustment allows for easier aiming as the `PlayerBot` and `TaskBot`
get closer together. get closer together.
*/ */
let arcAngle = playerBotAntenna.angleTo(taskBotAntenna) * targetDistanceRatio let arcAngle = playerBotAntenna.angleTo(target: taskBotAntenna) * targetDistanceRatio
if arcAngle > Float(GameplayConfiguration.Beam.maxArcAngle) { if arcAngle > Float(GameplayConfiguration.Beam.maxArcAngle) {
return false return false
} }
// Filter out `TaskBot`s where there is scenery between their antenna and the `PlayerBot`'s antenna. // Filter out `TaskBot`s where there is scenery between their antenna and the `PlayerBot`'s antenna.
var hasLineOfSite = true var hasLineOfSite = true
level.physicsWorld.enumerateBodiesAlongRayStart(playerBotAntenna.position, end: taskBotAntenna.position) { obstacleBody, _, _, stop in level.physicsWorld.enumerateBodies(alongRayStart: playerBotAntenna.position, end: taskBotAntenna.position) { obstacleBody, _, _, stop in
// Ignore `EntityNode`s as they are not scenery. // Ignore nodes that have an entity as they are not scenery.
if obstacleBody.node is EntityNode { if obstacleBody.node?.entity != nil {
return return
} }
@ -162,7 +166,7 @@ class BeamComponent: GKComponent {
*/ */
if obstacleLowestY < taskBotNode.position.y || obstacleLowestY < playerBotNode.position.y { if obstacleLowestY < taskBotNode.position.y || obstacleLowestY < playerBotNode.position.y {
hasLineOfSite = false hasLineOfSite = false
stop.memory = true stop.pointee = true
} }
} }
@ -174,7 +178,7 @@ class BeamComponent: GKComponent {
let target: TaskBot? let target: TaskBot?
// If the current target is still targetable, continue to target it. // If the current target is still targetable, continue to target it.
if let currentTarget = currentTarget where botsInArc.contains(currentTarget) { if let currentTarget = currentTarget, botsInArc.contains(currentTarget) {
target = currentTarget target = currentTarget
} }
else { else {

View File

@ -15,7 +15,7 @@ class BeamCoolingState: GKState {
unowned var beamComponent: BeamComponent unowned var beamComponent: BeamComponent
/// The amount of time the beam has been cooling down. /// The amount of time the beam has been cooling down.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
// MARK: Initializers // MARK: Initializers
@ -25,24 +25,24 @@ class BeamCoolingState: GKState {
// MARK: GKState life cycle // MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
elapsedTime = 0.0 elapsedTime = 0.0
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
elapsedTime += seconds elapsedTime += seconds
// If the beam has spent long enough cooling down, enter `BeamIdleState`. // If the beam has spent long enough cooling down, enter `BeamIdleState`.
if elapsedTime >= GameplayConfiguration.Beam.coolDownDuration { if elapsedTime >= GameplayConfiguration.Beam.coolDownDuration {
stateMachine?.enterState(BeamIdleState.self) stateMachine?.enter(BeamIdleState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is BeamIdleState.Type, is BeamFiringState.Type: case is BeamIdleState.Type, is BeamFiringState.Type:
return true return true
@ -52,11 +52,11 @@ class BeamCoolingState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
if let playerBot = beamComponent.entity as? PlayerBot { if let playerBot = beamComponent.entity as? PlayerBot {
beamComponent.beamNode.updateWithBeamState(nextState, source: playerBot) beamComponent.beamNode.update(withBeamState: nextState, source: playerBot)
} }
} }
} }

View File

@ -18,7 +18,7 @@ class BeamFiringState: GKState {
var target: TaskBot? var target: TaskBot?
/// The amount of time the beam has been in its "firing" state. /// The amount of time the beam has been in its "firing" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `PlayerBot` associated with the `BeamComponent`'s `entity`. /// The `PlayerBot` associated with the `BeamComponent`'s `entity`.
var playerBot: PlayerBot { var playerBot: PlayerBot {
@ -28,7 +28,7 @@ class BeamFiringState: GKState {
/// The `RenderComponent` associated with the `BeamComponent`'s `entity`. /// The `RenderComponent` associated with the `BeamComponent`'s `entity`.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = beamComponent.entity?.componentForClass(RenderComponent.self) else { fatalError("A BeamFiringState's entity must have a RenderComponent.") } guard let renderComponent = beamComponent.entity?.component(ofType: RenderComponent.self) else { fatalError("A BeamFiringState's entity must have a RenderComponent.") }
return renderComponent return renderComponent
} }
@ -40,8 +40,8 @@ class BeamFiringState: GKState {
// MARK: GKState life cycle // MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the "amount of time firing" tracker when we enter the "firing" state. // Reset the "amount of time firing" tracker when we enter the "firing" state.
elapsedTime = 0.0 elapsedTime = 0.0
@ -63,7 +63,7 @@ class BeamFiringState: GKState {
*/ */
beamComponent.beamNode.zPosition = -1.0 beamComponent.beamNode.zPosition = -1.0
let aboveCharactersNode = scene.worldLayerNodes[.AboveCharacters]! let aboveCharactersNode = scene.worldLayerNodes[.aboveCharacters]!
aboveCharactersNode.addChild(beamComponent.beamNode) aboveCharactersNode.addChild(beamComponent.beamNode)
// Constrain the `BeamNode` to the antenna position on the `PlayerBot`'s node. // Constrain the `BeamNode` to the antenna position on the `PlayerBot`'s node.
@ -76,11 +76,11 @@ class BeamFiringState: GKState {
beamComponent.beamNode.constraints = [constraint] beamComponent.beamNode.constraints = [constraint]
} }
updateBeamNodeWithDeltaTime(0.0) updateBeamNode(withDeltaTime: 0.0)
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the "amount of time firing" tracker. // Update the "amount of time firing" tracker.
elapsedTime += seconds elapsedTime += seconds
@ -90,18 +90,18 @@ class BeamFiringState: GKState {
The player has been firing the beam for too long. Enter the `BeamCoolingState` The player has been firing the beam for too long. Enter the `BeamCoolingState`
to disable firing until the beam has had time to cool down. to disable firing until the beam has had time to cool down.
*/ */
stateMachine?.enterState(BeamCoolingState.self) stateMachine?.enter(BeamCoolingState.self)
} }
else if !beamComponent.isTriggered { else if !beamComponent.isTriggered {
// The beam is no longer being fired. Enter the `BeamIdleState`. // The beam is no longer being fired. Enter the `BeamIdleState`.
stateMachine?.enterState(BeamIdleState.self) stateMachine?.enter(BeamIdleState.self)
} }
else { else {
updateBeamNodeWithDeltaTime(seconds) updateBeamNode(withDeltaTime: seconds)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is BeamIdleState.Type, is BeamCoolingState.Type: case is BeamIdleState.Type, is BeamCoolingState.Type:
return true return true
@ -111,35 +111,35 @@ class BeamFiringState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
// Clear the current target. // Clear the current target.
target = nil target = nil
// Update the beam component with the next state. // Update the beam component with the next state.
beamComponent.beamNode.updateWithBeamState(nextState, source: beamComponent.playerBot) beamComponent.beamNode.update(withBeamState: nextState, source: beamComponent.playerBot)
} }
// MARK: Convenience // MARK: Convenience
func updateBeamNodeWithDeltaTime(seconds: NSTimeInterval) { func updateBeamNode(withDeltaTime seconds: TimeInterval) {
// Find an appropriate target for the beam. // Find an appropriate target for the beam.
target = beamComponent.findTargetInBeamArcWithCurrentTarget(target) target = beamComponent.findTargetInBeamArc(withCurrentTarget: target)
// If the beam has a target with a charge component, drain charge from it. // If the beam has a target with a charge component, drain charge from it.
if let chargeComponent = target?.componentForClass(ChargeComponent.self) { if let chargeComponent = target?.component(ofType: ChargeComponent.self) {
let chargeToLose = GameplayConfiguration.Beam.chargeLossPerSecond * seconds let chargeToLose = GameplayConfiguration.Beam.chargeLossPerSecond * seconds
chargeComponent.loseCharge(chargeToLose) chargeComponent.loseCharge(chargeToLose: chargeToLose)
} }
// Update the appearance, position, size and orientation of the `BeamNode`. // Update the appearance, position, size and orientation of the `BeamNode`.
beamComponent.beamNode.updateWithBeamState(self, source: playerBot, target: target) beamComponent.beamNode.update(withBeamState: self, source: playerBot, target: target)
// If the current target has been turned good, deactivate the beam and move to the idle state. // If the current target has been turned good, deactivate the beam and move to the idle state.
if let currentTarget = target where currentTarget.isGood { if let currentTarget = target, currentTarget.isGood {
beamComponent.isTriggered = false beamComponent.isTriggered = false
stateMachine?.enterState(BeamIdleState.self) stateMachine?.enter(BeamIdleState.self)
} }
} }

View File

@ -22,16 +22,16 @@ class BeamIdleState: GKState {
// MARK: GKState life cycle // MARK: GKState life cycle
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// If the beam has been triggered, enter `BeamFiringState`. // If the beam has been triggered, enter `BeamFiringState`.
if beamComponent.isTriggered { if beamComponent.isTriggered {
stateMachine?.enterState(BeamFiringState.self) stateMachine?.enter(BeamFiringState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is BeamFiringState.Type return stateClass is BeamFiringState.Type
} }
} }

View File

@ -64,6 +64,10 @@ class ChargeComponent: GKComponent {
chargeBar?.level = percentageCharge chargeBar?.level = percentageCharge
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Component actions // MARK: Component actions
@ -78,7 +82,7 @@ class ChargeComponent: GKComponent {
if newCharge < charge { if newCharge < charge {
charge = newCharge charge = newCharge
chargeBar?.level = percentageCharge chargeBar?.level = percentageCharge
delegate?.chargeComponentDidLoseCharge(self) delegate?.chargeComponentDidLoseCharge(chargeComponent: self)
} }
} }

View File

@ -10,18 +10,18 @@ import CoreGraphics
/// The different directions that an animated character can be facing. /// The different directions that an animated character can be facing.
enum CompassDirection: Int { enum CompassDirection: Int {
case East = 0, EastByNorthEast, NorthEast, NorthByNorthEast case east = 0, eastByNorthEast, northEast, northByNorthEast
case North, NorthByNorthWest, NorthWest, WestByNorthWest case north, northByNorthWest, northWest, westByNorthWest
case West, WestBySouthWest, SouthWest, SouthBySouthWest case west, westBySouthWest, southWest, southBySouthWest
case South, SouthBySouthEast, SouthEast, EastBySouthEast case south, southBySouthEast, southEast, eastBySouthEast
/// Convenience array of all available directions. /// Convenience array of all available directions.
static let allDirections: [CompassDirection] = static let allDirections: [CompassDirection] =
[ [
.East, .EastByNorthEast, .NorthEast, .NorthByNorthEast, .east, .eastByNorthEast, .northEast, .northByNorthEast,
.North, .NorthByNorthWest, .NorthWest, .WestByNorthWest, .north, .northByNorthWest, .northWest, .westByNorthWest,
.West, .WestBySouthWest, .SouthWest, .SouthBySouthWest, .west, .westBySouthWest, .southWest, .southBySouthWest,
.South, .SouthBySouthEast, .SouthEast, .EastBySouthEast .south, .southBySouthEast, .southEast, .eastBySouthEast
] ]
/// The angle of rotation that the orientation represents. /// The angle of rotation that the orientation represents.
@ -37,13 +37,13 @@ enum CompassDirection: Int {
let twoPi = M_PI * 2 let twoPi = M_PI * 2
// Normalize the node's rotation. // Normalize the node's rotation.
let rotation = (Double(zRotation) + twoPi) % twoPi let rotation = (Double(zRotation) + twoPi).truncatingRemainder(dividingBy: twoPi)
// Convert the rotation of the node to a percentage of a circle. // Convert the rotation of the node to a percentage of a circle.
let orientation = rotation / twoPi let orientation = rotation / twoPi
// Scale the percentage to a value between 0 and 15. // Scale the percentage to a value between 0 and 15.
let rawFacingValue = round(orientation * 16.0) % 16.0 let rawFacingValue = round(orientation * 16.0).truncatingRemainder(dividingBy: 16.0)
// Select the appropriate `CompassDirection` based on its members' raw values, which also run from 0 to 15. // Select the appropriate `CompassDirection` based on its members' raw values, which also run from 0 to 15.
self = CompassDirection(rawValue: Int(rawFacingValue))! self = CompassDirection(rawValue: Int(rawFacingValue))!
@ -52,28 +52,28 @@ enum CompassDirection: Int {
init(string: String) { init(string: String) {
switch string { switch string {
case "North": case "North":
self = .North self = .north
case "NorthEast": case "NorthEast":
self = .NorthEast self = .northEast
case "East": case "East":
self = .East self = .east
case "SouthEast": case "SouthEast":
self = .SouthEast self = .southEast
case "South": case "South":
self = .South self = .south
case "SouthWest": case "SouthWest":
self = .SouthWest self = .southWest
case "West": case "West":
self = .West self = .west
case "NorthWest": case "NorthWest":
self = .NorthWest self = .northWest
default: default:
fatalError("Unknown or unsupported string - \(string)") fatalError("Unknown or unsupported string - \(string)")

View File

@ -24,17 +24,17 @@ class FlyingBotBlastState: GKState {
var currentEmitterNode: SKEmitterNode? var currentEmitterNode: SKEmitterNode?
/// The amount of time the `TaskBot` has been in the "blast" state. /// The amount of time the `TaskBot` has been in the "blast" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A FlyingBotBlastState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A FlyingBotBlastState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
/// The `RenderComponent` associated with the `entity`. /// The `RenderComponent` associated with the `entity`.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("A FlyingBotBlastState's entity must have a RenderComponent.") } guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("A FlyingBotBlastState's entity must have a RenderComponent.") }
return renderComponent return renderComponent
} }
@ -61,8 +61,8 @@ class FlyingBotBlastState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the "length of this blast" tracker when entering the "blast" state. // Reset the "length of this blast" tracker when entering the "blast" state.
elapsedTime = 0.0 elapsedTime = 0.0
@ -81,17 +81,17 @@ class FlyingBotBlastState: GKState {
renderComponent.node.addChild(currentEmitterNode!) renderComponent.node.addChild(currentEmitterNode!)
// Request the appropriate "attack" animation for this `TaskBot`. // Request the appropriate "attack" animation for this `TaskBot`.
animationComponent.requestedAnimationState = .Attack animationComponent.requestedAnimationState = .attack
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Check if the `FlyingBot` has reached the end of its blast duration. // Check if the `FlyingBot` has reached the end of its blast duration.
elapsedTime += seconds elapsedTime += seconds
if elapsedTime >= GameplayConfiguration.FlyingBot.blastDuration { if elapsedTime >= GameplayConfiguration.FlyingBot.blastDuration {
// Return to an agent-controlled state if the blast has completed. // Return to an agent-controlled state if the blast has completed.
stateMachine?.enterState(TaskBotAgentControlledState.self) stateMachine?.enter(TaskBotAgentControlledState.self)
return return
} }
else if elapsedTime < GameplayConfiguration.FlyingBot.blastEffectDuration { else if elapsedTime < GameplayConfiguration.FlyingBot.blastEffectDuration {
@ -100,12 +100,12 @@ class FlyingBotBlastState: GKState {
performGoodBlast() performGoodBlast()
} }
else { else {
performBadBlastWithDeltaTime(seconds) performBadBlast(withDeltaTime: seconds)
} }
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type: case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
return true return true
@ -115,8 +115,8 @@ class FlyingBotBlastState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
// Remove the blast effect emitter node from the `TaskBot` when leaving the blast state. // Remove the blast effect emitter node from the `TaskBot` when leaving the blast state.
currentEmitterNode?.removeFromParent() currentEmitterNode?.removeFromParent()
@ -129,7 +129,7 @@ class FlyingBotBlastState: GKState {
func entitiesInRange() -> [GKEntity] { func entitiesInRange() -> [GKEntity] {
// Retrieve an entity snapshot containing the distances from this `TaskBot` to other entities in the `LevelScene`. // Retrieve an entity snapshot containing the distances from this `TaskBot` to other entities in the `LevelScene`.
guard let level = renderComponent.node.scene as? LevelScene else { return [] } guard let level = renderComponent.node.scene as? LevelScene else { return [] }
guard let entitySnapshot = level.entitySnapshotForEntity(entity) else { return [] } guard let entitySnapshot = level.entitySnapshotForEntity(entity: entity) else { return [] }
// Convert the array of `EntityDistance`s to an array of `GKEntity`s where the distance to the entity is within the blast radius. // Convert the array of `EntityDistance`s to an array of `GKEntity`s where the distance to the entity is within the blast radius.
let entitiesInRange: [GKEntity] = entitySnapshot.entityDistances.flatMap { let entitiesInRange: [GKEntity] = entitySnapshot.entityDistances.flatMap {
@ -150,7 +150,7 @@ class FlyingBotBlastState: GKState {
// Iterate through the `TaskBot`s in range. // Iterate through the `TaskBot`s in range.
for taskBot in taskBotsInRange { for taskBot in taskBotsInRange {
// Retrieve the current intelligence state for the `TaskBot`. // Retrieve the current intelligence state for the `TaskBot`.
guard let currentState = taskBot.componentForClass(IntelligenceComponent.self)?.stateMachine.currentState else { continue } guard let currentState = taskBot.component(ofType: IntelligenceComponent.self)?.stateMachine.currentState else { continue }
// If the entity is a "bad" `TaskBot` that isn't currently attacking, turn it "good". // If the entity is a "bad" `TaskBot` that isn't currently attacking, turn it "good".
if taskBot.isGood { continue } if taskBot.isGood { continue }
@ -166,7 +166,7 @@ class FlyingBotBlastState: GKState {
} }
/// Performs a "bad" blast that removes charge from the `PlayerBot` and turns "good" `TaskBot`s "bad". /// Performs a "bad" blast that removes charge from the `PlayerBot` and turns "good" `TaskBot`s "bad".
func performBadBlastWithDeltaTime(seconds: NSTimeInterval) { func performBadBlast(withDeltaTime seconds: TimeInterval) {
// Calculate how much charge `PlayerBot`s should lose if hit by this application of the blast attack. // Calculate how much charge `PlayerBot`s should lose if hit by this application of the blast attack.
let chargeToLose = GameplayConfiguration.FlyingBot.blastChargeLossPerSecond * seconds let chargeToLose = GameplayConfiguration.FlyingBot.blastChargeLossPerSecond * seconds
@ -174,12 +174,12 @@ class FlyingBotBlastState: GKState {
let entities = entitiesInRange() let entities = entitiesInRange()
for entity in entities { for entity in entities {
if let playerBot = entity as? PlayerBot where !playerBot.isPoweredDown, if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown,
let chargeComponent = entity.componentForClass(ChargeComponent.self) { let chargeComponent = entity.component(ofType: ChargeComponent.self) {
// Decrease the charge of a `PlayerBot` if it is in range and not powered down. // Decrease the charge of a `PlayerBot` if it is in range and not powered down.
chargeComponent.loseCharge(chargeToLose) chargeComponent.loseCharge(chargeToLose: chargeToLose)
} }
else if let taskBot = entity as? TaskBot where taskBot.isGood { else if let taskBot = entity as? TaskBot, taskBot.isGood {
// Turn a `TaskBot` "bad" if it is in range and "good". // Turn a `TaskBot` "bad" if it is in range and "good".
taskBot.isGood = false taskBot.isGood = false
} }

View File

@ -15,11 +15,11 @@ class FlyingBotPreAttackState: GKState {
unowned var entity: FlyingBot unowned var entity: FlyingBot
/// The amount of time the `FlyingBot` has been in its "pre-attack" state. /// The amount of time the `FlyingBot` has been in its "pre-attack" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A FlyingBotPreAttackState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A FlyingBotPreAttackState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
@ -31,18 +31,18 @@ class FlyingBotPreAttackState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the tracking of how long the `TaskBot` has been in a "pre-attack" state. // Reset the tracking of how long the `TaskBot` has been in a "pre-attack" state.
elapsedTime = 0.0 elapsedTime = 0.0
// Request the "attack" animation for this `FlyingBot`. // Request the "attack" animation for this `FlyingBot`.
animationComponent.requestedAnimationState = .Attack animationComponent.requestedAnimationState = .attack
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the time that the `TaskBot` has been in its "pre-attack" state. // Update the time that the `TaskBot` has been in its "pre-attack" state.
elapsedTime += seconds elapsedTime += seconds
@ -52,11 +52,11 @@ class FlyingBotPreAttackState: GKState {
move to the attack state. move to the attack state.
*/ */
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration { if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
stateMachine?.enterState(FlyingBotBlastState.self) stateMachine?.enter(FlyingBotBlastState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotAgentControlledState.Type, is FlyingBotBlastState.Type, is TaskBotZappedState.Type: case is TaskBotAgentControlledState.Type, is FlyingBotBlastState.Type, is TaskBotZappedState.Type:
return true return true

View File

@ -19,13 +19,13 @@ class GroundBotAttackState: GKState {
/// The `MovementComponent` associated with the `entity`. /// The `MovementComponent` associated with the `entity`.
var movementComponent: MovementComponent { var movementComponent: MovementComponent {
guard let movementComponent = entity.componentForClass(MovementComponent.self) else { fatalError("A GroundBotAttackState's entity must have a MovementComponent.") } guard let movementComponent = entity.component(ofType: MovementComponent.self) else { fatalError("A GroundBotAttackState's entity must have a MovementComponent.") }
return movementComponent return movementComponent
} }
/// The `PhysicsComponent` associated with the `entity`. /// The `PhysicsComponent` associated with the `entity`.
var physicsComponent: PhysicsComponent { var physicsComponent: PhysicsComponent {
guard let physicsComponent = entity.componentForClass(PhysicsComponent.self) else { fatalError("A GroundBotAttackState's entity must have a PhysicsComponent.") } guard let physicsComponent = entity.component(ofType: PhysicsComponent.self) else { fatalError("A GroundBotAttackState's entity must have a PhysicsComponent.") }
return physicsComponent return physicsComponent
} }
@ -43,14 +43,14 @@ class GroundBotAttackState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Apply damage to any entities the `GroundBot` is already in contact with. // Apply damage to any entities the `GroundBot` is already in contact with.
let contactedBodies = physicsComponent.physicsBody.allContactedBodies() let contactedBodies = physicsComponent.physicsBody.allContactedBodies()
for contactedBody in contactedBodies { for contactedBody in contactedBodies {
guard let entity = (contactedBody.node as? EntityNode)?.entity else { continue } guard let entity = contactedBody.node?.entity else { continue }
applyDamageToEntity(entity) applyDamageToEntity(entity: entity)
} }
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times. // `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
@ -74,8 +74,8 @@ class GroundBotAttackState: GKState {
movementComponent.nextRotation = nil movementComponent.nextRotation = nil
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times. // `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
let targetPosition = self.targetPosition let targetPosition = self.targetPosition
@ -86,7 +86,7 @@ class GroundBotAttackState: GKState {
let currentDistanceToTarget = hypot(dx, dy) let currentDistanceToTarget = hypot(dx, dy)
if currentDistanceToTarget < GameplayConfiguration.GroundBot.attackEndProximity { if currentDistanceToTarget < GameplayConfiguration.GroundBot.attackEndProximity {
stateMachine?.enterState(TaskBotAgentControlledState.self) stateMachine?.enter(TaskBotAgentControlledState.self)
return return
} }
@ -95,7 +95,7 @@ class GroundBotAttackState: GKState {
its target because it has been knocked off course. its target because it has been knocked off course.
*/ */
if currentDistanceToTarget > lastDistanceToTarget { if currentDistanceToTarget > lastDistanceToTarget {
stateMachine?.enterState(TaskBotAgentControlledState.self) stateMachine?.enter(TaskBotAgentControlledState.self)
return return
} }
@ -103,7 +103,7 @@ class GroundBotAttackState: GKState {
lastDistanceToTarget = currentDistanceToTarget lastDistanceToTarget = currentDistanceToTarget
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type: case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
return true return true
@ -113,8 +113,8 @@ class GroundBotAttackState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times. // `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let movementComponent = self.movementComponent let movementComponent = self.movementComponent
@ -129,11 +129,11 @@ class GroundBotAttackState: GKState {
// MARK: Convenience // MARK: Convenience
func applyDamageToEntity(entity: GKEntity) { func applyDamageToEntity(entity: GKEntity) {
if let playerBot = entity as? PlayerBot, chargeComponent = playerBot.componentForClass(ChargeComponent.self) where !playerBot.isPoweredDown { if let playerBot = entity as? PlayerBot, let chargeComponent = playerBot.component(ofType: ChargeComponent.self), !playerBot.isPoweredDown {
// If the other entity is a `PlayerBot` that isn't powered down, reduce its charge. // If the other entity is a `PlayerBot` that isn't powered down, reduce its charge.
chargeComponent.loseCharge(GameplayConfiguration.GroundBot.chargeLossPerContact) chargeComponent.loseCharge(chargeToLose: GameplayConfiguration.GroundBot.chargeLossPerContact)
} }
else if let taskBot = entity as? TaskBot where taskBot.isGood { else if let taskBot = entity as? TaskBot, taskBot.isGood {
// If the other entity is a good `TaskBot`, turn it bad. // If the other entity is a good `TaskBot`, turn it bad.
taskBot.isGood = false taskBot.isGood = false
} }

View File

@ -15,11 +15,11 @@ class GroundBotPreAttackState: GKState {
unowned var entity: GroundBot unowned var entity: GroundBot
/// The amount of time the `GroundBot` has been in its "pre-attack" state. /// The amount of time the `GroundBot` has been in its "pre-attack" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A GroundBotPreAttackState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A GroundBotPreAttackState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
@ -31,18 +31,18 @@ class GroundBotPreAttackState: GKState {
// MARK: GPState Life Cycle // MARK: GPState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the tracking of how long the `GroundBot` has been in a "pre-attack" state. // Reset the tracking of how long the `GroundBot` has been in a "pre-attack" state.
elapsedTime = 0.0 elapsedTime = 0.0
// Request the "attack" animation for this state's `GroundBot`. // Request the "attack" animation for this state's `GroundBot`.
animationComponent.requestedAnimationState = .Attack animationComponent.requestedAnimationState = .attack
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
elapsedTime += seconds elapsedTime += seconds
@ -51,11 +51,11 @@ class GroundBotPreAttackState: GKState {
move to the attack state. move to the attack state.
*/ */
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration { if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
stateMachine?.enterState(GroundBotAttackState.self) stateMachine?.enter(GroundBotAttackState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotAgentControlledState.Type, is GroundBotAttackState.Type, is TaskBotZappedState.Type: case is TaskBotAgentControlledState.Type, is GroundBotAttackState.Type, is TaskBotZappedState.Type:
return true return true

View File

@ -16,13 +16,13 @@ class GroundBotRotateToAttackState: GKState {
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
/// The `OrientationComponent` associated with the `entity`. /// The `OrientationComponent` associated with the `entity`.
var orientationComponent: OrientationComponent { var orientationComponent: OrientationComponent {
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an OrientationComponent.") } guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an OrientationComponent.") }
return orientationComponent return orientationComponent
} }
@ -40,21 +40,21 @@ class GroundBotRotateToAttackState: GKState {
// MARK: GPState Life Cycle // MARK: GPState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Request the "walk forward" animation for this `GroundBot`. // Request the "walk forward" animation for this `GroundBot`.
animationComponent.requestedAnimationState = .WalkForward animationComponent.requestedAnimationState = .walkForward
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// `orientationComponent` is a computed property. Declare a local version so we don't compute it multiple times. // `orientationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let orientationComponent = self.orientationComponent let orientationComponent = self.orientationComponent
// Calculate the angle the `GroundBot` needs to turn to face the `targetPosition`. // Calculate the angle the `GroundBot` needs to turn to face the `targetPosition`.
let angleDeltaToTarget = shortestAngleDeltaToTargetFromRotation(Float(orientationComponent.zRotation)) let angleDeltaToTarget = shortestAngleDeltaToTargetFromRotation(entityRotation: Float(orientationComponent.zRotation))
// Calculate the amount of rotation that should be applied during this update. // Calculate the amount of rotation that should be applied during this update.
var delta = CGFloat(seconds * GameplayConfiguration.GroundBot.preAttackRotationSpeed) var delta = CGFloat(seconds * GameplayConfiguration.GroundBot.preAttackRotationSpeed)
@ -66,7 +66,7 @@ class GroundBotRotateToAttackState: GKState {
if abs(delta) >= abs(angleDeltaToTarget) { if abs(delta) >= abs(angleDeltaToTarget) {
// Finish the rotation and enter `GroundBotPreAttackState`. // Finish the rotation and enter `GroundBotPreAttackState`.
orientationComponent.zRotation += angleDeltaToTarget orientationComponent.zRotation += angleDeltaToTarget
stateMachine?.enterState(GroundBotPreAttackState.self) stateMachine?.enter(GroundBotPreAttackState.self)
return return
} }
@ -74,10 +74,10 @@ class GroundBotRotateToAttackState: GKState {
orientationComponent.zRotation += delta orientationComponent.zRotation += delta
// The `GroundBot` may have rotated into a new `FacingDirection`, so re-request the "walk forward" animation. // The `GroundBot` may have rotated into a new `FacingDirection`, so re-request the "walk forward" animation.
animationComponent.requestedAnimationState = .WalkForward animationComponent.requestedAnimationState = .walkForward
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotAgentControlledState.Type, is GroundBotPreAttackState.Type, is TaskBotZappedState.Type: case is TaskBotAgentControlledState.Type, is GroundBotPreAttackState.Type, is TaskBotZappedState.Type:
return true return true

View File

@ -33,11 +33,11 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
didSet { didSet {
if isEnabled { if isEnabled {
// Apply the current input state to the movement and beam components. // Apply the current input state to the movement and beam components.
applyInputState(state) applyInputState(state: state)
} }
else { else {
// Apply a state of no input to the movement and beam components. // Apply a state of no input to the movement and beam components.
applyInputState(InputState.noInput) applyInputState(state: InputState.noInput)
} }
} }
} }
@ -45,22 +45,22 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
var state = InputState() { var state = InputState() {
didSet { didSet {
if isEnabled { if isEnabled {
applyInputState(state) applyInputState(state: state)
} }
} }
} }
// MARK: ControlInputSourceDelegate // MARK: ControlInputSourceDelegate
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) { func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) {
state.translation = MovementKind(displacement: displacement) state.translation = MovementKind(displacement: displacement)
} }
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) { func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) {
state.rotation = MovementKind(displacement: angularDisplacement) state.rotation = MovementKind(displacement: angularDisplacement)
} }
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2) { func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2) {
/* /*
Create a `MovementKind` instance indicating whether the displacement Create a `MovementKind` instance indicating whether the displacement
should translate the entity forwards or backwards from the direction should translate the entity forwards or backwards from the direction
@ -69,7 +69,7 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
state.translation = MovementKind(displacement: relativeDisplacement, relativeToOrientation: true) state.translation = MovementKind(displacement: relativeDisplacement, relativeToOrientation: true)
} }
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) { func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) {
/* /*
Create a `MovementKind` instance indicating whether the displacement Create a `MovementKind` instance indicating whether the displacement
should rotate the entity clockwise or counter-clockwise from the direction should rotate the entity clockwise or counter-clockwise from the direction
@ -78,26 +78,26 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
state.rotation = MovementKind(displacement: relativeAngularDisplacement, relativeToOrientation: true) state.rotation = MovementKind(displacement: relativeAngularDisplacement, relativeToOrientation: true)
} }
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType) { func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType) {
state.allowsStrafing = controlInputSource.allowsStrafing state.allowsStrafing = controlInputSource.allowsStrafing
state.beamIsTriggered = true state.beamIsTriggered = true
} }
func controlInputSourceDidFinishAttacking(controlInputSource: ControlInputSourceType) { func controlInputSourceDidFinishAttacking(_ controlInputSource: ControlInputSourceType) {
state.beamIsTriggered = false state.beamIsTriggered = false
} }
// MARK: Convenience // MARK: Convenience
func applyInputState(state: InputState) { func applyInputState(state: InputState) {
if let movementComponent = entity?.componentForClass(MovementComponent.self) { if let movementComponent = entity?.component(ofType: MovementComponent.self) {
movementComponent.allowsStrafing = state.allowsStrafing movementComponent.allowsStrafing = state.allowsStrafing
movementComponent.nextRotation = state.rotation movementComponent.nextRotation = state.rotation
movementComponent.nextTranslation = state.translation movementComponent.nextTranslation = state.translation
} }
if let beamComponent = entity?.componentForClass(BeamComponent.self) { if let beamComponent = entity?.component(ofType: BeamComponent.self) {
beamComponent.isTriggered = state.beamIsTriggered beamComponent.isTriggered = state.beamIsTriggered
} }
} }
} }

View File

@ -21,20 +21,26 @@ class IntelligenceComponent: GKComponent {
init(states: [GKState]) { init(states: [GKState]) {
stateMachine = GKStateMachine(states: states) stateMachine = GKStateMachine(states: states)
initialStateClass = states.first!.dynamicType let firstState = states.first!
initialStateClass = type(of: firstState)
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
// MARK: GKComponent Life Cycle // MARK: GKComponent Life Cycle
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
stateMachine.updateWithDeltaTime(seconds) stateMachine.update(deltaTime: seconds)
} }
// MARK: Actions // MARK: Actions
func enterInitialState() { func enterInitialState() {
stateMachine.enterState(initialStateClass) stateMachine.enter(initialStateClass)
} }
} }

View File

@ -55,19 +55,19 @@ class MovementComponent: GKComponent {
/// The `RenderComponent` for this component's entity. /// The `RenderComponent` for this component's entity.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = entity?.componentForClass(RenderComponent.self) else { fatalError("A MovementComponent's entity must have a RenderComponent") } guard let renderComponent = entity?.component(ofType: RenderComponent.self) else { fatalError("A MovementComponent's entity must have a RenderComponent") }
return renderComponent return renderComponent
} }
/// The `AnimationComponent` for this component's entity. /// The `AnimationComponent` for this component's entity.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity?.componentForClass(AnimationComponent.self) else { fatalError("A MovementComponent's entity must have an AnimationComponent") } guard let animationComponent = entity?.component(ofType: AnimationComponent.self) else { fatalError("A MovementComponent's entity must have an AnimationComponent") }
return animationComponent return animationComponent
} }
/// The `OrientationComponent` for this component's entity. /// The `OrientationComponent` for this component's entity.
var orientationComponent: OrientationComponent { var orientationComponent: OrientationComponent {
guard let orientationComponent = entity?.componentForClass(OrientationComponent.self) else { fatalError("A MovementComponent's entity must have an OrientationComponent") } guard let orientationComponent = entity?.component(ofType: OrientationComponent.self) else { fatalError("A MovementComponent's entity must have an OrientationComponent") }
return orientationComponent return orientationComponent
} }
@ -82,12 +82,17 @@ class MovementComponent: GKComponent {
override init() { override init() {
movementSpeed = GameplayConfiguration.PlayerBot.movementSpeed movementSpeed = GameplayConfiguration.PlayerBot.movementSpeed
angularSpeed = GameplayConfiguration.PlayerBot.angularSpeed angularSpeed = GameplayConfiguration.PlayerBot.angularSpeed
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
// MARK: GKComponent Life Cycle // MARK: GKComponent Life Cycle
override func updateWithDeltaTime(deltaTime: NSTimeInterval) { override func update(deltaTime: TimeInterval) {
super.updateWithDeltaTime(deltaTime) super.update(deltaTime: deltaTime)
// Declare local versions of computed properties so we don't compute them multiple times. // Declare local versions of computed properties so we don't compute them multiple times.
let node = renderComponent.node let node = renderComponent.node
@ -104,10 +109,10 @@ class MovementComponent: GKComponent {
nextRotation = MovementKind(displacement: targetVector) nextRotation = MovementKind(displacement: targetVector)
} }
if let movement = nextRotation, newRotation = angleForRotatingNode(node, withRotationalMovement: movement, duration: deltaTime) { if let movement = nextRotation, let newRotation = angleForRotatingNode(node: node, withRotationalMovement: movement, duration: deltaTime) {
// Update the node's `zRotation` with new rotation information. // Update the node's `zRotation` with new rotation information.
orientationComponent.zRotation = newRotation orientationComponent.zRotation = newRotation
animationState = .Idle animationState = .idle
} }
else { else {
// Clear the rotation if a valid angle could not be created. // Clear the rotation if a valid angle could not be created.
@ -115,7 +120,7 @@ class MovementComponent: GKComponent {
} }
// Update the node's `position` with new displacement information. // Update the node's `position` with new displacement information.
if let movement = nextTranslation, newPosition = pointForTranslatingNode(node, withTranslationalMovement: movement, duration: deltaTime) { if let movement = nextTranslation, let newPosition = pointForTranslatingNode(node: node, withTranslationalMovement: movement, duration: deltaTime) {
node.position = newPosition node.position = newPosition
// If no explicit rotation is being provided, orient in the direction of movement. // If no explicit rotation is being provided, orient in the direction of movement.
@ -127,7 +132,7 @@ class MovementComponent: GKComponent {
Always request a walking animation, but distinguish between walking Always request a walking animation, but distinguish between walking
forward and backwards based on node's `zRotation`. forward and backwards based on node's `zRotation`.
*/ */
animationState = animationStateForDestination(node, destination: newPosition) animationState = animationStateForDestination(node: node, destination: newPosition)
} }
else { else {
// Clear the translation if a valid point could not be created. // Clear the translation if a valid point could not be created.
@ -143,7 +148,7 @@ class MovementComponent: GKComponent {
// `animationComponent` is a computed property. Declare a local version so we don't compute it multiple times. // `animationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let animationComponent = self.animationComponent let animationComponent = self.animationComponent
if animationStateCanBeOverwritten(animationComponent.currentAnimation?.animationState) && animationStateCanBeOverwritten(animationComponent.requestedAnimationState) { if animationStateCanBeOverwritten(animationState: animationComponent.currentAnimation?.animationState) && animationStateCanBeOverwritten(animationState: animationComponent.requestedAnimationState) {
animationComponent.requestedAnimationState = animationState animationComponent.requestedAnimationState = animationState
} }
} }
@ -153,10 +158,10 @@ class MovementComponent: GKComponent {
/// Creates a vector towards the current target of a `BeamComponent` attack (if one exists). /// Creates a vector towards the current target of a `BeamComponent` attack (if one exists).
func vectorForBeamTowardsCurrentTarget() -> float2? { func vectorForBeamTowardsCurrentTarget() -> float2? {
guard let beamComponent = entity?.componentForClass(BeamComponent.self) else { return nil } guard let beamComponent = entity?.component(ofType: BeamComponent.self) else { return nil }
let target = (beamComponent.stateMachine.currentState as? BeamFiringState)?.target let target = (beamComponent.stateMachine.currentState as? BeamFiringState)?.target
guard let taskBotPosition = target?.componentForClass(RenderComponent)?.node.position else { return nil } guard let taskBotPosition = target?.component(ofType: RenderComponent.self)?.node.position else { return nil }
let playerBotPosition = beamComponent.playerBotAntenna.position let playerBotPosition = beamComponent.playerBotAntenna.position
// Return a vector translating from the `taskBotPosition` to the `playerBotPosition`. // Return a vector translating from the `taskBotPosition` to the `playerBotPosition`.
@ -164,7 +169,7 @@ class MovementComponent: GKComponent {
} }
/// Produces the destination point for the node, based on the provided translation. /// Produces the destination point for the node, based on the provided translation.
func pointForTranslatingNode(node: SKNode, withTranslationalMovement translation: MovementKind, duration: NSTimeInterval) -> CGPoint? { func pointForTranslatingNode(node: SKNode, withTranslationalMovement translation: MovementKind, duration: TimeInterval) -> CGPoint? {
// No translation if the vector is a zeroVector. // No translation if the vector is a zeroVector.
guard translation.displacement != float2() else { return nil } guard translation.displacement != float2() else { return nil }
@ -176,7 +181,7 @@ class MovementComponent: GKComponent {
if translation.isRelativeToOrientation { if translation.isRelativeToOrientation {
// Ensure the relative displacement component is non-zero. // Ensure the relative displacement component is non-zero.
guard displacement.x != 0 else { return nil } guard displacement.x != 0 else { return nil }
displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(displacement) displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(relativeDisplacement: displacement)
} }
let angle = CGFloat(atan2(displacement.y, displacement.x)) let angle = CGFloat(atan2(displacement.y, displacement.x))
@ -210,7 +215,7 @@ class MovementComponent: GKComponent {
return CGPoint(x: node.position.x + dx, y: node.position.y + dy) return CGPoint(x: node.position.x + dx, y: node.position.y + dy)
} }
func angleForRotatingNode(node: SKNode, withRotationalMovement rotation: MovementKind, duration: NSTimeInterval) -> CGFloat? { func angleForRotatingNode(node: SKNode, withRotationalMovement rotation: MovementKind, duration: TimeInterval) -> CGFloat? {
// No rotation if the vector is a zeroVector. // No rotation if the vector is a zeroVector.
guard rotation.displacement != float2() else { return nil } guard rotation.displacement != float2() else { return nil }
@ -251,7 +256,7 @@ class MovementComponent: GKComponent {
private func animationStateForDestination(node: SKNode, destination: CGPoint) -> AnimationState { private func animationStateForDestination(node: SKNode, destination: CGPoint) -> AnimationState {
// Ensures nodes rotation is the same direction as the destination point. // Ensures nodes rotation is the same direction as the destination point.
let isMovingWithOrientation = (orientationComponent.zRotation * atan2(destination.y, destination.x)) > 0 let isMovingWithOrientation = (orientationComponent.zRotation * atan2(destination.y, destination.x)) > 0
return isMovingWithOrientation ? .WalkForward : .WalkBackward return isMovingWithOrientation ? .walkForward : .walkBackward
} }
/** /**
@ -288,7 +293,7 @@ class MovementComponent: GKComponent {
*/ */
private func animationStateCanBeOverwritten(animationState: AnimationState?) -> Bool { private func animationStateCanBeOverwritten(animationState: AnimationState?) -> Bool {
switch animationState { switch animationState {
case nil, .Idle?, .WalkForward?, .WalkBackward?: case .idle?, .walkForward?, .walkBackward?:
return true return true
default: default:

View File

@ -15,7 +15,7 @@ class OrientationComponent: GKComponent {
var zRotation: CGFloat = 0.0 { var zRotation: CGFloat = 0.0 {
didSet { didSet {
let twoPi = CGFloat(M_PI * 2) let twoPi = CGFloat(M_PI * 2)
zRotation = (zRotation + twoPi) % twoPi zRotation = (zRotation + twoPi).truncatingRemainder(dividingBy: twoPi)
} }
} }
@ -28,4 +28,4 @@ class OrientationComponent: GKComponent {
zRotation = newValue.zRotation zRotation = newValue.zRotation
} }
} }
} }

View File

@ -21,5 +21,10 @@ class PhysicsComponent: GKComponent {
self.physicsBody.categoryBitMask = colliderType.categoryMask self.physicsBody.categoryBitMask = colliderType.categoryMask
self.physicsBody.collisionBitMask = colliderType.collisionMask self.physicsBody.collisionBitMask = colliderType.collisionMask
self.physicsBody.contactTestBitMask = colliderType.contactMask self.physicsBody.contactTestBitMask = colliderType.contactMask
super.init()
} }
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -15,29 +15,29 @@ class PlayerBotAppearState: GKState {
unowned var entity: PlayerBot unowned var entity: PlayerBot
/// The amount of time the `PlayerBot` has been in the "appear" state. /// The amount of time the `PlayerBot` has been in the "appear" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
/// The `RenderComponent` associated with the `entity`. /// The `RenderComponent` associated with the `entity`.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an RenderComponent.") } guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an RenderComponent.") }
return renderComponent return renderComponent
} }
/// The `OrientationComponent` associated with the `entity`. /// The `OrientationComponent` associated with the `entity`.
var orientationComponent: OrientationComponent { var orientationComponent: OrientationComponent {
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an OrientationComponent.") } guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an OrientationComponent.") }
return orientationComponent return orientationComponent
} }
/// The `InputComponent` associated with the `entity`. /// The `InputComponent` associated with the `entity`.
var inputComponent: InputComponent { var inputComponent: InputComponent {
guard let inputComponent = entity.componentForClass(InputComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an InputComponent.") } guard let inputComponent = entity.component(ofType: InputComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an InputComponent.") }
return inputComponent return inputComponent
} }
@ -52,8 +52,8 @@ class PlayerBotAppearState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the elapsed time. // Reset the elapsed time.
elapsedTime = 0.0 elapsedTime = 0.0
@ -78,14 +78,14 @@ class PlayerBotAppearState: GKState {
renderComponent.node.addChild(node) renderComponent.node.addChild(node)
// Hide the animation component node until the `PlayerBot` exits this state. // Hide the animation component node until the `PlayerBot` exits this state.
animationComponent.node.hidden = true animationComponent.node.isHidden = true
// Disable the input component while the `PlayerBot` appears. // Disable the input component while the `PlayerBot` appears.
inputComponent.isEnabled = false inputComponent.isEnabled = false
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the amount of time that the `PlayerBot` has been teleporting in to the level. // Update the amount of time that the `PlayerBot` has been teleporting in to the level.
elapsedTime += seconds elapsedTime += seconds
@ -96,19 +96,19 @@ class PlayerBotAppearState: GKState {
node.removeFromParent() node.removeFromParent()
// Switch the `PlayerBot` over to a "player controlled" state. // Switch the `PlayerBot` over to a "player controlled" state.
stateMachine?.enterState(PlayerBotPlayerControlledState.self) stateMachine?.enter(PlayerBotPlayerControlledState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is PlayerBotPlayerControlledState.Type return stateClass is PlayerBotPlayerControlledState.Type
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
// Un-hide the animation component node. // Un-hide the animation component node.
animationComponent.node.hidden = false animationComponent.node.isHidden = false
// Re-enable the input component // Re-enable the input component
inputComponent.isEnabled = true inputComponent.isEnabled = true

View File

@ -15,11 +15,11 @@ class PlayerBotHitState: GKState {
unowned var entity: PlayerBot unowned var entity: PlayerBot
/// The amount of time the `PlayerBot` has been in the "hit" state. /// The amount of time the `PlayerBot` has been in the "hit" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotHitState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotHitState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
@ -31,18 +31,18 @@ class PlayerBotHitState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the elapsed "hit" duration on entering this state. // Reset the elapsed "hit" duration on entering this state.
elapsedTime = 0.0 elapsedTime = 0.0
// Request the "hit" animation for this `PlayerBot`. // Request the "hit" animation for this `PlayerBot`.
animationComponent.requestedAnimationState = .Hit animationComponent.requestedAnimationState = .hit
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the amount of time the `PlayerBot` has been in the "hit" state. // Update the amount of time the `PlayerBot` has been in the "hit" state.
elapsedTime += seconds elapsedTime += seconds
@ -50,15 +50,15 @@ class PlayerBotHitState: GKState {
// When the `PlayerBot` has been in this state for long enough, transition to the appropriate next state. // When the `PlayerBot` has been in this state for long enough, transition to the appropriate next state.
if elapsedTime >= GameplayConfiguration.PlayerBot.hitStateDuration { if elapsedTime >= GameplayConfiguration.PlayerBot.hitStateDuration {
if entity.isPoweredDown { if entity.isPoweredDown {
stateMachine?.enterState(PlayerBotRechargingState.self) stateMachine?.enter(PlayerBotRechargingState.self)
} }
else { else {
stateMachine?.enterState(PlayerBotPlayerControlledState.self) stateMachine?.enter(PlayerBotPlayerControlledState.self)
} }
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is PlayerBotPlayerControlledState.Type, is PlayerBotRechargingState.Type: case is PlayerBotPlayerControlledState.Type, is PlayerBotRechargingState.Type:
return true return true

View File

@ -16,19 +16,19 @@ class PlayerBotPlayerControlledState: GKState {
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
/// The `MovementComponent` associated with the `entity`. /// The `MovementComponent` associated with the `entity`.
var movementComponent: MovementComponent { var movementComponent: MovementComponent {
guard let movementComponent = entity.componentForClass(MovementComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have a MovementComponent.") } guard let movementComponent = entity.component(ofType: MovementComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have a MovementComponent.") }
return movementComponent return movementComponent
} }
/// The `InputComponent` associated with the `entity`. /// The `InputComponent` associated with the `entity`.
var inputComponent: InputComponent { var inputComponent: InputComponent {
guard let inputComponent = entity.componentForClass(InputComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an InputComponent.") } guard let inputComponent = entity.component(ofType: InputComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an InputComponent.") }
return inputComponent return inputComponent
} }
@ -40,24 +40,24 @@ class PlayerBotPlayerControlledState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Turn on controller input for the `PlayerBot` when entering the player-controlled state. // Turn on controller input for the `PlayerBot` when entering the player-controlled state.
inputComponent.isEnabled = true inputComponent.isEnabled = true
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
/* /*
Assume an animation of "idle" that can then be overwritten by the movement Assume an animation of "idle" that can then be overwritten by the movement
component in response to user input. component in response to user input.
*/ */
animationComponent.requestedAnimationState = .Idle animationComponent.requestedAnimationState = .idle
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is PlayerBotHitState.Type, is PlayerBotRechargingState.Type: case is PlayerBotHitState.Type, is PlayerBotRechargingState.Type:
return true return true
@ -67,11 +67,11 @@ class PlayerBotPlayerControlledState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
// Turn off controller input for the `PlayerBot` when leaving the player-controlled state. // Turn off controller input for the `PlayerBot` when leaving the player-controlled state.
entity.componentForClass(InputComponent.self)?.isEnabled = false entity.component(ofType: InputComponent.self)?.isEnabled = false
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times. // `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let movementComponent = self.movementComponent let movementComponent = self.movementComponent

View File

@ -15,17 +15,17 @@ class PlayerBotRechargingState: GKState {
unowned var entity: PlayerBot unowned var entity: PlayerBot
/// The amount of time the `PlayerBot` has been in the "recharging" state. /// The amount of time the `PlayerBot` has been in the "recharging" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
/// The `ChargeComponent` associated with the `entity`. /// The `ChargeComponent` associated with the `entity`.
var chargeComponent: ChargeComponent { var chargeComponent: ChargeComponent {
guard let chargeComponent = entity.componentForClass(ChargeComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have a ChargeComponent.") } guard let chargeComponent = entity.component(ofType: ChargeComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have a ChargeComponent.") }
return chargeComponent return chargeComponent
} }
@ -37,18 +37,18 @@ class PlayerBotRechargingState: GKState {
// MARK: GKState life cycle // MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the recharge duration when entering this state. // Reset the recharge duration when entering this state.
elapsedTime = 0.0 elapsedTime = 0.0
// Request the "inactive" animation for the `PlayerBot`. // Request the "inactive" animation for the `PlayerBot`.
animationComponent.requestedAnimationState = .Inactive animationComponent.requestedAnimationState = .inactive
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the elapsed recharge duration. // Update the elapsed recharge duration.
elapsedTime += seconds elapsedTime += seconds
@ -64,16 +64,16 @@ class PlayerBotRechargingState: GKState {
// Add charge to the `PlayerBot`. // Add charge to the `PlayerBot`.
let amountToRecharge = GameplayConfiguration.PlayerBot.rechargeAmountPerSecond * seconds let amountToRecharge = GameplayConfiguration.PlayerBot.rechargeAmountPerSecond * seconds
chargeComponent.addCharge(amountToRecharge) chargeComponent.addCharge(chargeToAdd: amountToRecharge)
// If the `PlayerBot` is fully charged it can become player controlled again. // If the `PlayerBot` is fully charged it can become player controlled again.
if chargeComponent.isFullyCharged { if chargeComponent.isFullyCharged {
entity.isPoweredDown = false entity.isPoweredDown = false
stateMachine?.enterState(PlayerBotPlayerControlledState.self) stateMachine?.enter(PlayerBotPlayerControlledState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is PlayerBotPlayerControlledState.Type return stateClass is PlayerBotPlayerControlledState.Type
} }
} }

View File

@ -3,7 +3,7 @@
See LICENSE.txt for this samples licensing information See LICENSE.txt for this samples licensing information
Abstract: Abstract:
A `GKComponent` that provides an `SKNode` for an entity. This enables it to be represented in the SpriteKit world. The node is provided as an `EntityNode` instance, so that the entity can be discovered from the node. A `GKComponent` that provides an `SKNode` for an entity. This enables it to be represented in the SpriteKit world.
*/ */
import SpriteKit import SpriteKit
@ -13,9 +13,15 @@ class RenderComponent: GKComponent {
// MARK: Properties // MARK: Properties
// The `RenderComponent` vends a node allowing an entity to be rendered in a scene. // The `RenderComponent` vends a node allowing an entity to be rendered in a scene.
let node = EntityNode() let node = SKNode()
// MARK: GKComponent
init(entity: GKEntity) { override func didAddToEntity() {
node.entity = entity node.entity = entity
} }
override func willRemoveFromEntity() {
node.entity = nil
}
} }

View File

@ -21,22 +21,28 @@ class RulesComponent: GKComponent {
var ruleSystem: GKRuleSystem var ruleSystem: GKRuleSystem
/// The amount of time that has passed since the `TaskBot` last evaluated its rules. /// The amount of time that has passed since the `TaskBot` last evaluated its rules.
private var timeSinceRulesUpdate: NSTimeInterval = 0.0 private var timeSinceRulesUpdate: TimeInterval = 0.0
// MARK: Initializers // MARK: Initializers
override init() { override init() {
ruleSystem = GKRuleSystem() ruleSystem = GKRuleSystem()
super.init()
} }
init(rules: [GKRule]) { init(rules: [GKRule]) {
ruleSystem = GKRuleSystem() ruleSystem = GKRuleSystem()
ruleSystem.addRulesFromArray(rules) ruleSystem.add(rules)
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
// MARK: GKComponent Life Cycle // MARK: GKComponent Life Cycle
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
timeSinceRulesUpdate += seconds timeSinceRulesUpdate += seconds
if timeSinceRulesUpdate < GameplayConfiguration.TaskBot.rulesUpdateWaitDuration { return } if timeSinceRulesUpdate < GameplayConfiguration.TaskBot.rulesUpdateWaitDuration { return }
@ -44,8 +50,9 @@ class RulesComponent: GKComponent {
timeSinceRulesUpdate = 0.0 timeSinceRulesUpdate = 0.0
if let taskBot = entity as? TaskBot, if let taskBot = entity as? TaskBot,
level = taskBot.componentForClass(RenderComponent)?.node.scene as? LevelScene, let level = taskBot.component(ofType: RenderComponent.self)?.node.scene as? LevelScene,
entitySnapshot = level.entitySnapshotForEntity(taskBot) where !taskBot.isGood { let entitySnapshot = level.entitySnapshotForEntity(entity: taskBot),
!taskBot.isGood {
ruleSystem.reset() ruleSystem.reset()
@ -53,7 +60,7 @@ class RulesComponent: GKComponent {
ruleSystem.evaluate() ruleSystem.evaluate()
delegate?.rulesComponent(self, didFinishEvaluatingRuleSystem: ruleSystem) delegate?.rulesComponent(rulesComponent: self, didFinishEvaluatingRuleSystem: ruleSystem)
} }
} }
} }

View File

@ -24,6 +24,11 @@ class ShadowComponent: GKComponent {
node.alpha = 0.25 node.alpha = 0.25
node.size = size node.size = size
node.position = offset node.position = offset
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
} }

View File

@ -15,10 +15,10 @@ class TaskBotAgentControlledState: GKState {
unowned var entity: TaskBot unowned var entity: TaskBot
/// The amount of time that has passed since the `TaskBot` became agent-controlled. /// The amount of time that has passed since the `TaskBot` became agent-controlled.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The amount of time that has passed since the `TaskBot` last determined an appropriate behavior. /// The amount of time that has passed since the `TaskBot` last determined an appropriate behavior.
var timeSinceBehaviorUpdate: NSTimeInterval = 0.0 var timeSinceBehaviorUpdate: TimeInterval = 0.0
// MARK: Initializers // MARK: Initializers
@ -28,8 +28,8 @@ class TaskBotAgentControlledState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the amount of time since the last behavior update. // Reset the amount of time since the last behavior update.
timeSinceBehaviorUpdate = 0.0 timeSinceBehaviorUpdate = 0.0
@ -42,14 +42,14 @@ class TaskBotAgentControlledState: GKState {
`TaskBot`s recover to a full charge if they're hit with the beam but don't become "good". `TaskBot`s recover to a full charge if they're hit with the beam but don't become "good".
If this `TaskBot` has any charge, restore it to the full amount. If this `TaskBot` has any charge, restore it to the full amount.
*/ */
if let chargeComponent = entity.componentForClass(ChargeComponent.self) where chargeComponent.hasCharge { if let chargeComponent = entity.component(ofType: ChargeComponent.self), chargeComponent.hasCharge {
let chargeToAdd = chargeComponent.maximumCharge - chargeComponent.charge let chargeToAdd = chargeComponent.maximumCharge - chargeComponent.charge
chargeComponent.addCharge(chargeToAdd) chargeComponent.addCharge(chargeToAdd: chargeToAdd)
} }
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Update the "time since last behavior update" tracker. // Update the "time since last behavior update" tracker.
timeSinceBehaviorUpdate += seconds timeSinceBehaviorUpdate += seconds
@ -59,8 +59,8 @@ class TaskBotAgentControlledState: GKState {
if timeSinceBehaviorUpdate >= GameplayConfiguration.TaskBot.behaviorUpdateWaitDuration { if timeSinceBehaviorUpdate >= GameplayConfiguration.TaskBot.behaviorUpdateWaitDuration {
// When a `TaskBot` is returning to its path patrol start, and gets near enough, it should start to patrol. // When a `TaskBot` is returning to its path patrol start, and gets near enough, it should start to patrol.
if case let .ReturnToPositionOnPath(position) = entity.mandate where entity.distanceToPoint(position) <= GameplayConfiguration.TaskBot.thresholdProximityToPatrolPathStartPoint { if case let .returnToPositionOnPath(position) = entity.mandate, entity.distanceToPoint(otherPoint: position) <= GameplayConfiguration.TaskBot.thresholdProximityToPatrolPathStartPoint {
entity.mandate = entity.isGood ? .FollowGoodPatrolPath : .FollowBadPatrolPath entity.mandate = entity.isGood ? .followGoodPatrolPath : .followBadPatrolPath
} }
// Ensure the agent's behavior is the appropriate behavior for its current mandate. // Ensure the agent's behavior is the appropriate behavior for its current mandate.
@ -71,7 +71,7 @@ class TaskBotAgentControlledState: GKState {
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is FlyingBotPreAttackState.Type, is GroundBotRotateToAttackState.Type, is TaskBotZappedState.Type: case is FlyingBotPreAttackState.Type, is GroundBotRotateToAttackState.Type, is TaskBotZappedState.Type:
return true return true
@ -81,8 +81,8 @@ class TaskBotAgentControlledState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
/* /*
The `TaskBot` will no longer be controlled by an agent in the steering simulation The `TaskBot` will no longer be controlled by an agent in the steering simulation

View File

@ -14,16 +14,16 @@ class TaskBotBehavior: GKBehavior {
// MARK: Behavior factory methods // MARK: Behavior factory methods
/// Constructs a behavior to hunt a `TaskBot` or `PlayerBot` via a computed path. /// Constructs a behavior to hunt a `TaskBot` or `PlayerBot` via a computed path.
static func behaviorAndPathPointsForAgent(agent: GKAgent2D, huntingAgent target: GKAgent2D, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) { static func behaviorAndPathPoints(forAgent agent: GKAgent2D, huntingAgent target: GKAgent2D, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
let behavior = TaskBotBehavior() let behavior = TaskBotBehavior()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles. // Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed) behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene) behavior.addAvoidObstaclesGoal(forScene: scene)
// Find any nearby "bad" TaskBots to flock with. // Find any nearby "bad" TaskBots to flock with.
let agentsToFlockWith: [GKAgent2D] = scene.entities.flatMap { entity in let agentsToFlockWith: [GKAgent2D] = scene.entities.flatMap { entity in
if let taskBot = entity as? TaskBot where !taskBot.isGood && taskBot.agent !== agent && taskBot.distanceToAgent(agent) <= GameplayConfiguration.Flocking.agentSearchDistanceForFlocking { if let taskBot = entity as? TaskBot, !taskBot.isGood && taskBot.agent !== agent && taskBot.distanceToAgent(otherAgent: agent) <= GameplayConfiguration.Flocking.agentSearchDistanceForFlocking {
return taskBot.agent return taskBot.agent
} }
@ -32,54 +32,56 @@ class TaskBotBehavior: GKBehavior {
if !agentsToFlockWith.isEmpty { if !agentsToFlockWith.isEmpty {
// Add flocking goals for any nearby "bad" `TaskBot`s. // Add flocking goals for any nearby "bad" `TaskBot`s.
let separationGoal = GKGoal(toSeparateFromAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle) let separationGoal = GKGoal(toSeparateFrom: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle)
behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, forGoal: separationGoal) behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, for: separationGoal)
let alignmentGoal = GKGoal(toAlignWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle) let alignmentGoal = GKGoal(toAlignWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle)
behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, forGoal: alignmentGoal) behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, for: alignmentGoal)
let cohesionGoal = GKGoal(toCohereWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle) let cohesionGoal = GKGoal(toCohereWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle)
behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, forGoal: cohesionGoal) behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, for: cohesionGoal)
} }
// Add goals to follow a calculated path from the `TaskBot` to its target. // Add goals to follow a calculated path from the `TaskBot` to its target.
let pathPoints = behavior.addGoalsToFollowPathFromStartPoint(agent.position, toEndPoint: target.position, pathRadius: pathRadius, inScene: scene) let pathPoints = behavior.addGoalsToFollowPath(from: agent.position, to: target.position, pathRadius: pathRadius, inScene: scene)
// Return a tuple containing the new behavior, and the found path points for debug drawing. // Return a tuple containing the new behavior, and the found path points for debug drawing.
return (behavior, pathPoints) return (behavior, pathPoints)
} }
/// Constructs a behavior to return to the start of a `TaskBot` patrol path. /// Constructs a behavior to return to the start of a `TaskBot` patrol path.
static func behaviorAndPathPointsForAgent(agent: GKAgent2D, returningToPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) { static func behaviorAndPathPoints(forAgent agent: GKAgent2D, returningToPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
let behavior = TaskBotBehavior() let behavior = TaskBotBehavior()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles. // Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed) behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene) behavior.addAvoidObstaclesGoal(forScene: scene)
// Add goals to follow a calculated path from the `TaskBot` to the start of its patrol path. // Add goals to follow a calculated path from the `TaskBot` to the start of its patrol path.
let pathPoints = behavior.addGoalsToFollowPathFromStartPoint(agent.position, toEndPoint: endPoint, pathRadius: pathRadius, inScene: scene) let pathPoints = behavior.addGoalsToFollowPath(from: agent.position, to: endPoint, pathRadius: pathRadius, inScene: scene)
// Return a tuple containing the new behavior, and the found path points for debug drawing. // Return a tuple containing the new behavior, and the found path points for debug drawing.
return (behavior, pathPoints) return (behavior, pathPoints)
} }
/// Constructs a behavior to patrol a path of points, avoiding obstacles along the way. /// Constructs a behavior to patrol a path of points, avoiding obstacles along the way.
static func behaviorForAgent(agent: GKAgent2D, patrollingPathWithPoints patrolPathPoints: [CGPoint], pathRadius: Float, inScene scene: LevelScene) -> GKBehavior { static func behavior(forAgent agent: GKAgent2D, patrollingPathWithPoints patrolPathPoints: [CGPoint], pathRadius: Float, inScene scene: LevelScene) -> GKBehavior {
let behavior = TaskBotBehavior() let behavior = TaskBotBehavior()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles. // Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed) behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene) behavior.addAvoidObstaclesGoal(forScene: scene)
// Convert the patrol path to an array of `float2`s. // Convert the patrol path to an array of `float2`s.
let pathVectorPoints = patrolPathPoints.map(float2.init)
let pathVectorPoints = patrolPathPoints.map { float2($0) }
// Create a cyclical (closed) `GKPath` from the provided path points with the requested path radius. // Create a cyclical (closed) `GKPath` from the provided path points with the requested path radius.
let path = GKPath(points: UnsafeMutablePointer<float2>(pathVectorPoints), count: pathVectorPoints.count, radius: pathRadius, cyclical: true) // GKPath(points: &pathVectorPoints, radius: <#T##Float#>, cyclical: <#T##Bool#>)
let path = GKPath(points: pathVectorPoints, radius: pathRadius, cyclical: true)
// Add "follow path" and "stay on path" goals for this path. // Add "follow path" and "stay on path" goals for this path.
behavior.addFollowAndStayOnPathGoalsForPath(path) behavior.addFollowAndStayOnPathGoals(for: path)
return behavior return behavior
} }
@ -90,7 +92,7 @@ class TaskBotBehavior: GKBehavior {
Calculates all of the extruded obstacles that the provided point resides near. Calculates all of the extruded obstacles that the provided point resides near.
The extrusion is based on the buffer radius of the pathfinding graph. The extrusion is based on the buffer radius of the pathfinding graph.
*/ */
private func extrudedObstaclesContainingPoint(point: float2, inScene scene: LevelScene) -> [GKPolygonObstacle] { private func extrudedObstaclesContaining(point: float2, inScene scene: LevelScene) -> [GKPolygonObstacle] {
/* /*
Add a small fudge factor (+5) to the extrusion radius to make sure Add a small fudge factor (+5) to the extrusion radius to make sure
we're including all obstacles. we're including all obstacles.
@ -108,14 +110,14 @@ class TaskBotBehavior: GKBehavior {
// Retrieve all vertices for the polygon obstacle. // Retrieve all vertices for the polygon obstacle.
let range = 0..<obstacle.vertexCount let range = 0..<obstacle.vertexCount
let polygonVertices = range.map { obstacle.vertexAtIndex($0) } let polygonVertices = range.map { obstacle.vertex(at: $0) }
guard !polygonVertices.isEmpty else { return false } guard !polygonVertices.isEmpty else { return false }
let maxX = polygonVertices.maxElement { $0.x < $1.x }!.x + extrusionRadius let maxX = polygonVertices.max { $0.x < $1.x }!.x + extrusionRadius
let maxY = polygonVertices.maxElement { $0.y < $1.y }!.y + extrusionRadius let maxY = polygonVertices.max { $0.y < $1.y }!.y + extrusionRadius
let minX = polygonVertices.minElement { $0.x < $1.x }!.x - extrusionRadius let minX = polygonVertices.min { $0.x < $1.x }!.x - extrusionRadius
let minY = polygonVertices.minElement { $0.y < $1.y }!.y - extrusionRadius let minY = polygonVertices.min { $0.y < $1.y }!.y - extrusionRadius
return (point.x > minX && point.x < maxX) && (point.y > minY && point.y < maxY) return (point.x > minX && point.x < maxX) && (point.y > minY && point.y < maxY)
} }
@ -127,12 +129,12 @@ class TaskBotBehavior: GKBehavior {
Returns `nil` if a valid connection could not be made. Returns `nil` if a valid connection could not be made.
*/ */
private func connectedNodeForPoint(point: float2, onObstacleGraphInScene scene: LevelScene) -> GKGraphNode2D? { private func connectedNode(forPoint point: float2, onObstacleGraphInScene scene: LevelScene) -> GKGraphNode2D? {
// Create a graph node for this point. // Create a graph node for this point.
let pointNode = GKGraphNode2D(point: point) let pointNode = GKGraphNode2D(point: point)
// Try to connect this node to the graph. // Try to connect this node to the graph.
scene.graph.connectNodeUsingObstacles(pointNode) scene.graph.connectUsingObstacles(node: pointNode)
/* /*
Check to see if we were able to connect the node to the graph. Check to see if we were able to connect the node to the graph.
@ -143,20 +145,20 @@ class TaskBotBehavior: GKBehavior {
*/ */
if pointNode.connectedNodes.isEmpty { if pointNode.connectedNodes.isEmpty {
// The previous connection attempt failed, so remove the node from the graph. // The previous connection attempt failed, so remove the node from the graph.
scene.graph.removeNodes([pointNode]) scene.graph.remove([pointNode])
// Search the graph for all intersecting obstacles. // Search the graph for all intersecting obstacles.
let intersectingObstacles = extrudedObstaclesContainingPoint(point, inScene: scene) let intersectingObstacles = extrudedObstaclesContaining(point: point, inScene: scene)
/* /*
Connect this node to the graph ignoring the buffer radius of any Connect this node to the graph ignoring the buffer radius of any
obstacles that the point is currently intersecting. obstacles that the point is currently intersecting.
*/ */
scene.graph.connectNodeUsingObstacles(pointNode, ignoringBufferRadiusOfObstacles: intersectingObstacles) scene.graph.connectUsingObstacles(node: pointNode, ignoringBufferRadiusOf: intersectingObstacles)
// If still no connection could be made, return `nil`. // If still no connection could be made, return `nil`.
if pointNode.connectedNodes.isEmpty { if pointNode.connectedNodes.isEmpty {
scene.graph.removeNodes([pointNode]) scene.graph.remove([pointNode])
return nil return nil
} }
} }
@ -165,16 +167,16 @@ class TaskBotBehavior: GKBehavior {
} }
/// Pathfinds around obstacles to create a path between two points, and adds goals to follow that path. /// Pathfinds around obstacles to create a path between two points, and adds goals to follow that path.
private func addGoalsToFollowPathFromStartPoint(startPoint: float2, toEndPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> [CGPoint] { private func addGoalsToFollowPath(from startPoint: float2, to endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> [CGPoint] {
// Convert the provided `CGPoint`s into nodes for the `GPGraph`. // Convert the provided `CGPoint`s into nodes for the `GPGraph`.
guard let startNode = connectedNodeForPoint(startPoint, onObstacleGraphInScene: scene), guard let startNode = connectedNode(forPoint: startPoint, onObstacleGraphInScene: scene),
endNode = connectedNodeForPoint(endPoint, onObstacleGraphInScene: scene) else { return [] } let endNode = connectedNode(forPoint: endPoint, onObstacleGraphInScene: scene) else { return [] }
// Remove the "start" and "end" nodes when exiting this scope. // Remove the "start" and "end" nodes when exiting this scope.
defer { scene.graph.removeNodes([startNode, endNode]) } defer { scene.graph.remove([startNode, endNode]) }
// Find a path between these two nodes. // Find a path between these two nodes.
let pathNodes = scene.graph.findPathFromNode(startNode, toNode: endNode) as! [GKGraphNode2D] let pathNodes = scene.graph.findPath(from: startNode, to: endNode) as! [GKGraphNode2D]
// A valid `GKPath` can not be created if fewer than 2 path nodes were found, return. // A valid `GKPath` can not be created if fewer than 2 path nodes were found, return.
guard pathNodes.count > 1 else { return [] } guard pathNodes.count > 1 else { return [] }
@ -183,7 +185,7 @@ class TaskBotBehavior: GKBehavior {
let path = GKPath(graphNodes: pathNodes, radius: pathRadius) let path = GKPath(graphNodes: pathNodes, radius: pathRadius)
// Add "follow path" and "stay on path" goals for this path. // Add "follow path" and "stay on path" goals for this path.
addFollowAndStayOnPathGoalsForPath(path) addFollowAndStayOnPathGoals(for: path)
// Convert the `GKGraphNode2D` nodes into `CGPoint`s for debug drawing. // Convert the `GKGraphNode2D` nodes into `CGPoint`s for debug drawing.
let pathPoints = pathNodes.map { CGPoint($0.position) } let pathPoints = pathNodes.map { CGPoint($0.position) }
@ -191,21 +193,21 @@ class TaskBotBehavior: GKBehavior {
} }
/// Adds a goal to avoid all polygon obstacles in the scene. /// Adds a goal to avoid all polygon obstacles in the scene.
private func addAvoidObstaclesGoalForScene(scene: LevelScene) { private func addAvoidObstaclesGoal(forScene scene: LevelScene) {
setWeight(1.0, forGoal: GKGoal(toAvoidObstacles: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance)) setWeight(1.0, for: GKGoal(toAvoid: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance))
} }
/// Adds a goal to attain a target speed. /// Adds a goal to attain a target speed.
private func addTargetSpeedGoal(speed: Float) { private func addTargetSpeedGoal(speed: Float) {
setWeight(0.5, forGoal: GKGoal(toReachTargetSpeed: speed)) setWeight(0.5, for: GKGoal(toReachTargetSpeed: speed))
} }
/// Adds goals to follow and stay on a path. /// Adds goals to follow and stay on a path.
private func addFollowAndStayOnPathGoalsForPath(path: GKPath) { private func addFollowAndStayOnPathGoals(for path: GKPath) {
// The "follow path" goal tries to keep the agent facing in a forward direction when it is on this path. // The "follow path" goal tries to keep the agent facing in a forward direction when it is on this path.
setWeight(1.0, forGoal: GKGoal(toFollowPath: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath, forward: true)) setWeight(1.0, for: GKGoal(toFollow: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath, forward: true))
// The "stay on path" goal tries to keep the agent on the path within the path's radius. // The "stay on path" goal tries to keep the agent on the path within the path's radius.
setWeight(1.0, forGoal: GKGoal(toStayOnPath: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath)) setWeight(1.0, for: GKGoal(toStayOn: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath))
} }
} }

View File

@ -15,11 +15,11 @@ class TaskBotZappedState: GKState {
unowned var entity: TaskBot unowned var entity: TaskBot
/// The amount of time the `TaskBot` has been in its "zapped" state. /// The amount of time the `TaskBot` has been in its "zapped" state.
var elapsedTime: NSTimeInterval = 0.0 var elapsedTime: TimeInterval = 0.0
/// The `AnimationComponent` associated with the `entity`. /// The `AnimationComponent` associated with the `entity`.
var animationComponent: AnimationComponent { var animationComponent: AnimationComponent {
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A TaskBotZappedState's entity must have an AnimationComponent.") } guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A TaskBotZappedState's entity must have an AnimationComponent.") }
return animationComponent return animationComponent
} }
@ -31,14 +31,14 @@ class TaskBotZappedState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Reset the elapsed time. // Reset the elapsed time.
elapsedTime = 0.0 elapsedTime = 0.0
// Check if the `TaskBot` has a movement component. (`GroundBot`s do, `FlyingBot`s do not.) // Check if the `TaskBot` has a movement component. (`GroundBot`s do, `FlyingBot`s do not.)
if let movementComponent = entity.componentForClass(MovementComponent.self) { if let movementComponent = entity.component(ofType: MovementComponent.self) {
// Clear any pending movement. // Clear any pending movement.
movementComponent.nextTranslation = nil movementComponent.nextTranslation = nil
movementComponent.nextRotation = nil movementComponent.nextRotation = nil
@ -46,11 +46,11 @@ class TaskBotZappedState: GKState {
} }
// Request the "zapped" animation for this `TaskBot`. // Request the "zapped" animation for this `TaskBot`.
animationComponent.requestedAnimationState = .Zapped animationComponent.requestedAnimationState = .zapped
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
elapsedTime += seconds elapsedTime += seconds
@ -59,11 +59,11 @@ class TaskBotZappedState: GKState {
re-enter `TaskBotAgentControlledState`. re-enter `TaskBotAgentControlledState`.
*/ */
if entity.isGood || elapsedTime >= GameplayConfiguration.TaskBot.zappedStateDuration { if entity.isGood || elapsedTime >= GameplayConfiguration.TaskBot.zappedStateDuration {
stateMachine?.enterState(TaskBotAgentControlledState.self) stateMachine?.enter(TaskBotAgentControlledState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is TaskBotZappedState.Type: case is TaskBotZappedState.Type:
/* /*

View File

@ -9,7 +9,7 @@
import simd import simd
enum ControlInputDirection: Int { enum ControlInputDirection: Int {
case Up = 0, Down, Left, Right case up = 0, down, left, right
init?(vector: float2) { init?(vector: float2) {
// Require sufficient displacement to specify direction. // Require sufficient displacement to specify direction.
@ -17,25 +17,25 @@ enum ControlInputDirection: Int {
// Take the max displacement as the specified axis. // Take the max displacement as the specified axis.
if abs(vector.x) > abs(vector.y) { if abs(vector.x) > abs(vector.y) {
self = vector.x > 0 ? .Right : .Left self = vector.x > 0 ? .right : .left
} }
else { else {
self = vector.y > 0 ? .Up : .Down self = vector.y > 0 ? .up : .down
} }
} }
} }
/// Delegate methods for responding to control input that applies to the game as a whole. /// Delegate methods for responding to control input that applies to the game as a whole.
protocol ControlInputSourceGameStateDelegate: class { protocol ControlInputSourceGameStateDelegate: class {
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType) func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType)
func controlInputSource(controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection) func controlInputSource(_ controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection)
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType)
#if DEBUG #if DEBUG
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType) func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType)
#endif #endif
} }
@ -49,14 +49,14 @@ protocol ControlInputSourceDelegate: class {
Left: (-1.0, 0.0) Left: (-1.0, 0.0)
Right: (1.0, 0.0) Right: (1.0, 0.0)
*/ */
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2)
/** /**
Update the `ControlInputSourceDelegate` with new angular displacement Update the `ControlInputSourceDelegate` with new angular displacement
denoting both the requested angle, and magnitude with which to rotate. denoting both the requested angle, and magnitude with which to rotate.
Measured in radians. Measured in radians.
*/ */
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2)
/** /**
Update the `ControlInputSourceDelegate` to move forward or backward Update the `ControlInputSourceDelegate` to move forward or backward
@ -64,7 +64,7 @@ protocol ControlInputSourceDelegate: class {
Forward: (0.0, 1.0) Forward: (0.0, 1.0)
Backward: (0.0, -1.0) Backward: (0.0, -1.0)
*/ */
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2) func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2)
/** /**
Update the `ControlInputSourceDelegate` with new angular displacement Update the `ControlInputSourceDelegate` with new angular displacement
@ -72,13 +72,13 @@ protocol ControlInputSourceDelegate: class {
Clockwise: (-1.0, 0.0) Clockwise: (-1.0, 0.0)
CounterClockwise: (1.0, 0.0) CounterClockwise: (1.0, 0.0)
*/ */
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2)
/// Instructs the `ControlInputSourceDelegate` to cause the player to attack. /// Instructs the `ControlInputSourceDelegate` to cause the player to attack.
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType) func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType)
/// Instructs the `ControlInputSourceDelegate` to end the player's attack. /// Instructs the `ControlInputSourceDelegate` to end the player's attack.
func controlInputSourceDidFinishAttacking(controlInputSource: ControlInputSourceType) func controlInputSourceDidFinishAttacking(_ controlInputSource: ControlInputSourceType)
} }
/// A protocol to be adopted by classes that provide control input and notify their delegates when input is available. /// A protocol to be adopted by classes that provide control input and notify their delegates when input is available.
@ -92,4 +92,4 @@ protocol ControlInputSourceType: class {
var allowsStrafing: Bool { get } var allowsStrafing: Bool { get }
func resetControlState() func resetControlState()
} }

View File

@ -68,7 +68,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
} }
// Create components that define how the entity looks and behaves. // Create components that define how the entity looks and behaves.
let renderComponent = RenderComponent(entity: self) let renderComponent = RenderComponent()
addComponent(renderComponent) addComponent(renderComponent)
let orientationComponent = OrientationComponent() let orientationComponent = OrientationComponent()
@ -107,35 +107,39 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: ContactableType // MARK: ContactableType
override func contactWithEntityDidBegin(entity: GKEntity) { override func contactWithEntityDidBegin(_ entity: GKEntity) {
super.contactWithEntityDidBegin(entity) super.contactWithEntityDidBegin(entity)
guard !isGood else { return } guard !isGood else { return }
var shouldStartAttack = false var shouldStartAttack = false
if let otherTaskBot = entity as? TaskBot where otherTaskBot.isGood { if let otherTaskBot = entity as? TaskBot, otherTaskBot.isGood {
// Contact with good task bot will trigger an attack. // Contact with good task bot will trigger an attack.
shouldStartAttack = true shouldStartAttack = true
} }
else if let playerBot = entity as? PlayerBot where !playerBot.isPoweredDown { else if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown {
// Contact with an active `PlayerBot` will trigger an attack. // Contact with an active `PlayerBot` will trigger an attack.
shouldStartAttack = true shouldStartAttack = true
} }
if let stateMachine = componentForClass(IntelligenceComponent)?.stateMachine where shouldStartAttack { if let stateMachine = component(ofType: IntelligenceComponent.self)?.stateMachine, shouldStartAttack {
stateMachine.enterState(FlyingBotPreAttackState.self) stateMachine.enter(FlyingBotPreAttackState.self)
} }
} }
// MARK: ChargeComponentDelegate // MARK: ChargeComponentDelegate
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) { func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
guard let intelligenceComponent = componentForClass(IntelligenceComponent) else { return } guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self) intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
isGood = !chargeComponent.hasCharge isGood = !chargeComponent.hasCharge
} }
@ -145,7 +149,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
return goodAnimations == nil || badAnimations == nil return goodAnimations == nil || badAnimations == nil
} }
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) { static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
// Load `TaskBot`s shared assets. // Load `TaskBot`s shared assets.
super.loadSharedAssets() super.loadSharedAssets()
@ -170,13 +174,13 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
after the `FlyingBot` texture atlases have finished preloading. after the `FlyingBot` texture atlases have finished preloading.
*/ */
goodAnimations = [:] goodAnimations = [:]
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .WalkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale") goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
goodAnimations![.Attack] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") goodAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
badAnimations = [:] badAnimations = [:]
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .WalkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale") badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
badAnimations![.Attack] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
badAnimations![.Zapped] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .Zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") badAnimations![.zapped] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
// Invoke the passed `completionHandler` to indicate that loading has completed. // Invoke the passed `completionHandler` to indicate that loading has completed.
completionHandler() completionHandler()

View File

@ -73,7 +73,7 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
} }
// Create components that define how the entity looks and behaves. // Create components that define how the entity looks and behaves.
let renderComponent = RenderComponent(entity: self) let renderComponent = RenderComponent()
addComponent(renderComponent) addComponent(renderComponent)
let orientationComponent = OrientationComponent() let orientationComponent = OrientationComponent()
@ -116,22 +116,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
beamTargetOffset = GameplayConfiguration.GroundBot.beamTargetOffset beamTargetOffset = GameplayConfiguration.GroundBot.beamTargetOffset
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: ContactableType // MARK: ContactableType
override func contactWithEntityDidBegin(entity: GKEntity) { override func contactWithEntityDidBegin(_ entity: GKEntity) {
super.contactWithEntityDidBegin(entity) super.contactWithEntityDidBegin(entity)
// Retrieve the current state from this `GroundBot` as a `GroundBotAttackState`. // Retrieve the current state from this `GroundBot` as a `GroundBotAttackState`.
guard let attackState = componentForClass(IntelligenceComponent)?.stateMachine.currentState as? GroundBotAttackState else { return } guard let attackState = component(ofType: IntelligenceComponent.self)?.stateMachine.currentState as? GroundBotAttackState else { return }
// Use the `GroundBotAttackState` to apply the appropriate damage to the contacted entity. // Use the `GroundBotAttackState` to apply the appropriate damage to the contacted entity.
attackState.applyDamageToEntity(entity) attackState.applyDamageToEntity(entity: entity)
} }
// MARK: RulesComponentDelegate // MARK: RulesComponentDelegate
override func rulesComponent(rulesComponent: RulesComponent, didFinishEvaluatingRuleSystem ruleSystem: GKRuleSystem) { override func rulesComponent(rulesComponent: RulesComponent, didFinishEvaluatingRuleSystem ruleSystem: GKRuleSystem) {
super.rulesComponent(rulesComponent, didFinishEvaluatingRuleSystem: ruleSystem) super.rulesComponent(rulesComponent: rulesComponent, didFinishEvaluatingRuleSystem: ruleSystem)
/* /*
A `GroundBot` will attack a location in the scene if the following conditions are met: A `GroundBot` will attack a location in the scene if the following conditions are met:
@ -140,26 +144,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
3) The target is within the `GroundBot`'s attack range. 3) The target is within the `GroundBot`'s attack range.
4) There is no scenery between the `GroundBot` and the target. 4) There is no scenery between the `GroundBot` and the target.
*/ */
guard let scene = componentForClass(RenderComponent.self)?.node.scene else { return } guard let scene = component(ofType: RenderComponent.self)?.node.scene else { return }
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return } guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
guard let agentControlledState = intelligenceComponent.stateMachine.currentState as? TaskBotAgentControlledState else { return } guard let agentControlledState = intelligenceComponent.stateMachine.currentState as? TaskBotAgentControlledState else { return }
// 1) Check if enough time has passed since the `GroundBot`'s last attack. // 1) Check if enough time has passed since the `GroundBot`'s last attack.
guard agentControlledState.elapsedTime >= GameplayConfiguration.GroundBot.delayBetweenAttacks else { return } guard agentControlledState.elapsedTime >= GameplayConfiguration.GroundBot.delayBetweenAttacks else { return }
// 2) Check if the current mandate is to hunt an agent. // 2) Check if the current mandate is to hunt an agent.
guard case let .HuntAgent(targetAgent) = mandate else { return } guard case let .huntAgent(targetAgent) = mandate else { return }
// 3) Check if the target is within the `GroundBot`'s attack range. // 3) Check if the target is within the `GroundBot`'s attack range.
guard distanceToAgent(targetAgent) <= GameplayConfiguration.GroundBot.maximumAttackDistance else { return } guard distanceToAgent(otherAgent: targetAgent) <= GameplayConfiguration.GroundBot.maximumAttackDistance else { return }
// 4) Check if any walls or obstacles are between the `GroundBot` and its hunt target position. // 4) Check if any walls or obstacles are between the `GroundBot` and its hunt target position.
var hasLineOfSight = true var hasLineOfSight = true
scene.physicsWorld.enumerateBodiesAlongRayStart(CGPoint(agent.position), end: CGPoint(targetAgent.position)) { body, _, _, stop in scene.physicsWorld.enumerateBodies(alongRayStart: CGPoint(agent.position), end: CGPoint(targetAgent.position)) { body, _, _, stop in
if ColliderType(rawValue: body.categoryBitMask).contains(.Obstacle) { if ColliderType(rawValue: body.categoryBitMask).contains(.Obstacle) {
hasLineOfSight = false hasLineOfSight = false
stop.memory = true stop.pointee = true
} }
} }
@ -167,18 +171,18 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
// The `GroundBot` is ready to attack the `targetAgent`'s current position. // The `GroundBot` is ready to attack the `targetAgent`'s current position.
targetPosition = targetAgent.position targetPosition = targetAgent.position
intelligenceComponent.stateMachine.enterState(GroundBotRotateToAttackState.self) intelligenceComponent.stateMachine.enter(GroundBotRotateToAttackState.self)
} }
// MARK: ChargeComponentDelegate // MARK: ChargeComponentDelegate
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) { func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
guard let intelligenceComponent = componentForClass(IntelligenceComponent) else { return } guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
isGood = !chargeComponent.hasCharge isGood = !chargeComponent.hasCharge
if !isGood { if !isGood {
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self) intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
} }
} }
@ -188,7 +192,7 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
return goodAnimations == nil || badAnimations == nil return goodAnimations == nil || badAnimations == nil
} }
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) { static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
// Load `TaskBot`s shared assets. // Load `TaskBot`s shared assets.
super.loadSharedAssets() super.loadSharedAssets()
@ -213,12 +217,12 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
after the `GroundBot` texture atlases have finished preloading. after the `GroundBot` texture atlases have finished preloading.
*/ */
goodAnimations = [:] goodAnimations = [:]
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .WalkForward) goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .walkForward)
badAnimations = [:] badAnimations = [:]
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .WalkForward) badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .walkForward)
badAnimations![.Attack] = AnimationComponent.animationsFromAtlas(groundBotAtlases[2], withImageIdentifier: "GroundBotAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake", repeatTexturesForever: false) badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[2], withImageIdentifier: "GroundBotAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake", repeatTexturesForever: false)
badAnimations![.Zapped] = AnimationComponent.animationsFromAtlas(groundBotAtlases[3], withImageIdentifier: "GroundBotZapped", forAnimationState: .Zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake") badAnimations![.zapped] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[3], withImageIdentifier: "GroundBotZapped", forAnimationState: .zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
// Invoke the passed `completionHandler` to indicate that loading has completed. // Invoke the passed `completionHandler` to indicate that loading has completed.
completionHandler() completionHandler()

View File

@ -48,7 +48,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
It is not targetable when appearing or recharging. It is not targetable when appearing or recharging.
*/ */
var isTargetable: Bool { var isTargetable: Bool {
guard let currentState = componentForClass(IntelligenceComponent.self)?.stateMachine.currentState else { return false } guard let currentState = component(ofType: IntelligenceComponent.self)?.stateMachine.currentState else { return false }
switch currentState { switch currentState {
case is PlayerBotPlayerControlledState, is PlayerBotHitState: case is PlayerBotPlayerControlledState, is PlayerBotHitState:
@ -64,7 +64,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
/// The `RenderComponent` associated with this `PlayerBot`. /// The `RenderComponent` associated with this `PlayerBot`.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = componentForClass(RenderComponent.self) else { fatalError("A PlayerBot must have an RenderComponent.") } guard let renderComponent = component(ofType: RenderComponent.self) else { fatalError("A PlayerBot must have an RenderComponent.") }
return renderComponent return renderComponent
} }
@ -81,7 +81,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
so that they have the render node available to them when first entered so that they have the render node available to them when first entered
(e.g. so that `PlayerBotAppearState` can add a shader to the render node). (e.g. so that `PlayerBotAppearState` can add a shader to the render node).
*/ */
let renderComponent = RenderComponent(entity: self) let renderComponent = RenderComponent()
addComponent(renderComponent) addComponent(renderComponent)
let orientationComponent = OrientationComponent() let orientationComponent = OrientationComponent()
@ -133,16 +133,20 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
addComponent(intelligenceComponent) addComponent(intelligenceComponent)
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Charge component delegate // MARK: Charge component delegate
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) { func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
if let intelligenceComponent = componentForClass(IntelligenceComponent.self) { if let intelligenceComponent = component(ofType: IntelligenceComponent.self) {
if !chargeComponent.hasCharge { if !chargeComponent.hasCharge {
isPoweredDown = true isPoweredDown = true
intelligenceComponent.stateMachine.enterState(PlayerBotRechargingState.self) intelligenceComponent.stateMachine.enter(PlayerBotRechargingState.self)
} }
else { else {
intelligenceComponent.stateMachine.enterState(PlayerBotHitState.self) intelligenceComponent.stateMachine.enter(PlayerBotHitState.self)
} }
} }
} }
@ -153,7 +157,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
return appearTextures == nil || animations == nil return appearTextures == nil || animations == nil
} }
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) { static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
loadMiscellaneousAssets() loadMiscellaneousAssets()
let playerBotAtlasNames = [ let playerBotAtlasNames = [
@ -181,16 +185,16 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
*/ */
appearTextures = [:] appearTextures = [:]
for orientation in CompassDirection.allDirections { for orientation in CompassDirection.allDirections {
appearTextures![orientation] = AnimationComponent.firstTextureForOrientation(orientation, inAtlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle") appearTextures![orientation] = AnimationComponent.firstTextureForOrientation(compassDirection: orientation, inAtlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle")
} }
// Set up all of the `PlayerBot`s animations. // Set up all of the `PlayerBot`s animations.
animations = [:] animations = [:]
animations![.Idle] = AnimationComponent.animationsFromAtlas(playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .Idle) animations![.idle] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .idle)
animations![.WalkForward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkForward) animations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkForward)
animations![.WalkBackward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkBackward, playBackwards: true) animations![.walkBackward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkBackward, playBackwards: true)
animations![.Inactive] = AnimationComponent.animationsFromAtlas(playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .Inactive) animations![.inactive] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .inactive)
animations![.Hit] = AnimationComponent.animationsFromAtlas(playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .Hit, repeatTexturesForever: false) animations![.hit] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .hit, repeatTexturesForever: false)
// Invoke the passed `completionHandler` to indicate that loading has completed. // Invoke the passed `completionHandler` to indicate that loading has completed.
completionHandler() completionHandler()

View File

@ -15,16 +15,16 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
/// Encapsulates a `TaskBot`'s current mandate, i.e. the aim that the `TaskBot` is setting out to achieve. /// Encapsulates a `TaskBot`'s current mandate, i.e. the aim that the `TaskBot` is setting out to achieve.
enum TaskBotMandate { enum TaskBotMandate {
// Hunt another agent (either a `PlayerBot` or a "good" `TaskBot`). // Hunt another agent (either a `PlayerBot` or a "good" `TaskBot`).
case HuntAgent(GKAgent2D) case huntAgent(GKAgent2D)
// Follow the `TaskBot`'s "good" patrol path. // Follow the `TaskBot`'s "good" patrol path.
case FollowGoodPatrolPath case followGoodPatrolPath
// Follow the `TaskBot`'s "bad" patrol path. // Follow the `TaskBot`'s "bad" patrol path.
case FollowBadPatrolPath case followBadPatrolPath
// Return to a given position on a patrol path. // Return to a given position on a patrol path.
case ReturnToPositionOnPath(float2) case returnToPositionOnPath(float2)
} }
// MARK: Properties // MARK: Properties
@ -36,12 +36,12 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
guard isGood != oldValue else { return } guard isGood != oldValue else { return }
// Get the components we will need to access in response to the value changing. // Get the components we will need to access in response to the value changing.
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { fatalError("TaskBots must have an intelligence component.") } guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { fatalError("TaskBots must have an intelligence component.") }
guard let animationComponent = componentForClass(AnimationComponent.self) else { fatalError("TaskBots must have an animation component.") } guard let animationComponent = component(ofType: AnimationComponent.self) else { fatalError("TaskBots must have an animation component.") }
guard let chargeComponent = componentForClass(ChargeComponent.self) else { fatalError("TaskBots must have a charge component.") } guard let chargeComponent = component(ofType: ChargeComponent.self) else { fatalError("TaskBots must have a charge component.") }
// Update the `TaskBot`'s speed and acceleration to suit the new value of `isGood`. // Update the `TaskBot`'s speed and acceleration to suit the new value of `isGood`.
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood) agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood: isGood)
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
if isGood { if isGood {
@ -49,16 +49,16 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
The `TaskBot` just turned from "bad" to "good". The `TaskBot` just turned from "bad" to "good".
Set its mandate to `.ReturnToPositionOnPath` for the closest point on its "good" patrol path. Set its mandate to `.ReturnToPositionOnPath` for the closest point on its "good" patrol path.
*/ */
let closestPointOnGoodPath = closestPointOnPath(goodPathPoints) let closestPointOnGoodPath = closestPointOnPath(path: goodPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnGoodPath)) mandate = .returnToPositionOnPath(float2(closestPointOnGoodPath))
if self is FlyingBot { if self is FlyingBot {
// Enter the `FlyingBotBlastState` so it performs a curing blast. // Enter the `FlyingBotBlastState` so it performs a curing blast.
intelligenceComponent.stateMachine.enterState(FlyingBotBlastState.self) intelligenceComponent.stateMachine.enter(FlyingBotBlastState.self)
} }
else { else {
// Make sure the `TaskBot`s state is `TaskBotAgentControlledState` so that it follows its mandate. // Make sure the `TaskBot`s state is `TaskBotAgentControlledState` so that it follows its mandate.
intelligenceComponent.stateMachine.enterState(TaskBotAgentControlledState.self) intelligenceComponent.stateMachine.enter(TaskBotAgentControlledState.self)
} }
// Update the animation component to use the "good" animations. // Update the animation component to use the "good" animations.
@ -73,8 +73,8 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
Default to a `.ReturnToPositionOnPath` mandate for the closest point on its "bad" patrol path. Default to a `.ReturnToPositionOnPath` mandate for the closest point on its "bad" patrol path.
This may be overridden by a `.HuntAgent` mandate when the `TaskBot`'s rules are next evaluated. This may be overridden by a `.HuntAgent` mandate when the `TaskBot`'s rules are next evaluated.
*/ */
let closestPointOnBadPath = closestPointOnPath(badPathPoints) let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath)) mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
// Update the animation component to use the "bad" animations. // Update the animation component to use the "bad" animations.
animationComponent.animations = badAnimations animationComponent.animations = badAnimations
@ -83,7 +83,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
chargeComponent.charge = chargeComponent.maximumCharge chargeComponent.charge = chargeComponent.maximumCharge
// Enter the "zapped" state. // Enter the "zapped" state.
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self) intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
} }
} }
} }
@ -100,7 +100,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
/// The appropriate `GKBehavior` for the `TaskBot`, based on its current `mandate`. /// The appropriate `GKBehavior` for the `TaskBot`, based on its current `mandate`.
var behaviorForCurrentMandate: GKBehavior { var behaviorForCurrentMandate: GKBehavior {
// Return an empty behavior if this `TaskBot` is not yet in a `LevelScene`. // Return an empty behavior if this `TaskBot` is not yet in a `LevelScene`.
guard let levelScene = componentForClass(RenderComponent.self)?.node.scene as? LevelScene else { guard let levelScene = component(ofType: RenderComponent.self)?.node.scene as? LevelScene else {
return GKBehavior() return GKBehavior()
} }
@ -113,28 +113,28 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
let debugColor: SKColor let debugColor: SKColor
switch mandate { switch mandate {
case .FollowGoodPatrolPath, .FollowBadPatrolPath: case .followGoodPatrolPath, .followBadPatrolPath:
let pathPoints = isGood ? goodPathPoints : badPathPoints let pathPoints = isGood ? goodPathPoints : badPathPoints
radius = GameplayConfiguration.TaskBot.patrolPathRadius radius = GameplayConfiguration.TaskBot.patrolPathRadius
agentBehavior = TaskBotBehavior.behaviorForAgent(agent, patrollingPathWithPoints: pathPoints, pathRadius: radius, inScene: levelScene) agentBehavior = TaskBotBehavior.behavior(forAgent: agent, patrollingPathWithPoints: pathPoints, pathRadius: radius, inScene: levelScene)
debugPathPoints = pathPoints debugPathPoints = pathPoints
// Patrol paths are always closed loops, so the debug drawing of the path should cycle back round to the start. // Patrol paths are always closed loops, so the debug drawing of the path should cycle back round to the start.
debugPathShouldCycle = true debugPathShouldCycle = true
debugColor = isGood ? SKColor.greenColor() : SKColor.purpleColor() debugColor = isGood ? SKColor.green : SKColor.purple
case let .HuntAgent(targetAgent): case let .huntAgent(targetAgent):
radius = GameplayConfiguration.TaskBot.huntPathRadius radius = GameplayConfiguration.TaskBot.huntPathRadius
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene) (agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.redColor() debugColor = SKColor.red
case let .ReturnToPositionOnPath(position): case let .returnToPositionOnPath(position):
radius = GameplayConfiguration.TaskBot.returnToPatrolPathRadius radius = GameplayConfiguration.TaskBot.returnToPatrolPathRadius
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, returningToPoint: position, pathRadius: radius, inScene: levelScene) (agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, returningToPoint: position, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.yellowColor() debugColor = SKColor.yellow
} }
if levelScene.debugDrawingEnabled { if levelScene.debugDrawingEnabled {
drawDebugPath(debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius) drawDebugPath(path: debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius)
} }
else { else {
debugNode.removeAllChildren() debugNode.removeAllChildren()
@ -155,13 +155,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
/// The `GKAgent` associated with this `TaskBot`. /// The `GKAgent` associated with this `TaskBot`.
var agent: TaskBotAgent { var agent: TaskBotAgent {
guard let agent = componentForClass(TaskBotAgent.self) else { fatalError("A TaskBot entity must have a GKAgent2D component.") } guard let agent = component(ofType: TaskBotAgent.self) else { fatalError("A TaskBot entity must have a GKAgent2D component.") }
return agent return agent
} }
/// The `RenderComponent` associated with this `TaskBot`. /// The `RenderComponent` associated with this `TaskBot`.
var renderComponent: RenderComponent { var renderComponent: RenderComponent {
guard let renderComponent = componentForClass(RenderComponent.self) else { fatalError("A TaskBot must have an RenderComponent.") } guard let renderComponent = component(ofType: RenderComponent.self) else { fatalError("A TaskBot must have an RenderComponent.") }
return renderComponent return renderComponent
} }
@ -186,7 +186,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
Because a `TaskBot` is positioned at the appropriate path's start point when the level is created, Because a `TaskBot` is positioned at the appropriate path's start point when the level is created,
there is no need for it to pathfind to the start of its path, and it can patrol immediately. there is no need for it to pathfind to the start of its path, and it can patrol immediately.
*/ */
mandate = isGood ? .FollowGoodPatrolPath : .FollowBadPatrolPath mandate = isGood ? .followGoodPatrolPath : .followBadPatrolPath
super.init() super.init()
@ -195,7 +195,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
agent.delegate = self agent.delegate = self
// Configure the agent's characteristics for the steering physics simulation. // Configure the agent's characteristics for the steering physics simulation.
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood) agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood: isGood)
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
agent.mass = GameplayConfiguration.TaskBot.agentMass agent.mass = GameplayConfiguration.TaskBot.agentMass
agent.radius = GameplayConfiguration.TaskBot.agentRadius agent.radius = GameplayConfiguration.TaskBot.agentRadius
@ -224,6 +224,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
rulesComponent.delegate = self rulesComponent.delegate = self
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: GKAgentDelegate // MARK: GKAgentDelegate
func agentWillUpdate(_: GKAgent) { func agentWillUpdate(_: GKAgent) {
@ -243,13 +247,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
} }
func agentDidUpdate(_: GKAgent) { func agentDidUpdate(_: GKAgent) {
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return } guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return } guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
if intelligenceComponent.stateMachine.currentState is TaskBotAgentControlledState { if intelligenceComponent.stateMachine.currentState is TaskBotAgentControlledState {
// `TaskBot`s always move in a forward direction when they are agent-controlled. // `TaskBot`s always move in a forward direction when they are agent-controlled.
componentForClass(AnimationComponent.self)?.requestedAnimationState = .WalkForward component(ofType: AnimationComponent.self)?.requestedAnimationState = .walkForward
// When the `TaskBot` is agent-controlled, the node position follows the agent position. // When the `TaskBot` is agent-controlled, the node position follows the agent position.
updateNodePositionToMatchAgentPosition() updateNodePositionToMatchAgentPosition()
@ -288,9 +292,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
// A series of situations in which we prefer this `TaskBot` to hunt the player. // A series of situations in which we prefer this `TaskBot` to hunt the player.
let huntPlayerBotRaw = [ let huntPlayerBotRaw = [
// "Number of bad TaskBots is high" AND "Player is nearby". // "Number of bad TaskBots is high" AND "Player is nearby".
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageHigh.rawValue, Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
Fact.PlayerBotNear.rawValue Fact.playerBotNear.rawValue as AnyObject
]), ]),
/* /*
@ -299,9 +303,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
*/ */
// "Number of bad `TaskBot`s is medium" AND "Player is nearby". // "Number of bad `TaskBot`s is medium" AND "Player is nearby".
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageMedium.rawValue, Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.PlayerBotNear.rawValue Fact.playerBotNear.rawValue as AnyObject
]), ]),
/* /*
There are already a reasonable number of bad `TaskBots` on the level, There are already a reasonable number of bad `TaskBots` on the level,
@ -312,10 +316,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
"Number of bad TaskBots is high" AND "Player is at medium proximity" "Number of bad TaskBots is high" AND "Player is at medium proximity"
AND "nearest good `TaskBot` is at medium proximity". AND "nearest good `TaskBot` is at medium proximity".
*/ */
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageHigh.rawValue, Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
Fact.PlayerBotMedium.rawValue, Fact.playerBotMedium.rawValue as AnyObject,
Fact.GoodTaskBotMedium.rawValue Fact.goodTaskBotMedium.rawValue as AnyObject
]), ]),
/* /*
There are already a lot of bad `TaskBot`s on the level, so even though There are already a lot of bad `TaskBot`s on the level, so even though
@ -325,15 +329,15 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
] ]
// Find the maximum of the minima from above. // Find the maximum of the minima from above.
let huntPlayerBot = huntPlayerBotRaw.reduce(0.0, combine: max) let huntPlayerBot = huntPlayerBotRaw.reduce(0.0, max)
// A series of situations in which we prefer this `TaskBot` to hunt the nearest "good" TaskBot. // A series of situations in which we prefer this `TaskBot` to hunt the nearest "good" TaskBot.
let huntTaskBotRaw = [ let huntTaskBotRaw = [
// "Number of bad TaskBots is low" AND "Nearest good `TaskBot` is nearby". // "Number of bad TaskBots is low" AND "Nearest good `TaskBot` is nearby".
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageLow.rawValue, Fact.badTaskBotPercentageLow.rawValue as AnyObject,
Fact.GoodTaskBotNear.rawValue Fact.goodTaskBotNear.rawValue as AnyObject
]), ]),
/* /*
There are not many bad `TaskBot`s on the level, and a good `TaskBot` There are not many bad `TaskBot`s on the level, and a good `TaskBot`
@ -341,9 +345,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
*/ */
// "Number of bad TaskBots is medium" AND "Nearest good TaskBot is nearby". // "Number of bad TaskBots is medium" AND "Nearest good TaskBot is nearby".
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageMedium.rawValue, Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.GoodTaskBotNear.rawValue Fact.goodTaskBotNear.rawValue as AnyObject
]), ]),
/* /*
There are a reasonable number of `TaskBot`s on the level, but a good There are a reasonable number of `TaskBot`s on the level, but a good
@ -354,10 +358,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
"Number of bad TaskBots is low" AND "Player is at medium proximity" "Number of bad TaskBots is low" AND "Player is at medium proximity"
AND "Nearest good TaskBot is at medium proximity". AND "Nearest good TaskBot is at medium proximity".
*/ */
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageLow.rawValue, Fact.badTaskBotPercentageLow.rawValue as AnyObject,
Fact.PlayerBotMedium.rawValue, Fact.playerBotMedium.rawValue as AnyObject,
Fact.GoodTaskBotMedium.rawValue Fact.goodTaskBotMedium.rawValue as AnyObject
]), ]),
/* /*
There are not many bad `TaskBot`s on the level, so even though both There are not many bad `TaskBot`s on the level, so even though both
@ -369,10 +373,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
"Number of bad `TaskBot`s is medium" AND "Player is far away" AND "Number of bad `TaskBot`s is medium" AND "Player is far away" AND
"Nearest good `TaskBot` is at medium proximity". "Nearest good `TaskBot` is at medium proximity".
*/ */
ruleSystem.minimumGradeForFacts([ ruleSystem.minimumGrade(forFacts: [
Fact.BadTaskBotPercentageMedium.rawValue, Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.PlayerBotFar.rawValue, Fact.playerBotFar.rawValue as AnyObject,
Fact.GoodTaskBotMedium.rawValue Fact.goodTaskBotMedium.rawValue as AnyObject
]), ]),
/* /*
There are a reasonable number of bad `TaskBot`s on the level, the There are a reasonable number of bad `TaskBot`s on the level, the
@ -382,36 +386,36 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
] ]
// Find the maximum of the minima from above. // Find the maximum of the minima from above.
let huntTaskBot = huntTaskBotRaw.reduce(0.0, combine: max) let huntTaskBot = huntTaskBotRaw.reduce(0.0, max)
if huntPlayerBot >= huntTaskBot && huntPlayerBot > 0.0 { if huntPlayerBot >= huntTaskBot && huntPlayerBot > 0.0 {
// The rules provided greater motivation to hunt the PlayerBot. Ignore any motivation to hunt the nearest good TaskBot. // The rules provided greater motivation to hunt the PlayerBot. Ignore any motivation to hunt the nearest good TaskBot.
guard let playerBotAgent = state.playerBotTarget?.target.agent else { return } guard let playerBotAgent = state.playerBotTarget?.target.agent else { return }
mandate = .HuntAgent(playerBotAgent) mandate = .huntAgent(playerBotAgent)
} }
else if huntTaskBot > huntPlayerBot { else if huntTaskBot > huntPlayerBot {
// The rules provided greater motivation to hunt the nearest good TaskBot. Ignore any motivation to hunt the PlayerBot. // The rules provided greater motivation to hunt the nearest good TaskBot. Ignore any motivation to hunt the PlayerBot.
mandate = .HuntAgent(state.nearestGoodTaskBotTarget!.target.agent) mandate = .huntAgent(state.nearestGoodTaskBotTarget!.target.agent)
} }
else { else {
// The rules provided no motivation to hunt, so patrol in the "bad" state. // The rules provided no motivation to hunt, so patrol in the "bad" state.
switch mandate { switch mandate {
case .FollowBadPatrolPath: case .followBadPatrolPath:
// The `TaskBot` is already on its "bad" patrol path, so no update is needed. // The `TaskBot` is already on its "bad" patrol path, so no update is needed.
break break
default: default:
// Send the `TaskBot` to the closest point on its "bad" patrol path. // Send the `TaskBot` to the closest point on its "bad" patrol path.
let closestPointOnBadPath = closestPointOnPath(badPathPoints) let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath)) mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
} }
} }
} }
// MARK: ContactableType // MARK: ContactableType
func contactWithEntityDidBegin(entity: GKEntity) {} func contactWithEntityDidBegin(_ entity: GKEntity) {}
func contactWithEntityDidEnd(entity: GKEntity) {} func contactWithEntityDidEnd(_ entity: GKEntity) {}
// MARK: Convenience // MARK: Convenience
@ -433,7 +437,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
func closestPointOnPath(path: [CGPoint]) -> CGPoint { func closestPointOnPath(path: [CGPoint]) -> CGPoint {
// Find the closest point to the `TaskBot`. // Find the closest point to the `TaskBot`.
let taskBotPosition = agent.position let taskBotPosition = agent.position
let closestPoint = path.minElement { let closestPoint = path.min {
return distance_squared(taskBotPosition, float2($0)) < distance_squared(taskBotPosition, float2($1)) return distance_squared(taskBotPosition, float2($0)) < distance_squared(taskBotPosition, float2($1))
} }
@ -451,7 +455,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
/// Sets the `TaskBot` `GKAgent` rotation to match the `TaskBot`'s orientation. /// Sets the `TaskBot` `GKAgent` rotation to match the `TaskBot`'s orientation.
func updateAgentRotationToMatchTaskBotOrientation() { func updateAgentRotationToMatchTaskBotOrientation() {
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return } guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
agent.rotation = Float(orientationComponent.zRotation) agent.rotation = Float(orientationComponent.zRotation)
} }
@ -500,7 +504,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
let deltaX = next.x - current.x let deltaX = next.x - current.x
let deltaY = next.y - current.y let deltaY = next.y - current.y
let rectNode = SKShapeNode(rectOfSize: CGSize(width: hypot(deltaX, deltaY), height: CGFloat(radius) * 2)) let rectNode = SKShapeNode(rectOf: CGSize(width: hypot(deltaX, deltaY), height: CGFloat(radius) * 2))
rectNode.strokeColor = strokeColor rectNode.strokeColor = strokeColor
rectNode.fillColor = fillColor rectNode.fillColor = fillColor
rectNode.zRotation = atan(deltaY / deltaX) rectNode.zRotation = atan(deltaY / deltaX)

View File

@ -46,11 +46,11 @@ class GameControllerInputSource: ControlInputSourceType {
self.delegate?.controlInputSourceDidBeginAttacking(self) self.delegate?.controlInputSourceDidBeginAttacking(self)
#if os(tvOS) #if os(tvOS)
if let microGamepad = self.gameController.microGamepad where button == microGamepad.buttonA || button == microGamepad.buttonX { if let microGamepad = self.gameController.microGamepad, button == microGamepad.buttonA || button == microGamepad.buttonX {
self.gameStateDelegate?.controlInputSourceDidSelect(self) self.gameStateDelegate?.controlInputSourceDidSelect(self)
} }
#else #else
if let gamepad = self.gameController.gamepad where button == gamepad.buttonA || button == gamepad.buttonX { if let gamepad = self.gameController.gamepad, button == gamepad.buttonA || button == gamepad.buttonX {
self.gameStateDelegate?.controlInputSourceDidSelect(self) self.gameStateDelegate?.controlInputSourceDidSelect(self)
} }
#endif #endif

View File

@ -33,7 +33,7 @@ final class GameInput {
var isGameControllerConnected: Bool { var isGameControllerConnected: Bool {
var isGameControllerConnected = false var isGameControllerConnected = false
dispatch_sync(controlsQueue) { controlsQueue.sync {
isGameControllerConnected = (self.secondaryControlInputSource != nil) || (self.nativeControlInputSource is GameControllerInputSource) isGameControllerConnected = (self.secondaryControlInputSource != nil) || (self.nativeControlInputSource is GameControllerInputSource)
} }
return isGameControllerConnected return isGameControllerConnected
@ -49,13 +49,13 @@ final class GameInput {
weak var delegate: GameInputDelegate? { weak var delegate: GameInputDelegate? {
didSet { didSet {
// Ensure the delegate is aware of the player's current controls. // Ensure the delegate is aware of the player's current controls.
delegate?.gameInputDidUpdateControlInputSources(self) delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
} }
} }
/// An internal queue to protect accessing the player's control input sources. /// An internal queue to protect accessing the player's control input sources.
private let controlsQueue = dispatch_queue_create("com.example.apple-samplecode.player.controlsqueue", DISPATCH_QUEUE_SERIAL) private let controlsQueue = DispatchQueue(label: "com.example.apple-samplecode.player.controlsqueue")
// MARK: Initialization // MARK: Initialization
init(nativeControlInputSource: ControlInputSourceType) { init(nativeControlInputSource: ControlInputSourceType) {
@ -68,7 +68,7 @@ final class GameInput {
init() { init() {
// Search for paired game controllers. // Search for paired game controllers.
for pairedController in GCController.controllers() { for pairedController in GCController.controllers() {
updateWithGameController(pairedController) update(withGameController: pairedController)
} }
registerForGameControllerNotifications() registerForGameControllerNotifications()
@ -77,17 +77,17 @@ final class GameInput {
/// Register for `GCGameController` pairing notifications. /// Register for `GCGameController` pairing notifications.
func registerForGameControllerNotifications() { func registerForGameControllerNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(_:)), name: GCControllerDidConnectNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(notification:)), name: NSNotification.Name.GCControllerDidConnect, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(_:)), name: GCControllerDidDisconnectNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(notification:)), name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
} }
deinit { deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidConnectNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidConnect, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidDisconnectNotification, object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
} }
func updateWithGameController(gameController: GCController) { func update(withGameController gameController: GCController) {
dispatch_sync(controlsQueue) { controlsQueue.sync {
#if os(tvOS) #if os(tvOS)
// Assign a controller to the `nativeControlInputSource` if one does not already exist. // Assign a controller to the `nativeControlInputSource` if one does not already exist.
if self.nativeControlInputSource == nil { if self.nativeControlInputSource == nil {
@ -103,7 +103,7 @@ final class GameInput {
if self.secondaryControlInputSource == nil { if self.secondaryControlInputSource == nil {
let gameControllerInputSource = GameControllerInputSource(gameController: gameController) let gameControllerInputSource = GameControllerInputSource(gameController: gameController)
self.secondaryControlInputSource = gameControllerInputSource self.secondaryControlInputSource = gameControllerInputSource
gameController.playerIndex = .Index1 gameController.playerIndex = .index1
} }
} }
} }
@ -113,8 +113,8 @@ final class GameInput {
@objc func handleControllerDidConnectNotification(notification: NSNotification) { @objc func handleControllerDidConnectNotification(notification: NSNotification) {
let connectedGameController = notification.object as! GCController let connectedGameController = notification.object as! GCController
updateWithGameController(connectedGameController) update(withGameController: connectedGameController)
delegate?.gameInputDidUpdateControlInputSources(self) delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
} }
@objc func handleControllerDidDisconnectNotification(notification: NSNotification) { @objc func handleControllerDidDisconnectNotification(notification: NSNotification) {
@ -122,16 +122,16 @@ final class GameInput {
// Check if the player was being controlled by the disconnected controller. // Check if the player was being controlled by the disconnected controller.
if secondaryControlInputSource?.gameController == disconnectedGameController { if secondaryControlInputSource?.gameController == disconnectedGameController {
dispatch_sync(controlsQueue) { controlsQueue.sync {
self.secondaryControlInputSource = nil self.secondaryControlInputSource = nil
} }
// Check for any other connected controllers. // Check for any other connected controllers.
if let gameController = GCController.controllers().first { if let gameController = GCController.controllers().first {
updateWithGameController(gameController) update(withGameController: gameController)
} }
delegate?.gameInputDidUpdateControlInputSources(self) delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
} }
} }
} }

View File

@ -21,13 +21,13 @@ struct GameplayConfiguration {
static let maxArcAngle = CGFloat(0.35) static let maxArcAngle = CGFloat(0.35)
/// The maximum number of seconds for which the beam can be fired before recharging. /// The maximum number of seconds for which the beam can be fired before recharging.
static let maximumFireDuration: NSTimeInterval = 2.0 static let maximumFireDuration: TimeInterval = 2.0
/// The amount of charge points the beam drains from `TaskBot`s per second. /// The amount of charge points the beam drains from `TaskBot`s per second.
static let chargeLossPerSecond = 90.0 static let chargeLossPerSecond = 90.0
/// The length of time that the beam takes to recharge when it is fully depleted. /// The length of time that the beam takes to recharge when it is fully depleted.
static let coolDownDuration: NSTimeInterval = 1.0 static let coolDownDuration: TimeInterval = 1.0
} }
struct PlayerBot { struct PlayerBot {
@ -62,24 +62,24 @@ struct GameplayConfiguration {
static let maximumCharge = 100.0 static let maximumCharge = 100.0
/// The length of time for which the `PlayerBot` remains in its "hit" state. /// The length of time for which the `PlayerBot` remains in its "hit" state.
static let hitStateDuration: NSTimeInterval = 0.75 static let hitStateDuration: TimeInterval = 0.75
/// The length of time that it takes the `PlayerBot` to recharge when deactivated. /// The length of time that it takes the `PlayerBot` to recharge when deactivated.
static let rechargeDelayWhenInactive: NSTimeInterval = 2.0 static let rechargeDelayWhenInactive: TimeInterval = 2.0
/// The amount of charge that the `PlayerBot` gains per second when recharging. /// The amount of charge that the `PlayerBot` gains per second when recharging.
static let rechargeAmountPerSecond = 10.0 static let rechargeAmountPerSecond = 10.0
/// The amount of time it takes the `PlayerBot` to appear in a level before becoming controllable by the player. /// The amount of time it takes the `PlayerBot` to appear in a level before becoming controllable by the player.
static let appearDuration: NSTimeInterval = 0.50 static let appearDuration: TimeInterval = 0.50
} }
struct TaskBot { struct TaskBot {
/// The length of time a `TaskBot` waits before re-evaluating its rules. /// The length of time a `TaskBot` waits before re-evaluating its rules.
static let rulesUpdateWaitDuration: NSTimeInterval = 1.0 static let rulesUpdateWaitDuration: TimeInterval = 1.0
/// The length of time a `TaskBot` waits before re-checking for an appropriate behavior. /// The length of time a `TaskBot` waits before re-checking for an appropriate behavior.
static let behaviorUpdateWaitDuration: NSTimeInterval = 0.25 static let behaviorUpdateWaitDuration: TimeInterval = 0.25
/// How close a `TaskBot` has to be to a patrol path start point in order to start patrolling. /// How close a `TaskBot` has to be to a patrol path start point in order to start patrolling.
static let thresholdProximityToPatrolPathStartPoint: Float = 50.0 static let thresholdProximityToPatrolPathStartPoint: Float = 50.0
@ -118,10 +118,10 @@ struct GameplayConfiguration {
static let agentOffset = physicsBodyOffset static let agentOffset = physicsBodyOffset
/// The maximum time to look ahead when following a path. /// The maximum time to look ahead when following a path.
static let maxPredictionTimeWhenFollowingPath: NSTimeInterval = 1.0 static let maxPredictionTimeWhenFollowingPath: TimeInterval = 1.0
/// The maximum time to look ahead for obstacles to be avoided. /// The maximum time to look ahead for obstacles to be avoided.
static let maxPredictionTimeForObstacleAvoidance: NSTimeInterval = 1.0 static let maxPredictionTimeForObstacleAvoidance: TimeInterval = 1.0
/// The radius of the path along which an agent patrols. /// The radius of the path along which an agent patrols.
static let patrolPathRadius: Float = 10.0 static let patrolPathRadius: Float = 10.0
@ -136,10 +136,10 @@ struct GameplayConfiguration {
static let pathfindingGraphBufferRadius: Float = 30.0 static let pathfindingGraphBufferRadius: Float = 30.0
/// The duration of a `TaskBot`'s pre-attack state. /// The duration of a `TaskBot`'s pre-attack state.
static let preAttackStateDuration: NSTimeInterval = 0.8 static let preAttackStateDuration: TimeInterval = 0.8
/// The duration of a `TaskBot`'s zapped state. /// The duration of a `TaskBot`'s zapped state.
static let zappedStateDuration: NSTimeInterval = 0.75 static let zappedStateDuration: TimeInterval = 0.75
} }
struct FlyingBot { struct FlyingBot {
@ -153,10 +153,10 @@ struct GameplayConfiguration {
static let blastChargeLossPerSecond = 25.0 static let blastChargeLossPerSecond = 25.0
/// The duration of a `FlyingBot` blast. /// The duration of a `FlyingBot` blast.
static let blastDuration: NSTimeInterval = 1.25 static let blastDuration: TimeInterval = 1.25
/// The duration over which a `FlyingBot` blast affects entities in its blast radius. /// The duration over which a `FlyingBot` blast affects entities in its blast radius.
static let blastEffectDuration: NSTimeInterval = 0.75 static let blastEffectDuration: TimeInterval = 0.75
/// The offset from the `FlyingBot`'s position for the blast particle emitter node. /// The offset from the `FlyingBot`'s position for the blast particle emitter node.
static let blastEmitterOffset = CGPoint(x: 0.0, y: 20.0) static let blastEmitterOffset = CGPoint(x: 0.0, y: 20.0)
@ -188,7 +188,7 @@ struct GameplayConfiguration {
static let angularSpeedMultiplierWhenAttacking: CGFloat = 2.5 static let angularSpeedMultiplierWhenAttacking: CGFloat = 2.5
/// The amount of time to wait between `GroundBot` attacks. /// The amount of time to wait between `GroundBot` attacks.
static let delayBetweenAttacks: NSTimeInterval = 2.0 static let delayBetweenAttacks: TimeInterval = 2.0
/// The offset from the `GroundBot`'s position that should be used for beam targeting. /// The offset from the `GroundBot`'s position that should be used for beam targeting.
static let beamTargetOffset = CGPoint(x: 0.0, y: 40.0) static let beamTargetOffset = CGPoint(x: 0.0, y: 40.0)
@ -224,10 +224,10 @@ struct GameplayConfiguration {
struct SceneManager { struct SceneManager {
/// The duration of a transition between loaded scenes. /// The duration of a transition between loaded scenes.
static let transitionDuration: NSTimeInterval = 2.0 static let transitionDuration: TimeInterval = 2.0
/// The duration of a transition from the progress scene to its loaded scene. /// The duration of a transition from the progress scene to its loaded scene.
static let progressSceneTransitionDuration: NSTimeInterval = 0.5 static let progressSceneTransitionDuration: TimeInterval = 0.5
} }
struct Timer { struct Timer {
@ -245,4 +245,4 @@ struct GameplayConfiguration {
static let paddingSize: CGFloat = 0.2 static let paddingSize: CGFloat = 0.2
#endif #endif
} }
} }

View File

@ -13,60 +13,60 @@ class HomeEndScene: BaseScene {
/// Returns the background node from the scene. /// Returns the background node from the scene.
override var backgroundNode: SKSpriteNode? { override var backgroundNode: SKSpriteNode? {
return childNodeWithName("backgroundNode") as? SKSpriteNode return childNode(withName: "backgroundNode") as? SKSpriteNode
} }
/// The screen recorder button for the scene (if it has one). /// The screen recorder button for the scene (if it has one).
var screenRecorderButton: ButtonNode? { var screenRecorderButton: ButtonNode? {
return backgroundNode?.childNodeWithName(ButtonIdentifier.ScreenRecorderToggle.rawValue) as? ButtonNode return backgroundNode?.childNode(withName: ButtonIdentifier.screenRecorderToggle.rawValue) as? ButtonNode
} }
/// The "NEW GAME" button which allows the player to proceed to the first level. /// The "NEW GAME" button which allows the player to proceed to the first level.
var proceedButton: ButtonNode? { var proceedButton: ButtonNode? {
return backgroundNode?.childNodeWithName(ButtonIdentifier.ProceedToNextScene.rawValue) as? ButtonNode return backgroundNode?.childNode(withName: ButtonIdentifier.proceedToNextScene.rawValue) as? ButtonNode
} }
/// An array of objects for `SceneLoader` notifications. /// An array of objects for `SceneLoader` notifications.
private var sceneLoaderNotificationObservers = [AnyObject]() private var sceneLoaderNotificationObservers = [Any]()
// MARK: Deinitialization // MARK: Deinitialization
deinit { deinit {
// Deregister for scene loader notifications. // Deregister for scene loader notifications.
for observer in sceneLoaderNotificationObservers { for observer in sceneLoaderNotificationObservers {
NSNotificationCenter.defaultCenter().removeObserver(observer) NotificationCenter.default.removeObserver(observer)
} }
} }
// MARK: Scene Life Cycle // MARK: Scene Life Cycle
override func didMoveToView(view: SKView) { override func didMove(to view: SKView) {
super.didMoveToView(view) super.didMove(to: view)
#if os(iOS) #if os(iOS)
screenRecorderButton?.isSelected = screenRecordingToggleEnabled screenRecorderButton?.isSelected = screenRecordingToggleEnabled
#else #else
screenRecorderButton?.hidden = true screenRecorderButton?.isHidden = true
#endif #endif
// Enable focus based navigation. // Enable focus based navigation.
focusChangesEnabled = true focusChangesEnabled = true
registerForNotifications() registerForNotifications()
centerCameraOnPoint(backgroundNode!.position) centerCameraOnPoint(point: backgroundNode!.position)
// Begin loading the first level as soon as the view appears. // Begin loading the first level as soon as the view appears.
sceneManager.prepareSceneWithSceneIdentifier(.Level(1)) sceneManager.prepareScene(identifier: .level(1))
let levelLoader = sceneManager.sceneLoaderForSceneIdentifier(.Level(1)) let levelLoader = sceneManager.sceneLoader(forSceneIdentifier: .level(1))
// If the first level is not ready, hide the buttons until we are notified. // If the first level is not ready, hide the buttons until we are notified.
if !(levelLoader.stateMachine.currentState is SceneLoaderResourcesReadyState) { if !(levelLoader.stateMachine.currentState is SceneLoaderResourcesReadyState) {
proceedButton?.alpha = 0.0 proceedButton?.alpha = 0.0
proceedButton?.userInteractionEnabled = false proceedButton?.isUserInteractionEnabled = false
screenRecorderButton?.alpha = 0.0 screenRecorderButton?.alpha = 0.0
screenRecorderButton?.userInteractionEnabled = false screenRecorderButton?.isUserInteractionEnabled = false
} }
} }
@ -75,21 +75,21 @@ class HomeEndScene: BaseScene {
guard sceneLoaderNotificationObservers.isEmpty else { return } guard sceneLoaderNotificationObservers.isEmpty else { return }
// Create a closure to pass as a notification handler for when loading completes or fails. // Create a closure to pass as a notification handler for when loading completes or fails.
let handleSceneLoaderNotification: (NSNotification) -> () = { [unowned self] notification in let handleSceneLoaderNotification: (Notification) -> () = { [unowned self] notification in
let sceneLoader = notification.object as! SceneLoader let sceneLoader = notification.object as! SceneLoader
// Show the proceed button if the `sceneLoader` pertains to a `LevelScene`. // Show the proceed button if the `sceneLoader` pertains to a `LevelScene`.
if sceneLoader.sceneMetadata.sceneType is LevelScene.Type { if sceneLoader.sceneMetadata.sceneType is LevelScene.Type {
// Allow the proceed and screen to be tapped or clicked. // Allow the proceed and screen to be tapped or clicked.
self.proceedButton?.userInteractionEnabled = true self.proceedButton?.isUserInteractionEnabled = true
self.screenRecorderButton?.userInteractionEnabled = true self.screenRecorderButton?.isUserInteractionEnabled = true
// Fade in the proceed and screen recorder buttons. // Fade in the proceed and screen recorder buttons.
self.screenRecorderButton?.runAction(SKAction.fadeInWithDuration(1.0)) self.screenRecorderButton?.run(SKAction.fadeIn(withDuration: 1.0))
// Clear the initial `proceedButton` focus. // Clear the initial `proceedButton` focus.
self.proceedButton?.isFocused = false self.proceedButton?.isFocused = false
self.proceedButton?.runAction(SKAction.fadeInWithDuration(1.0)) { self.proceedButton?.run(SKAction.fadeIn(withDuration: 1.0)) {
// Indicate that the `proceedButton` is focused. // Indicate that the `proceedButton` is focused.
self.resetFocus() self.resetFocus()
} }
@ -97,8 +97,8 @@ class HomeEndScene: BaseScene {
} }
// Register for scene loader notifications. // Register for scene loader notifications.
let completeNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidCompleteNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: handleSceneLoaderNotification) let completeNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil, queue: OperationQueue.main, using: handleSceneLoaderNotification)
let failNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidFailNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: handleSceneLoaderNotification) let failNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidFailNotification, object: nil, queue: OperationQueue.main, using: handleSceneLoaderNotification)
// Keep track of the notifications we are registered to so we can remove them in `deinit`. // Keep track of the notifications we are registered to so we can remove them in `deinit`.
sceneLoaderNotificationObservers += [completeNotification, failNotification] sceneLoaderNotificationObservers += [completeNotification, failNotification]

View File

@ -51,7 +51,7 @@ class KeyboardControlInputSource: ControlInputSourceType {
} }
/// The logic matching a key press to `ControlInputSourceDelegate` calls. /// The logic matching a key press to `ControlInputSourceDelegate` calls.
func handleKeyDownForCharacter(character: Character) { func handleKeyDown(forCharacter character: Character) {
// Ignore repeat input. // Ignore repeat input.
if downKeys.contains(character) { if downKeys.contains(character) {
return return
@ -110,8 +110,8 @@ class KeyboardControlInputSource: ControlInputSourceType {
} }
// Handle the logic matching when a key is released to `ControlInputSource` delegate calls. // Handle the logic matching when a key is released to `ControlInputSource` delegate calls.
func handleKeyUpForCharacter(character: Character) { func handleKeyUp(forCharacter character: Character) {
// Ensure the character was accounted for by `handleKeyDownForCharacter(_:)`. // Ensure the character was accounted for by `handleKeyDown(forCharacter:)`.
guard downKeys.remove(character) != nil else { return } guard downKeys.remove(character) != nil else { return }
if let relativeDisplacement = relativeDisplacementForCharacter(character) { if let relativeDisplacement = relativeDisplacementForCharacter(character) {
@ -158,35 +158,35 @@ class KeyboardControlInputSource: ControlInputSourceType {
// MARK: Convenience // MARK: Convenience
private func isDirectionalDisplacementVector(displacement: float2) -> Bool { private func isDirectionalDisplacementVector(_ displacement: float2) -> Bool {
return displacement == KeyboardControlInputSource.forwardVector return displacement == KeyboardControlInputSource.forwardVector
|| displacement == KeyboardControlInputSource.backwardVector || displacement == KeyboardControlInputSource.backwardVector
} }
private func relativeDisplacementForCharacter(character: Character) -> float2? { private func relativeDisplacementForCharacter(_ character: Character) -> float2? {
let mapping: [Character: float2] = [ let mapping: [Character: float2] = [
// Up arrow. // Up arrow.
Character(UnicodeScalar(0xF700)): KeyboardControlInputSource.forwardVector, Character(UnicodeScalar(0xF700)!): KeyboardControlInputSource.forwardVector,
"w": KeyboardControlInputSource.forwardVector, "w": KeyboardControlInputSource.forwardVector,
// Down arrow. // Down arrow.
Character(UnicodeScalar(0xF701)): KeyboardControlInputSource.backwardVector, Character(UnicodeScalar(0xF701)!): KeyboardControlInputSource.backwardVector,
"s": KeyboardControlInputSource.backwardVector, "s": KeyboardControlInputSource.backwardVector,
// Left arrow. // Left arrow.
Character(UnicodeScalar(0xF702)): KeyboardControlInputSource.counterClockwiseVector, Character(UnicodeScalar(0xF702)!): KeyboardControlInputSource.counterClockwiseVector,
"a": KeyboardControlInputSource.counterClockwiseVector, "a": KeyboardControlInputSource.counterClockwiseVector,
// Right arrow. // Right arrow.
Character(UnicodeScalar(0xF703)): KeyboardControlInputSource.clockwiseVector, Character(UnicodeScalar(0xF703)!): KeyboardControlInputSource.clockwiseVector,
"d": KeyboardControlInputSource.clockwiseVector "d": KeyboardControlInputSource.clockwiseVector
] ]
return mapping[character] return mapping[character]
} }
/// Indicates if the provided character should trigger an attack. /// Indicates if the provided character should trigger an attack.
private func isAttackCharacter(character: Character) -> Bool { private func isAttackCharacter(_ character: Character) -> Bool {
return ["f", " ", "\r"].contains(character) return ["f", " ", "\r"].contains(character)
} }
} }

View File

@ -18,8 +18,8 @@ struct LevelConfiguration {
/// The different types of `TaskBot` that can exist in a level. /// The different types of `TaskBot` that can exist in a level.
enum Locomotion { enum Locomotion {
case Ground case ground
case Flying case flying
} }
let locomotion: Locomotion let locomotion: Locomotion
@ -41,10 +41,10 @@ struct LevelConfiguration {
init(botConfigurationInfo: [String: AnyObject]) { init(botConfigurationInfo: [String: AnyObject]) {
switch botConfigurationInfo["locomotion"] as! String { switch botConfigurationInfo["locomotion"] as! String {
case "ground": case "ground":
locomotion = .Ground locomotion = .ground
case "flying": case "flying":
locomotion = .Flying locomotion = .flying
default: default:
fatalError("Unknown locomotion found while parsing `taskBot` data") fatalError("Unknown locomotion found while parsing `taskBot` data")
@ -81,8 +81,8 @@ struct LevelConfiguration {
} }
/// The time limit (in seconds) for this level. /// The time limit (in seconds) for this level.
var timeLimit: NSTimeInterval { var timeLimit: TimeInterval {
return configurationInfo["timeLimit"] as! NSTimeInterval return configurationInfo["timeLimit"] as! TimeInterval
} }
/// The factor used to normalize distances between characters for 'fuzzy' logic. /// The factor used to normalize distances between characters for 'fuzzy' logic.
@ -95,9 +95,9 @@ struct LevelConfiguration {
init(fileName: String) { init(fileName: String) {
self.fileName = fileName self.fileName = fileName
let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: "plist")! let url = Bundle.main.url(forResource: fileName, withExtension: "plist")
configurationInfo = NSDictionary(contentsOfURL: url) as! [String: AnyObject] configurationInfo = NSDictionary(contentsOf: url!) as! [String: AnyObject]
// Extract the data for every `TaskBot` in this level as an array of `TaskBotConfiguration` values. // Extract the data for every `TaskBot` in this level as an array of `TaskBotConfiguration` values.
let botConfigurations = configurationInfo["taskBotConfigurations"] as! [[String: AnyObject]] let botConfigurations = configurationInfo["taskBotConfigurations"] as! [[String: AnyObject]]
@ -107,4 +107,4 @@ struct LevelConfiguration {
initialPlayerBotOrientation = CompassDirection(string: configurationInfo["initialPlayerBotOrientation"] as! String) initialPlayerBotOrientation = CompassDirection(string: configurationInfo["initialPlayerBotOrientation"] as! String)
} }
} }

View File

@ -45,7 +45,7 @@ extension LevelScene {
for destination in node.connectedNodes as! [GKGraphNode2D] { for destination in node.connectedNodes as! [GKGraphNode2D] {
let points = [CGPoint(node.position), CGPoint(destination.position)] let points = [CGPoint(node.position), CGPoint(destination.position)]
let shapeNode = SKShapeNode(points: UnsafeMutablePointer<CGPoint>(points), count: 2) let shapeNode = SKShapeNode(points: UnsafeMutablePointer<CGPoint>(mutating: points), count: 2)
shapeNode.strokeColor = SKColor(white: 1.0, alpha: 0.1) shapeNode.strokeColor = SKColor(white: 1.0, alpha: 0.1)
shapeNode.lineWidth = 2.0 shapeNode.lineWidth = 2.0
shapeNode.zPosition = -1 shapeNode.zPosition = -1
@ -76,22 +76,22 @@ extension SKSpriteNode {
if newValue == true { if newValue == true {
let bufferRadius = CGFloat(GameplayConfiguration.TaskBot.pathfindingGraphBufferRadius) let bufferRadius = CGFloat(GameplayConfiguration.TaskBot.pathfindingGraphBufferRadius)
let bufferFrame = frame.insetBy(dx: -bufferRadius, dy: -bufferRadius) let bufferFrame = frame.insetBy(dx: -bufferRadius, dy: -bufferRadius)
let bufferedShape = SKShapeNode(rectOfSize: bufferFrame.size) let bufferedShape = SKShapeNode(rectOf: bufferFrame.size)
bufferedShape.fillColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.2) bufferedShape.fillColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.2))
bufferedShape.strokeColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.4) bufferedShape.strokeColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.4))
bufferedShape.name = debugBufferShapeName bufferedShape.name = debugBufferShapeName
addChild(bufferedShape) addChild(bufferedShape)
} }
else { else {
// Remove any existing debug shape layer if we are turning off debug drawing for this node. // Remove any existing debug shape layer if we are turning off debug drawing for this node.
guard let debugBufferShape = childNodeWithName(debugBufferShapeName) else { return } guard let debugBufferShape = childNode(withName: debugBufferShapeName) else { return }
removeChildrenInArray([debugBufferShape]) removeChildren(in: [debugBufferShape])
} }
} }
get { get {
// Debug drawing is considered "enabled" if we have the debug node as a child. // Debug drawing is considered "enabled" if we have the debug node as a child.
return childNodeWithName(debugBufferShapeName) != nil return childNode(withName: debugBufferShapeName) != nil
} }
} }
} }

View File

@ -20,24 +20,24 @@ extension LevelScene {
app enters the background. Override to check if an `overlay` node is app enters the background. Override to check if an `overlay` node is
being presented to determine if the game should be paused. being presented to determine if the game should be paused.
*/ */
override var paused: Bool { override var isPaused: Bool {
didSet { didSet {
if overlay != nil { if overlay != nil {
worldNode.paused = true worldNode.isPaused = true
} }
} }
} }
/// Platform specific notifications about the app becoming inactive. /// Platform specific notifications about the app becoming inactive.
var pauseNotificationNames: [String] { private var pauseNotificationNames: [NSNotification.Name] {
#if os(OSX) #if os(OSX)
return [ return [
NSApplicationWillResignActiveNotification, .NSApplicationWillResignActive,
NSWindowDidMiniaturizeNotification .NSWindowDidMiniaturize
] ]
#else #else
return [ return [
UIApplicationWillResignActiveNotification NSNotification.Name.UIApplicationWillResignActive
] ]
#endif #endif
} }
@ -50,17 +50,17 @@ extension LevelScene {
*/ */
func registerForPauseNotifications() { func registerForPauseNotifications() {
for notificationName in pauseNotificationNames { for notificationName in pauseNotificationNames {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(LevelScene.pauseGame), name: notificationName, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(LevelScene.pauseGame), name: notificationName, object: nil)
} }
} }
func pauseGame() { func pauseGame() {
stateMachine.enterState(LevelScenePauseState.self) stateMachine.enter(LevelScenePauseState.self)
} }
func unregisterForPauseNotifications() { func unregisterForPauseNotifications() {
for notificationName in pauseNotificationNames { for notificationName in pauseNotificationNames {
NSNotificationCenter.defaultCenter().removeObserver(self, name: notificationName, object: nil) NotificationCenter.default.removeObserver(self, name: notificationName, object: nil)
} }
} }
} }

View File

@ -15,27 +15,27 @@ enum WorldLayer: CGFloat {
static let zSpacePerCharacter: CGFloat = 100 static let zSpacePerCharacter: CGFloat = 100
// Specifying `AboveCharacters` as 1000 gives room for 9 enemies on a level. // Specifying `AboveCharacters` as 1000 gives room for 9 enemies on a level.
case Board = -100, Debug = -75, Shadows = -50, Obstacles = -25, Characters = 0, AboveCharacters = 1000, Top = 1100 case board = -100, debug = -75, shadows = -50, obstacles = -25, characters = 0, aboveCharacters = 1000, top = 1100
// The expected name for this node in the scene file. // The expected name for this node in the scene file.
var nodeName: String { var nodeName: String {
switch self { switch self {
case .Board: return "board" case .board: return "board"
case .Debug: return "debug" case .debug: return "debug"
case .Shadows: return "shadows" case .shadows: return "shadows"
case .Obstacles: return "obstacles" case .obstacles: return "obstacles"
case .Characters: return "characters" case .characters: return "characters"
case .AboveCharacters: return "above_characters" case .aboveCharacters: return "above_characters"
case .Top: return "top" case .top: return "top"
} }
} }
// The full path to this node, for use with `childNodeWithName(_:)`. // The full path to this node, for use with `childNode(withName name:)`.
var nodePath: String { var nodePath: String {
return "/world/\(nodeName)" return "/world/\(nodeName)"
} }
static var allLayers = [Board, Debug, Shadows, Obstacles, Characters, AboveCharacters, Top] static var allLayers = [board, debug, shadows, obstacles, characters, aboveCharacters, top]
} }
class LevelScene: BaseScene, SKPhysicsContactDelegate { class LevelScene: BaseScene, SKPhysicsContactDelegate {
@ -45,14 +45,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
var worldLayerNodes = [WorldLayer: SKNode]() var worldLayerNodes = [WorldLayer: SKNode]()
var worldNode: SKNode { var worldNode: SKNode {
return childNodeWithName("world")! return childNode(withName: "world")!
} }
let playerBot = PlayerBot() let playerBot = PlayerBot()
var entities = Set<GKEntity>() var entities = Set<GKEntity>()
var lastUpdateTimeInterval: NSTimeInterval = 0 var lastUpdateTimeInterval: TimeInterval = 0
let maximumUpdateDeltaTime: NSTimeInterval = 1.0 / 60.0 let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
var levelConfiguration: LevelConfiguration! var levelConfiguration: LevelConfiguration!
@ -78,7 +78,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
lazy var obstacleSpriteNodes: [SKSpriteNode] = self["world/obstacles/*"] as! [SKSpriteNode] lazy var obstacleSpriteNodes: [SKSpriteNode] = self["world/obstacles/*"] as! [SKSpriteNode]
lazy var polygonObstacles: [GKPolygonObstacle] = SKNode.obstaclesFromNodePhysicsBodies(self.obstacleSpriteNodes) lazy var polygonObstacles: [GKPolygonObstacle] = SKNode.obstacles(fromNodePhysicsBodies: self.obstacleSpriteNodes)
// MARK: Pathfinding Debug // MARK: Pathfinding Debug
@ -127,8 +127,8 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: Scene Life Cycle // MARK: Scene Life Cycle
override func didMoveToView(view: SKView) { override func didMove(to view: SKView) {
super.didMoveToView(view) super.didMove(to: view)
// Load the level's configuration from the level data file. // Load the level's configuration from the level data file.
levelConfiguration = LevelConfiguration(fileName: sceneManager.currentSceneMetadata!.fileName) levelConfiguration = LevelConfiguration(fileName: sceneManager.currentSceneMetadata!.fileName)
@ -152,24 +152,24 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self physicsWorld.contactDelegate = self
// Move to the active state, starting the level timer. // Move to the active state, starting the level timer.
stateMachine.enterState(LevelSceneActiveState.self) stateMachine.enter(LevelSceneActiveState.self)
// Add the debug layers to the scene. // Add the debug layers to the scene.
addNode(graphLayer, toWorldLayer: .Debug) addNode(node: graphLayer, toWorldLayer: .debug)
addNode(debugObstacleLayer, toWorldLayer: .Debug) addNode(node: debugObstacleLayer, toWorldLayer: .debug)
// Configure the `timerNode` and add it to the camera node. // Configure the `timerNode` and add it to the camera node.
timerNode.zPosition = WorldLayer.AboveCharacters.rawValue timerNode.zPosition = WorldLayer.aboveCharacters.rawValue
timerNode.fontColor = SKColor.whiteColor() timerNode.fontColor = SKColor.white
timerNode.fontName = GameplayConfiguration.Timer.fontName timerNode.fontName = GameplayConfiguration.Timer.fontName
timerNode.horizontalAlignmentMode = .Center timerNode.horizontalAlignmentMode = .center
timerNode.verticalAlignmentMode = .Top timerNode.verticalAlignmentMode = .top
scaleTimerNode() scaleTimerNode()
camera!.addChild(timerNode) camera!.addChild(timerNode)
// A convenience function to find node locations given a set of node names. // A convenience function to find node locations given a set of node names.
func nodePointsFromNodeNames(nodeNames: [String]) -> [CGPoint] { func nodePointsFromNodeNames(nodeNames: [String]) -> [CGPoint] {
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)! let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
return nodeNames.map { return nodeNames.map {
charactersNode[$0].first!.position charactersNode[$0].first!.position
} }
@ -180,20 +180,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
let taskBot: TaskBot let taskBot: TaskBot
// Find the locations of the nodes that define the `TaskBot`'s "good" and "bad" patrol paths. // Find the locations of the nodes that define the `TaskBot`'s "good" and "bad" patrol paths.
let goodPathPoints = nodePointsFromNodeNames(taskBotConfiguration.goodPathNodeNames) let goodPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.goodPathNodeNames)
let badPathPoints = nodePointsFromNodeNames(taskBotConfiguration.badPathNodeNames) let badPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.badPathNodeNames)
// Create the appropriate type `TaskBot` (ground or flying). // Create the appropriate type `TaskBot` (ground or flying).
switch taskBotConfiguration.locomotion { switch taskBotConfiguration.locomotion {
case .Flying: case .flying:
taskBot = FlyingBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints) taskBot = FlyingBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
case .Ground: case .ground:
taskBot = GroundBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints) taskBot = GroundBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
} }
// Set the `TaskBot`'s initial orientation so that it is facing the correct way. // Set the `TaskBot`'s initial orientation so that it is facing the correct way.
guard let orientationComponent = taskBot.componentForClass(OrientationComponent.self) else { guard let orientationComponent = taskBot.component(ofType: OrientationComponent.self) else {
fatalError("A task bot must have an orientation component to be able to be added to a level") fatalError("A task bot must have an orientation component to be able to be added to a level")
} }
orientationComponent.compassDirection = taskBotConfiguration.initialOrientation orientationComponent.compassDirection = taskBotConfiguration.initialOrientation
@ -204,10 +204,10 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
taskBot.updateAgentPositionToMatchNodePosition() taskBot.updateAgentPositionToMatchNodePosition()
// Add the `TaskBot` to the scene and the component systems. // Add the `TaskBot` to the scene and the component systems.
addEntity(taskBot) addEntity(entity: taskBot)
// Add the `TaskBot`'s debug drawing node beneath all characters. // Add the `TaskBot`'s debug drawing node beneath all characters.
addNode(taskBot.debugNode, toWorldLayer: .Debug) addNode(node: taskBot.debugNode, toWorldLayer: .debug)
} }
#if os(iOS) #if os(iOS)
@ -223,7 +223,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
#endif #endif
} }
override func didChangeSize(oldSize: CGSize) { override func didChangeSize(_ oldSize: CGSize) {
super.didChangeSize(oldSize) super.didChangeSize(oldSize)
/* /*
@ -239,7 +239,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: SKScene Processing // MARK: SKScene Processing
/// Called before each frame is rendered. /// Called before each frame is rendered.
override func update(currentTime: NSTimeInterval) { override func update(_ currentTime: TimeInterval) {
super.update(currentTime) super.update(currentTime)
// Don't perform any updates if the scene isn't in a view. // Don't perform any updates if the scene isn't in a view.
@ -262,10 +262,10 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
Pausing a subsection of the node tree allows the `camera` Pausing a subsection of the node tree allows the `camera`
and `overlay` nodes to remain interactive. and `overlay` nodes to remain interactive.
*/ */
if worldNode.paused { return } if worldNode.isPaused { return }
// Update the level's state machine. // Update the level's state machine.
stateMachine.updateWithDeltaTime(deltaTime) stateMachine.update(deltaTime: deltaTime)
/* /*
Update each component system. Update each component system.
@ -273,13 +273,13 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
and was determined when the `componentSystems` array was instantiated. and was determined when the `componentSystems` array was instantiated.
*/ */
for componentSystem in componentSystems { for componentSystem in componentSystems {
componentSystem.updateWithDeltaTime(deltaTime) componentSystem.update(deltaTime: deltaTime)
} }
} }
override func didFinishUpdate() { override func didFinishUpdate() {
// Check if the `playerBot` has been added to this scene. // Check if the `playerBot` has been added to this scene.
if let playerBotNode = playerBot.componentForClass(RenderComponent.self)?.node where playerBotNode.scene == self { if let playerBotNode = playerBot.component(ofType: RenderComponent.self)?.node, playerBotNode.scene == self {
/* /*
Update the `PlayerBot`'s agent position to match its node position. Update the `PlayerBot`'s agent position to match its node position.
This makes sure that the agent is in a valid location in the SpriteKit This makes sure that the agent is in a valid location in the SpriteKit
@ -289,9 +289,9 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
} }
// Sort the entities in the scene by ascending y-position. // Sort the entities in the scene by ascending y-position.
let ySortedEntities = entities.sort { let ySortedEntities = entities.sorted {
let nodeA = $0.0.componentForClass(RenderComponent.self)!.node let nodeA = $0.0.component(ofType: RenderComponent.self)!.node
let nodeB = $0.1.componentForClass(RenderComponent.self)!.node let nodeB = $0.1.component(ofType: RenderComponent.self)!.node
return nodeA.position.y > nodeB.position.y return nodeA.position.y > nodeB.position.y
} }
@ -299,7 +299,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// Set the `zPosition` of each entity so that entities with a higher y-position are rendered above those with a lower y-position. // Set the `zPosition` of each entity so that entities with a higher y-position are rendered above those with a lower y-position.
var characterZPosition = WorldLayer.zSpacePerCharacter var characterZPosition = WorldLayer.zSpacePerCharacter
for entity in ySortedEntities { for entity in ySortedEntities {
let node = entity.componentForClass(RenderComponent.self)!.node let node = entity.component(ofType: RenderComponent.self)!.node
node.zPosition = characterZPosition node.zPosition = characterZPosition
// Use a large enough z-position increment to leave space for emitter effects. // Use a large enough z-position increment to leave space for emitter effects.
@ -309,14 +309,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: SKPhysicsContactDelegate // MARK: SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) { @objc(didBeginContact:) func didBegin(_ contact: SKPhysicsContact) {
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
ContactNotifiableType.contactWithEntityDidBegin(otherEntity) ContactNotifiableType.contactWithEntityDidBegin(otherEntity)
} }
} }
func didEndContact(contact: SKPhysicsContact) { @objc(didEndContact:) func didEnd(_ contact: SKPhysicsContact) {
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
ContactNotifiableType.contactWithEntityDidEnd(otherEntity) ContactNotifiableType.contactWithEntityDidEnd(otherEntity)
} }
} }
@ -329,20 +329,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
let colliderTypeB = ColliderType(rawValue: contact.bodyB.categoryBitMask) let colliderTypeB = ColliderType(rawValue: contact.bodyB.categoryBitMask)
// Determine which `ColliderType` should be notified of the contact. // Determine which `ColliderType` should be notified of the contact.
let aWantsCallback = colliderTypeA.notifyOnContactWithColliderType(colliderTypeB) let aWantsCallback = colliderTypeA.notifyOnContactWith(colliderTypeB)
let bWantsCallback = colliderTypeB.notifyOnContactWithColliderType(colliderTypeA) let bWantsCallback = colliderTypeB.notifyOnContactWith(colliderTypeA)
// Make sure that at least one of the entities wants to handle this contact. // Make sure that at least one of the entities wants to handle this contact.
assert(aWantsCallback || bWantsCallback, "Unhandled physics contact - A = \(colliderTypeA), B = \(colliderTypeB)") assert(aWantsCallback || bWantsCallback, "Unhandled physics contact - A = \(colliderTypeA), B = \(colliderTypeB)")
let entityA = (contact.bodyA.node as? EntityNode)?.entity let entityA = contact.bodyA.node?.entity
let entityB = (contact.bodyB.node as? EntityNode)?.entity let entityB = contact.bodyB.node?.entity
/* /*
If `entityA` is a notifiable type and `colliderTypeA` specifies that it should be notified If `entityA` is a notifiable type and `colliderTypeA` specifies that it should be notified
of contact with `colliderTypeB`, call the callback on `entityA`. of contact with `colliderTypeB`, call the callback on `entityA`.
*/ */
if let notifiableEntity = entityA as? ContactNotifiableType, otherEntity = entityB where aWantsCallback { if let notifiableEntity = entityA as? ContactNotifiableType, let otherEntity = entityB, aWantsCallback {
contactCallback(notifiableEntity, otherEntity) contactCallback(notifiableEntity, otherEntity)
} }
@ -350,7 +350,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
If `entityB` is a notifiable type and `colliderTypeB` specifies that it should be notified If `entityB` is a notifiable type and `colliderTypeB` specifies that it should be notified
of contact with `colliderTypeA`, call the callback on `entityB`. of contact with `colliderTypeA`, call the callback on `entityB`.
*/ */
if let notifiableEntity = entityB as? ContactNotifiableType, otherEntity = entityA where bWantsCallback { if let notifiableEntity = entityB as? ContactNotifiableType, let otherEntity = entityA, bWantsCallback {
contactCallback(notifiableEntity, otherEntity) contactCallback(notifiableEntity, otherEntity)
} }
} }
@ -380,19 +380,19 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
entities.insert(entity) entities.insert(entity)
for componentSystem in self.componentSystems { for componentSystem in self.componentSystems {
componentSystem.addComponentWithEntity(entity) componentSystem.addComponent(foundIn: entity)
} }
// If the entity has a `RenderComponent`, add its node to the scene. // If the entity has a `RenderComponent`, add its node to the scene.
if let renderNode = entity.componentForClass(RenderComponent.self)?.node { if let renderNode = entity.component(ofType: RenderComponent.self)?.node {
addNode(renderNode, toWorldLayer: .Characters) addNode(node: renderNode, toWorldLayer: .characters)
/* /*
If the entity has a `ShadowComponent`, add its shadow node to the scene. If the entity has a `ShadowComponent`, add its shadow node to the scene.
Constrain the `ShadowComponent`'s node to the `RenderComponent`'s node. Constrain the `ShadowComponent`'s node to the `RenderComponent`'s node.
*/ */
if let shadowNode = entity.componentForClass(ShadowComponent.self)?.node { if let shadowNode = entity.component(ofType: ShadowComponent.self)?.node {
addNode(shadowNode, toWorldLayer: .Shadows) addNode(node: shadowNode, toWorldLayer: .shadows)
// Constrain the shadow node's position to the render node. // Constrain the shadow node's position to the render node.
let xRange = SKRange(constantValue: shadowNode.position.x) let xRange = SKRange(constantValue: shadowNode.position.x)
@ -408,8 +408,8 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
If the entity has a `ChargeComponent` with a `ChargeBar`, add the `ChargeBar` If the entity has a `ChargeComponent` with a `ChargeBar`, add the `ChargeBar`
to the scene. Constrain the `ChargeBar` to the `RenderComponent`'s node. to the scene. Constrain the `ChargeBar` to the `RenderComponent`'s node.
*/ */
if let chargeBar = entity.componentForClass(ChargeComponent.self)?.chargeBar { if let chargeBar = entity.component(ofType: ChargeComponent.self)?.chargeBar {
addNode(chargeBar, toWorldLayer: .AboveCharacters) addNode(node: chargeBar, toWorldLayer: .aboveCharacters)
// Constrain the `ChargeBar`'s node position to the render node. // Constrain the `ChargeBar`'s node position to the render node.
let xRange = SKRange(constantValue: GameplayConfiguration.PlayerBot.chargeBarOffset.x) let xRange = SKRange(constantValue: GameplayConfiguration.PlayerBot.chargeBarOffset.x)
@ -423,7 +423,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
} }
// If the entity has an `IntelligenceComponent`, enter its initial state. // If the entity has an `IntelligenceComponent`, enter its initial state.
if let intelligenceComponent = entity.componentForClass(IntelligenceComponent.self) { if let intelligenceComponent = entity.component(ofType: IntelligenceComponent.self) {
intelligenceComponent.enterInitialState() intelligenceComponent.enterInitialState()
} }
} }
@ -437,14 +437,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: GameInputDelegate // MARK: GameInputDelegate
override func gameInputDidUpdateControlInputSources(gameInput: GameInput) { override func gameInputDidUpdateControlInputSources(gameInput: GameInput) {
super.gameInputDidUpdateControlInputSources(gameInput) super.gameInputDidUpdateControlInputSources(gameInput: gameInput)
/* /*
Update the player's `controlInputSources` to delegate input Update the player's `controlInputSources` to delegate input
to the playerBot's `InputComponent`. to the playerBot's `InputComponent`.
*/ */
for controlInputSource in gameInput.controlInputSources { for controlInputSource in gameInput.controlInputSources {
controlInputSource.delegate = playerBot.componentForClass(InputComponent.self) controlInputSource.delegate = playerBot.component(ofType: InputComponent.self)
} }
#if os(iOS) #if os(iOS)
@ -455,17 +455,17 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: ControlInputSourceGameStateDelegate // MARK: ControlInputSourceGameStateDelegate
override func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) { override func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
if stateMachine.currentState is LevelSceneActiveState { if stateMachine.currentState is LevelSceneActiveState {
stateMachine.enterState(LevelScenePauseState.self) stateMachine.enter(LevelScenePauseState.self)
} }
else { else {
stateMachine.enterState(LevelSceneActiveState.self) stateMachine.enter(LevelSceneActiveState.self)
} }
} }
#if DEBUG #if DEBUG
override func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) { override func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
debugDrawingEnabled = !debugDrawingEnabled debugDrawingEnabled = !debugDrawingEnabled
if let view = view { if let view = view {
@ -476,15 +476,15 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
} }
} }
override func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType) { override func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType) {
if stateMachine.currentState is LevelSceneActiveState { if stateMachine.currentState is LevelSceneActiveState {
stateMachine.enterState(LevelSceneSuccessState.self) stateMachine.enter(LevelSceneSuccessState.self)
} }
} }
override func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) { override func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
if stateMachine.currentState is LevelSceneActiveState { if stateMachine.currentState is LevelSceneActiveState {
stateMachine.enterState(LevelSceneFailState.self) stateMachine.enter(LevelSceneFailState.self)
} }
} }
@ -494,12 +494,12 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
override func buttonTriggered(button: ButtonNode) { override func buttonTriggered(button: ButtonNode) {
switch button.buttonIdentifier! { switch button.buttonIdentifier! {
case .Resume: case .resume:
stateMachine.enterState(LevelSceneActiveState.self) stateMachine.enter(LevelSceneActiveState.self)
default: default:
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`. // Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
super.buttonTriggered(button) super.buttonTriggered(button: button)
} }
} }
@ -513,7 +513,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// Constrain the camera to stay a constant distance of 0 points from the player node. // Constrain the camera to stay a constant distance of 0 points from the player node.
let zeroRange = SKRange(constantValue: 0.0) let zeroRange = SKRange(constantValue: 0.0)
let playerNode = playerBot.renderComponent.node let playerNode = playerBot.renderComponent.node
let playerBotLocationConstraint = SKConstraint.distance(zeroRange, toNode: playerNode) let playerBotLocationConstraint = SKConstraint.distance(zeroRange, to: playerNode)
/* /*
Also constrain the camera to avoid it moving to the very edges of the scene. Also constrain the camera to avoid it moving to the very edges of the scene.
@ -527,7 +527,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
Find the root "board" node in the scene (the container node for Find the root "board" node in the scene (the container node for
the level's background tiles). the level's background tiles).
*/ */
let boardNode = childNodeWithName(WorldLayer.Board.nodePath)! let boardNode = childNode(withName: WorldLayer.board.nodePath)!
/* /*
Calculate the accumulated frame of this node. Calculate the accumulated frame of this node.
@ -583,11 +583,11 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
private func beamInPlayerBot() { private func beamInPlayerBot() {
// Find the location of the player's initial position. // Find the location of the player's initial position.
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)! let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
let transporterCoordinate = charactersNode.childNodeWithName("transporter_coordinate")! let transporterCoordinate = charactersNode.childNode(withName: "transporter_coordinate")!
// Set the initial orientation. // Set the initial orientation.
guard let orientationComponent = playerBot.componentForClass(OrientationComponent.self) else { guard let orientationComponent = playerBot.component(ofType: OrientationComponent.self) else {
fatalError("A player bot must have an orientation component to be able to be added to a level") fatalError("A player bot must have an orientation component to be able to be added to a level")
} }
orientationComponent.compassDirection = levelConfiguration.initialPlayerBotOrientation orientationComponent.compassDirection = levelConfiguration.initialPlayerBotOrientation
@ -601,7 +601,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
setCameraConstraints() setCameraConstraints()
// Add the `PlayerBot` to the scene and component systems. // Add the `PlayerBot` to the scene and component systems.
addEntity(playerBot) addEntity(entity: playerBot)
} }
} }

View File

@ -14,16 +14,16 @@ class LevelSceneActiveState: GKState {
unowned let levelScene: LevelScene unowned let levelScene: LevelScene
var timeRemaining: NSTimeInterval = 0.0 var timeRemaining: TimeInterval = 0.0
/* /*
A formatter for individual date components used to provide an appropriate A formatter for individual date components used to provide an appropriate
display value for the timer. display value for the timer.
*/ */
let timeRemainingFormatter: NSDateComponentsFormatter = { let timeRemainingFormatter: DateComponentsFormatter = {
let formatter = NSDateComponentsFormatter() let formatter = DateComponentsFormatter()
formatter.zeroFormattingBehavior = .Pad formatter.zeroFormattingBehavior = .pad
formatter.allowedUnits = [.Minute, .Second] formatter.allowedUnits = [.minute, .second]
return formatter return formatter
}() }()
@ -33,7 +33,7 @@ class LevelSceneActiveState: GKState {
let components = NSDateComponents() let components = NSDateComponents()
components.second = Int(max(0.0, timeRemaining)) components.second = Int(max(0.0, timeRemaining))
return timeRemainingFormatter.stringFromDateComponents(components)! return timeRemainingFormatter.string(from: components as DateComponents)!
} }
// MARK: Initializers // MARK: Initializers
@ -46,14 +46,14 @@ class LevelSceneActiveState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
levelScene.timerNode.text = timeRemainingString levelScene.timerNode.text = timeRemainingString
} }
override func updateWithDeltaTime(seconds: NSTimeInterval) { override func update(deltaTime seconds: TimeInterval) {
super.updateWithDeltaTime(seconds) super.update(deltaTime: seconds)
// Subtract the elapsed time from the remaining time. // Subtract the elapsed time from the remaining time.
timeRemaining -= seconds timeRemaining -= seconds
@ -72,15 +72,15 @@ class LevelSceneActiveState: GKState {
if allTaskBotsAreGood { if allTaskBotsAreGood {
// If all the TaskBots are good, the player has completed the level. // If all the TaskBots are good, the player has completed the level.
stateMachine?.enterState(LevelSceneSuccessState.self) stateMachine?.enter(LevelSceneSuccessState.self)
} }
else if timeRemaining <= 0.0 { else if timeRemaining <= 0.0 {
// If there is no time remaining, the player has failed to complete the level. // If there is no time remaining, the player has failed to complete the level.
stateMachine?.enterState(LevelSceneFailState.self) stateMachine?.enter(LevelSceneFailState.self)
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is LevelScenePauseState.Type, is LevelSceneFailState.Type, is LevelSceneSuccessState.Type: case is LevelScenePauseState.Type, is LevelSceneFailState.Type, is LevelSceneSuccessState.Type:
return true return true

View File

@ -18,15 +18,15 @@ class LevelSceneFailState: LevelSceneOverlayState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
if let inputComponent = levelScene.playerBot.componentForClass(InputComponent.self) { if let inputComponent = levelScene.playerBot.component(ofType: InputComponent.self) {
inputComponent.isEnabled = false inputComponent.isEnabled = false
} }
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return false return false
} }
} }

View File

@ -27,50 +27,50 @@ class LevelSceneOverlayState: GKState {
super.init() super.init()
overlay = SceneOverlay(overlaySceneFileName: overlaySceneFileName, zPosition: WorldLayer.Top.rawValue) overlay = SceneOverlay(overlaySceneFileName: overlaySceneFileName, zPosition: WorldLayer.top.rawValue)
/* /*
Set the level preview image to the image for this state's level if this state Set the level preview image to the image for this state's level if this state
has a "view recorded content" button, with a child node called "levelPreview". has a "view recorded content" button, with a child node called "levelPreview".
*/ */
if let viewRecordedContentButton = buttonWithIdentifier(.ViewRecordedContent) , levelPreviewNode = viewRecordedContentButton.childNodeWithName("levelPreview") as? SKSpriteNode { if let viewRecordedContentButton = button(withIdentifier: .viewRecordedContent), let levelPreviewNode = viewRecordedContentButton.childNode(withName: "levelPreview") as? SKSpriteNode {
levelPreviewNode.texture = SKTexture(imageNamed: levelScene.levelConfiguration.fileName) levelPreviewNode.texture = SKTexture(imageNamed: levelScene.levelConfiguration.fileName)
} }
} }
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
#if os(iOS) #if os(iOS)
// Show the appropriate state for the recording buttons. // Show the appropriate state for the recording buttons.
buttonWithIdentifier(.ScreenRecorderToggle)?.isSelected = levelScene.screenRecordingToggleEnabled button(withIdentifier: .screenRecorderToggle)?.isSelected = levelScene.screenRecordingToggleEnabled
if self is LevelSceneSuccessState || self is LevelSceneFailState { if self is LevelSceneSuccessState || self is LevelSceneFailState {
if let viewRecordedContentButton = buttonWithIdentifier(.ViewRecordedContent) { if let viewRecordedContentButton = button(withIdentifier: .viewRecordedContent) {
viewRecordedContentButton.hidden = true viewRecordedContentButton.isHidden = true
// Stop screen recording and update view recorded content button when complete. // Stop screen recording and update view recorded content button when complete.
levelScene.stopScreenRecordingWithHandler { levelScene.stopScreenRecording() {
// Only show the view button if the recording is enabled and there's a valid `previewViewController` to present. // Only show the view button if the recording is enabled and there's a valid `previewViewController` to present.
let recordingEnabledAndPreviewAvailable = self.levelScene.screenRecordingToggleEnabled && self.levelScene.previewViewController != nil let recordingEnabledAndPreviewAvailable = self.levelScene.screenRecordingToggleEnabled && self.levelScene.previewViewController != nil
viewRecordedContentButton.hidden = !recordingEnabledAndPreviewAvailable viewRecordedContentButton.isHidden = !recordingEnabledAndPreviewAvailable
} }
} }
} }
#else #else
// Hide replay buttons on OSX and tvOS. // Hide replay buttons on OSX and tvOS.
buttonWithIdentifier(.ScreenRecorderToggle)?.hidden = true button(withIdentifier: .screenRecorderToggle)?.isHidden = true
buttonWithIdentifier(.ViewRecordedContent)?.hidden = true button(withIdentifier: .viewRecordedContent)?.isHidden = true
#endif #endif
// Provide the levelScene with a reference to the overlay node. // Provide the levelScene with a reference to the overlay node.
levelScene.overlay = overlay levelScene.overlay = overlay
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
levelScene.overlay = nil levelScene.overlay = nil
@ -84,7 +84,7 @@ class LevelSceneOverlayState: GKState {
// MARK: Convenience // MARK: Convenience
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? { func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
return overlay.contentNode.childNodeWithName("//\(identifier.rawValue)") as? ButtonNode return overlay.contentNode.childNode(withName: "//\(identifier.rawValue)") as? ButtonNode
} }
} }

View File

@ -18,19 +18,19 @@ class LevelScenePauseState: LevelSceneOverlayState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
levelScene.worldNode.paused = true levelScene.worldNode.isPaused = true
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is LevelSceneActiveState.Type return stateClass is LevelSceneActiveState.Type
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
levelScene.worldNode.paused = false levelScene.worldNode.isPaused = false
} }
} }

View File

@ -18,18 +18,18 @@ class LevelSceneSuccessState: LevelSceneOverlayState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
if let inputComponent = levelScene.playerBot.componentForClass(InputComponent.self) { if let inputComponent = levelScene.playerBot.component(ofType: InputComponent.self) {
inputComponent.isEnabled = false inputComponent.isEnabled = false
} }
// Begin preloading the next scene in preparation for the user to advance. // Begin preloading the next scene in preparation for the user to advance.
levelScene.sceneManager.prepareSceneWithSceneIdentifier(.NextLevel) levelScene.sceneManager.prepareScene(identifier: .nextLevel)
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return false return false
} }
} }

View File

@ -9,20 +9,20 @@
import Foundation import Foundation
class LoadResourcesOperation: Operation, NSProgressReporting { class LoadResourcesOperation: SceneOperation, ProgressReporting {
// MARK: Properties // MARK: Properties
/// A class that conforms to the `ResourceLoadableType` protocol. /// A class that conforms to the `ResourceLoadableType` protocol.
let loadableType: ResourceLoadableType.Type let loadableType: ResourceLoadableType.Type
let progress: NSProgress let progress: Progress
// MARK: Initialization // MARK: Initialization
init(loadableType: ResourceLoadableType.Type) { init(loadableType: ResourceLoadableType.Type) {
self.loadableType = loadableType self.loadableType = loadableType
progress = NSProgress(totalUnitCount: 1) progress = Progress(totalUnitCount: 1)
super.init() super.init()
} }
@ -30,9 +30,9 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
override func start() { override func start() {
// If the operation is cancelled there's nothing to do. // If the operation is cancelled there's nothing to do.
guard !cancelled else { return } guard !isCancelled else { return }
if progress.cancelled { if progress.isCancelled {
// Ensure the operation is marked as `cancelled`. // Ensure the operation is marked as `cancelled`.
cancel() cancel()
return return
@ -45,10 +45,10 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
} }
// Mark the operation as executing. // Mark the operation as executing.
state = .Executing state = .executing
// Begin loading the resources. // Begin loading the resources.
loadableType.loadResourcesWithCompletionHandler { [unowned self] in loadableType.loadResources() { [unowned self] in
// Mark the operation as complete once the resources are loaded. // Mark the operation as complete once the resources are loaded.
self.finish() self.finish()
} }
@ -56,6 +56,6 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
func finish() { func finish() {
progress.completedUnitCount = 1 progress.completedUnitCount = 1
state = .Finished state = .finished
} }
} }

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
class LoadSceneOperation: Operation, NSProgressReporting { class LoadSceneOperation: SceneOperation, ProgressReporting {
// MARK: Properties // MARK: Properties
/// The metadata for the scene to load. /// The metadata for the scene to load.
@ -19,14 +19,14 @@ class LoadSceneOperation: Operation, NSProgressReporting {
var scene: BaseScene? var scene: BaseScene?
/// Progress used to report on the status of this operation. /// Progress used to report on the status of this operation.
let progress: NSProgress let progress: Progress
// MARK: Initialization // MARK: Initialization
init(sceneMetadata: SceneMetadata) { init(sceneMetadata: SceneMetadata) {
self.sceneMetadata = sceneMetadata self.sceneMetadata = sceneMetadata
progress = NSProgress(totalUnitCount: 1) progress = Progress(totalUnitCount: 1)
super.init() super.init()
} }
@ -34,16 +34,16 @@ class LoadSceneOperation: Operation, NSProgressReporting {
override func start() { override func start() {
// If the operation is cancelled there's nothing to do. // If the operation is cancelled there's nothing to do.
guard !cancelled else { return } guard !isCancelled else { return }
if progress.cancelled { if progress.isCancelled {
// Ensure the operation is marked as `cancelled`. // Ensure the operation is marked as `cancelled`.
cancel() cancel()
return return
} }
// Mark the operation as executing. // Mark the operation as executing.
state = .Executing state = .executing
// Load the scene into memory using `SKNode(fileNamed:)`. // Load the scene into memory using `SKNode(fileNamed:)`.
let scene = sceneMetadata.sceneType.init(fileNamed: sceneMetadata.fileName)! let scene = sceneMetadata.sceneType.init(fileNamed: sceneMetadata.fileName)!
@ -55,6 +55,6 @@ class LoadSceneOperation: Operation, NSProgressReporting {
// Update the progress object's completed unit count. // Update the progress object's completed unit count.
progress.completedUnitCount = 1 progress.completedUnitCount = 1
state = .Finished state = .finished
} }
} }

View File

@ -26,7 +26,7 @@ class BeamNode: SKNode, ResourceLoadableType {
static let lineNodeTemplate: SKSpriteNode = { static let lineNodeTemplate: SKSpriteNode = {
let templateScene = SKScene(fileNamed: "BeamLine.sks")! let templateScene = SKScene(fileNamed: "BeamLine.sks")!
return templateScene.childNodeWithName("BeamLine") as! SKSpriteNode return templateScene.childNode(withName: "BeamLine") as! SKSpriteNode
}() }()
// MARK: Properties // MARK: Properties
@ -48,21 +48,22 @@ class BeamNode: SKNode, ResourceLoadableType {
override init() { override init() {
sourceNode = SKSpriteNode() sourceNode = SKSpriteNode()
sourceNode.size = BeamNode.dotTextureSize sourceNode.size = BeamNode.dotTextureSize
sourceNode.hidden = true sourceNode.isHidden = true
destinationNode = SKSpriteNode() destinationNode = SKSpriteNode()
destinationNode.size = BeamNode.dotTextureSize destinationNode.size = BeamNode.dotTextureSize
destinationNode.hidden = true destinationNode.isHidden = true
let arcPath = CGPathCreateMutable() let arcPath = CGMutablePath.init()
CGPathAddArc(arcPath, nil, 0.0, 0.0, GameplayConfiguration.Beam.arcLength, GameplayConfiguration.Beam.arcAngle * 0.5, GameplayConfiguration.Beam.arcAngle * -0.5, true) let center = CGPoint(x: 0.0, y: 0.0)
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0) arcPath.addArc(center: center, radius: GameplayConfiguration.Beam.arcLength, startAngle: GameplayConfiguration.Beam.arcAngle * 0.5, endAngle: GameplayConfiguration.Beam.arcAngle * -0.5, clockwise: true)
arcPath.addLine(to: center)
debugNode = SKShapeNode(path: arcPath) debugNode = SKShapeNode(path: arcPath)
debugNode.fillColor = SKColor.blueColor() debugNode.fillColor = SKColor.blue
debugNode.lineWidth = 0.0 debugNode.lineWidth = 0.0
debugNode.alpha = 0.5 debugNode.alpha = 0.5
debugNode.hidden = true debugNode.isHidden = true
super.init() super.init()
@ -77,9 +78,9 @@ class BeamNode: SKNode, ResourceLoadableType {
// MARK: Actions // MARK: Actions
func updateWithBeamState(state: GKState, source: PlayerBot, target: TaskBot? = nil) { func update(withBeamState state: GKState, source: PlayerBot, target: TaskBot? = nil) {
// Constrain the position of the target's antenna if it's not already constrained to it. // Constrain the position of the target's antenna if it's not already constrained to it.
if let target = target, targetNode = target.componentForClass(RenderComponent.self)?.node where destinationNode.constraints?.first?.referenceNode != targetNode { if let target = target, let targetNode = target.component(ofType: RenderComponent.self)?.node, destinationNode.constraints?.first?.referenceNode != targetNode {
let xRange = SKRange(constantValue: target.beamTargetOffset.x) let xRange = SKRange(constantValue: target.beamTargetOffset.x)
let yRange = SKRange(constantValue: target.beamTargetOffset.y) let yRange = SKRange(constantValue: target.beamTargetOffset.y)
@ -92,14 +93,14 @@ class BeamNode: SKNode, ResourceLoadableType {
switch state { switch state {
case is BeamIdleState: case is BeamIdleState:
// Hide the source and destination nodes. // Hide the source and destination nodes.
sourceNode.hidden = true sourceNode.isHidden = true
destinationNode.hidden = true destinationNode.isHidden = true
// Remove the `lineNode` from the scene. // Remove the `lineNode` from the scene.
lineNode?.removeFromParent() lineNode?.removeFromParent()
lineNode = nil lineNode = nil
debugNode.hidden = true debugNode.isHidden = true
case is BeamFiringState: case is BeamFiringState:
/* /*
@ -109,38 +110,38 @@ class BeamNode: SKNode, ResourceLoadableType {
*/ */
if lineNode == nil { if lineNode == nil {
lineNode = BeamNode.lineNodeTemplate.copy() as? SKSpriteNode lineNode = BeamNode.lineNodeTemplate.copy() as? SKSpriteNode
lineNode!.hidden = true lineNode!.isHidden = true
addChild(lineNode!) addChild(lineNode!)
} }
if let target = target { if let target = target {
// Show the `sourceNode` with the its firing animation. // Show the `sourceNode` with the its firing animation.
sourceNode.hidden = false sourceNode.isHidden = false
animateNode(sourceNode, withAction: AnimationActions.source) animate(sourceNode, withAction: AnimationActions.source)
// Show the `destinationNode` with its animation. // Show the `destinationNode` with its animation.
destinationNode.hidden = false destinationNode.isHidden = false
animateNode(destinationNode, withAction: AnimationActions.destination) animate(destinationNode, withAction: AnimationActions.destination)
// Position the `lineNode` and make sure it's visible. // Position the `lineNode` and make sure it's visible.
positionLineNodeFrom(source, to: target) positionLineNode(from: source, to: target)
lineNode?.hidden = false lineNode?.isHidden = false
} }
else { else {
// Show the `sourceNode` with the its untargeted animation. // Show the `sourceNode` with the its untargeted animation.
sourceNode.hidden = false sourceNode.isHidden = false
animateNode(sourceNode, withAction: AnimationActions.untargetedSource) animate(sourceNode, withAction: AnimationActions.untargetedSource)
// Hide the `destinationNode` and `lineNode`. // Hide the `destinationNode` and `lineNode`.
destinationNode.hidden = true destinationNode.isHidden = true
lineNode?.hidden = true lineNode?.isHidden = true
} }
// Update the debug node if debug drawing is enabled. // Update the debug node if debug drawing is enabled.
debugNode.hidden = !debugDrawingEnabled debugNode.isHidden = !debugDrawingEnabled
if debugDrawingEnabled { if debugDrawingEnabled {
guard let sourceOrientation = source.componentForClass(OrientationComponent.self) else { guard let sourceOrientation = source.component(ofType: OrientationComponent.self) else {
fatalError("BeamNodees must be associated with entities that have an orientation node") fatalError("BeamNodees must be associated with entities that have an orientation node")
} }
@ -151,15 +152,17 @@ class BeamNode: SKNode, ResourceLoadableType {
This allows for easier aiming the closer the source is to This allows for easier aiming the closer the source is to
the target. the target.
*/ */
let arcPath = CGPathCreateMutable() let arcPath = CGMutablePath.init()
// Only draw beam arc if there is a target. // Only draw beam arc if there is a target.
if let target = target { if let target = target {
let distanceRatio = GameplayConfiguration.Beam.arcLength / CGFloat(distance(source.agent.position, target.agent.position)) let distanceRatio = GameplayConfiguration.Beam.arcLength / CGFloat(distance(source.agent.position, target.agent.position))
let arcAngle = min(GameplayConfiguration.Beam.arcAngle * distanceRatio, 1 / GameplayConfiguration.Beam.maxArcAngle) let arcAngle = min(GameplayConfiguration.Beam.arcAngle * distanceRatio, 1 / GameplayConfiguration.Beam.maxArcAngle)
CGPathAddArc(arcPath, nil, 0.0, 0.0, GameplayConfiguration.Beam.arcLength, arcAngle * 0.5, -arcAngle * 0.5, true) let center = CGPoint(x: 0, y: 0)
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0)
arcPath.addArc(center: center, radius: GameplayConfiguration.Beam.arcLength, startAngle: arcAngle * 0.5, endAngle: -arcAngle * 0.5, clockwise: true)
arcPath.addLine(to: center)
} }
debugNode.path = arcPath debugNode.path = arcPath
@ -168,17 +171,17 @@ class BeamNode: SKNode, ResourceLoadableType {
case is BeamCoolingState: case is BeamCoolingState:
// Show the `sourceNode` with the "cooling" animation. // Show the `sourceNode` with the "cooling" animation.
sourceNode.hidden = false sourceNode.isHidden = false
animateNode(sourceNode, withAction: AnimationActions.cooling) animate(sourceNode, withAction: AnimationActions.cooling)
// Hide the `destinationNode`. // Hide the `destinationNode`.
destinationNode.hidden = true destinationNode.isHidden = true
// Remove the `lineNode` from the scene. // Remove the `lineNode` from the scene.
lineNode?.removeFromParent() lineNode?.removeFromParent()
lineNode = nil lineNode = nil
debugNode.hidden = true debugNode.isHidden = true
default: default:
break break
@ -187,23 +190,23 @@ class BeamNode: SKNode, ResourceLoadableType {
// MARK: Convenience // MARK: Convenience
func animateNode(node: SKSpriteNode, withAction action: SKAction) { func animate(_ node: SKSpriteNode, withAction action: SKAction) {
if runningNodeAnimations[node] != action { if runningNodeAnimations[node] != action {
node.runAction(action, withKey: BeamNode.animationActionKey) node.run(action, withKey: BeamNode.animationActionKey)
runningNodeAnimations[node] = action runningNodeAnimations[node] = action
} }
} }
func positionLineNodeFrom(source: PlayerBot, to target: TaskBot) { func positionLineNode(from source: PlayerBot, to target: TaskBot) {
guard let lineNode = lineNode else { fatalError("positionLineNodeFrom(_: to:) requires a lineNode to have been created.") } guard let lineNode = lineNode else { fatalError("positionLineNodeFrom(_: to:) requires a lineNode to have been created.") }
// Calculate the source and destination positions. // Calculate the source and destination positions.
let sourcePosition: CGPoint = { let sourcePosition: CGPoint = {
guard let node = source.componentForClass(RenderComponent.self)?.node, nodeParent = node.parent else { guard let node = source.component(ofType: RenderComponent.self)?.node, let nodeParent = node.parent else {
fatalError("positionLineNodeFrom(_: to:) requires the source to have a node with a parent.") fatalError("positionLineNodeFrom(_: to:) requires the source to have a node with a parent.")
} }
var position = convertPoint(node.position, fromNode: nodeParent) var position = convert(node.position, from: nodeParent)
position.x += source.antennaOffset.x position.x += source.antennaOffset.x
position.y += source.antennaOffset.y position.y += source.antennaOffset.y
@ -211,11 +214,11 @@ class BeamNode: SKNode, ResourceLoadableType {
}() }()
let destinationPosition: CGPoint = { let destinationPosition: CGPoint = {
guard let node = target.componentForClass(RenderComponent.self)?.node, nodeParent = node.parent else { guard let node = target.component(ofType: RenderComponent.self)?.node, let nodeParent = node.parent else {
fatalError("positionLineNodeFrom(_: to:) requires the destination to have a node with a parent.") fatalError("positionLineNodeFrom(_: to:) requires the destination to have a node with a parent.")
} }
var position = convertPoint(node.position, fromNode: nodeParent) var position = convert(node.position, from: nodeParent)
position.x += target.beamTargetOffset.x position.x += target.beamTargetOffset.x
position.y += target.beamTargetOffset.y position.y += target.beamTargetOffset.y
@ -240,7 +243,7 @@ class BeamNode: SKNode, ResourceLoadableType {
return AnimationActions.source == nil || AnimationActions.untargetedSource == nil || AnimationActions.destination == nil || AnimationActions.cooling == nil return AnimationActions.source == nil || AnimationActions.untargetedSource == nil || AnimationActions.destination == nil || AnimationActions.cooling == nil
} }
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) { static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
let beamAtlasNames = [ let beamAtlasNames = [
"BeamDot", "BeamDot",
"BeamCharging" "BeamCharging"
@ -255,11 +258,11 @@ class BeamNode: SKNode, ResourceLoadableType {
fatalError("One or more texture atlases could not be found: \(error)") fatalError("One or more texture atlases could not be found: \(error)")
} }
let beamDotAction = AnimationComponent.actionForAllTexturesInAtlas(beamAtlases[0]) let beamDotAction = AnimationComponent.actionForAllTexturesInAtlas(atlas: beamAtlases[0])
AnimationActions.source = beamDotAction AnimationActions.source = beamDotAction
AnimationActions.untargetedSource = beamDotAction AnimationActions.untargetedSource = beamDotAction
AnimationActions.destination = beamDotAction AnimationActions.destination = beamDotAction
AnimationActions.cooling = AnimationComponent.actionForAllTexturesInAtlas(beamAtlases[1]) AnimationActions.cooling = AnimationComponent.actionForAllTexturesInAtlas(atlas: beamAtlases[1])
// Invoke the passed `completionHandler` to indicate that loading has completed. // Invoke the passed `completionHandler` to indicate that loading has completed.
completionHandler() completionHandler()
@ -272,4 +275,4 @@ class BeamNode: SKNode, ResourceLoadableType {
AnimationActions.untargetedSource = nil AnimationActions.untargetedSource = nil
AnimationActions.cooling = nil AnimationActions.cooling = nil
} }
} }

View File

@ -16,24 +16,24 @@ protocol ButtonNodeResponderType: class {
/// The complete set of button identifiers supported in the app. /// The complete set of button identifiers supported in the app.
enum ButtonIdentifier: String { enum ButtonIdentifier: String {
case Resume case resume = "Resume"
case Home case home = "Home"
case ProceedToNextScene case proceedToNextScene = "ProceedToNextScene"
case Replay case replay = "Replay"
case Retry case retry = "Retry"
case Cancel case cancel = "Cancel"
case ScreenRecorderToggle case screenRecorderToggle = "ScreenRecorderToggle"
case ViewRecordedContent case viewRecordedContent = "ViewRecordedContent"
/// Convenience array of all available button identifiers. /// Convenience array of all available button identifiers.
static let allButtonIdentifiers: [ButtonIdentifier] = [ static let allButtonIdentifiers: [ButtonIdentifier] = [
.Resume, .Home, .ProceedToNextScene, .Replay, .Retry, .Cancel, .ScreenRecorderToggle, .ViewRecordedContent .resume, .home, .proceedToNextScene, .replay, .retry, .cancel, .screenRecorderToggle, .viewRecordedContent
] ]
/// The name of the texture to use for a button when the button is selected. /// The name of the texture to use for a button when the button is selected.
var selectedTextureName: String? { var selectedTextureName: String? {
switch self { switch self {
case .ScreenRecorderToggle: case .screenRecorderToggle:
return "ButtonAutoRecordOn" return "ButtonAutoRecordOn"
default: default:
return nil return nil
@ -71,14 +71,14 @@ class ButtonNode: SKSpriteNode {
// Create a scale action to make the button look like it is slightly depressed. // Create a scale action to make the button look like it is slightly depressed.
let newScale: CGFloat = isHighlighted ? 0.99 : 1.01 let newScale: CGFloat = isHighlighted ? 0.99 : 1.01
let scaleAction = SKAction.scaleBy(newScale, duration: 0.15) let scaleAction = SKAction.scale(by: newScale, duration: 0.15)
// Create a color blend action to darken the button slightly when it is depressed. // Create a color blend action to darken the button slightly when it is depressed.
let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0 let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0
let colorBlendAction = SKAction.colorizeWithColorBlendFactor(newColorBlendFactor, duration: 0.15) let colorBlendAction = SKAction.colorize(withColorBlendFactor: newColorBlendFactor, duration: 0.15)
// Run the two actions at the same time. // Run the two actions at the same time.
runAction(SKAction.group([scaleAction, colorBlendAction])) run(SKAction.group([scaleAction, colorBlendAction]))
} }
} }
@ -112,26 +112,26 @@ class ButtonNode: SKSpriteNode {
var isFocused = false { var isFocused = false {
didSet { didSet {
if isFocused { if isFocused {
runAction(SKAction.scaleTo(1.08, duration: 0.20)) run(SKAction.scale(to: 1.08, duration: 0.20))
focusRing.alpha = 0.0 focusRing.alpha = 0.0
focusRing.hidden = false focusRing.isHidden = false
focusRing.runAction(SKAction.fadeInWithDuration(0.2)) focusRing.run(SKAction.fadeIn(withDuration: 0.2))
} }
else { else {
runAction(SKAction.scaleTo(1.0, duration: 0.20)) run(SKAction.scale(to: 1.0, duration: 0.20))
focusRing.hidden = true focusRing.isHidden = true
} }
} }
} }
/// A node to indicate when a button has the input focus. /// A node to indicate when a button has the input focus.
lazy var focusRing: SKNode = self.childNodeWithName("focusRing")! lazy var focusRing: SKNode = self.childNode(withName: "focusRing")!
// MARK: Initializers // MARK: Initializers
/// Overridden to support `copyWithZone(_:)`. /// Overridden to support `copy(with zone:)`.
override init(texture: SKTexture?, color: SKColor, size: CGSize) { override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size) super.init(texture: texture, color: color, size: size)
} }
@ -140,7 +140,7 @@ class ButtonNode: SKSpriteNode {
super.init(coder: aDecoder) super.init(coder: aDecoder)
// Ensure that the node has a supported button identifier as its name. // Ensure that the node has a supported button identifier as its name.
guard let nodeName = name, buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else { guard let nodeName = name, let buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else {
fatalError("Unsupported button name found.") fatalError("Unsupported button name found.")
} }
self.buttonIdentifier = buttonIdentifier self.buttonIdentifier = buttonIdentifier
@ -158,14 +158,14 @@ class ButtonNode: SKSpriteNode {
} }
// The focus ring should be hidden until the button is given the input focus. // The focus ring should be hidden until the button is given the input focus.
focusRing.hidden = true focusRing.isHidden = true
// Enable user interaction on the button node to detect tap and click events. // Enable user interaction on the button node to detect tap and click events.
userInteractionEnabled = true isUserInteractionEnabled = true
} }
override func copyWithZone(zone: NSZone) -> AnyObject { override func copy(with zone: NSZone? = nil) -> Any {
let newButton = super.copyWithZone(zone) as! ButtonNode let newButton = super.copy(with: zone) as! ButtonNode
// Copy the `ButtonNode` specific properties. // Copy the `ButtonNode` specific properties.
newButton.buttonIdentifier = buttonIdentifier newButton.buttonIdentifier = buttonIdentifier
@ -176,9 +176,9 @@ class ButtonNode: SKSpriteNode {
} }
func buttonTriggered() { func buttonTriggered() {
if userInteractionEnabled { if isUserInteractionEnabled {
// Forward the button press event through to the responder. // Forward the button press event through to the responder.
responder.buttonTriggered(self) responder.buttonTriggered(button: self)
} }
} }
@ -189,43 +189,43 @@ class ButtonNode: SKSpriteNode {
*/ */
func performInvalidFocusChangeAnimationForDirection(direction: ControlInputDirection) { func performInvalidFocusChangeAnimationForDirection(direction: ControlInputDirection) {
let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey" let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey"
guard actionForKey(animationKey) == nil else { return } guard action(forKey: animationKey) == nil else { return }
// Find the reference action from `ButtonFocusActions.sks`. // Find the reference action from `ButtonFocusActions.sks`.
let action: SKAction let theAction: SKAction
switch direction { switch direction {
case .Up: action = SKAction(named: "InvalidFocusChange_Up")! case .up: theAction = SKAction(named: "InvalidFocusChange_Up")!
case .Down: action = SKAction(named: "InvalidFocusChange_Down")! case .down: theAction = SKAction(named: "InvalidFocusChange_Down")!
case .Left: action = SKAction(named: "InvalidFocusChange_Left")! case .left: theAction = SKAction(named: "InvalidFocusChange_Left")!
case .Right: action = SKAction(named: "InvalidFocusChange_Right")! case .right: theAction = SKAction(named: "InvalidFocusChange_Right")!
} }
runAction(action, withKey: animationKey) run(theAction, withKey: animationKey)
} }
// MARK: Responder // MARK: Responder
#if os(iOS) #if os(iOS)
/// UIResponder touch handling. /// UIResponder touch handling.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, withEvent: event) super.touchesBegan(touches, with: event)
isHighlighted = true isHighlighted = true
} }
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, withEvent: event) super.touchesEnded(touches, with: event)
isHighlighted = false isHighlighted = false
// Touch up inside behavior. // Touch up inside behavior.
if containsTouches(touches) { if containsTouches(touches: touches) {
buttonTriggered() buttonTriggered()
} }
} }
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event) super.touchesCancelled(touches!, with: event)
isHighlighted = false isHighlighted = false
} }
@ -235,22 +235,22 @@ class ButtonNode: SKSpriteNode {
guard let scene = scene else { fatalError("Button must be used within a scene.") } guard let scene = scene else { fatalError("Button must be used within a scene.") }
return touches.contains { touch in return touches.contains { touch in
let touchPoint = touch.locationInNode(scene) let touchPoint = touch.location(in: scene)
let touchedNode = scene.nodeAtPoint(touchPoint) let touchedNode = scene.atPoint(touchPoint)
return touchedNode === self || touchedNode.inParentHierarchy(self) return touchedNode === self || touchedNode.inParentHierarchy(self)
} }
} }
#elseif os(OSX) #elseif os(OSX)
/// NSResponder mouse handling. /// NSResponder mouse handling.
override func mouseDown(event: NSEvent) { override func mouseDown(with event: NSEvent) {
super.mouseDown(event) super.mouseDown(with: event)
isHighlighted = true isHighlighted = true
} }
override func mouseUp(event: NSEvent) { override func mouseUp(with event: NSEvent) {
super.mouseUp(event) super.mouseUp(with: event)
isHighlighted = false isHighlighted = false
@ -261,11 +261,11 @@ class ButtonNode: SKSpriteNode {
} }
/// Determine if the event location is within the `ButtonNode`. /// Determine if the event location is within the `ButtonNode`.
private func containsLocationForEvent(event: NSEvent) -> Bool { private func containsLocationForEvent(_ event: NSEvent) -> Bool {
guard let scene = scene else { fatalError("Button must be used within a scene.") } guard let scene = scene else { fatalError("Button must be used within a scene.") }
let location = event.locationInNode(scene) let location = event.location(in: scene)
let clickedNode = scene.nodeAtPoint(location) let clickedNode = scene.atPoint(location)
return clickedNode === self || clickedNode.inParentHierarchy(self) return clickedNode === self || clickedNode.inParentHierarchy(self)
} }
#endif #endif

View File

@ -19,13 +19,13 @@ class ChargeBar: SKSpriteNode {
static let chargeLevelNodeSize = CGSize(width: 70.0, height: 6.0) static let chargeLevelNodeSize = CGSize(width: 70.0, height: 6.0)
/// The duration used for actions to update the level indicator. /// The duration used for actions to update the level indicator.
static let levelUpdateDuration: NSTimeInterval = 0.1 static let levelUpdateDuration: TimeInterval = 0.1
/// The background color. /// The background color.
static let backgroundColor = SKColor.blackColor() static let backgroundColor = SKColor.black
/// The charge level node color. /// The charge level node color.
static let chargeLevelColor = SKColor.greenColor() static let chargeLevelColor = SKColor.green
} }
// MARK: Properties // MARK: Properties
@ -33,10 +33,10 @@ class ChargeBar: SKSpriteNode {
var level: Double = 1.0 { var level: Double = 1.0 {
didSet { didSet {
// Scale the level bar node based on the current health level. // Scale the level bar node based on the current health level.
let action = SKAction.scaleXTo(CGFloat(level), duration: Configuration.levelUpdateDuration) let action = SKAction.scaleX(to: CGFloat(level), duration: Configuration.levelUpdateDuration)
action.timingMode = .EaseInEaseOut action.timingMode = .easeInEaseOut
chargeLevelNode.runAction(action) chargeLevelNode.run(action)
} }
} }

View File

@ -1,16 +0,0 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
A simple `SKNode` subclass that stores a `weak` reference to an associated `GKEntity`. Provides a way to discover the entity associated with a node.
*/
import SpriteKit
import GameplayKit
class EntityNode: SKNode {
// MARK: Properties
weak var entity: GKEntity!
}

View File

@ -54,9 +54,9 @@ class ThumbStickNode: SKSpriteNode {
let touchPadTexture = SKTexture(imageNamed: "ControlPad") let touchPadTexture = SKTexture(imageNamed: "ControlPad")
// `touchPad` is the inner touch pad that follows the user's thumb. // `touchPad` is the inner touch pad that follows the user's thumb.
touchPad = SKSpriteNode(texture: touchPadTexture, color: UIColor.clearColor(), size: touchPadSize) touchPad = SKSpriteNode(texture: touchPadTexture, color: UIColor.clear, size: touchPadSize)
super.init(texture: touchPadTexture, color: UIColor.clearColor(), size: size) super.init(texture: touchPadTexture, color: UIColor.clear, size: size)
alpha = normalAlpha alpha = normalAlpha
@ -69,25 +69,25 @@ class ThumbStickNode: SKSpriteNode {
// MARK: UIResponder // MARK: UIResponder
override func canBecomeFirstResponder() -> Bool { override var canBecomeFirstResponder: Bool {
return true return true
} }
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, withEvent: event) super.touchesBegan(touches, with: event)
// Highlight that the control is being used by adjusting the alpha. // Highlight that the control is being used by adjusting the alpha.
alpha = selectedAlpha alpha = selectedAlpha
// Inform the delegate that the control is being pressed. // Inform the delegate that the control is being pressed.
delegate?.thumbStickNode(self, isPressed: true) delegate?.thumbStickNode(thumbStickNode: self, isPressed: true)
} }
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, withEvent: event) super.touchesMoved(touches, with: event)
// For each touch, calculate the movement of the touchPad. // For each touch, calculate the movement of the touchPad.
for touch in touches { for touch in touches {
let touchLocation = touch.locationInNode(self) let touchLocation = touch.location(in: self)
var dx = touchLocation.x - center.x var dx = touchLocation.x - center.x
var dy = touchLocation.y - center.y var dy = touchLocation.y - center.y
@ -111,12 +111,12 @@ class ThumbStickNode: SKSpriteNode {
// Normalize the displacements between [-1.0, 1.0]. // Normalize the displacements between [-1.0, 1.0].
let normalizedDx = Float(dx / trackingDistance) let normalizedDx = Float(dx / trackingDistance)
let normalizedDy = Float(dy / trackingDistance) let normalizedDy = Float(dy / trackingDistance)
delegate?.thumbStickNode(self, didUpdateXValue: normalizedDx, yValue: normalizedDy) delegate?.thumbStickNode(thumbStickNode: self, didUpdateXValue: normalizedDx, yValue: normalizedDy)
} }
} }
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, withEvent: event) super.touchesEnded(touches, with: event)
// If the touches set is empty, return immediately. // If the touches set is empty, return immediately.
guard !touches.isEmpty else { return } guard !touches.isEmpty else { return }
@ -124,8 +124,8 @@ class ThumbStickNode: SKSpriteNode {
resetTouchPad() resetTouchPad()
} }
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event) super.touchesCancelled(touches!, with: event)
resetTouchPad() resetTouchPad()
} }
@ -133,10 +133,10 @@ class ThumbStickNode: SKSpriteNode {
func resetTouchPad() { func resetTouchPad() {
alpha = normalAlpha alpha = normalAlpha
let restoreToCenter = SKAction.moveTo(CGPoint.zero, duration: 0.2) let restoreToCenter = SKAction.move(to: CGPoint.zero, duration: 0.2)
touchPad.runAction(restoreToCenter) touchPad.run(restoreToCenter)
delegate?.thumbStickNode(self, isPressed: false) delegate?.thumbStickNode(thumbStickNode: self, isPressed: false)
delegate?.thumbStickNode(self, didUpdateXValue: 0, yValue: 0) delegate?.thumbStickNode(thumbStickNode: self, didUpdateXValue: 0, yValue: 0)
} }
} }

View File

@ -9,7 +9,7 @@
import SpriteKit import SpriteKit
import GameplayKit import GameplayKit
struct ColliderType: OptionSetType, Hashable, CustomDebugStringConvertible { struct ColliderType: OptionSet, Hashable, CustomDebugStringConvertible {
// MARK: Static properties // MARK: Static properties
/// A dictionary to specify which `ColliderType`s should be notified of contacts with other `ColliderType`s. /// A dictionary to specify which `ColliderType`s should be notified of contacts with other `ColliderType`s.
@ -87,7 +87,7 @@ struct ColliderType: OptionSetType, Hashable, CustomDebugStringConvertible {
Returns `true` if the `ContactNotifiableType` associated with this `ColliderType` should be Returns `true` if the `ContactNotifiableType` associated with this `ColliderType` should be
notified of contact with the passed `ColliderType`. notified of contact with the passed `ColliderType`.
*/ */
func notifyOnContactWithColliderType(colliderType: ColliderType) -> Bool { func notifyOnContactWith(_ colliderType: ColliderType) -> Bool {
if let requestedContacts = ColliderType.requestedContactNotifications[self] { if let requestedContacts = ColliderType.requestedContactNotifications[self] {
return requestedContacts.contains(colliderType) return requestedContacts.contains(colliderType)
} }

View File

@ -19,15 +19,15 @@ class ProgressScene: BaseScene {
/// Returns the background node from the scene. /// Returns the background node from the scene.
override var backgroundNode: SKSpriteNode? { override var backgroundNode: SKSpriteNode? {
return childNodeWithName("backgroundNode") as? SKSpriteNode return childNode(withName: "backgroundNode") as? SKSpriteNode
} }
var labelNode: SKLabelNode { var labelNode: SKLabelNode {
return backgroundNode!.childNodeWithName("label") as! SKLabelNode return backgroundNode!.childNode(withName: "label") as! SKLabelNode
} }
var progressBarNode: SKSpriteNode { var progressBarNode: SKSpriteNode {
return backgroundNode!.childNodeWithName("progressBar") as! SKSpriteNode return backgroundNode!.childNode(withName: "progressBar") as! SKSpriteNode
} }
/* /*
@ -35,7 +35,7 @@ class ProgressScene: BaseScene {
the scene from a file, but `init(fileNamed:)` is not a designated init), the scene from a file, but `init(fileNamed:)` is not a designated init),
we need to make most of the properties `var` and implicitly unwrapped we need to make most of the properties `var` and implicitly unwrapped
optional so we can set the properties after creating the scene with optional so we can set the properties after creating the scene with
`progressSceneWithSceneLoader(sceneLoader:)`. `progressScene(withSceneLoader sceneLoader:)`.
*/ */
/// The scene loader currently handling the requested scene. /// The scene loader currently handling the requested scene.
@ -45,13 +45,13 @@ class ProgressScene: BaseScene {
var progressBarInitialWidth: CGFloat! var progressBarInitialWidth: CGFloat!
/// Add child progress objects to track downloading and loading states. /// Add child progress objects to track downloading and loading states.
var progress: NSProgress? { var progress: Progress? {
didSet { didSet {
// Unregister as an observer on the old value for the "fractionCompleted" property. // Unregister as an observer on the old value for the "fractionCompleted" property.
oldValue?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressSceneKVOContext) oldValue?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressSceneKVOContext)
// Register as an observer on the initial and for changes to the "fractionCompleted" property. // Register as an observer on the initial and for changes to the "fractionCompleted" property.
progress?.addObserver(self, forKeyPath: "fractionCompleted", options: [.New, .Initial], context: &progressSceneKVOContext) progress?.addObserver(self, forKeyPath: "fractionCompleted", options: [.new, .initial], context: &progressSceneKVOContext)
} }
} }
@ -65,20 +65,20 @@ class ProgressScene: BaseScene {
progress of on demand resources and the loading progress of bringing progress of on demand resources and the loading progress of bringing
assets into memory. assets into memory.
*/ */
static func progressSceneWithSceneLoader(sceneLoader: SceneLoader) -> ProgressScene { static func progressScene(withSceneLoader loader: SceneLoader) -> ProgressScene {
// Load the progress scene from its sks file. // Load the progress scene from its sks file.
let progressScene = ProgressScene(fileNamed: "ProgressScene")! let progressScene = ProgressScene(fileNamed: "ProgressScene")!
progressScene.createCamera() progressScene.createCamera()
progressScene.setupWithSceneLoader(sceneLoader) progressScene.setup(withSceneLoader: loader)
// Return the setup progress scene. // Return the setup progress scene.
return progressScene return progressScene
} }
func setupWithSceneLoader(sceneLoader: SceneLoader) { func setup(withSceneLoader loader: SceneLoader) {
// Set the sceneLoader. This may be in the downloading or preparing state. // Set the sceneLoader. This may be in the downloading or preparing state.
self.sceneLoader = sceneLoader self.sceneLoader = loader
// Grab the `sceneLoader`'s progress if it is already loading. // Grab the `sceneLoader`'s progress if it is already loading.
if let progress = sceneLoader.progress { if let progress = sceneLoader.progress {
@ -90,18 +90,18 @@ class ProgressScene: BaseScene {
} }
// Register for notifications posted when the `SceneDownloader` fails. // Register for notifications posted when the `SceneDownloader` fails.
let defaultCenter = NSNotificationCenter.defaultCenter() let defaultCenter = NotificationCenter.default
downloadFailedObserver = defaultCenter.addObserverForName(SceneLoaderDidFailNotification, object: sceneLoader, queue: NSOperationQueue.mainQueue()) { [unowned self] notification in downloadFailedObserver = defaultCenter.addObserver(forName: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader, queue: OperationQueue.main) { [unowned self] notification in
guard let sceneLoader = notification.object as? SceneLoader, error = sceneLoader.error else { fatalError("The scene loader has no error to show.") } guard let loader = notification.object as? SceneLoader, let error = loader.error else { fatalError("The scene loader has no error to show.") }
self.showErrorStateForError(error) self.showError(error as NSError)
} }
} }
deinit { deinit {
// Unregister as an observer of 'SceneLoaderDownloadFailedNotification' notifications. // Unregister as an observer of 'SceneLoaderDownloadFailedNotification' notifications.
if let downloadFailedObserver = downloadFailedObserver { if let downloadFailedObserver = downloadFailedObserver {
NSNotificationCenter.defaultCenter().removeObserver(downloadFailedObserver, name: SceneLoaderDidFailNotification, object: sceneLoader) NotificationCenter.default.removeObserver(downloadFailedObserver, name: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader)
} }
// Set the progress property to nil which will remove this object as an observer. // Set the progress property to nil which will remove this object as an observer.
@ -110,17 +110,17 @@ class ProgressScene: BaseScene {
// MARK: Scene Life Cycle // MARK: Scene Life Cycle
override func didMoveToView(view: SKView) { override func didMove(to view: SKView) {
super.didMoveToView(view) super.didMove(to: view)
centerCameraOnPoint(backgroundNode!.position) centerCameraOnPoint(point: backgroundNode!.position)
// Remember the progress bar's initial width. It will change to indicate progress. // Remember the progress bar's initial width. It will change to indicate progress.
progressBarInitialWidth = progressBarNode.frame.width progressBarInitialWidth = progressBarNode.frame.width
if let error = sceneLoader.error { if let error = sceneLoader.error {
// Show the scene loader's error. // Show the scene loader's error.
showErrorStateForError(error) showError(error as NSError)
} }
else { else {
showDefaultState() showDefaultState()
@ -128,12 +128,18 @@ class ProgressScene: BaseScene {
} }
// MARK: Key Value Observing (KVO) for NSProgress // MARK: Key Value Observing (KVO) for NSProgress
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String: AnyObject]?, context: UnsafeMutablePointer<Void>) { @nonobjc override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Check if this is the KVO notification we need. // Check if this is the KVO notification we need.
if context == &progressSceneKVOContext && keyPath == "fractionCompleted" && object === progress {
guard context == &progressSceneKVOContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if let changedProgress = object as? Progress, changedProgress == progress, keyPath == "fractionCompleted" {
// Update the progress UI on the main queue. // Update the progress UI on the main queue.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
guard let progress = self.progress else { return } guard let progress = self.progress else { return }
// Update the progress bar to match the amount of progress completed. // Update the progress bar to match the amount of progress completed.
@ -143,28 +149,25 @@ class ProgressScene: BaseScene {
self.labelNode.text = progress.localizedDescription self.labelNode.text = progress.localizedDescription
} }
} }
else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
} }
// MARK: ButtonNodeResponderType // MARK: ButtonNodeResponderType
override func buttonTriggered(button: ButtonNode) { override func buttonTriggered(button: ButtonNode) {
switch button.buttonIdentifier! { switch button.buttonIdentifier! {
case .Retry: case .retry:
// Set up the progress for a new preparation attempt. // Set up the progress for a new preparation attempt.
progress = sceneLoader.asynchronouslyLoadSceneForPresentation() progress = sceneLoader.asynchronouslyLoadSceneForPresentation()
sceneLoader.requestedForPresentation = true sceneLoader.requestedForPresentation = true
showDefaultState() showDefaultState()
case .Cancel: case .cancel:
/* /*
Canceling the parent progress propagates the cancellation to the child Canceling the parent progress propagates the cancellation to the child
progress objects. progress objects.
In `SceneLoaderDownloadingResourcesState` this will cause the completionHandler to In `SceneLoaderDownloadingResourcesState` this will cause the completionHandler to
be invoked on `beginAccessingResourcesWithCompletionHandler(_:)` be invoked on `beginAccessingResources(withCompletionHandler completionHandler:)`
with an NSUserCancelledError. See the NSBundleResourceRequest documentation with an NSUserCancelledError. See the NSBundleResourceRequest documentation
for more information. for more information.
@ -174,39 +177,39 @@ class ProgressScene: BaseScene {
default: default:
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`. // Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
super.buttonTriggered(button) super.buttonTriggered(button: button)
} }
} }
// MARK: Convenience // MARK: Convenience
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? { func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
return backgroundNode?.childNodeWithName(identifier.rawValue) as? ButtonNode return backgroundNode?.childNode(withName: identifier.rawValue) as? ButtonNode
} }
func showDefaultState() { func showDefaultState() {
progressBarNode.hidden = false progressBarNode.isHidden = false
// Only display the "Cancel" button. // Only display the "Cancel" button.
buttonWithIdentifier(.Home)?.hidden = true button(withIdentifier: .home)?.isHidden = true
buttonWithIdentifier(.Retry)?.hidden = true button(withIdentifier: .retry)?.isHidden = true
buttonWithIdentifier(.Cancel)?.hidden = false button(withIdentifier: .cancel)?.isHidden = false
// Reset the button focus. // Reset the button focus.
resetFocus() resetFocus()
} }
func showErrorStateForError(error: NSError) { func showError(_ error: NSError) {
// A new progress object will have to be created for any subsequent loading attempts. // A new progress object will have to be created for any subsequent loading attempts.
progress = nil progress = nil
// Display "Quit" and "Retry" buttons. // Display "Quit" and "Retry" buttons.
buttonWithIdentifier(.Home)?.hidden = false button(withIdentifier: .home)?.isHidden = false
buttonWithIdentifier(.Retry)?.hidden = false button(withIdentifier: .retry)?.isHidden = false
buttonWithIdentifier(.Cancel)?.hidden = true button(withIdentifier: .cancel)?.isHidden = true
// Hide normal state. // Hide normal state.
progressBarNode.hidden = true progressBarNode.isHidden = true
progressBarNode.size.width = 0.0 progressBarNode.size.width = 0.0
// Reset the button focus. // Reset the button focus.
@ -217,11 +220,11 @@ class ProgressScene: BaseScene {
labelNode.text = NSLocalizedString("Cancelled", comment: "Displayed when the user cancels loading.") labelNode.text = NSLocalizedString("Cancelled", comment: "Displayed when the user cancels loading.")
} }
else { else {
showErrorAlert(error) showAlert(for: error)
} }
} }
func showErrorAlert(error: NSError) { func showAlert(for error: NSError) {
labelNode.text = NSLocalizedString("Failed", comment: "Displayed when the scene loader fails to load a scene.") labelNode.text = NSLocalizedString("Failed", comment: "Displayed when the scene loader fails to load a scene.")
// Display the error description in a native alert. // Display the error description in a native alert.
@ -229,15 +232,15 @@ class ProgressScene: BaseScene {
guard let window = view?.window else { fatalError("Attempting to present an error when the scene is not in a window.") } guard let window = view?.window else { fatalError("Attempting to present an error when the scene is not in a window.") }
let alert = NSAlert(error: error) let alert = NSAlert(error: error)
alert.beginSheetModalForWindow(window, completionHandler: nil) alert.beginSheetModal(for: window, completionHandler: nil)
#else #else
guard let rootViewController = view?.window?.rootViewController else { fatalError("Attempting to present an error when the scene is not in a view controller.") } guard let rootViewController = view?.window?.rootViewController else { fatalError("Attempting to present an error when the scene is not in a view controller.") }
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .Alert) let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .Default, handler: nil) let alertAction = UIAlertAction(title: "OK", style: .`default`, handler: nil)
alert.addAction(alertAction) alert.addAction(alertAction)
rootViewController.presentViewController(alert, animated: true, completion: nil) rootViewController.present(alert, animated: true, completion: nil)
#endif #endif
} }
} }

View File

@ -10,7 +10,7 @@ import GameplayKit
protocol ContactNotifiableType { protocol ContactNotifiableType {
func contactWithEntityDidBegin(entity: GKEntity) func contactWithEntityDidBegin(_ entity: GKEntity)
func contactWithEntityDidEnd(entity: GKEntity) func contactWithEntityDidEnd(_ entity: GKEntity)
} }

View File

@ -12,7 +12,7 @@ protocol ResourceLoadableType: class {
static var resourcesNeedLoading: Bool { get } static var resourcesNeedLoading: Bool { get }
/// Loads static resources into memory. /// Loads static resources into memory.
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) static func loadResources(withCompletionHandler completionHandler: @escaping () -> ())
/// Releases any static resources that can be loaded again later. /// Releases any static resources that can be loaded again later.
static func purgeResources() static func purgeResources()

View File

@ -30,7 +30,7 @@ class FuzzyTaskBotRule: GKRule {
// MARK: GPRule Overrides // MARK: GPRule Overrides
override func evaluatePredicateWithSystem(system: GKRuleSystem) -> Bool { override func evaluatePredicate(in system: GKRuleSystem) -> Bool {
snapshot = system.state["snapshot"] as! EntitySnapshot snapshot = system.state["snapshot"] as! EntitySnapshot
if grade() >= 0.0 { if grade() >= 0.0 {
@ -40,7 +40,7 @@ class FuzzyTaskBotRule: GKRule {
return false return false
} }
override func performActionWithSystem(system: GKRuleSystem) { override func performAction(in system: GKRuleSystem) {
system.assertFact(fact.rawValue, grade: grade()) system.assertFact(fact.rawValue as NSObject, grade: grade())
} }
} }

View File

@ -32,7 +32,7 @@ class LevelStateSnapshot {
/// Returns the `GKAgent2D` for a `PlayerBot` or `TaskBot`. /// Returns the `GKAgent2D` for a `PlayerBot` or `TaskBot`.
func agentForEntity(entity: GKEntity) -> GKAgent2D { func agentForEntity(entity: GKEntity) -> GKAgent2D {
if let agent = entity.componentForClass(TaskBotAgent.self) { if let agent = entity.component(ofType: TaskBotAgent.self) {
return agent return agent
} }
else if let playerBot = entity as? PlayerBot { else if let playerBot = entity as? PlayerBot {
@ -56,22 +56,16 @@ class LevelStateSnapshot {
Because we want to use the current index value from the outer loop as the seed for the inner loop, Because we want to use the current index value from the outer loop as the seed for the inner loop,
we work with the `Set` index values directly. we work with the `Set` index values directly.
*/ */
for sourceIndex in scene.entities.startIndex ..< scene.entities.endIndex { for sourceEntity in scene.entities {
let sourceIndex = scene.entities.index(of: sourceEntity)!
// Retrieve the source entity for this index.
let sourceEntity = scene.entities[sourceIndex]
// Retrieve the `GKAgent` for the source entity. // Retrieve the `GKAgent` for the source entity.
let sourceAgent = agentForEntity(sourceEntity) let sourceAgent = agentForEntity(entity: sourceEntity)
// Iterate over the remaining entities to calculate their distance from the source agent. // Iterate over the remaining entities to calculate their distance from the source agent.
for targetIndex in sourceIndex.successor() ..< scene.entities.endIndex { for targetEntity in scene.entities[scene.entities.index(after: sourceIndex) ..< scene.entities.endIndex] {
// Retrieve the target entity for this index.
let targetEntity = scene.entities[targetIndex]
// Retrieve the `GKAgent` for the target entity. // Retrieve the `GKAgent` for the target entity.
let targetAgent = agentForEntity(targetEntity) let targetAgent = agentForEntity(entity: targetEntity)
// Calculate the distance between the two agents. // Calculate the distance between the two agents.
let dx = targetAgent.position.x - sourceAgent.position.x let dx = targetAgent.position.x - sourceAgent.position.x
@ -140,7 +134,7 @@ class EntitySnapshot {
self.proximityFactor = proximityFactor self.proximityFactor = proximityFactor
// Sort the `entityDistances` array by distance (nearest first), and store the sorted version. // Sort the `entityDistances` array by distance (nearest first), and store the sorted version.
self.entityDistances = entityDistances.sort { self.entityDistances = entityDistances.sorted {
return $0.distance < $1.distance return $0.distance < $1.distance
} }
@ -152,10 +146,10 @@ class EntitySnapshot {
(if it is targetable) and the nearest "good" `TaskBot`. (if it is targetable) and the nearest "good" `TaskBot`.
*/ */
for entityDistance in self.entityDistances { for entityDistance in self.entityDistances {
if let target = entityDistance.target as? PlayerBot where playerBotTarget == nil && target.isTargetable { if let target = entityDistance.target as? PlayerBot, playerBotTarget == nil && target.isTargetable {
playerBotTarget = (target: target, distance: entityDistance.distance) playerBotTarget = (target: target, distance: entityDistance.distance)
} }
else if let target = entityDistance.target as? TaskBot where nearestGoodTaskBotTarget == nil && target.isGood { else if let target = entityDistance.target as? TaskBot, nearestGoodTaskBotTarget == nil && target.isGood {
nearestGoodTaskBotTarget = (target: target, distance: entityDistance.distance) nearestGoodTaskBotTarget = (target: target, distance: entityDistance.distance)
} }

View File

@ -22,19 +22,19 @@ import GameplayKit
enum Fact: String { enum Fact: String {
// Fuzzy rules pertaining to the proportion of "bad" bots in the level. // Fuzzy rules pertaining to the proportion of "bad" bots in the level.
case BadTaskBotPercentageLow = "BadTaskBotPercentageLow" case badTaskBotPercentageLow = "BadTaskBotPercentageLow"
case BadTaskBotPercentageMedium = "BadTaskBotPercentageMedium" case badTaskBotPercentageMedium = "BadTaskBotPercentageMedium"
case BadTaskBotPercentageHigh = "BadTaskBotPercentageHigh" case badTaskBotPercentageHigh = "BadTaskBotPercentageHigh"
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the `PlayerBot`. // Fuzzy rules pertaining to this `TaskBot`'s proximity to the `PlayerBot`.
case PlayerBotNear = "PlayerBotNear" case playerBotNear = "PlayerBotNear"
case PlayerBotMedium = "PlayerBotMedium" case playerBotMedium = "PlayerBotMedium"
case PlayerBotFar = "PlayerBotFar" case playerBotFar = "PlayerBotFar"
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the nearest "good" `TaskBot`. // Fuzzy rules pertaining to this `TaskBot`'s proximity to the nearest "good" `TaskBot`.
case GoodTaskBotNear = "GoodTaskBotNear" case goodTaskBotNear = "GoodTaskBotNear"
case GoodTaskBotMedium = "GoodTaskBotMedium" case goodTaskBotMedium = "GoodTaskBotMedium"
case GoodTaskBotFar = "GoodTaskBotFar" case goodTaskBotFar = "GoodTaskBotFar"
} }
/// Asserts whether the number of "bad" `TaskBot`s is considered "low". /// Asserts whether the number of "bad" `TaskBot`s is considered "low".
@ -47,7 +47,7 @@ class BadTaskBotPercentageLowRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageLow) } init() { super.init(fact: .badTaskBotPercentageLow) }
} }
/// Asserts whether the number of "bad" `TaskBot`s is considered "medium". /// Asserts whether the number of "bad" `TaskBot`s is considered "medium".
@ -65,7 +65,7 @@ class BadTaskBotPercentageMediumRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageMedium) } init() { super.init(fact: .badTaskBotPercentageMedium) }
} }
/// Asserts whether the number of "bad" `TaskBot`s is considered "high". /// Asserts whether the number of "bad" `TaskBot`s is considered "high".
@ -78,7 +78,7 @@ class BadTaskBotPercentageHighRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageHigh) } init() { super.init(fact: .badTaskBotPercentageHigh) }
} }
/// Asserts whether the `PlayerBot` is considered to be "near" to this `TaskBot`. /// Asserts whether the `PlayerBot` is considered to be "near" to this `TaskBot`.
@ -93,7 +93,7 @@ class PlayerBotNearRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .PlayerBotNear) } init() { super.init(fact: .playerBotNear) }
} }
/// Asserts whether the `PlayerBot` is considered to be at a "medium" distance from this `TaskBot`. /// Asserts whether the `PlayerBot` is considered to be at a "medium" distance from this `TaskBot`.
@ -108,7 +108,7 @@ class PlayerBotMediumRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .PlayerBotMedium) } init() { super.init(fact: .playerBotMedium) }
} }
/// Asserts whether the `PlayerBot` is considered to be "far" from this `TaskBot`. /// Asserts whether the `PlayerBot` is considered to be "far" from this `TaskBot`.
@ -123,7 +123,7 @@ class PlayerBotFarRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .PlayerBotFar) } init() { super.init(fact: .playerBotFar) }
} }
// MARK: TaskBot Proximity Rules // MARK: TaskBot Proximity Rules
@ -140,7 +140,7 @@ class GoodTaskBotNearRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .GoodTaskBotNear) } init() { super.init(fact: .goodTaskBotNear) }
} }
/// Asserts whether the nearest "good" `TaskBot` is considered to be at a "medium" distance from this `TaskBot`. /// Asserts whether the nearest "good" `TaskBot` is considered to be at a "medium" distance from this `TaskBot`.
@ -155,7 +155,7 @@ class GoodTaskBotMediumRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .GoodTaskBotMedium) } init() { super.init(fact: .goodTaskBotMedium) }
} }
/// Asserts whether the nearest "good" `TaskBot` is considered to be "far" from this `TaskBot`. /// Asserts whether the nearest "good" `TaskBot` is considered to be "far" from this `TaskBot`.
@ -170,5 +170,5 @@ class GoodTaskBotFarRule: FuzzyTaskBotRule {
// MARK: Initializers // MARK: Initializers
init() { super.init(fact: .GoodTaskBotFar) } init() { super.init(fact: .goodTaskBotFar) }
} }

View File

@ -14,8 +14,10 @@ import GameplayKit
The `object` property of the notification will contain the `SceneLoader`. The `object` property of the notification will contain the `SceneLoader`.
*/ */
let SceneLoaderDidCompleteNotification = "SceneLoaderDidCompleteNotification" extension NSNotification.Name {
let SceneLoaderDidFailNotification = "SceneLoaderDidFailNotification" public static let SceneLoaderDidCompleteNotification = NSNotification.Name(rawValue: "SceneLoaderDidCompleteNotification")
public static let SceneLoaderDidFailNotification = NSNotification.Name(rawValue: "SceneLoaderDidFailNotification")
}
/// A class encapsulating the work necessary to load a scene and its resources based on a given `SceneMetadata` instance. /// A class encapsulating the work necessary to load a scene and its resources based on a given `SceneMetadata` instance.
class SceneLoader { class SceneLoader {
@ -47,7 +49,7 @@ class SceneLoader {
var scene: BaseScene? var scene: BaseScene?
/// The error, if one occurs, from fetching resources. /// The error, if one occurs, from fetching resources.
var error: NSError? var error: Error?
/** /**
A parent progress, constructed when `prepareSceneForPresentation()` A parent progress, constructed when `prepareSceneForPresentation()`
@ -55,7 +57,7 @@ class SceneLoader {
`SceneLoaderDownloadingResourcesState` and `SceneLoaderPreparingResourcesState` `SceneLoaderDownloadingResourcesState` and `SceneLoaderPreparingResourcesState`
states. states.
*/ */
var progress: NSProgress? { var progress: Progress? {
didSet { didSet {
guard let progress = progress else { return } guard let progress = progress else { return }
@ -65,7 +67,7 @@ class SceneLoader {
self.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) self.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
// Notify any interested objects that the download was not completed. // Notify any interested objects that the download was not completed.
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: self) NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: self)
} }
} }
} }
@ -120,7 +122,7 @@ class SceneLoader {
the scene's resources, so bump up the quality of service of the scene's resources, so bump up the quality of service of
the operation queue that is preparing the resources. the operation queue that is preparing the resources.
*/ */
preparingState.operationQueue.qualityOfService = .UserInteractive preparingState.operationQueue.qualityOfService = .userInteractive
} }
} }
} }
@ -131,7 +133,7 @@ class SceneLoader {
self.sceneMetadata = sceneMetadata self.sceneMetadata = sceneMetadata
// Enter the initial state as soon as the scene loader is created. // Enter the initial state as soon as the scene loader is created.
stateMachine.enterState(SceneLoaderInitialState) stateMachine.enter(SceneLoaderInitialState.self)
} }
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
@ -141,10 +143,10 @@ class SceneLoader {
*/ */
func downloadResourcesIfNecessary() { func downloadResourcesIfNecessary() {
if sceneMetadata.requiresOnDemandResources { if sceneMetadata.requiresOnDemandResources {
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self) stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
} }
else { else {
stateMachine.enterState(SceneLoaderResourcesAvailableState.self) stateMachine.enter(SceneLoaderResourcesAvailableState.self)
} }
} }
#endif #endif
@ -156,20 +158,20 @@ class SceneLoader {
Note: On iOS there are two distinct steps to loading: downloading on demand resources Note: On iOS there are two distinct steps to loading: downloading on demand resources
-> loading assets into memory. -> loading assets into memory.
*/ */
func asynchronouslyLoadSceneForPresentation() -> NSProgress { func asynchronouslyLoadSceneForPresentation() -> Progress {
// If a valid progress already exists it means the scene is already being prepared. // If a valid progress already exists it means the scene is already being prepared.
if let progress = progress where !progress.cancelled { if let progress = progress , !progress.isCancelled {
return progress return progress
} }
switch stateMachine.currentState { switch stateMachine.currentState {
case is SceneLoaderResourcesReadyState: case is SceneLoaderResourcesReadyState:
// No additional work needs to be done. // No additional work needs to be done.
progress = NSProgress(totalUnitCount: 0) progress = Progress(totalUnitCount: 0)
case is SceneLoaderResourcesAvailableState: case is SceneLoaderResourcesAvailableState:
progress = NSProgress(totalUnitCount: 1) progress = Progress(totalUnitCount: 1)
/* /*
Begin preparing the scene's resources. Begin preparing the scene's resources.
@ -177,17 +179,17 @@ class SceneLoader {
The `SceneLoaderPreparingResourcesState`'s progress is added to the `SceneLoader`s The `SceneLoaderPreparingResourcesState`'s progress is added to the `SceneLoader`s
progress when the operation is started. progress when the operation is started.
*/ */
stateMachine.enterState(SceneLoaderPreparingResourcesState.self) stateMachine.enter(SceneLoaderPreparingResourcesState.self)
default: default:
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
// Set two units of progress to account for both downloading and then loading into memory. // Set two units of progress to account for both downloading and then loading into memory.
progress = NSProgress(totalUnitCount: 2) progress = Progress(totalUnitCount: 2)
let downloadingState = stateMachine.stateForClass(SceneLoaderDownloadingResourcesState)! let downloadingState = stateMachine.state(forClass: SceneLoaderDownloadingResourcesState.self)!
downloadingState.enterPreparingStateWhenFinished = true downloadingState.enterPreparingStateWhenFinished = true
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self) stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
guard let bundleResourceRequest = bundleResourceRequest else { guard let bundleResourceRequest = bundleResourceRequest else {
fatalError("In the `SceneLoaderDownloadingResourcesState`, but a valid resource request has not been created.") fatalError("In the `SceneLoaderDownloadingResourcesState`, but a valid resource request has not been created.")
@ -219,7 +221,7 @@ class SceneLoader {
progress?.cancel() progress?.cancel()
// Reset the state machine back to the initial state. // Reset the state machine back to the initial state.
stateMachine.enterState(SceneLoaderInitialState) stateMachine.enter(SceneLoaderInitialState.self)
// Unpin any on demand resources. // Unpin any on demand resources.
bundleResourceRequest = nil bundleResourceRequest = nil
@ -231,4 +233,4 @@ class SceneLoader {
error = nil error = nil
} }
#endif #endif
} }

View File

@ -21,17 +21,17 @@ class SceneLoaderDownloadFailedState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Clear the `sceneLoader`'s progress. // Clear the `sceneLoader`'s progress.
sceneLoader.progress = nil sceneLoader.progress = nil
// Notify any interested objects that the download has failed. // Notify any interested objects that the download has failed.
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: sceneLoader) NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader)
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is SceneLoaderDownloadingResourcesState.Type return stateClass is SceneLoaderDownloadingResourcesState.Type
} }
} }

View File

@ -24,15 +24,15 @@ class SceneLoaderDownloadingResourcesState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Clear any previous errors, and begin downloading the scene's resources. // Clear any previous errors, and begin downloading the scene's resources.
sceneLoader.error = nil sceneLoader.error = nil
beginDownloadingScene() beginDownloadingScene()
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is SceneLoaderDownloadFailedState.Type, is SceneLoaderResourcesAvailableState.Type, is SceneLoaderPreparingResourcesState.Type: case is SceneLoaderDownloadFailedState.Type, is SceneLoaderResourcesAvailableState.Type, is SceneLoaderPreparingResourcesState.Type:
return true return true
@ -56,10 +56,10 @@ class SceneLoaderDownloadingResourcesState: GKState {
sceneLoader.bundleResourceRequest = bundleResourceRequest sceneLoader.bundleResourceRequest = bundleResourceRequest
// Begin downloading the on demand resources. // Begin downloading the on demand resources.
bundleResourceRequest.beginAccessingResourcesWithCompletionHandler { error in bundleResourceRequest.beginAccessingResources { error in
// Progress to the next appropriate state from the main queue. // Progress to the next appropriate state from the main queue.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
if let error = error { if let error = error {
// Release the resources because we'll need to start a new request. // Release the resources because we'll need to start a new request.
bundleResourceRequest.endAccessingResources() bundleResourceRequest.endAccessingResources()
@ -67,16 +67,16 @@ class SceneLoaderDownloadingResourcesState: GKState {
// Set the error on the sceneLoader. // Set the error on the sceneLoader.
self.sceneLoader.error = error self.sceneLoader.error = error
self.stateMachine!.enterState(SceneLoaderDownloadFailedState.self) self.stateMachine!.enter(SceneLoaderDownloadFailedState.self)
} }
else if self.enterPreparingStateWhenFinished { else if self.enterPreparingStateWhenFinished {
// If requested, proceed to the preparing state immediately. // If requested, proceed to the preparing state immediately.
self.stateMachine!.enterState(SceneLoaderPreparingResourcesState.self) self.stateMachine!.enter(SceneLoaderPreparingResourcesState.self)
} }
else { else {
self.stateMachine!.enterState(SceneLoaderResourcesAvailableState.self) self.stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
} }
} }
} }
} }
} }

View File

@ -21,19 +21,19 @@ class SceneLoaderInitialState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
// Move the `stateMachine` to the available state if no on-demand resources are required. // Move the `stateMachine` to the available state if no on-demand resources are required.
if !sceneLoader.sceneMetadata.requiresOnDemandResources { if !sceneLoader.sceneMetadata.requiresOnDemandResources {
stateMachine!.enterState(SceneLoaderResourcesAvailableState.self) stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
} }
#elseif os(OSX) #elseif os(OSX)
// On OS X the resources will always be in local storage available for download. // On OS X the resources will always be in local storage available for download.
stateMachine!.enterState(SceneLoaderResourcesAvailableState.self) _ = stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
#endif #endif
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
if stateClass is SceneLoaderDownloadingResourcesState.Type { if stateClass is SceneLoaderDownloadingResourcesState.Type {
return true return true
@ -42,4 +42,4 @@ class SceneLoaderInitialState: GKState {
return stateClass is SceneLoaderResourcesAvailableState.Type return stateClass is SceneLoaderResourcesAvailableState.Type
} }
} }

View File

@ -14,13 +14,13 @@ class SceneLoaderPreparingResourcesState: GKState {
unowned let sceneLoader: SceneLoader unowned let sceneLoader: SceneLoader
/// An internal operation queue for loading scene resources in the background. /// An internal operation queue for loading scene resources in the background.
let operationQueue = NSOperationQueue() let operationQueue = OperationQueue()
/** /**
An NSProgress object that can be used to query and monitor progress of An NSProgress object that can be used to query and monitor progress of
the resources being loaded. Also supports cancellation. the resources being loaded. Also supports cancellation.
*/ */
var progress: NSProgress? { var progress: Progress? {
didSet { didSet {
guard let progress = progress else { return } guard let progress = progress else { return }
@ -47,19 +47,19 @@ class SceneLoaderPreparingResourcesState: GKState {
state machine. Setting the `qualityOfService` as `.Utility` reflects the state machine. Setting the `qualityOfService` as `.Utility` reflects the
fact that this is an important task, but is not blocking the user. fact that this is an important task, but is not blocking the user.
*/ */
operationQueue.qualityOfService = .Utility operationQueue.qualityOfService = .utility
} }
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Begin loading the scene and associated resources in the background. // Begin loading the scene and associated resources in the background.
loadResourcesAsynchronously() loadResourcesAsynchronously()
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
// Only valid if the `sceneLoader`'s scene has been loaded. // Only valid if the `sceneLoader`'s scene has been loaded.
case is SceneLoaderResourcesReadyState.Type where sceneLoader.scene != nil: case is SceneLoaderResourcesReadyState.Type where sceneLoader.scene != nil:
@ -90,7 +90,7 @@ class SceneLoaderPreparingResourcesState: GKState {
Create an `NSProgress` object with the total unit count equal to the number of entities that Create an `NSProgress` object with the total unit count equal to the number of entities that
need to be loaded plus a unit for loading the scene itself. need to be loaded plus a unit for loading the scene itself.
*/ */
let loadingProgress = NSProgress(totalUnitCount: sceneMetadata.loadableTypes.count + 1) let loadingProgress = Progress(totalUnitCount: sceneMetadata.loadableTypes.count + 1)
// Add the `SceneLoaderPreparingResourcesState`'s progress to the overall `sceneLoader`'s progress. // Add the `SceneLoaderPreparingResourcesState`'s progress to the overall `sceneLoader`'s progress.
sceneLoader.progress?.addChild(loadingProgress, withPendingUnitCount: 1) sceneLoader.progress?.addChild(loadingProgress, withPendingUnitCount: 1)
@ -105,10 +105,10 @@ class SceneLoaderPreparingResourcesState: GKState {
loadSceneOperation.completionBlock = { [unowned self] in loadSceneOperation.completionBlock = { [unowned self] in
// Enter the next state on the main queue. // Enter the next state on the main queue.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.sceneLoader.scene = loadSceneOperation.scene self.sceneLoader.scene = loadSceneOperation.scene
let didEnterReadyState = self.stateMachine!.enterState(SceneLoaderResourcesReadyState.self) let didEnterReadyState = self.stateMachine!.enter(SceneLoaderResourcesReadyState.self)
assert(didEnterReadyState, "Failed to transition to `ReadyState` after resources were prepared.") assert(didEnterReadyState, "Failed to transition to `ReadyState` after resources were prepared.")
} }
} }
@ -142,11 +142,11 @@ class SceneLoaderPreparingResourcesState: GKState {
sceneLoader.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) sceneLoader.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
// Enter the next state on the main queue. // Enter the next state on the main queue.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.stateMachine!.enterState(SceneLoaderResourcesAvailableState.self) self.stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
// Notify that loading was not completed. // Notify that loading was not completed.
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: self.sceneLoader) NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: self.sceneLoader)
} }
} }
} }

View File

@ -21,7 +21,7 @@ class SceneLoaderResourcesAvailableState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is SceneLoaderInitialState.Type, is SceneLoaderPreparingResourcesState.Type: case is SceneLoaderInitialState.Type, is SceneLoaderPreparingResourcesState.Type:
return true return true
@ -31,4 +31,4 @@ class SceneLoaderResourcesAvailableState: GKState {
} }
} }
} }

View File

@ -21,17 +21,17 @@ class SceneLoaderResourcesReadyState: GKState {
// MARK: GKState Life Cycle // MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) { override func didEnter(from previousState: GKState?) {
super.didEnterWithPreviousState(previousState) super.didEnter(from: previousState)
// Clear the `sceneLoader`'s progress as loading is complete. // Clear the `sceneLoader`'s progress as loading is complete.
sceneLoader.progress = nil sceneLoader.progress = nil
// Notify to any interested objects that the download has completed. // Notify to any interested objects that the download has completed.
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidCompleteNotification, object: sceneLoader) NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidCompleteNotification, object: sceneLoader)
} }
override func isValidNextState(stateClass: AnyClass) -> Bool { override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass { switch stateClass {
case is SceneLoaderResourcesAvailableState.Type, is SceneLoaderInitialState.Type: case is SceneLoaderResourcesAvailableState.Type, is SceneLoaderInitialState.Type:
return true return true
@ -41,8 +41,8 @@ class SceneLoaderResourcesReadyState: GKState {
} }
} }
override func willExitWithNextState(nextState: GKState) { override func willExit(to nextState: GKState) {
super.willExitWithNextState(nextState) super.willExit(to: nextState)
/* /*
Presenting the scene is a one shot operation. Clear the scene when Presenting the scene is a one shot operation. Clear the scene when
@ -50,4 +50,4 @@ class SceneLoaderResourcesReadyState: GKState {
*/ */
sceneLoader.scene = nil sceneLoader.scene = nil
} }
} }

View File

@ -10,7 +10,7 @@ import SpriteKit
protocol SceneManagerDelegate: class { protocol SceneManagerDelegate: class {
// Called whenever a scene manager has transitioned to a new scene. // Called whenever a scene manager has transitioned to a new scene.
func sceneManagerDidTransitionToScene(scene: SKScene) func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene)
} }
/** /**
@ -21,9 +21,9 @@ final class SceneManager {
// MARK: Types // MARK: Types
enum SceneIdentifier { enum SceneIdentifier {
case Home, End case home, end
case CurrentLevel, NextLevel case currentLevel, nextLevel
case Level(Int) case level(Int)
} }
// MARK: Properties // MARK: Properties
@ -49,7 +49,7 @@ final class SceneManager {
// If there is no current scene, we can only transition back to the home scene. // If there is no current scene, we can only transition back to the home scene.
guard let currentSceneMetadata = currentSceneMetadata else { return homeScene } guard let currentSceneMetadata = currentSceneMetadata else { return homeScene }
let index = sceneConfigurationInfo.indexOf(currentSceneMetadata)! let index = sceneConfigurationInfo.index(of: currentSceneMetadata)!
if index + 1 < sceneConfigurationInfo.count { if index + 1 < sceneConfigurationInfo.count {
// Return the metadata for the next scene in the array. // Return the metadata for the next scene in the array.
@ -85,8 +85,8 @@ final class SceneManager {
Load the game's `SceneConfiguration` plist. This provides information Load the game's `SceneConfiguration` plist. This provides information
about every scene in the game, and the order in which they should be displayed. about every scene in the game, and the order in which they should be displayed.
*/ */
let url = NSBundle.mainBundle().URLForResource("SceneConfiguration", withExtension: "plist")! let url = Bundle.main.url(forResource: "SceneConfiguration", withExtension: "plist")!
let scenes = NSArray(contentsOfURL: url) as! [[String: AnyObject]] let scenes = NSArray(contentsOf: url) as! [[String: AnyObject]]
/* /*
Extract the configuration info dictionary for each possible scene, Extract the configuration info dictionary for each possible scene,
@ -116,7 +116,7 @@ final class SceneManager {
deinit { deinit {
// Unregister for `SceneLoader` notifications if the observer is still around. // Unregister for `SceneLoader` notifications if the observer is still around.
if let loadingCompletedObserver = loadingCompletedObserver { if let loadingCompletedObserver = loadingCompletedObserver {
NSNotificationCenter.defaultCenter().removeObserver(loadingCompletedObserver, name: SceneLoaderDidCompleteNotification, object: nil) NotificationCenter.default.removeObserver(loadingCompletedObserver, name: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil)
} }
} }
@ -129,9 +129,9 @@ final class SceneManager {
This method should be called in preparation for the user needing to transition This method should be called in preparation for the user needing to transition
to the scene in order to minimize the amount of load time. to the scene in order to minimize the amount of load time.
*/ */
func prepareSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) { func prepareScene(identifier sceneIdentifier: SceneIdentifier) {
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier) let loader = sceneLoader(forSceneIdentifier: sceneIdentifier)
sceneLoader.asynchronouslyLoadSceneForPresentation() _ = loader.asynchronouslyLoadSceneForPresentation()
} }
/** /**
@ -139,26 +139,25 @@ final class SceneManager {
currently in memory. Otherwise, presents a progress scene to monitor the progress currently in memory. Otherwise, presents a progress scene to monitor the progress
of the resources being downloaded, or display an error if one has occurred. of the resources being downloaded, or display an error if one has occurred.
*/ */
func transitionToSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) { func transitionToScene(identifier sceneIdentifier: SceneIdentifier) {
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier) let loader = self.sceneLoader(forSceneIdentifier: sceneIdentifier)
if loader.stateMachine.currentState is SceneLoaderResourcesReadyState {
if sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState {
// The scene is ready to be displayed. // The scene is ready to be displayed.
presentSceneForSceneLoader(sceneLoader) presentScene(for: loader)
} }
else { else {
sceneLoader.asynchronouslyLoadSceneForPresentation() _ = loader.asynchronouslyLoadSceneForPresentation()
/* /*
Mark the `sceneLoader` as `requestedForPresentation` to automatically Mark the `sceneLoader` as `requestedForPresentation` to automatically
present the scene when loading completes. present the scene when loading completes.
*/ */
sceneLoader.requestedForPresentation = true loader.requestedForPresentation = true
// The scene requires a progress scene to be displayed while its resources are prepared. // The scene requires a progress scene to be displayed while its resources are prepared.
if sceneLoader.requiresProgressSceneForPreparing { if loader.requiresProgressSceneForPreparing {
presentProgressScene(sceneLoader) presentProgressScene(for: loader)
} }
} }
} }
@ -166,17 +165,17 @@ final class SceneManager {
// MARK: Scene Presentation // MARK: Scene Presentation
/// Configures and presents a scene. /// Configures and presents a scene.
func presentSceneForSceneLoader(sceneLoader: SceneLoader) { func presentScene(for loader: SceneLoader) {
guard let scene = sceneLoader.scene else { guard let scene = loader.scene else {
assertionFailure("Requested presentation for a `sceneLoader` without a valid `scene`.") assertionFailure("Requested presentation for a `sceneLoader` without a valid `scene`.")
return return
} }
// Hold on to a reference to the currently requested scene's metadata. // Hold on to a reference to the currently requested scene's metadata.
currentSceneMetadata = sceneLoader.sceneMetadata currentSceneMetadata = loader.sceneMetadata
// Ensure we present the scene on the main queue. // Ensure we present the scene on the main queue.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
/* /*
Provide the scene with a reference to the `SceneLoadingManger` Provide the scene with a reference to the `SceneLoadingManger`
so that it can coordinate the next scene that should be loaded. so that it can coordinate the next scene that should be loaded.
@ -184,7 +183,7 @@ final class SceneManager {
scene.sceneManager = self scene.sceneManager = self
// Present the scene with a transition. // Present the scene with a transition.
let transition = SKTransition.fadeWithDuration(GameplayConfiguration.SceneManager.transitionDuration) let transition = SKTransition.fade(withDuration: GameplayConfiguration.SceneManager.transitionDuration)
self.presentingView.presentScene(scene, transition: transition) self.presentingView.presentScene(scene, transition: transition)
/* /*
@ -199,23 +198,23 @@ final class SceneManager {
self.progressScene = nil self.progressScene = nil
// Notify the delegate that the manager has presented a scene. // Notify the delegate that the manager has presented a scene.
self.delegate?.sceneManagerDidTransitionToScene(scene) self.delegate?.sceneManager(self, didTransitionTo: scene)
// Restart the scene loading process. // Restart the scene loading process.
sceneLoader.stateMachine.enterState(SceneLoaderInitialState.self) loader.stateMachine.enter(SceneLoaderInitialState.self)
} }
} }
/// Configures the progress scene to show the progress of the `sceneLoader`. /// Configures the progress scene to show the progress of the `sceneLoader`.
func presentProgressScene(sceneLoader: SceneLoader) { func presentProgressScene(for loader: SceneLoader) {
// If the `progressScene` is already being displayed, there's nothing to do. // If the `progressScene` is already being displayed, there's nothing to do.
guard progressScene == nil else { return } guard progressScene == nil else { return }
// Create a `ProgressScene` for the scene loader. // Create a `ProgressScene` for the scene loader.
progressScene = ProgressScene.progressSceneWithSceneLoader(sceneLoader) progressScene = ProgressScene.progressScene(withSceneLoader: loader)
progressScene!.sceneManager = self progressScene!.sceneManager = self
let transition = SKTransition.doorsCloseHorizontalWithDuration(GameplayConfiguration.SceneManager.progressSceneTransitionDuration) let transition = SKTransition.doorsCloseHorizontal(withDuration: GameplayConfiguration.SceneManager.progressSceneTransitionDuration)
presentingView.presentScene(progressScene!, transition: transition) presentingView.presentScene(progressScene!, transition: transition)
} }
@ -233,9 +232,9 @@ final class SceneManager {
} }
// Clean up scenes that are no longer accessible. // Clean up scenes that are no longer accessible.
let allScenes = Set(sceneLoaderForMetadata.keys) var unreachableScenes = Set(sceneLoaderForMetadata.keys)
let unreachableScenes = allScenes.subtract(possibleScenes) unreachableScenes.subtract(possibleScenes)
for sceneMetadata in unreachableScenes { for sceneMetadata in unreachableScenes {
let resourceRequest = sceneLoaderForMetadata[sceneMetadata]! let resourceRequest = sceneLoaderForMetadata[sceneMetadata]!
resourceRequest.purgeResources() resourceRequest.purgeResources()
@ -269,11 +268,11 @@ final class SceneManager {
// Avoid reregistering for the notification. // Avoid reregistering for the notification.
guard loadingCompletedObserver == nil else { return } guard loadingCompletedObserver == nil else { return }
loadingCompletedObserver = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidCompleteNotification, object: nil, queue: NSOperationQueue.mainQueue()) { [unowned self] notification in loadingCompletedObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil, queue: OperationQueue.main) { [unowned self] notification in
let sceneLoader = notification.object as! SceneLoader let sceneLoader = notification.object as! SceneLoader
// Ensure this is a `sceneLoader` managed by this `SceneManager`. // Ensure this is a `sceneLoader` managed by this `SceneManager`.
guard let managedSceneLoader = self.sceneLoaderForMetadata[sceneLoader.sceneMetadata] where managedSceneLoader === sceneLoader else { return } guard let managedSceneLoader = self.sceneLoaderForMetadata[sceneLoader.sceneMetadata], managedSceneLoader === sceneLoader else { return }
guard sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState else { guard sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState else {
fatalError("Received complete notification, but the `stateMachine`'s current state is not ready.") fatalError("Received complete notification, but the `stateMachine`'s current state is not ready.")
@ -287,7 +286,7 @@ final class SceneManager {
a progress scene. a progress scene.
*/ */
if sceneLoader.requestedForPresentation { if sceneLoader.requestedForPresentation {
self.presentSceneForSceneLoader(sceneLoader) self.presentScene(for: sceneLoader)
} }
// Reset the scene loader's presentation preference. // Reset the scene loader's presentation preference.
@ -298,25 +297,25 @@ final class SceneManager {
// MARK: Convenience // MARK: Convenience
/// Returns the scene loader associated with the scene identifier. /// Returns the scene loader associated with the scene identifier.
func sceneLoaderForSceneIdentifier(sceneIdentifier: SceneIdentifier) -> SceneLoader { func sceneLoader(forSceneIdentifier sceneIdentifier: SceneIdentifier) -> SceneLoader {
let sceneMetadata: SceneMetadata let sceneMetadata: SceneMetadata
switch sceneIdentifier { switch sceneIdentifier {
case .Home: case .home:
sceneMetadata = sceneConfigurationInfo.first! sceneMetadata = sceneConfigurationInfo.first!
case .CurrentLevel: case .currentLevel:
guard let currentSceneMetadata = currentSceneMetadata else { guard let currentSceneMetadata = currentSceneMetadata else {
fatalError("Current scene doesn't exist.") fatalError("Current scene doesn't exist.")
} }
sceneMetadata = currentSceneMetadata sceneMetadata = currentSceneMetadata
case .Level(let number): case .level(let number):
sceneMetadata = sceneConfigurationInfo[number] sceneMetadata = sceneConfigurationInfo[number]
case .NextLevel: case .nextLevel:
sceneMetadata = nextSceneMetadata sceneMetadata = nextSceneMetadata
case .End: case .end:
sceneMetadata = sceneConfigurationInfo.last! sceneMetadata = sceneConfigurationInfo.last!
} }

View File

@ -9,7 +9,7 @@ A subclass of `NSOperation` that maps the different states of an `NSOperation`
import Foundation import Foundation
class Operation: NSOperation { class SceneOperation: Operation {
// MARK: Types // MARK: Types
/** /**
@ -18,35 +18,35 @@ class Operation: NSOperation {
*/ */
@objc enum State: Int { @objc enum State: Int {
/// The `Operation` is ready to begin execution. /// The `Operation` is ready to begin execution.
case Ready case ready
/// The `Operation` is executing. /// The `Operation` is executing.
case Executing case executing
/// The `Operation` has finished executing. /// The `Operation` has finished executing.
case Finished case finished
/// The `Operation` has been cancelled. /// The `Operation` has been cancelled.
case Cancelled case cancelled
} }
// MARK: Properties // MARK: Properties
/// Marking `state` as dynamic allows this property to be key-value observed. /// Marking `state` as dynamic allows this property to be key-value observed.
dynamic var state = State.Ready dynamic var state = State.ready
// MARK: NSOperation // MARK: NSOperation
override var executing: Bool { override var isExecuting: Bool {
return state == .Executing return state == .executing
} }
override var finished: Bool { override var isFinished: Bool {
return state == .Finished return state == .finished
} }
override var cancelled: Bool { override var isCancelled: Bool {
return state == .Cancelled return state == .cancelled
} }
/** /**

View File

@ -22,7 +22,7 @@ class SceneOverlay {
init(overlaySceneFileName fileName: String, zPosition: CGFloat) { init(overlaySceneFileName fileName: String, zPosition: CGFloat) {
// Load the scene and get the overlay node from it. // Load the scene and get the overlay node from it.
let overlayScene = SKScene(fileNamed: fileName)! let overlayScene = SKScene(fileNamed: fileName)!
let contentTemplateNode = overlayScene.childNodeWithName("Overlay") as! SKSpriteNode let contentTemplateNode = overlayScene.childNode(withName: "Overlay") as! SKSpriteNode
// Create a background node with the same color as the template. // Create a background node with the same color as the template.
backgroundNode = SKSpriteNode(color: contentTemplateNode.color, size: contentTemplateNode.size) backgroundNode = SKSpriteNode(color: contentTemplateNode.color, size: contentTemplateNode.size)
@ -34,7 +34,7 @@ class SceneOverlay {
backgroundNode.addChild(contentNode) backgroundNode.addChild(contentNode)
// Set the content node to a clear color to allow the background node to be seen through it. // Set the content node to a clear color to allow the background node to be seen through it.
contentNode.color = .clearColor() contentNode.color = .clear
// Store the current size of the content to allow it to be scaled correctly. // Store the current size of the content to allow it to be scaled correctly.
nativeContentSize = contentNode.size nativeContentSize = contentNode.size

View File

@ -32,8 +32,8 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
let centerDividerWidth: CGFloat let centerDividerWidth: CGFloat
var hideThumbStickNodes: Bool = false { var hideThumbStickNodes: Bool = false {
didSet { didSet {
leftThumbStickNode.hidden = hideThumbStickNodes leftThumbStickNode.isHidden = hideThumbStickNodes
rightThumbStickNode.hidden = hideThumbStickNodes rightThumbStickNode.isHidden = hideThumbStickNodes
} }
} }
@ -59,10 +59,10 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
// Setup pause button. // Setup pause button.
let buttonSize = CGSize(width: frame.height / 4, height: frame.height / 4) let buttonSize = CGSize(width: frame.height / 4, height: frame.height / 4)
pauseButton = SKSpriteNode(texture: nil, color: UIColor.clearColor(), size: buttonSize) pauseButton = SKSpriteNode(texture: nil, color: UIColor.clear, size: buttonSize)
pauseButton.position = CGPoint(x: 0, y: frame.height / 2) pauseButton.position = CGPoint(x: 0, y: frame.height / 2)
super.init(texture: nil, color: UIColor.clearColor(), size: frame.size) super.init(texture: nil, color: UIColor.clear, size: frame.size)
rightThumbStickNode.delegate = self rightThumbStickNode.delegate = self
leftThumbStickNode.delegate = self leftThumbStickNode.delegate = self
@ -74,7 +74,7 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
A `TouchControlInputNode` is designed to receive all user interaction A `TouchControlInputNode` is designed to receive all user interaction
and forwards it along to the child nodes. and forwards it along to the child nodes.
*/ */
userInteractionEnabled = true isUserInteractionEnabled = true
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -121,11 +121,11 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
// MARK: UIResponder // MARK: UIResponder
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, withEvent: event) super.touchesBegan(touches, with: event)
for touch in touches { for touch in touches {
let touchPoint = touch.locationInNode(self) let touchPoint = touch.location(in: self)
/* /*
Ignore touches if the thumb stick controls are hidden, or if Ignore touches if the thumb stick controls are hidden, or if
@ -137,20 +137,20 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
} }
if touchPoint.x < 0 { if touchPoint.x < 0 {
leftControlTouches.unionInPlace([touch]) leftControlTouches.formUnion([touch])
leftThumbStickNode.position = pointByCheckingControlOffset(touchPoint) leftThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
leftThumbStickNode.touchesBegan([touch], withEvent: event) leftThumbStickNode.touchesBegan([touch], with: event)
} }
else { else {
rightControlTouches.unionInPlace([touch]) rightControlTouches.formUnion([touch])
rightThumbStickNode.position = pointByCheckingControlOffset(touchPoint) rightThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
rightThumbStickNode.touchesBegan([touch], withEvent: event) rightThumbStickNode.touchesBegan([touch], with: event)
} }
} }
} }
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, withEvent: event) super.touchesMoved(touches, with: event)
/* /*
If the touch pertains to a `thumbStickNode`, pass the If the touch pertains to a `thumbStickNode`, pass the
touch along to be handled. touch along to be handled.
@ -160,44 +160,44 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
over the the `rightThumbStickNode`s zone or vice versa, over the the `rightThumbStickNode`s zone or vice versa,
while ensuring it is handled by the correct thumb stick. while ensuring it is handled by the correct thumb stick.
*/ */
let movedLeftTouches = touches.intersect(leftControlTouches) let movedLeftTouches = touches.intersection(leftControlTouches)
leftThumbStickNode.touchesMoved(movedLeftTouches, withEvent: event) leftThumbStickNode.touchesMoved(movedLeftTouches, with: event)
let movedRightTouches = touches.intersect(rightControlTouches) let movedRightTouches = touches.intersection(rightControlTouches)
rightThumbStickNode.touchesMoved(movedRightTouches, withEvent: event) rightThumbStickNode.touchesMoved(movedRightTouches, with: event)
} }
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, withEvent: event) super.touchesEnded(touches, with: event)
for touch in touches { for touch in touches {
let touchPoint = touch.locationInNode(self) let touchPoint = touch.location(in: self)
/// Toggle pause when touching in the pause node. /// Toggle pause when touching in the pause node.
if pauseButton === nodeAtPoint(touchPoint) { if pauseButton === atPoint(touchPoint) {
gameStateDelegate?.controlInputSourceDidTogglePauseState(self) gameStateDelegate?.controlInputSourceDidTogglePauseState(self)
break break
} }
} }
let endedLeftTouches = touches.intersect(leftControlTouches) let endedLeftTouches = touches.intersection(leftControlTouches)
leftThumbStickNode.touchesEnded(endedLeftTouches, withEvent: event) leftThumbStickNode.touchesEnded(endedLeftTouches, with: event)
leftControlTouches.subtractInPlace(endedLeftTouches) leftControlTouches.subtract(endedLeftTouches)
let endedRightTouches = touches.intersect(rightControlTouches) let endedRightTouches = touches.intersection(rightControlTouches)
rightThumbStickNode.touchesEnded(endedRightTouches, withEvent: event) rightThumbStickNode.touchesEnded(endedRightTouches, with: event)
rightControlTouches.subtractInPlace(endedRightTouches) rightControlTouches.subtract(endedRightTouches)
} }
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event) super.touchesCancelled(touches!, with: event)
leftThumbStickNode.resetTouchPad() leftThumbStickNode.resetTouchPad()
rightThumbStickNode.resetTouchPad() rightThumbStickNode.resetTouchPad()
// Keep the set's capacity, because roughly the same number of touch events are being received. // Keep the set's capacity, because roughly the same number of touch events are being received.
leftControlTouches.removeAll(keepCapacity: true) leftControlTouches.removeAll(keepingCapacity: true)
rightControlTouches.removeAll(keepCapacity: true) rightControlTouches.removeAll(keepingCapacity: true)
} }
// MARK: Convenience Methods // MARK: Convenience Methods
@ -228,4 +228,4 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
return CGPoint(x: boundX, y: boundY) return CGPoint(x: boundX, y: boundY)
} }
} }

View File

@ -1,5 +1,5 @@
Sample code project: DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit Sample code project: DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
Version: 2.2 Version: 2.3
IMPORTANT: This Apple software is supplied to you by Apple IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following Inc. ("Apple") in consideration of your agreement to the following

View File

@ -1,8 +1,8 @@
# DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit # DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
DemoBots is a fully-featured 2D game built with SpriteKit and GameplayKit, and written in Swift 2.0. It demonstrates how to use agents, goals, and behaviors to drive the movement of characters in your game, and how to use rule systems and state machines to provide those characters with intelligent behavior. You'll see how to integrate on-demand resources into a game to optimize resource usage and reduce the time needed to download additional levels. DemoBots is a fully-featured 2D game built with SpriteKit and GameplayKit, and written in Swift. It demonstrates how to use agents, goals, and behaviors to drive the movement of characters in your game, and how to use rule systems and state machines to provide those characters with intelligent behavior. You'll see how to integrate on-demand resources into a game to optimize resource usage and reduce the time needed to download additional levels.
DemoBots takes advantage of the Xcode 7 scene and actions editor to create detailed level designs and animations. The sample also contains assets tailored to ensure the best experience on every supported device. DemoBots takes advantage of the Xcode scene and actions editor to create detailed level designs and animations. The sample also contains assets tailored to ensure the best experience on every supported device.
## Release Note ## Release Note
@ -12,11 +12,11 @@ DemoBots takes advantage of the Xcode 7 scene and actions editor to create detai
### Build ### Build
Xcode 7.3, OS X 10.11 SDK, iOS 9.0 SDK, tvOS 9.0 SDK Xcode 8.0, OS X 10.12 SDK, iOS 10.0 SDK, tvOS 10.0 SDK
### Runtime ### Runtime
OS X 10.11, iOS 9.0, tvOS 9.0 OS X 10.12, iOS 10.0, tvOS 10.0
## About DemoBots ## About DemoBots