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
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@ -27,6 +27,6 @@ class GameViewController: NSViewController {
let skView = view as! SKView
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
func windowWillStartLiveResize(notification: NSNotification) {
func windowWillStartLiveResize(_ notification: Notification) {
// 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 {
levelScene.paused = true
if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
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.
if let levelScene = view.scene as? LevelScene where levelScene.stateMachine.currentState is LevelSceneActiveState {
levelScene.paused = false
if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
levelScene.isPaused = false
}
}

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<?xml version="1.0" encoding="UTF-8"?>
<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>
<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="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
@ -16,25 +18,20 @@
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
</layoutGuides>
<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"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
<rect key="frame" x="20" y="0.0" width="560" height="115"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<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"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
</constraints>
</imageView>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<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"/>
@ -52,6 +49,6 @@
</scene>
</scenes>
<resources>
<image name="DemoBotsLogo" width="387" height="248"/>
<image name="DemoBotsLogo" width="904" height="579"/>
</resources>
</document>

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<?xml version="1.0" encoding="UTF-8"?>
<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>
<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="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Game View Controller-->
@ -16,25 +18,20 @@
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
</layoutGuides>
<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"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
<rect key="frame" x="20" y="0.0" width="560" height="115"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<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"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
</constraints>
</imageView>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<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"/>
@ -55,6 +52,6 @@
</scene>
</scenes>
<resources>
<image name="DemoBotsLogo" width="387" height="248"/>
<image name="DemoBotsLogo" width="904" height="579"/>
</resources>
</document>

View File

@ -39,23 +39,22 @@ class GameViewController: UIViewController, SceneManagerDelegate {
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
sceneManager.delegate = self
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
sceneManager.transitionToScene(identifier: .home)
}
// Hide status bar during game play.
override func prefersStatusBarHidden() -> Bool {
override var prefersStatusBarHidden: Bool {
return true
}
// 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.
UIView.animateWithDuration(0.2, delay: 0.0, options: [], animations: {
UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
self.logoView.alpha = 0.0
}, 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.delegate = self
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
sceneManager.transitionToScene(identifier: .home)
}
// MARK: SceneManagerDelegate
func sceneManagerDidTransitionToScene(scene: SKScene) {
func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene) {
/*
When transitioning to the `HomeEndScene` set
`controllerUserInteractionEnabled` to `true` to allow the

View File

@ -73,7 +73,6 @@
0A38AC181B73BE0000AE9C43 /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEEA9B61AE1E3C300C519E7 /* GeometryExtensions.swift */; };
0A38AC191B73BE0500AE9C43 /* BeamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC4A091AF263C700ABC4A6 /* BeamNode.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 */; };
0A38AC1F1B73BE0A00AE9C43 /* ContactNotifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5FB611B1B7E3D007D0C86 /* ContactNotifiableType.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 */; };
0A38AC2B1B73BE2400AE9C43 /* SceneLoaderResourcesReadyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.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 */; };
0A38AC2F1B73BE2400AE9C43 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
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 */; };
0A73CB431B1CE6AE0030BB13 /* 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 */; };
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; };
0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
0A788CD51B6AD483006B3799 /* 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 */; };
@ -192,8 +191,6 @@
16D800131AF4254F00979C5F /* TaskBotBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */; };
16E3D95A1AA4F3B80001E9D1 /* 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 */; };
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.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>"; };
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>"; };
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>"; };
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>"; };
@ -385,7 +382,6 @@
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>"; };
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>"; };
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>"; };
@ -539,7 +535,7 @@
0A724CDF1B17949200F64132 /* SceneLoader.swift */,
0A724CE51B17C53000F64132 /* SceneLoader States */,
0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */,
0A788CD11B6AC873006B3799 /* Operation.swift */,
0A788CD11B6AC873006B3799 /* SceneOperation.swift */,
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */,
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */,
);
@ -608,7 +604,6 @@
B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */,
B5B2DA681BE0DD1A00DF201F /* ButtonNode.swift */,
168482F61AA66D2400EAB4C0 /* ChargeBar.swift */,
16F685FB1A9FF32800B28C12 /* EntityNode.swift */,
B5B2DA6C1BE0DD4200DF201F /* ThumbStickNode.swift */,
);
path = Nodes;
@ -951,8 +946,9 @@
Level2,
Level3,
);
LastSwiftMigration = 0800;
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Apple, Inc.";
TargetAttributes = {
0A38ABAF1B73013F00AE9C43 = {
@ -1131,7 +1127,6 @@
B5B2DA671BE0DD0400DF201F /* ProgressScene.swift in Sources */,
0A38AC0F1B73BDF800AE9C43 /* CompassDirection.swift in Sources */,
0A38AC121B73BDF800AE9C43 /* RulesComponent.swift in Sources */,
0A38AC1C1B73BE0500AE9C43 /* EntityNode.swift in Sources */,
0A38AC231B73BE1D00AE9C43 /* SceneManager.swift in Sources */,
B5B2DA6E1BE0DD4900DF201F /* ThumbStickNode.swift in Sources */,
0A38AC031B73BDF300AE9C43 /* PlayerBotHitState.swift in Sources */,
@ -1148,7 +1143,7 @@
0A38ABFB1B73BDEB00AE9C43 /* BeamComponent.swift in Sources */,
0A18365B1B82B11900177830 /* LevelScene+Pause.swift in Sources */,
0A38AC211B73BE1400AE9C43 /* ControlInputSource.swift in Sources */,
0A38AC2D1B73BE2400AE9C43 /* Operation.swift in Sources */,
0A38AC2D1B73BE2400AE9C43 /* SceneOperation.swift in Sources */,
0A38AC101B73BDF800AE9C43 /* PhysicsComponent.swift in Sources */,
0A38AC111B73BDF800AE9C43 /* RenderComponent.swift in Sources */,
0A38AC0C1B73BDF800AE9C43 /* TaskBotZappedState.swift in Sources */,
@ -1221,7 +1216,7 @@
B5B2DA831BE0DDF200DF201F /* GameWindowController.swift in Sources */,
B545AF1E1AF0F422002BC931 /* BeamComponent.swift in Sources */,
0A6F773E1B02EA060091C645 /* SceneManager.swift in Sources */,
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */,
0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */,
B5CAD52D1AEBAE9D00D9B7A9 /* FlyingBotBlastState.swift in Sources */,
16873A0C1A9E6DC2003FB425 /* RenderComponent.swift in Sources */,
B52D2DFB1AFD11E000469E3B /* PlayerBotAppearState.swift in Sources */,
@ -1245,7 +1240,6 @@
0ADCE3381B1BE3F200E85CEB /* ResourceLoadableType.swift in Sources */,
16109B641AF54D3800A780AE /* FuzzyTaskBotRule.swift in Sources */,
0A255E2B1B5EB9EF0085D218 /* BaseScene+Focus.swift in Sources */,
16F685FD1A9FF32800B28C12 /* EntityNode.swift in Sources */,
16D8000D1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
0ADAB8C91B1660E700F98E5A /* SceneMetadata.swift in Sources */,
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */,
@ -1284,7 +1278,6 @@
3E91BAC41A89755500CE240A /* LevelScene.swift in Sources */,
B5CAD5251AEBA48E00D9B7A9 /* FlyingBot.swift in Sources */,
B545AF1D1AF0F418002BC931 /* BeamComponent.swift in Sources */,
16F685FC1A9FF32800B28C12 /* EntityNode.swift in Sources */,
16D8000C1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */,
55A24EF41AF033C70039A2DB /* LevelConfiguration.swift in Sources */,
@ -1316,7 +1309,7 @@
B54A81591B15140A00D33DF8 /* SceneOverlay.swift in Sources */,
B543D7DD1B1B976000F0AA9D /* OrientationComponent.swift in Sources */,
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */,
0A788CD21B6AC873006B3799 /* Operation.swift in Sources */,
0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */,
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */,
0A2619A91B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift in Sources */,
3E8513071AA0BEA800AF1B97 /* TouchControlInputNode.swift in Sources */,
@ -1387,7 +1380,10 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1396,8 +1392,9 @@
PRODUCT_MODULE_NAME = DemoBots;
PRODUCT_NAME = DemoBots;
SDKROOT = appletvos;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
TVOS_DEPLOYMENT_TARGET = 10.0;
};
name = Debug;
};
@ -1406,6 +1403,9 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1414,8 +1414,9 @@
PRODUCT_MODULE_NAME = DemoBots;
PRODUCT_NAME = DemoBots;
SDKROOT = appletvos;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
TVOS_DEPLOYMENT_TARGET = 10.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -1424,6 +1425,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPRESSION = lossless;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@ -1455,14 +1457,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = 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;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "";
PRODUCT_NAME = DemoBots;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TOOLCHAINS = default;
};
name = Debug;
};
@ -1470,6 +1472,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPRESSION = "respect-asset-catalog";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
@ -1495,11 +1498,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = 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;
PRODUCT_NAME = DemoBots;
SDKROOT = macosx;
TOOLCHAINS = default;
};
name = Release;
};
@ -1508,20 +1511,22 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(BUILD_DIR)",
"$(inherited)",
);
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
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";
OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@ -1530,18 +1535,20 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(BUILD_DIR)",
"$(inherited)",
);
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
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";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 3.0;
};
name = Release;
};
@ -1558,21 +1565,18 @@
"$(BUILD_DIR)",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots;
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -1590,14 +1594,15 @@
"$(inherited)",
);
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_MODULE_NAME = DemoBots;
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_INSTALL_OBJC_HEADER = YES;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
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",
"role" : "primary-app-icon"
},
{
"size" : "2320x720",
"idiom" : "tv",
"filename" : "Top Shelf Image Wide.imageset",
"role" : "top-shelf-image-wide"
},
{
"size" : "1920x720",
"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,
"author" : "xcode"
}
}
}

View File

@ -14,7 +14,7 @@ extension BaseScene: ButtonNodeResponderType {
/// Searches the scene for all `ButtonNode`s.
func findAllButtonsInScene() -> [ButtonNode] {
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) {
switch button.buttonIdentifier! {
case .Home:
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
case .home:
sceneManager.transitionToScene(identifier: .home)
case .ProceedToNextScene:
sceneManager.transitionToSceneWithSceneIdentifier(.NextLevel)
case .proceedToNextScene:
sceneManager.transitionToScene(identifier: .nextLevel)
case .Replay:
sceneManager.transitionToSceneWithSceneIdentifier(.CurrentLevel)
case .replay:
sceneManager.transitionToScene(identifier: .currentLevel)
case .ScreenRecorderToggle:
case .screenRecorderToggle:
#if os(iOS)
toggleScreenRecording(button)
toggleScreenRecording(button: button)
#endif
case .ViewRecordedContent:
case .viewRecordedContent:
#if os(iOS)
displayRecordedContent()
#endif
default:
fatalError("Unsupported ButtonNode type in Scene.")
}
}
}
}

View File

@ -27,7 +27,7 @@ extension BaseScene {
/// A computed property to determine which buttons are focusable.
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] {
return [
.Resume,
.ProceedToNextScene,
.Replay,
.Retry,
.Home,
.Cancel,
.ViewRecordedContent,
.ScreenRecorderToggle
.resume,
.proceedToNextScene,
.replay,
.retry,
.home,
.cancel,
.viewRecordedContent,
.screenRecorderToggle
]
}
@ -60,7 +60,7 @@ extension BaseScene {
could be expanded to include horizontal navigation if necessary.
*/
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.
sortedFocusableButtons.forEach { $0.focusableNeighbors.removeAll() }
@ -71,8 +71,8 @@ extension BaseScene {
let nextNode = sortedFocusableButtons[i + 1]
// Create a bidirectional connection between the nodes.
node.focusableNeighbors[.Down] = nextNode
nextNode.focusableNeighbors[.Up] = node
node.focusableNeighbors[.down] = nextNode
nextNode.focusableNeighbors[.up] = node
}
}
@ -87,14 +87,14 @@ extension BaseScene {
// On iOS, ensure a game controller is connected otherwise return without providing focus.
guard sceneManager.gameInput.isGameControllerConnected else { return }
#endif
// 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.
let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(lhsButton.buttonIdentifier)!
let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(rhsButton.buttonIdentifier)!
let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: lhsButton.buttonIdentifier)!
let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: rhsButton.buttonIdentifier)!
return lhsPriority > rhsPriority
}
}
}
}

View File

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

View File

@ -12,7 +12,7 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// MARK: Computed Properties
var screenRecordingToggleEnabled: Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(screenRecorderEnabledKey)
return UserDefaults.standard.bool(forKey: screenRecorderEnabledKey)
}
// MARK: Start/Stop Screen Recording
@ -21,25 +21,25 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// Do nothing if screen recording hasn't been enabled.
guard screenRecordingToggleEnabled else { return }
let sharedRecorder = RPScreenRecorder.sharedRecorder()
let sharedRecorder = RPScreenRecorder.shared()
// Register as the recorder's delegate to handle errors.
sharedRecorder.delegate = self
sharedRecorder.startRecordingWithMicrophoneEnabled(true) { error in
sharedRecorder.startRecording() { error in
if let error = error {
self.showScreenRecordingAlert(error.localizedDescription)
self.showScreenRecordingAlert(message: error.localizedDescription)
}
}
}
func stopScreenRecordingWithHandler(handler:(() -> Void)) {
let sharedRecorder = RPScreenRecorder.sharedRecorder()
func stopScreenRecording(withHandler handler:@escaping (() -> Void)) {
let sharedRecorder = RPScreenRecorder.shared()
sharedRecorder.stopRecordingWithHandler { (previewViewController: RPPreviewViewController?, error: NSError?) in
sharedRecorder.stopRecording { previewViewController, error in
if let error = error {
// If an error has occurred, display an alert to the user.
self.showScreenRecordingAlert(error.localizedDescription)
self.showScreenRecordingAlert(message: error.localizedDescription)
return
}
@ -60,13 +60,13 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
func showScreenRecordingAlert(message: String) {
// 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.
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
self.paused = false
let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.`default`) { _ in
self.isPaused = false
}
alertController.addAction(alertAction)
@ -74,23 +74,23 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
`ReplayKit` event handlers may be called on a background queue. Ensure
this alert is presented on the main queue.
*/
dispatch_async(dispatch_get_main_queue()) {
self.view?.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
DispatchQueue.main.async() {
self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
func discardRecording() {
// 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
}
}
// 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.
showScreenRecordingAlert(error.localizedDescription)
showScreenRecordingAlert(message: error.localizedDescription)
/// Hold onto a reference of the `previewViewController` if not nil.
if previewViewController != nil {
@ -101,6 +101,6 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
// MARK: RPPreviewViewControllerDelegate
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
// 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
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.
camera.addChild(touchControlInputNode)
@ -48,4 +48,4 @@ extension BaseScene {
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.
buttons = []
if let overlay = overlay, camera = camera {
if let overlay = overlay, let camera = camera {
overlay.backgroundNode.removeFromParent()
camera.addChild(overlay.backgroundNode)
// Animate the overlay in.
overlay.backgroundNode.alpha = 0.0
overlay.backgroundNode.runAction(SKAction.fadeInWithDuration(0.25))
overlay.backgroundNode.run(SKAction.fadeIn(withDuration: 0.25))
overlay.updateScale()
buttons = findAllButtonsInScene()
@ -70,7 +70,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
}
// Animate the old overlay out.
oldValue?.backgroundNode.runAction(SKAction.fadeOutWithDuration(0.25)) {
oldValue?.backgroundNode.run(SKAction.fadeOut(withDuration: 0.25)) {
oldValue?.backgroundNode.removeFromParent()
}
}
@ -81,8 +81,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
// MARK: SKScene Life Cycle
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
override func didMove(to view: SKView) {
super.didMove(to: view)
updateCameraScale()
overlay?.updateScale()
@ -95,7 +95,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
resetFocus()
}
override func didChangeSize(oldSize: CGSize) {
override func didChangeSize(_ oldSize: CGSize) {
super.didChangeSize(oldSize)
updateCameraScale()
@ -122,11 +122,11 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
// MARK: ControlInputSourceGameStateDelegate
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType) {
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.
guard focusChangesEnabled else { return }
@ -155,7 +155,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
constant input.
*/
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
/*
@ -167,7 +168,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
}
else {
// Indicate that a neighboring button does not exist for the requested direction.
currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction)
currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction: direction)
}
}
else {
@ -176,20 +177,20 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
}
}
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
// Subclasses implement to toggle pause state.
}
#if DEBUG
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
// 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.
}
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
// Implemented by subclasses to force failing the level while debugging.
}
#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
NSUserDefaults.standardUserDefaults().setBool(button.isSelected, forKey: screenRecorderEnabledKey)
UserDefaults.standard.set(button.isSelected, forKey: screenRecorderEnabledKey)
}
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.") }
// `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.
enum AnimationState: String {
case Idle = "Idle"
case WalkForward = "WalkForward"
case WalkBackward = "WalkBackward"
case PreAttack = "PreAttack"
case Attack = "Attack"
case Zapped = "Zapped"
case Hit = "Hit"
case Inactive = "Inactive"
case idle = "Idle"
case walkForward = "WalkForward"
case walkBackward = "WalkBackward"
case preAttack = "PreAttack"
case attack = "Attack"
case zapped = "Zapped"
case hit = "Hit"
case inactive = "Inactive"
}
/**
@ -86,7 +86,7 @@ class AnimationComponent: GKComponent {
static let textureActionKey = "textureAction"
/// 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
@ -109,18 +109,23 @@ class AnimationComponent: GKComponent {
private(set) var currentAnimation: Animation?
/// 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
init(textureSize: CGSize, animations: [AnimationState: [CompassDirection: Animation]]) {
node = SKSpriteNode(texture: nil, size: textureSize)
self.animations = animations
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 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.
elapsedAnimationDuration += deltaTime
@ -145,21 +150,21 @@ class AnimationComponent: GKComponent {
// Check if the action for the body node has changed.
if currentAnimation?.bodyActionName != animation.bodyActionName {
// 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).
node.position = CGPoint.zero
// Add the new body action to the node if an action exists.
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.
if currentAnimation?.shadowActionName != animation.shadowActionName {
// 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).
shadowNode?.position = CGPoint.zero
@ -170,12 +175,12 @@ class AnimationComponent: GKComponent {
// Add the new shadow action to the shadow node if an action exists.
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.
node.removeActionForKey(AnimationComponent.textureActionKey)
node.removeAction(forKey: AnimationComponent.textureActionKey)
// Create a new action to display the appropriate animation textures.
let texturesAction: SKAction
@ -210,15 +215,15 @@ class AnimationComponent: GKComponent {
// Create an appropriate action from the (possibly offset) animation frames.
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 {
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.
node.runAction(texturesAction, withKey: AnimationComponent.textureActionKey)
node.run(texturesAction, withKey: AnimationComponent.textureActionKey)
// Remember the animation we are currently running.
currentAnimation = animation
@ -229,14 +234,14 @@ class AnimationComponent: GKComponent {
// MARK: GKComponent Life Cycle
override func updateWithDeltaTime(deltaTime: NSTimeInterval) {
super.updateWithDeltaTime(deltaTime)
override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
// If an animation has been requested, run the animation.
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
}
}
@ -248,7 +253,7 @@ class AnimationComponent: GKComponent {
// Filter for this facing direction, and sort the resulting texture names alphabetically.
let textureNames = atlas.textureNames.filter {
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
}.sort()
}.sorted()
// Find and return the first texture for this direction.
return atlas.textureNamed(textureNames.first!)
@ -257,7 +262,7 @@ class AnimationComponent: GKComponent {
/// Creates a texture action from all textures in an atlas.
class func actionForAllTexturesInAtlas(atlas: SKTextureAtlas) -> SKAction {
// 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)
}
@ -266,8 +271,8 @@ class AnimationComponent: GKComponent {
return SKAction.setTexture(textures.first!)
}
else {
let texturesAction = SKAction.animateWithTextures(textures, timePerFrame: AnimationComponent.timePerFrame)
return SKAction.repeatActionForever(texturesAction)
let texturesAction = SKAction.animate(with: textures, timePerFrame: AnimationComponent.timePerFrame)
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.
let textures = atlas.textureNames.filter {
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
}.sort {
}.sorted {
playBackwards ? $0 > $1 : $0 < $1
}.map {
atlas.textureNamed($0)

View File

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

View File

@ -15,7 +15,7 @@ class BeamCoolingState: GKState {
unowned var beamComponent: BeamComponent
/// The amount of time the beam has been cooling down.
var elapsedTime: NSTimeInterval = 0.0
var elapsedTime: TimeInterval = 0.0
// MARK: Initializers
@ -25,24 +25,24 @@ class BeamCoolingState: GKState {
// MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
elapsedTime = 0.0
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
elapsedTime += seconds
// If the beam has spent long enough cooling down, enter `BeamIdleState`.
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 {
case is BeamIdleState.Type, is BeamFiringState.Type:
return true
@ -52,11 +52,11 @@ class BeamCoolingState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
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?
/// 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`.
var playerBot: PlayerBot {
@ -28,7 +28,7 @@ class BeamFiringState: GKState {
/// The `RenderComponent` associated with the `BeamComponent`'s `entity`.
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
}
@ -40,8 +40,8 @@ class BeamFiringState: GKState {
// MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the "amount of time firing" tracker when we enter the "firing" state.
elapsedTime = 0.0
@ -63,7 +63,7 @@ class BeamFiringState: GKState {
*/
beamComponent.beamNode.zPosition = -1.0
let aboveCharactersNode = scene.worldLayerNodes[.AboveCharacters]!
let aboveCharactersNode = scene.worldLayerNodes[.aboveCharacters]!
aboveCharactersNode.addChild(beamComponent.beamNode)
// Constrain the `BeamNode` to the antenna position on the `PlayerBot`'s node.
@ -76,11 +76,11 @@ class BeamFiringState: GKState {
beamComponent.beamNode.constraints = [constraint]
}
updateBeamNodeWithDeltaTime(0.0)
updateBeamNode(withDeltaTime: 0.0)
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the "amount of time firing" tracker.
elapsedTime += seconds
@ -90,18 +90,18 @@ class BeamFiringState: GKState {
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.
*/
stateMachine?.enterState(BeamCoolingState.self)
stateMachine?.enter(BeamCoolingState.self)
}
else if !beamComponent.isTriggered {
// The beam is no longer being fired. Enter the `BeamIdleState`.
stateMachine?.enterState(BeamIdleState.self)
stateMachine?.enter(BeamIdleState.self)
}
else {
updateBeamNodeWithDeltaTime(seconds)
updateBeamNode(withDeltaTime: seconds)
}
}
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is BeamIdleState.Type, is BeamCoolingState.Type:
return true
@ -111,35 +111,35 @@ class BeamFiringState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
// Clear the current target.
target = nil
// 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
func updateBeamNodeWithDeltaTime(seconds: NSTimeInterval) {
func updateBeamNode(withDeltaTime seconds: TimeInterval) {
// 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 let chargeComponent = target?.componentForClass(ChargeComponent.self) {
if let chargeComponent = target?.component(ofType: ChargeComponent.self) {
let chargeToLose = GameplayConfiguration.Beam.chargeLossPerSecond * seconds
chargeComponent.loseCharge(chargeToLose)
chargeComponent.loseCharge(chargeToLose: chargeToLose)
}
// 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 let currentTarget = target where currentTarget.isGood {
if let currentTarget = target, currentTarget.isGood {
beamComponent.isTriggered = false
stateMachine?.enterState(BeamIdleState.self)
stateMachine?.enter(BeamIdleState.self)
}
}

View File

@ -22,16 +22,16 @@ class BeamIdleState: GKState {
// MARK: GKState life cycle
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// If the beam has been triggered, enter `BeamFiringState`.
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
}
}

View File

@ -64,6 +64,10 @@ class ChargeComponent: GKComponent {
chargeBar?.level = percentageCharge
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Component actions
@ -78,7 +82,7 @@ class ChargeComponent: GKComponent {
if newCharge < charge {
charge = newCharge
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.
enum CompassDirection: Int {
case East = 0, EastByNorthEast, NorthEast, NorthByNorthEast
case North, NorthByNorthWest, NorthWest, WestByNorthWest
case West, WestBySouthWest, SouthWest, SouthBySouthWest
case South, SouthBySouthEast, SouthEast, EastBySouthEast
case east = 0, eastByNorthEast, northEast, northByNorthEast
case north, northByNorthWest, northWest, westByNorthWest
case west, westBySouthWest, southWest, southBySouthWest
case south, southBySouthEast, southEast, eastBySouthEast
/// Convenience array of all available directions.
static let allDirections: [CompassDirection] =
[
.East, .EastByNorthEast, .NorthEast, .NorthByNorthEast,
.North, .NorthByNorthWest, .NorthWest, .WestByNorthWest,
.West, .WestBySouthWest, .SouthWest, .SouthBySouthWest,
.South, .SouthBySouthEast, .SouthEast, .EastBySouthEast
.east, .eastByNorthEast, .northEast, .northByNorthEast,
.north, .northByNorthWest, .northWest, .westByNorthWest,
.west, .westBySouthWest, .southWest, .southBySouthWest,
.south, .southBySouthEast, .southEast, .eastBySouthEast
]
/// The angle of rotation that the orientation represents.
@ -37,13 +37,13 @@ enum CompassDirection: Int {
let twoPi = M_PI * 2
// 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.
let orientation = rotation / twoPi
// 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.
self = CompassDirection(rawValue: Int(rawFacingValue))!
@ -52,28 +52,28 @@ enum CompassDirection: Int {
init(string: String) {
switch string {
case "North":
self = .North
self = .north
case "NorthEast":
self = .NorthEast
self = .northEast
case "East":
self = .East
self = .east
case "SouthEast":
self = .SouthEast
self = .southEast
case "South":
self = .South
self = .south
case "SouthWest":
self = .SouthWest
self = .southWest
case "West":
self = .West
self = .west
case "NorthWest":
self = .NorthWest
self = .northWest
default:
fatalError("Unknown or unsupported string - \(string)")

View File

@ -24,17 +24,17 @@ class FlyingBotBlastState: GKState {
var currentEmitterNode: SKEmitterNode?
/// 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`.
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
}
/// The `RenderComponent` associated with the `entity`.
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
}
@ -61,8 +61,8 @@ class FlyingBotBlastState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the "length of this blast" tracker when entering the "blast" state.
elapsedTime = 0.0
@ -81,17 +81,17 @@ class FlyingBotBlastState: GKState {
renderComponent.node.addChild(currentEmitterNode!)
// Request the appropriate "attack" animation for this `TaskBot`.
animationComponent.requestedAnimationState = .Attack
animationComponent.requestedAnimationState = .attack
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Check if the `FlyingBot` has reached the end of its blast duration.
elapsedTime += seconds
if elapsedTime >= GameplayConfiguration.FlyingBot.blastDuration {
// Return to an agent-controlled state if the blast has completed.
stateMachine?.enterState(TaskBotAgentControlledState.self)
stateMachine?.enter(TaskBotAgentControlledState.self)
return
}
else if elapsedTime < GameplayConfiguration.FlyingBot.blastEffectDuration {
@ -100,12 +100,12 @@ class FlyingBotBlastState: GKState {
performGoodBlast()
}
else {
performBadBlastWithDeltaTime(seconds)
performBadBlast(withDeltaTime: seconds)
}
}
}
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
return true
@ -115,8 +115,8 @@ class FlyingBotBlastState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
// Remove the blast effect emitter node from the `TaskBot` when leaving the blast state.
currentEmitterNode?.removeFromParent()
@ -129,7 +129,7 @@ class FlyingBotBlastState: GKState {
func entitiesInRange() -> [GKEntity] {
// 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 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.
let entitiesInRange: [GKEntity] = entitySnapshot.entityDistances.flatMap {
@ -150,7 +150,7 @@ class FlyingBotBlastState: GKState {
// Iterate through the `TaskBot`s in range.
for taskBot in taskBotsInRange {
// 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 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".
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.
let chargeToLose = GameplayConfiguration.FlyingBot.blastChargeLossPerSecond * seconds
@ -174,12 +174,12 @@ class FlyingBotBlastState: GKState {
let entities = entitiesInRange()
for entity in entities {
if let playerBot = entity as? PlayerBot where !playerBot.isPoweredDown,
let chargeComponent = entity.componentForClass(ChargeComponent.self) {
if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown,
let chargeComponent = entity.component(ofType: ChargeComponent.self) {
// 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".
taskBot.isGood = false
}

View File

@ -15,11 +15,11 @@ class FlyingBotPreAttackState: GKState {
unowned var entity: FlyingBot
/// 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`.
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
}
@ -31,18 +31,18 @@ class FlyingBotPreAttackState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the tracking of how long the `TaskBot` has been in a "pre-attack" state.
elapsedTime = 0.0
// Request the "attack" animation for this `FlyingBot`.
animationComponent.requestedAnimationState = .Attack
animationComponent.requestedAnimationState = .attack
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the time that the `TaskBot` has been in its "pre-attack" state.
elapsedTime += seconds
@ -52,11 +52,11 @@ class FlyingBotPreAttackState: GKState {
move to the attack state.
*/
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 {
case is TaskBotAgentControlledState.Type, is FlyingBotBlastState.Type, is TaskBotZappedState.Type:
return true

View File

@ -19,13 +19,13 @@ class GroundBotAttackState: GKState {
/// The `MovementComponent` associated with the `entity`.
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
}
/// The `PhysicsComponent` associated with the `entity`.
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
}
@ -43,14 +43,14 @@ class GroundBotAttackState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Apply damage to any entities the `GroundBot` is already in contact with.
let contactedBodies = physicsComponent.physicsBody.allContactedBodies()
for contactedBody in contactedBodies {
guard let entity = (contactedBody.node as? EntityNode)?.entity else { continue }
applyDamageToEntity(entity)
guard let entity = contactedBody.node?.entity else { continue }
applyDamageToEntity(entity: entity)
}
// `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
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
let targetPosition = self.targetPosition
@ -86,7 +86,7 @@ class GroundBotAttackState: GKState {
let currentDistanceToTarget = hypot(dx, dy)
if currentDistanceToTarget < GameplayConfiguration.GroundBot.attackEndProximity {
stateMachine?.enterState(TaskBotAgentControlledState.self)
stateMachine?.enter(TaskBotAgentControlledState.self)
return
}
@ -95,7 +95,7 @@ class GroundBotAttackState: GKState {
its target because it has been knocked off course.
*/
if currentDistanceToTarget > lastDistanceToTarget {
stateMachine?.enterState(TaskBotAgentControlledState.self)
stateMachine?.enter(TaskBotAgentControlledState.self)
return
}
@ -103,7 +103,7 @@ class GroundBotAttackState: GKState {
lastDistanceToTarget = currentDistanceToTarget
}
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
return true
@ -113,8 +113,8 @@ class GroundBotAttackState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let movementComponent = self.movementComponent
@ -129,11 +129,11 @@ class GroundBotAttackState: GKState {
// MARK: Convenience
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.
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.
taskBot.isGood = false
}

View File

@ -15,11 +15,11 @@ class GroundBotPreAttackState: GKState {
unowned var entity: GroundBot
/// 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`.
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
}
@ -31,18 +31,18 @@ class GroundBotPreAttackState: GKState {
// MARK: GPState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the tracking of how long the `GroundBot` has been in a "pre-attack" state.
elapsedTime = 0.0
// Request the "attack" animation for this state's `GroundBot`.
animationComponent.requestedAnimationState = .Attack
animationComponent.requestedAnimationState = .attack
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
elapsedTime += seconds
@ -51,11 +51,11 @@ class GroundBotPreAttackState: GKState {
move to the attack state.
*/
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 {
case is TaskBotAgentControlledState.Type, is GroundBotAttackState.Type, is TaskBotZappedState.Type:
return true

View File

@ -16,13 +16,13 @@ class GroundBotRotateToAttackState: GKState {
/// The `AnimationComponent` associated with the `entity`.
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
}
/// The `OrientationComponent` associated with the `entity`.
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
}
@ -40,21 +40,21 @@ class GroundBotRotateToAttackState: GKState {
// MARK: GPState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Request the "walk forward" animation for this `GroundBot`.
animationComponent.requestedAnimationState = .WalkForward
animationComponent.requestedAnimationState = .walkForward
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// `orientationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
let orientationComponent = self.orientationComponent
// 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.
var delta = CGFloat(seconds * GameplayConfiguration.GroundBot.preAttackRotationSpeed)
@ -66,7 +66,7 @@ class GroundBotRotateToAttackState: GKState {
if abs(delta) >= abs(angleDeltaToTarget) {
// Finish the rotation and enter `GroundBotPreAttackState`.
orientationComponent.zRotation += angleDeltaToTarget
stateMachine?.enterState(GroundBotPreAttackState.self)
stateMachine?.enter(GroundBotPreAttackState.self)
return
}
@ -74,10 +74,10 @@ class GroundBotRotateToAttackState: GKState {
orientationComponent.zRotation += delta
// 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 {
case is TaskBotAgentControlledState.Type, is GroundBotPreAttackState.Type, is TaskBotZappedState.Type:
return true

View File

@ -33,11 +33,11 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
didSet {
if isEnabled {
// Apply the current input state to the movement and beam components.
applyInputState(state)
applyInputState(state: state)
}
else {
// 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() {
didSet {
if isEnabled {
applyInputState(state)
applyInputState(state: state)
}
}
}
// MARK: ControlInputSourceDelegate
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) {
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) {
state.translation = MovementKind(displacement: displacement)
}
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) {
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) {
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
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)
}
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) {
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) {
/*
Create a `MovementKind` instance indicating whether the displacement
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)
}
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType) {
state.allowsStrafing = controlInputSource.allowsStrafing
state.beamIsTriggered = true
}
func controlInputSourceDidFinishAttacking(controlInputSource: ControlInputSourceType) {
func controlInputSourceDidFinishAttacking(_ controlInputSource: ControlInputSourceType) {
state.beamIsTriggered = false
}
// MARK: Convenience
func applyInputState(state: InputState) {
if let movementComponent = entity?.componentForClass(MovementComponent.self) {
if let movementComponent = entity?.component(ofType: MovementComponent.self) {
movementComponent.allowsStrafing = state.allowsStrafing
movementComponent.nextRotation = state.rotation
movementComponent.nextTranslation = state.translation
}
if let beamComponent = entity?.componentForClass(BeamComponent.self) {
if let beamComponent = entity?.component(ofType: BeamComponent.self) {
beamComponent.isTriggered = state.beamIsTriggered
}
}
}
}

View File

@ -21,20 +21,26 @@ class IntelligenceComponent: GKComponent {
init(states: [GKState]) {
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
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
stateMachine.updateWithDeltaTime(seconds)
stateMachine.update(deltaTime: seconds)
}
// MARK: Actions
func enterInitialState() {
stateMachine.enterState(initialStateClass)
stateMachine.enter(initialStateClass)
}
}

View File

@ -55,19 +55,19 @@ class MovementComponent: GKComponent {
/// The `RenderComponent` for this component's entity.
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
}
/// The `AnimationComponent` for this component's entity.
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
}
/// The `OrientationComponent` for this component's entity.
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
}
@ -82,12 +82,17 @@ class MovementComponent: GKComponent {
override init() {
movementSpeed = GameplayConfiguration.PlayerBot.movementSpeed
angularSpeed = GameplayConfiguration.PlayerBot.angularSpeed
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: GKComponent Life Cycle
override func updateWithDeltaTime(deltaTime: NSTimeInterval) {
super.updateWithDeltaTime(deltaTime)
override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
// Declare local versions of computed properties so we don't compute them multiple times.
let node = renderComponent.node
@ -104,10 +109,10 @@ class MovementComponent: GKComponent {
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.
orientationComponent.zRotation = newRotation
animationState = .Idle
animationState = .idle
}
else {
// 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.
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
// 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
forward and backwards based on node's `zRotation`.
*/
animationState = animationStateForDestination(node, destination: newPosition)
animationState = animationStateForDestination(node: node, destination: newPosition)
}
else {
// 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.
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
}
}
@ -153,10 +158,10 @@ class MovementComponent: GKComponent {
/// Creates a vector towards the current target of a `BeamComponent` attack (if one exists).
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
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
// 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.
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.
guard translation.displacement != float2() else { return nil }
@ -176,7 +181,7 @@ class MovementComponent: GKComponent {
if translation.isRelativeToOrientation {
// Ensure the relative displacement component is non-zero.
guard displacement.x != 0 else { return nil }
displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(displacement)
displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(relativeDisplacement: displacement)
}
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)
}
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.
guard rotation.displacement != float2() else { return nil }
@ -251,7 +256,7 @@ class MovementComponent: GKComponent {
private func animationStateForDestination(node: SKNode, destination: CGPoint) -> AnimationState {
// Ensures nodes rotation is the same direction as the destination point.
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 {
switch animationState {
case nil, .Idle?, .WalkForward?, .WalkBackward?:
case .idle?, .walkForward?, .walkBackward?:
return true
default:

View File

@ -15,7 +15,7 @@ class OrientationComponent: GKComponent {
var zRotation: CGFloat = 0.0 {
didSet {
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
}
}
}
}

View File

@ -21,5 +21,10 @@ class PhysicsComponent: GKComponent {
self.physicsBody.categoryBitMask = colliderType.categoryMask
self.physicsBody.collisionBitMask = colliderType.collisionMask
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
/// 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`.
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
}
/// The `RenderComponent` associated with the `entity`.
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
}
/// The `OrientationComponent` associated with the `entity`.
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
}
/// The `InputComponent` associated with the `entity`.
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
}
@ -52,8 +52,8 @@ class PlayerBotAppearState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the elapsed time.
elapsedTime = 0.0
@ -78,14 +78,14 @@ class PlayerBotAppearState: GKState {
renderComponent.node.addChild(node)
// 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.
inputComponent.isEnabled = false
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the amount of time that the `PlayerBot` has been teleporting in to the level.
elapsedTime += seconds
@ -96,19 +96,19 @@ class PlayerBotAppearState: GKState {
node.removeFromParent()
// 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
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
// Un-hide the animation component node.
animationComponent.node.hidden = false
animationComponent.node.isHidden = false
// Re-enable the input component
inputComponent.isEnabled = true

View File

@ -15,11 +15,11 @@ class PlayerBotHitState: GKState {
unowned var entity: PlayerBot
/// 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`.
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
}
@ -31,18 +31,18 @@ class PlayerBotHitState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the elapsed "hit" duration on entering this state.
elapsedTime = 0.0
// Request the "hit" animation for this `PlayerBot`.
animationComponent.requestedAnimationState = .Hit
animationComponent.requestedAnimationState = .hit
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the amount of time the `PlayerBot` has been in the "hit" state.
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.
if elapsedTime >= GameplayConfiguration.PlayerBot.hitStateDuration {
if entity.isPoweredDown {
stateMachine?.enterState(PlayerBotRechargingState.self)
stateMachine?.enter(PlayerBotRechargingState.self)
}
else {
stateMachine?.enterState(PlayerBotPlayerControlledState.self)
stateMachine?.enter(PlayerBotPlayerControlledState.self)
}
}
}
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is PlayerBotPlayerControlledState.Type, is PlayerBotRechargingState.Type:
return true

View File

@ -16,19 +16,19 @@ class PlayerBotPlayerControlledState: GKState {
/// The `AnimationComponent` associated with the `entity`.
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
}
/// The `MovementComponent` associated with the `entity`.
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
}
/// The `InputComponent` associated with the `entity`.
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
}
@ -40,24 +40,24 @@ class PlayerBotPlayerControlledState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Turn on controller input for the `PlayerBot` when entering the player-controlled state.
inputComponent.isEnabled = true
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
/*
Assume an animation of "idle" that can then be overwritten by the movement
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 {
case is PlayerBotHitState.Type, is PlayerBotRechargingState.Type:
return true
@ -67,11 +67,11 @@ class PlayerBotPlayerControlledState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
// 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.
let movementComponent = self.movementComponent

View File

@ -15,17 +15,17 @@ class PlayerBotRechargingState: GKState {
unowned var entity: PlayerBot
/// 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`.
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
}
/// The `ChargeComponent` associated with the `entity`.
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
}
@ -37,18 +37,18 @@ class PlayerBotRechargingState: GKState {
// MARK: GKState life cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the recharge duration when entering this state.
elapsedTime = 0.0
// Request the "inactive" animation for the `PlayerBot`.
animationComponent.requestedAnimationState = .Inactive
animationComponent.requestedAnimationState = .inactive
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the elapsed recharge duration.
elapsedTime += seconds
@ -64,16 +64,16 @@ class PlayerBotRechargingState: GKState {
// Add charge to the `PlayerBot`.
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 chargeComponent.isFullyCharged {
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
}
}

View File

@ -3,7 +3,7 @@
See LICENSE.txt for this samples licensing information
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
@ -13,9 +13,15 @@ class RenderComponent: GKComponent {
// MARK: Properties
// 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
}
override func willRemoveFromEntity() {
node.entity = nil
}
}

View File

@ -21,22 +21,28 @@ class RulesComponent: GKComponent {
var ruleSystem: GKRuleSystem
/// 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
override init() {
ruleSystem = GKRuleSystem()
super.init()
}
init(rules: [GKRule]) {
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
override func updateWithDeltaTime(seconds: NSTimeInterval) {
override func update(deltaTime seconds: TimeInterval) {
timeSinceRulesUpdate += seconds
if timeSinceRulesUpdate < GameplayConfiguration.TaskBot.rulesUpdateWaitDuration { return }
@ -44,8 +50,9 @@ class RulesComponent: GKComponent {
timeSinceRulesUpdate = 0.0
if let taskBot = entity as? TaskBot,
level = taskBot.componentForClass(RenderComponent)?.node.scene as? LevelScene,
entitySnapshot = level.entitySnapshotForEntity(taskBot) where !taskBot.isGood {
let level = taskBot.component(ofType: RenderComponent.self)?.node.scene as? LevelScene,
let entitySnapshot = level.entitySnapshotForEntity(entity: taskBot),
!taskBot.isGood {
ruleSystem.reset()
@ -53,7 +60,7 @@ class RulesComponent: GKComponent {
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.size = size
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
/// 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.
var timeSinceBehaviorUpdate: NSTimeInterval = 0.0
var timeSinceBehaviorUpdate: TimeInterval = 0.0
// MARK: Initializers
@ -28,8 +28,8 @@ class TaskBotAgentControlledState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the amount of time since the last behavior update.
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".
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
chargeComponent.addCharge(chargeToAdd)
chargeComponent.addCharge(chargeToAdd: chargeToAdd)
}
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Update the "time since last behavior update" tracker.
timeSinceBehaviorUpdate += seconds
@ -59,8 +59,8 @@ class TaskBotAgentControlledState: GKState {
if timeSinceBehaviorUpdate >= GameplayConfiguration.TaskBot.behaviorUpdateWaitDuration {
// 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 {
entity.mandate = entity.isGood ? .FollowGoodPatrolPath : .FollowBadPatrolPath
if case let .returnToPositionOnPath(position) = entity.mandate, entity.distanceToPoint(otherPoint: position) <= GameplayConfiguration.TaskBot.thresholdProximityToPatrolPathStartPoint {
entity.mandate = entity.isGood ? .followGoodPatrolPath : .followBadPatrolPath
}
// 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 {
case is FlyingBotPreAttackState.Type, is GroundBotRotateToAttackState.Type, is TaskBotZappedState.Type:
return true
@ -81,8 +81,8 @@ class TaskBotAgentControlledState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
/*
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
/// 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()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene)
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoal(forScene: scene)
// Find any nearby "bad" TaskBots to flock with.
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
}
@ -32,54 +32,56 @@ class TaskBotBehavior: GKBehavior {
if !agentsToFlockWith.isEmpty {
// Add flocking goals for any nearby "bad" `TaskBot`s.
let separationGoal = GKGoal(toSeparateFromAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle)
behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, forGoal: separationGoal)
let separationGoal = GKGoal(toSeparateFrom: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle)
behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, for: separationGoal)
let alignmentGoal = GKGoal(toAlignWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle)
behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, forGoal: alignmentGoal)
let alignmentGoal = GKGoal(toAlignWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle)
behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, for: alignmentGoal)
let cohesionGoal = GKGoal(toCohereWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle)
behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, forGoal: cohesionGoal)
let cohesionGoal = GKGoal(toCohereWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle)
behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, for: cohesionGoal)
}
// 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 (behavior, pathPoints)
}
/// 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()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene)
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoal(forScene: scene)
// 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 (behavior, pathPoints)
}
/// 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()
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
behavior.addTargetSpeedGoal(agent.maxSpeed)
behavior.addAvoidObstaclesGoalForScene(scene)
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
behavior.addAvoidObstaclesGoal(forScene: scene)
// 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.
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.
behavior.addFollowAndStayOnPathGoalsForPath(path)
behavior.addFollowAndStayOnPathGoals(for: path)
return behavior
}
@ -90,7 +92,7 @@ class TaskBotBehavior: GKBehavior {
Calculates all of the extruded obstacles that the provided point resides near.
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
we're including all obstacles.
@ -108,14 +110,14 @@ class TaskBotBehavior: GKBehavior {
// Retrieve all vertices for the polygon obstacle.
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 }
let maxX = polygonVertices.maxElement { $0.x < $1.x }!.x + extrusionRadius
let maxY = polygonVertices.maxElement { $0.y < $1.y }!.y + extrusionRadius
let maxX = polygonVertices.max { $0.x < $1.x }!.x + extrusionRadius
let maxY = polygonVertices.max { $0.y < $1.y }!.y + extrusionRadius
let minX = polygonVertices.minElement { $0.x < $1.x }!.x - extrusionRadius
let minY = polygonVertices.minElement { $0.y < $1.y }!.y - extrusionRadius
let minX = polygonVertices.min { $0.x < $1.x }!.x - extrusionRadius
let minY = polygonVertices.min { $0.y < $1.y }!.y - extrusionRadius
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.
*/
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.
let pointNode = GKGraphNode2D(point: point)
// 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.
@ -143,20 +145,20 @@ class TaskBotBehavior: GKBehavior {
*/
if pointNode.connectedNodes.isEmpty {
// 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.
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
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 pointNode.connectedNodes.isEmpty {
scene.graph.removeNodes([pointNode])
scene.graph.remove([pointNode])
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.
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`.
guard let startNode = connectedNodeForPoint(startPoint, onObstacleGraphInScene: scene),
endNode = connectedNodeForPoint(endPoint, onObstacleGraphInScene: scene) else { return [] }
guard let startNode = connectedNode(forPoint: startPoint, onObstacleGraphInScene: scene),
let endNode = connectedNode(forPoint: endPoint, onObstacleGraphInScene: scene) else { return [] }
// 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.
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.
guard pathNodes.count > 1 else { return [] }
@ -183,7 +185,7 @@ class TaskBotBehavior: GKBehavior {
let path = GKPath(graphNodes: pathNodes, radius: pathRadius)
// 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.
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.
private func addAvoidObstaclesGoalForScene(scene: LevelScene) {
setWeight(1.0, forGoal: GKGoal(toAvoidObstacles: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance))
private func addAvoidObstaclesGoal(forScene scene: LevelScene) {
setWeight(1.0, for: GKGoal(toAvoid: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance))
}
/// Adds a goal to attain a target speed.
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.
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.
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.
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
/// 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`.
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
}
@ -31,14 +31,14 @@ class TaskBotZappedState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Reset the elapsed time.
elapsedTime = 0.0
// 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.
movementComponent.nextTranslation = nil
movementComponent.nextRotation = nil
@ -46,11 +46,11 @@ class TaskBotZappedState: GKState {
}
// Request the "zapped" animation for this `TaskBot`.
animationComponent.requestedAnimationState = .Zapped
animationComponent.requestedAnimationState = .zapped
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
elapsedTime += seconds
@ -59,11 +59,11 @@ class TaskBotZappedState: GKState {
re-enter `TaskBotAgentControlledState`.
*/
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 {
case is TaskBotZappedState.Type:
/*

View File

@ -9,7 +9,7 @@
import simd
enum ControlInputDirection: Int {
case Up = 0, Down, Left, Right
case up = 0, down, left, right
init?(vector: float2) {
// Require sufficient displacement to specify direction.
@ -17,25 +17,25 @@ enum ControlInputDirection: Int {
// Take the max displacement as the specified axis.
if abs(vector.x) > abs(vector.y) {
self = vector.x > 0 ? .Right : .Left
self = vector.x > 0 ? .right : .left
}
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.
protocol ControlInputSourceGameStateDelegate: class {
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType)
func controlInputSource(controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection)
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType)
func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType)
func controlInputSource(_ controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection)
func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType)
#if DEBUG
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType)
func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType)
func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType)
#endif
}
@ -49,14 +49,14 @@ protocol ControlInputSourceDelegate: class {
Left: (-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
denoting both the requested angle, and magnitude with which to rotate.
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
@ -64,7 +64,7 @@ protocol ControlInputSourceDelegate: class {
Forward: (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
@ -72,13 +72,13 @@ protocol ControlInputSourceDelegate: class {
Clockwise: (-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.
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType)
func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType)
/// 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.
@ -92,4 +92,4 @@ protocol ControlInputSourceType: class {
var allowsStrafing: Bool { get }
func resetControlState()
}
}

View File

@ -68,7 +68,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
}
// Create components that define how the entity looks and behaves.
let renderComponent = RenderComponent(entity: self)
let renderComponent = RenderComponent()
addComponent(renderComponent)
let orientationComponent = OrientationComponent()
@ -107,35 +107,39 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: ContactableType
override func contactWithEntityDidBegin(entity: GKEntity) {
override func contactWithEntityDidBegin(_ entity: GKEntity) {
super.contactWithEntityDidBegin(entity)
guard !isGood else { return }
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.
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.
shouldStartAttack = true
}
if let stateMachine = componentForClass(IntelligenceComponent)?.stateMachine where shouldStartAttack {
stateMachine.enterState(FlyingBotPreAttackState.self)
if let stateMachine = component(ofType: IntelligenceComponent.self)?.stateMachine, shouldStartAttack {
stateMachine.enter(FlyingBotPreAttackState.self)
}
}
// MARK: ChargeComponentDelegate
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
}
@ -145,7 +149,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
return goodAnimations == nil || badAnimations == nil
}
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
// Load `TaskBot`s shared assets.
super.loadSharedAssets()
@ -170,13 +174,13 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
after the `FlyingBot` texture atlases have finished preloading.
*/
goodAnimations = [:]
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(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![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
goodAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
badAnimations = [:]
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(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![.Zapped] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .Zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .attack, 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.
completionHandler()

View File

@ -73,7 +73,7 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
}
// Create components that define how the entity looks and behaves.
let renderComponent = RenderComponent(entity: self)
let renderComponent = RenderComponent()
addComponent(renderComponent)
let orientationComponent = OrientationComponent()
@ -116,22 +116,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
beamTargetOffset = GameplayConfiguration.GroundBot.beamTargetOffset
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: ContactableType
override func contactWithEntityDidBegin(entity: GKEntity) {
override func contactWithEntityDidBegin(_ entity: GKEntity) {
super.contactWithEntityDidBegin(entity)
// 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.
attackState.applyDamageToEntity(entity)
attackState.applyDamageToEntity(entity: entity)
}
// MARK: RulesComponentDelegate
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:
@ -140,26 +144,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
3) The target is within the `GroundBot`'s attack range.
4) There is no scenery between the `GroundBot` and the target.
*/
guard let scene = componentForClass(RenderComponent.self)?.node.scene else { return }
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return }
guard let scene = component(ofType: RenderComponent.self)?.node.scene else { return }
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) 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.
guard agentControlledState.elapsedTime >= GameplayConfiguration.GroundBot.delayBetweenAttacks else { return }
// 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.
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.
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) {
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.
targetPosition = targetAgent.position
intelligenceComponent.stateMachine.enterState(GroundBotRotateToAttackState.self)
intelligenceComponent.stateMachine.enter(GroundBotRotateToAttackState.self)
}
// MARK: ChargeComponentDelegate
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
guard let intelligenceComponent = componentForClass(IntelligenceComponent) else { return }
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
isGood = !chargeComponent.hasCharge
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
}
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
// Load `TaskBot`s shared assets.
super.loadSharedAssets()
@ -213,12 +217,12 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
after the `GroundBot` texture atlases have finished preloading.
*/
goodAnimations = [:]
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .WalkForward)
goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .walkForward)
badAnimations = [:]
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .WalkForward)
badAnimations![.Attack] = AnimationComponent.animationsFromAtlas(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![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .walkForward)
badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[2], withImageIdentifier: "GroundBotAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake", repeatTexturesForever: false)
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.
completionHandler()

View File

@ -48,7 +48,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
It is not targetable when appearing or recharging.
*/
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 {
case is PlayerBotPlayerControlledState, is PlayerBotHitState:
@ -64,7 +64,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
/// The `RenderComponent` associated with this `PlayerBot`.
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
}
@ -81,7 +81,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
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).
*/
let renderComponent = RenderComponent(entity: self)
let renderComponent = RenderComponent()
addComponent(renderComponent)
let orientationComponent = OrientationComponent()
@ -133,16 +133,20 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
addComponent(intelligenceComponent)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Charge component delegate
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
if let intelligenceComponent = componentForClass(IntelligenceComponent.self) {
if let intelligenceComponent = component(ofType: IntelligenceComponent.self) {
if !chargeComponent.hasCharge {
isPoweredDown = true
intelligenceComponent.stateMachine.enterState(PlayerBotRechargingState.self)
intelligenceComponent.stateMachine.enter(PlayerBotRechargingState.self)
}
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
}
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
loadMiscellaneousAssets()
let playerBotAtlasNames = [
@ -181,16 +185,16 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
*/
appearTextures = [:]
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.
animations = [:]
animations![.Idle] = AnimationComponent.animationsFromAtlas(playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .Idle)
animations![.WalkForward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkForward)
animations![.WalkBackward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkBackward, playBackwards: true)
animations![.Inactive] = AnimationComponent.animationsFromAtlas(playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .Inactive)
animations![.Hit] = AnimationComponent.animationsFromAtlas(playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .Hit, repeatTexturesForever: false)
animations![.idle] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .idle)
animations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkForward)
animations![.walkBackward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkBackward, playBackwards: true)
animations![.inactive] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .inactive)
animations![.hit] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .hit, repeatTexturesForever: false)
// Invoke the passed `completionHandler` to indicate that loading has completed.
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.
enum TaskBotMandate {
// Hunt another agent (either a `PlayerBot` or a "good" `TaskBot`).
case HuntAgent(GKAgent2D)
case huntAgent(GKAgent2D)
// Follow the `TaskBot`'s "good" patrol path.
case FollowGoodPatrolPath
case followGoodPatrolPath
// Follow the `TaskBot`'s "bad" patrol path.
case FollowBadPatrolPath
case followBadPatrolPath
// Return to a given position on a patrol path.
case ReturnToPositionOnPath(float2)
case returnToPositionOnPath(float2)
}
// MARK: Properties
@ -36,12 +36,12 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
guard isGood != oldValue else { return }
// 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 animationComponent = componentForClass(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 intelligenceComponent = component(ofType: IntelligenceComponent.self) else { fatalError("TaskBots must have an intelligence component.") }
guard let animationComponent = component(ofType: AnimationComponent.self) else { fatalError("TaskBots must have an animation 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`.
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood)
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood: isGood)
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
if isGood {
@ -49,16 +49,16 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
The `TaskBot` just turned from "bad" to "good".
Set its mandate to `.ReturnToPositionOnPath` for the closest point on its "good" patrol path.
*/
let closestPointOnGoodPath = closestPointOnPath(goodPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnGoodPath))
let closestPointOnGoodPath = closestPointOnPath(path: goodPathPoints)
mandate = .returnToPositionOnPath(float2(closestPointOnGoodPath))
if self is FlyingBot {
// Enter the `FlyingBotBlastState` so it performs a curing blast.
intelligenceComponent.stateMachine.enterState(FlyingBotBlastState.self)
intelligenceComponent.stateMachine.enter(FlyingBotBlastState.self)
}
else {
// 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.
@ -73,8 +73,8 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
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.
*/
let closestPointOnBadPath = closestPointOnPath(badPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath))
let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
// Update the animation component to use the "bad" animations.
animationComponent.animations = badAnimations
@ -83,7 +83,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
chargeComponent.charge = chargeComponent.maximumCharge
// 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`.
var behaviorForCurrentMandate: GKBehavior {
// 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()
}
@ -113,28 +113,28 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
let debugColor: SKColor
switch mandate {
case .FollowGoodPatrolPath, .FollowBadPatrolPath:
case .followGoodPatrolPath, .followBadPatrolPath:
let pathPoints = isGood ? goodPathPoints : badPathPoints
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
// Patrol paths are always closed loops, so the debug drawing of the path should cycle back round to the start.
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
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.redColor()
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.red
case let .ReturnToPositionOnPath(position):
case let .returnToPositionOnPath(position):
radius = GameplayConfiguration.TaskBot.returnToPatrolPathRadius
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, returningToPoint: position, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.yellowColor()
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, returningToPoint: position, pathRadius: radius, inScene: levelScene)
debugColor = SKColor.yellow
}
if levelScene.debugDrawingEnabled {
drawDebugPath(debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius)
drawDebugPath(path: debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius)
}
else {
debugNode.removeAllChildren()
@ -155,13 +155,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
/// The `GKAgent` associated with this `TaskBot`.
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
}
/// The `RenderComponent` associated with this `TaskBot`.
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
}
@ -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,
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()
@ -195,7 +195,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
agent.delegate = self
// 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.mass = GameplayConfiguration.TaskBot.agentMass
agent.radius = GameplayConfiguration.TaskBot.agentRadius
@ -224,6 +224,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
rulesComponent.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: GKAgentDelegate
func agentWillUpdate(_: GKAgent) {
@ -243,13 +247,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
}
func agentDidUpdate(_: GKAgent) {
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return }
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return }
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
if intelligenceComponent.stateMachine.currentState is TaskBotAgentControlledState {
// `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.
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.
let huntPlayerBotRaw = [
// "Number of bad TaskBots is high" AND "Player is nearby".
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageHigh.rawValue,
Fact.PlayerBotNear.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
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".
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageMedium.rawValue,
Fact.PlayerBotNear.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.playerBotNear.rawValue as AnyObject
]),
/*
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"
AND "nearest good `TaskBot` is at medium proximity".
*/
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageHigh.rawValue,
Fact.PlayerBotMedium.rawValue,
Fact.GoodTaskBotMedium.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
Fact.playerBotMedium.rawValue as AnyObject,
Fact.goodTaskBotMedium.rawValue as AnyObject
]),
/*
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.
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.
let huntTaskBotRaw = [
// "Number of bad TaskBots is low" AND "Nearest good `TaskBot` is nearby".
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageLow.rawValue,
Fact.GoodTaskBotNear.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageLow.rawValue as AnyObject,
Fact.goodTaskBotNear.rawValue as AnyObject
]),
/*
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".
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageMedium.rawValue,
Fact.GoodTaskBotNear.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.goodTaskBotNear.rawValue as AnyObject
]),
/*
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"
AND "Nearest good TaskBot is at medium proximity".
*/
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageLow.rawValue,
Fact.PlayerBotMedium.rawValue,
Fact.GoodTaskBotMedium.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageLow.rawValue as AnyObject,
Fact.playerBotMedium.rawValue as AnyObject,
Fact.goodTaskBotMedium.rawValue as AnyObject
]),
/*
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
"Nearest good `TaskBot` is at medium proximity".
*/
ruleSystem.minimumGradeForFacts([
Fact.BadTaskBotPercentageMedium.rawValue,
Fact.PlayerBotFar.rawValue,
Fact.GoodTaskBotMedium.rawValue
ruleSystem.minimumGrade(forFacts: [
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
Fact.playerBotFar.rawValue as AnyObject,
Fact.goodTaskBotMedium.rawValue as AnyObject
]),
/*
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.
let huntTaskBot = huntTaskBotRaw.reduce(0.0, combine: max)
let huntTaskBot = huntTaskBotRaw.reduce(0.0, max)
if huntPlayerBot >= huntTaskBot && huntPlayerBot > 0.0 {
// 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 }
mandate = .HuntAgent(playerBotAgent)
mandate = .huntAgent(playerBotAgent)
}
else if huntTaskBot > huntPlayerBot {
// 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 {
// The rules provided no motivation to hunt, so patrol in the "bad" state.
switch mandate {
case .FollowBadPatrolPath:
case .followBadPatrolPath:
// The `TaskBot` is already on its "bad" patrol path, so no update is needed.
break
default:
// Send the `TaskBot` to the closest point on its "bad" patrol path.
let closestPointOnBadPath = closestPointOnPath(badPathPoints)
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath))
let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
}
}
}
// MARK: ContactableType
func contactWithEntityDidBegin(entity: GKEntity) {}
func contactWithEntityDidBegin(_ entity: GKEntity) {}
func contactWithEntityDidEnd(entity: GKEntity) {}
func contactWithEntityDidEnd(_ entity: GKEntity) {}
// MARK: Convenience
@ -433,7 +437,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
func closestPointOnPath(path: [CGPoint]) -> CGPoint {
// Find the closest point to the `TaskBot`.
let taskBotPosition = agent.position
let closestPoint = path.minElement {
let closestPoint = path.min {
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.
func updateAgentRotationToMatchTaskBotOrientation() {
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return }
guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
agent.rotation = Float(orientationComponent.zRotation)
}
@ -500,7 +504,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
let deltaX = next.x - current.x
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.fillColor = fillColor
rectNode.zRotation = atan(deltaY / deltaX)

View File

@ -46,11 +46,11 @@ class GameControllerInputSource: ControlInputSourceType {
self.delegate?.controlInputSourceDidBeginAttacking(self)
#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)
}
#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)
}
#endif

View File

@ -33,7 +33,7 @@ final class GameInput {
var isGameControllerConnected: Bool {
var isGameControllerConnected = false
dispatch_sync(controlsQueue) {
controlsQueue.sync {
isGameControllerConnected = (self.secondaryControlInputSource != nil) || (self.nativeControlInputSource is GameControllerInputSource)
}
return isGameControllerConnected
@ -49,13 +49,13 @@ final class GameInput {
weak var delegate: GameInputDelegate? {
didSet {
// 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.
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
init(nativeControlInputSource: ControlInputSourceType) {
@ -68,7 +68,7 @@ final class GameInput {
init() {
// Search for paired game controllers.
for pairedController in GCController.controllers() {
updateWithGameController(pairedController)
update(withGameController: pairedController)
}
registerForGameControllerNotifications()
@ -77,17 +77,17 @@ final class GameInput {
/// Register for `GCGameController` pairing notifications.
func registerForGameControllerNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(_:)), name: GCControllerDidConnectNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(_:)), name: GCControllerDidDisconnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(notification:)), name: NSNotification.Name.GCControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(notification:)), name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidConnectNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidDisconnectNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidConnect, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
}
func updateWithGameController(gameController: GCController) {
dispatch_sync(controlsQueue) {
func update(withGameController gameController: GCController) {
controlsQueue.sync {
#if os(tvOS)
// Assign a controller to the `nativeControlInputSource` if one does not already exist.
if self.nativeControlInputSource == nil {
@ -103,7 +103,7 @@ final class GameInput {
if self.secondaryControlInputSource == nil {
let gameControllerInputSource = GameControllerInputSource(gameController: gameController)
self.secondaryControlInputSource = gameControllerInputSource
gameController.playerIndex = .Index1
gameController.playerIndex = .index1
}
}
}
@ -113,8 +113,8 @@ final class GameInput {
@objc func handleControllerDidConnectNotification(notification: NSNotification) {
let connectedGameController = notification.object as! GCController
updateWithGameController(connectedGameController)
delegate?.gameInputDidUpdateControlInputSources(self)
update(withGameController: connectedGameController)
delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
}
@objc func handleControllerDidDisconnectNotification(notification: NSNotification) {
@ -122,16 +122,16 @@ final class GameInput {
// Check if the player was being controlled by the disconnected controller.
if secondaryControlInputSource?.gameController == disconnectedGameController {
dispatch_sync(controlsQueue) {
controlsQueue.sync {
self.secondaryControlInputSource = nil
}
// Check for any other connected controllers.
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)
/// 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.
static let chargeLossPerSecond = 90.0
/// 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 {
@ -62,24 +62,24 @@ struct GameplayConfiguration {
static let maximumCharge = 100.0
/// 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.
static let rechargeDelayWhenInactive: NSTimeInterval = 2.0
static let rechargeDelayWhenInactive: TimeInterval = 2.0
/// The amount of charge that the `PlayerBot` gains per second when recharging.
static let rechargeAmountPerSecond = 10.0
/// 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 {
/// 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.
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.
static let thresholdProximityToPatrolPathStartPoint: Float = 50.0
@ -118,10 +118,10 @@ struct GameplayConfiguration {
static let agentOffset = physicsBodyOffset
/// 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.
static let maxPredictionTimeForObstacleAvoidance: NSTimeInterval = 1.0
static let maxPredictionTimeForObstacleAvoidance: TimeInterval = 1.0
/// The radius of the path along which an agent patrols.
static let patrolPathRadius: Float = 10.0
@ -136,10 +136,10 @@ struct GameplayConfiguration {
static let pathfindingGraphBufferRadius: Float = 30.0
/// 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.
static let zappedStateDuration: NSTimeInterval = 0.75
static let zappedStateDuration: TimeInterval = 0.75
}
struct FlyingBot {
@ -153,10 +153,10 @@ struct GameplayConfiguration {
static let blastChargeLossPerSecond = 25.0
/// 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.
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.
static let blastEmitterOffset = CGPoint(x: 0.0, y: 20.0)
@ -188,7 +188,7 @@ struct GameplayConfiguration {
static let angularSpeedMultiplierWhenAttacking: CGFloat = 2.5
/// 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.
static let beamTargetOffset = CGPoint(x: 0.0, y: 40.0)
@ -224,10 +224,10 @@ struct GameplayConfiguration {
struct SceneManager {
/// 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.
static let progressSceneTransitionDuration: NSTimeInterval = 0.5
static let progressSceneTransitionDuration: TimeInterval = 0.5
}
struct Timer {
@ -245,4 +245,4 @@ struct GameplayConfiguration {
static let paddingSize: CGFloat = 0.2
#endif
}
}
}

View File

@ -13,60 +13,60 @@ class HomeEndScene: BaseScene {
/// Returns the background node from the scene.
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).
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.
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.
private var sceneLoaderNotificationObservers = [AnyObject]()
private var sceneLoaderNotificationObservers = [Any]()
// MARK: Deinitialization
deinit {
// Deregister for scene loader notifications.
for observer in sceneLoaderNotificationObservers {
NSNotificationCenter.defaultCenter().removeObserver(observer)
NotificationCenter.default.removeObserver(observer)
}
}
// MARK: Scene Life Cycle
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
override func didMove(to view: SKView) {
super.didMove(to: view)
#if os(iOS)
screenRecorderButton?.isSelected = screenRecordingToggleEnabled
#else
screenRecorderButton?.hidden = true
screenRecorderButton?.isHidden = true
#endif
// Enable focus based navigation.
focusChangesEnabled = true
registerForNotifications()
centerCameraOnPoint(backgroundNode!.position)
centerCameraOnPoint(point: backgroundNode!.position)
// 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 !(levelLoader.stateMachine.currentState is SceneLoaderResourcesReadyState) {
proceedButton?.alpha = 0.0
proceedButton?.userInteractionEnabled = false
proceedButton?.isUserInteractionEnabled = false
screenRecorderButton?.alpha = 0.0
screenRecorderButton?.userInteractionEnabled = false
screenRecorderButton?.isUserInteractionEnabled = false
}
}
@ -75,21 +75,21 @@ class HomeEndScene: BaseScene {
guard sceneLoaderNotificationObservers.isEmpty else { return }
// 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
// Show the proceed button if the `sceneLoader` pertains to a `LevelScene`.
if sceneLoader.sceneMetadata.sceneType is LevelScene.Type {
// Allow the proceed and screen to be tapped or clicked.
self.proceedButton?.userInteractionEnabled = true
self.screenRecorderButton?.userInteractionEnabled = true
self.proceedButton?.isUserInteractionEnabled = true
self.screenRecorderButton?.isUserInteractionEnabled = true
// 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.
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.
self.resetFocus()
}
@ -97,8 +97,8 @@ class HomeEndScene: BaseScene {
}
// Register for scene loader notifications.
let completeNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidCompleteNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: handleSceneLoaderNotification)
let failNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidFailNotification, 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 = 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`.
sceneLoaderNotificationObservers += [completeNotification, failNotification]

View File

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

View File

@ -18,8 +18,8 @@ struct LevelConfiguration {
/// The different types of `TaskBot` that can exist in a level.
enum Locomotion {
case Ground
case Flying
case ground
case flying
}
let locomotion: Locomotion
@ -41,10 +41,10 @@ struct LevelConfiguration {
init(botConfigurationInfo: [String: AnyObject]) {
switch botConfigurationInfo["locomotion"] as! String {
case "ground":
locomotion = .Ground
locomotion = .ground
case "flying":
locomotion = .Flying
locomotion = .flying
default:
fatalError("Unknown locomotion found while parsing `taskBot` data")
@ -81,8 +81,8 @@ struct LevelConfiguration {
}
/// The time limit (in seconds) for this level.
var timeLimit: NSTimeInterval {
return configurationInfo["timeLimit"] as! NSTimeInterval
var timeLimit: TimeInterval {
return configurationInfo["timeLimit"] as! TimeInterval
}
/// The factor used to normalize distances between characters for 'fuzzy' logic.
@ -95,9 +95,9 @@ struct LevelConfiguration {
init(fileName: String) {
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.
let botConfigurations = configurationInfo["taskBotConfigurations"] as! [[String: AnyObject]]
@ -107,4 +107,4 @@ struct LevelConfiguration {
initialPlayerBotOrientation = CompassDirection(string: configurationInfo["initialPlayerBotOrientation"] as! String)
}
}
}

View File

@ -45,7 +45,7 @@ extension LevelScene {
for destination in node.connectedNodes as! [GKGraphNode2D] {
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.lineWidth = 2.0
shapeNode.zPosition = -1
@ -76,22 +76,22 @@ extension SKSpriteNode {
if newValue == true {
let bufferRadius = CGFloat(GameplayConfiguration.TaskBot.pathfindingGraphBufferRadius)
let bufferFrame = frame.insetBy(dx: -bufferRadius, dy: -bufferRadius)
let bufferedShape = SKShapeNode(rectOfSize: bufferFrame.size)
bufferedShape.fillColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.2)
bufferedShape.strokeColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.4)
let bufferedShape = SKShapeNode(rectOf: bufferFrame.size)
bufferedShape.fillColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.2))
bufferedShape.strokeColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.4))
bufferedShape.name = debugBufferShapeName
addChild(bufferedShape)
}
else {
// Remove any existing debug shape layer if we are turning off debug drawing for this node.
guard let debugBufferShape = childNodeWithName(debugBufferShapeName) else { return }
removeChildrenInArray([debugBufferShape])
guard let debugBufferShape = childNode(withName: debugBufferShapeName) else { return }
removeChildren(in: [debugBufferShape])
}
}
get {
// 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
being presented to determine if the game should be paused.
*/
override var paused: Bool {
override var isPaused: Bool {
didSet {
if overlay != nil {
worldNode.paused = true
worldNode.isPaused = true
}
}
}
/// Platform specific notifications about the app becoming inactive.
var pauseNotificationNames: [String] {
private var pauseNotificationNames: [NSNotification.Name] {
#if os(OSX)
return [
NSApplicationWillResignActiveNotification,
NSWindowDidMiniaturizeNotification
.NSApplicationWillResignActive,
.NSWindowDidMiniaturize
]
#else
return [
UIApplicationWillResignActiveNotification
NSNotification.Name.UIApplicationWillResignActive
]
#endif
}
@ -50,17 +50,17 @@ extension LevelScene {
*/
func registerForPauseNotifications() {
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() {
stateMachine.enterState(LevelScenePauseState.self)
stateMachine.enter(LevelScenePauseState.self)
}
func unregisterForPauseNotifications() {
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
// 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.
var nodeName: String {
switch self {
case .Board: return "board"
case .Debug: return "debug"
case .Shadows: return "shadows"
case .Obstacles: return "obstacles"
case .Characters: return "characters"
case .AboveCharacters: return "above_characters"
case .Top: return "top"
case .board: return "board"
case .debug: return "debug"
case .shadows: return "shadows"
case .obstacles: return "obstacles"
case .characters: return "characters"
case .aboveCharacters: return "above_characters"
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 {
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 {
@ -45,14 +45,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
var worldLayerNodes = [WorldLayer: SKNode]()
var worldNode: SKNode {
return childNodeWithName("world")!
return childNode(withName: "world")!
}
let playerBot = PlayerBot()
var entities = Set<GKEntity>()
var lastUpdateTimeInterval: NSTimeInterval = 0
let maximumUpdateDeltaTime: NSTimeInterval = 1.0 / 60.0
var lastUpdateTimeInterval: TimeInterval = 0
let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
var levelConfiguration: LevelConfiguration!
@ -78,7 +78,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
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
@ -127,8 +127,8 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: Scene Life Cycle
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
override func didMove(to view: SKView) {
super.didMove(to: view)
// Load the level's configuration from the level data file.
levelConfiguration = LevelConfiguration(fileName: sceneManager.currentSceneMetadata!.fileName)
@ -152,24 +152,24 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
// Move to the active state, starting the level timer.
stateMachine.enterState(LevelSceneActiveState.self)
stateMachine.enter(LevelSceneActiveState.self)
// Add the debug layers to the scene.
addNode(graphLayer, toWorldLayer: .Debug)
addNode(debugObstacleLayer, toWorldLayer: .Debug)
addNode(node: graphLayer, toWorldLayer: .debug)
addNode(node: debugObstacleLayer, toWorldLayer: .debug)
// Configure the `timerNode` and add it to the camera node.
timerNode.zPosition = WorldLayer.AboveCharacters.rawValue
timerNode.fontColor = SKColor.whiteColor()
timerNode.zPosition = WorldLayer.aboveCharacters.rawValue
timerNode.fontColor = SKColor.white
timerNode.fontName = GameplayConfiguration.Timer.fontName
timerNode.horizontalAlignmentMode = .Center
timerNode.verticalAlignmentMode = .Top
timerNode.horizontalAlignmentMode = .center
timerNode.verticalAlignmentMode = .top
scaleTimerNode()
camera!.addChild(timerNode)
// A convenience function to find node locations given a set of node names.
func nodePointsFromNodeNames(nodeNames: [String]) -> [CGPoint] {
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)!
let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
return nodeNames.map {
charactersNode[$0].first!.position
}
@ -180,20 +180,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
let taskBot: TaskBot
// Find the locations of the nodes that define the `TaskBot`'s "good" and "bad" patrol paths.
let goodPathPoints = nodePointsFromNodeNames(taskBotConfiguration.goodPathNodeNames)
let badPathPoints = nodePointsFromNodeNames(taskBotConfiguration.badPathNodeNames)
let goodPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.goodPathNodeNames)
let badPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.badPathNodeNames)
// Create the appropriate type `TaskBot` (ground or flying).
switch taskBotConfiguration.locomotion {
case .Flying:
case .flying:
taskBot = FlyingBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
case .Ground:
case .ground:
taskBot = GroundBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
}
// 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")
}
orientationComponent.compassDirection = taskBotConfiguration.initialOrientation
@ -204,10 +204,10 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
taskBot.updateAgentPositionToMatchNodePosition()
// 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.
addNode(taskBot.debugNode, toWorldLayer: .Debug)
addNode(node: taskBot.debugNode, toWorldLayer: .debug)
}
#if os(iOS)
@ -223,7 +223,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
#endif
}
override func didChangeSize(oldSize: CGSize) {
override func didChangeSize(_ oldSize: CGSize) {
super.didChangeSize(oldSize)
/*
@ -239,7 +239,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: SKScene Processing
/// Called before each frame is rendered.
override func update(currentTime: NSTimeInterval) {
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
// 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`
and `overlay` nodes to remain interactive.
*/
if worldNode.paused { return }
if worldNode.isPaused { return }
// Update the level's state machine.
stateMachine.updateWithDeltaTime(deltaTime)
stateMachine.update(deltaTime: deltaTime)
/*
Update each component system.
@ -273,13 +273,13 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
and was determined when the `componentSystems` array was instantiated.
*/
for componentSystem in componentSystems {
componentSystem.updateWithDeltaTime(deltaTime)
componentSystem.update(deltaTime: deltaTime)
}
}
override func didFinishUpdate() {
// 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.
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.
let ySortedEntities = entities.sort {
let nodeA = $0.0.componentForClass(RenderComponent.self)!.node
let nodeB = $0.1.componentForClass(RenderComponent.self)!.node
let ySortedEntities = entities.sorted {
let nodeA = $0.0.component(ofType: RenderComponent.self)!.node
let nodeB = $0.1.component(ofType: RenderComponent.self)!.node
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.
var characterZPosition = WorldLayer.zSpacePerCharacter
for entity in ySortedEntities {
let node = entity.componentForClass(RenderComponent.self)!.node
let node = entity.component(ofType: RenderComponent.self)!.node
node.zPosition = characterZPosition
// Use a large enough z-position increment to leave space for emitter effects.
@ -309,14 +309,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) {
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
@objc(didBeginContact:) func didBegin(_ contact: SKPhysicsContact) {
handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
ContactNotifiableType.contactWithEntityDidBegin(otherEntity)
}
}
func didEndContact(contact: SKPhysicsContact) {
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
@objc(didEndContact:) func didEnd(_ contact: SKPhysicsContact) {
handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
ContactNotifiableType.contactWithEntityDidEnd(otherEntity)
}
}
@ -329,20 +329,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
let colliderTypeB = ColliderType(rawValue: contact.bodyB.categoryBitMask)
// Determine which `ColliderType` should be notified of the contact.
let aWantsCallback = colliderTypeA.notifyOnContactWithColliderType(colliderTypeB)
let bWantsCallback = colliderTypeB.notifyOnContactWithColliderType(colliderTypeA)
let aWantsCallback = colliderTypeA.notifyOnContactWith(colliderTypeB)
let bWantsCallback = colliderTypeB.notifyOnContactWith(colliderTypeA)
// Make sure that at least one of the entities wants to handle this contact.
assert(aWantsCallback || bWantsCallback, "Unhandled physics contact - A = \(colliderTypeA), B = \(colliderTypeB)")
let entityA = (contact.bodyA.node as? EntityNode)?.entity
let entityB = (contact.bodyB.node as? EntityNode)?.entity
let entityA = contact.bodyA.node?.entity
let entityB = contact.bodyB.node?.entity
/*
If `entityA` is a notifiable type and `colliderTypeA` specifies that it should be notified
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)
}
@ -350,7 +350,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
If `entityB` is a notifiable type and `colliderTypeB` specifies that it should be notified
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)
}
}
@ -380,19 +380,19 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
entities.insert(entity)
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 let renderNode = entity.componentForClass(RenderComponent.self)?.node {
addNode(renderNode, toWorldLayer: .Characters)
if let renderNode = entity.component(ofType: RenderComponent.self)?.node {
addNode(node: renderNode, toWorldLayer: .characters)
/*
If the entity has a `ShadowComponent`, add its shadow node to the scene.
Constrain the `ShadowComponent`'s node to the `RenderComponent`'s node.
*/
if let shadowNode = entity.componentForClass(ShadowComponent.self)?.node {
addNode(shadowNode, toWorldLayer: .Shadows)
if let shadowNode = entity.component(ofType: ShadowComponent.self)?.node {
addNode(node: shadowNode, toWorldLayer: .shadows)
// Constrain the shadow node's position to the render node.
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`
to the scene. Constrain the `ChargeBar` to the `RenderComponent`'s node.
*/
if let chargeBar = entity.componentForClass(ChargeComponent.self)?.chargeBar {
addNode(chargeBar, toWorldLayer: .AboveCharacters)
if let chargeBar = entity.component(ofType: ChargeComponent.self)?.chargeBar {
addNode(node: chargeBar, toWorldLayer: .aboveCharacters)
// Constrain the `ChargeBar`'s node position to the render node.
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 let intelligenceComponent = entity.componentForClass(IntelligenceComponent.self) {
if let intelligenceComponent = entity.component(ofType: IntelligenceComponent.self) {
intelligenceComponent.enterInitialState()
}
}
@ -437,14 +437,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: GameInputDelegate
override func gameInputDidUpdateControlInputSources(gameInput: GameInput) {
super.gameInputDidUpdateControlInputSources(gameInput)
super.gameInputDidUpdateControlInputSources(gameInput: gameInput)
/*
Update the player's `controlInputSources` to delegate input
to the playerBot's `InputComponent`.
*/
for controlInputSource in gameInput.controlInputSources {
controlInputSource.delegate = playerBot.componentForClass(InputComponent.self)
controlInputSource.delegate = playerBot.component(ofType: InputComponent.self)
}
#if os(iOS)
@ -455,17 +455,17 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
// MARK: ControlInputSourceGameStateDelegate
override func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) {
override func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
if stateMachine.currentState is LevelSceneActiveState {
stateMachine.enterState(LevelScenePauseState.self)
stateMachine.enter(LevelScenePauseState.self)
}
else {
stateMachine.enterState(LevelSceneActiveState.self)
stateMachine.enter(LevelSceneActiveState.self)
}
}
#if DEBUG
override func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) {
override func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
debugDrawingEnabled = !debugDrawingEnabled
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 {
stateMachine.enterState(LevelSceneSuccessState.self)
stateMachine.enter(LevelSceneSuccessState.self)
}
}
override func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) {
override func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
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) {
switch button.buttonIdentifier! {
case .Resume:
stateMachine.enterState(LevelSceneActiveState.self)
case .resume:
stateMachine.enter(LevelSceneActiveState.self)
default:
// 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.
let zeroRange = SKRange(constantValue: 0.0)
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.
@ -527,7 +527,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
Find the root "board" node in the scene (the container node for
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.
@ -583,11 +583,11 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
private func beamInPlayerBot() {
// Find the location of the player's initial position.
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)!
let transporterCoordinate = charactersNode.childNodeWithName("transporter_coordinate")!
let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
let transporterCoordinate = charactersNode.childNode(withName: "transporter_coordinate")!
// 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")
}
orientationComponent.compassDirection = levelConfiguration.initialPlayerBotOrientation
@ -601,7 +601,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
setCameraConstraints()
// 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
var timeRemaining: NSTimeInterval = 0.0
var timeRemaining: TimeInterval = 0.0
/*
A formatter for individual date components used to provide an appropriate
display value for the timer.
*/
let timeRemainingFormatter: NSDateComponentsFormatter = {
let formatter = NSDateComponentsFormatter()
formatter.zeroFormattingBehavior = .Pad
formatter.allowedUnits = [.Minute, .Second]
let timeRemainingFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.zeroFormattingBehavior = .pad
formatter.allowedUnits = [.minute, .second]
return formatter
}()
@ -33,7 +33,7 @@ class LevelSceneActiveState: GKState {
let components = NSDateComponents()
components.second = Int(max(0.0, timeRemaining))
return timeRemainingFormatter.stringFromDateComponents(components)!
return timeRemainingFormatter.string(from: components as DateComponents)!
}
// MARK: Initializers
@ -46,14 +46,14 @@ class LevelSceneActiveState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
levelScene.timerNode.text = timeRemainingString
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
override func update(deltaTime seconds: TimeInterval) {
super.update(deltaTime: seconds)
// Subtract the elapsed time from the remaining time.
timeRemaining -= seconds
@ -72,15 +72,15 @@ class LevelSceneActiveState: GKState {
if allTaskBotsAreGood {
// 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 {
// 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 {
case is LevelScenePauseState.Type, is LevelSceneFailState.Type, is LevelSceneSuccessState.Type:
return true

View File

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

View File

@ -27,50 +27,50 @@ class LevelSceneOverlayState: GKState {
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
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)
}
}
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
#if os(iOS)
// 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 let viewRecordedContentButton = buttonWithIdentifier(.ViewRecordedContent) {
viewRecordedContentButton.hidden = true
if let viewRecordedContentButton = button(withIdentifier: .viewRecordedContent) {
viewRecordedContentButton.isHidden = true
// 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.
let recordingEnabledAndPreviewAvailable = self.levelScene.screenRecordingToggleEnabled && self.levelScene.previewViewController != nil
viewRecordedContentButton.hidden = !recordingEnabledAndPreviewAvailable
viewRecordedContentButton.isHidden = !recordingEnabledAndPreviewAvailable
}
}
}
#else
// Hide replay buttons on OSX and tvOS.
buttonWithIdentifier(.ScreenRecorderToggle)?.hidden = true
buttonWithIdentifier(.ViewRecordedContent)?.hidden = true
button(withIdentifier: .screenRecorderToggle)?.isHidden = true
button(withIdentifier: .viewRecordedContent)?.isHidden = true
#endif
// Provide the levelScene with a reference to the overlay node.
levelScene.overlay = overlay
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
levelScene.overlay = nil
@ -84,7 +84,7 @@ class LevelSceneOverlayState: GKState {
// MARK: Convenience
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? {
return overlay.contentNode.childNodeWithName("//\(identifier.rawValue)") as? ButtonNode
func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
return overlay.contentNode.childNode(withName: "//\(identifier.rawValue)") as? ButtonNode
}
}
}

View File

@ -18,19 +18,19 @@ class LevelScenePauseState: LevelSceneOverlayState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
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
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
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
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
if let inputComponent = levelScene.playerBot.componentForClass(InputComponent.self) {
if let inputComponent = levelScene.playerBot.component(ofType: InputComponent.self) {
inputComponent.isEnabled = false
}
// 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
}
}

View File

@ -9,20 +9,20 @@
import Foundation
class LoadResourcesOperation: Operation, NSProgressReporting {
class LoadResourcesOperation: SceneOperation, ProgressReporting {
// MARK: Properties
/// A class that conforms to the `ResourceLoadableType` protocol.
let loadableType: ResourceLoadableType.Type
let progress: NSProgress
let progress: Progress
// MARK: Initialization
init(loadableType: ResourceLoadableType.Type) {
self.loadableType = loadableType
progress = NSProgress(totalUnitCount: 1)
progress = Progress(totalUnitCount: 1)
super.init()
}
@ -30,9 +30,9 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
override func start() {
// 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`.
cancel()
return
@ -45,10 +45,10 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
}
// Mark the operation as executing.
state = .Executing
state = .executing
// Begin loading the resources.
loadableType.loadResourcesWithCompletionHandler { [unowned self] in
loadableType.loadResources() { [unowned self] in
// Mark the operation as complete once the resources are loaded.
self.finish()
}
@ -56,6 +56,6 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
func finish() {
progress.completedUnitCount = 1
state = .Finished
state = .finished
}
}

View File

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

View File

@ -26,7 +26,7 @@ class BeamNode: SKNode, ResourceLoadableType {
static let lineNodeTemplate: SKSpriteNode = {
let templateScene = SKScene(fileNamed: "BeamLine.sks")!
return templateScene.childNodeWithName("BeamLine") as! SKSpriteNode
return templateScene.childNode(withName: "BeamLine") as! SKSpriteNode
}()
// MARK: Properties
@ -48,21 +48,22 @@ class BeamNode: SKNode, ResourceLoadableType {
override init() {
sourceNode = SKSpriteNode()
sourceNode.size = BeamNode.dotTextureSize
sourceNode.hidden = true
sourceNode.isHidden = true
destinationNode = SKSpriteNode()
destinationNode.size = BeamNode.dotTextureSize
destinationNode.hidden = true
destinationNode.isHidden = true
let arcPath = CGPathCreateMutable()
CGPathAddArc(arcPath, nil, 0.0, 0.0, GameplayConfiguration.Beam.arcLength, GameplayConfiguration.Beam.arcAngle * 0.5, GameplayConfiguration.Beam.arcAngle * -0.5, true)
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0)
let arcPath = CGMutablePath.init()
let center = CGPoint(x: 0.0, y: 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.fillColor = SKColor.blueColor()
debugNode.fillColor = SKColor.blue
debugNode.lineWidth = 0.0
debugNode.alpha = 0.5
debugNode.hidden = true
debugNode.isHidden = true
super.init()
@ -77,9 +78,9 @@ class BeamNode: SKNode, ResourceLoadableType {
// 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.
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 yRange = SKRange(constantValue: target.beamTargetOffset.y)
@ -92,14 +93,14 @@ class BeamNode: SKNode, ResourceLoadableType {
switch state {
case is BeamIdleState:
// Hide the source and destination nodes.
sourceNode.hidden = true
destinationNode.hidden = true
sourceNode.isHidden = true
destinationNode.isHidden = true
// Remove the `lineNode` from the scene.
lineNode?.removeFromParent()
lineNode = nil
debugNode.hidden = true
debugNode.isHidden = true
case is BeamFiringState:
/*
@ -109,38 +110,38 @@ class BeamNode: SKNode, ResourceLoadableType {
*/
if lineNode == nil {
lineNode = BeamNode.lineNodeTemplate.copy() as? SKSpriteNode
lineNode!.hidden = true
lineNode!.isHidden = true
addChild(lineNode!)
}
if let target = target {
// Show the `sourceNode` with the its firing animation.
sourceNode.hidden = false
animateNode(sourceNode, withAction: AnimationActions.source)
sourceNode.isHidden = false
animate(sourceNode, withAction: AnimationActions.source)
// Show the `destinationNode` with its animation.
destinationNode.hidden = false
animateNode(destinationNode, withAction: AnimationActions.destination)
destinationNode.isHidden = false
animate(destinationNode, withAction: AnimationActions.destination)
// Position the `lineNode` and make sure it's visible.
positionLineNodeFrom(source, to: target)
lineNode?.hidden = false
positionLineNode(from: source, to: target)
lineNode?.isHidden = false
}
else {
// Show the `sourceNode` with the its untargeted animation.
sourceNode.hidden = false
animateNode(sourceNode, withAction: AnimationActions.untargetedSource)
sourceNode.isHidden = false
animate(sourceNode, withAction: AnimationActions.untargetedSource)
// Hide the `destinationNode` and `lineNode`.
destinationNode.hidden = true
lineNode?.hidden = true
destinationNode.isHidden = true
lineNode?.isHidden = true
}
// Update the debug node if debug drawing is enabled.
debugNode.hidden = !debugDrawingEnabled
debugNode.isHidden = !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")
}
@ -151,15 +152,17 @@ class BeamNode: SKNode, ResourceLoadableType {
This allows for easier aiming the closer the source is to
the target.
*/
let arcPath = CGPathCreateMutable()
let arcPath = CGMutablePath.init()
// Only draw beam arc if there is a target.
if let target = target {
let distanceRatio = GameplayConfiguration.Beam.arcLength / CGFloat(distance(source.agent.position, target.agent.position))
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)
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0)
let center = CGPoint(x: 0, y: 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
@ -168,17 +171,17 @@ class BeamNode: SKNode, ResourceLoadableType {
case is BeamCoolingState:
// Show the `sourceNode` with the "cooling" animation.
sourceNode.hidden = false
animateNode(sourceNode, withAction: AnimationActions.cooling)
sourceNode.isHidden = false
animate(sourceNode, withAction: AnimationActions.cooling)
// Hide the `destinationNode`.
destinationNode.hidden = true
destinationNode.isHidden = true
// Remove the `lineNode` from the scene.
lineNode?.removeFromParent()
lineNode = nil
debugNode.hidden = true
debugNode.isHidden = true
default:
break
@ -187,23 +190,23 @@ class BeamNode: SKNode, ResourceLoadableType {
// MARK: Convenience
func animateNode(node: SKSpriteNode, withAction action: SKAction) {
func animate(_ node: SKSpriteNode, withAction action: SKAction) {
if runningNodeAnimations[node] != action {
node.runAction(action, withKey: BeamNode.animationActionKey)
node.run(action, withKey: BeamNode.animationActionKey)
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.") }
// Calculate the source and destination positions.
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.")
}
var position = convertPoint(node.position, fromNode: nodeParent)
var position = convert(node.position, from: nodeParent)
position.x += source.antennaOffset.x
position.y += source.antennaOffset.y
@ -211,11 +214,11 @@ class BeamNode: SKNode, ResourceLoadableType {
}()
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.")
}
var position = convertPoint(node.position, fromNode: nodeParent)
var position = convert(node.position, from: nodeParent)
position.x += target.beamTargetOffset.x
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
}
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
let beamAtlasNames = [
"BeamDot",
"BeamCharging"
@ -255,11 +258,11 @@ class BeamNode: SKNode, ResourceLoadableType {
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.untargetedSource = 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.
completionHandler()
@ -272,4 +275,4 @@ class BeamNode: SKNode, ResourceLoadableType {
AnimationActions.untargetedSource = nil
AnimationActions.cooling = nil
}
}
}

View File

@ -16,24 +16,24 @@ protocol ButtonNodeResponderType: class {
/// 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
case resume = "Resume"
case home = "Home"
case proceedToNextScene = "ProceedToNextScene"
case replay = "Replay"
case retry = "Retry"
case cancel = "Cancel"
case screenRecorderToggle = "ScreenRecorderToggle"
case viewRecordedContent = "ViewRecordedContent"
/// Convenience array of all available button identifiers.
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.
var selectedTextureName: String? {
switch self {
case .ScreenRecorderToggle:
case .screenRecorderToggle:
return "ButtonAutoRecordOn"
default:
return nil
@ -71,14 +71,14 @@ class ButtonNode: SKSpriteNode {
// 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)
let scaleAction = SKAction.scale(by: 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)
let colorBlendAction = SKAction.colorize(withColorBlendFactor: newColorBlendFactor, duration: 0.15)
// 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 {
didSet {
if isFocused {
runAction(SKAction.scaleTo(1.08, duration: 0.20))
run(SKAction.scale(to: 1.08, duration: 0.20))
focusRing.alpha = 0.0
focusRing.hidden = false
focusRing.runAction(SKAction.fadeInWithDuration(0.2))
focusRing.isHidden = false
focusRing.run(SKAction.fadeIn(withDuration: 0.2))
}
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.
lazy var focusRing: SKNode = self.childNodeWithName("focusRing")!
lazy var focusRing: SKNode = self.childNode(withName: "focusRing")!
// MARK: Initializers
/// Overridden to support `copyWithZone(_:)`.
/// Overridden to support `copy(with zone:)`.
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
@ -140,7 +140,7 @@ class ButtonNode: SKSpriteNode {
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 {
guard let nodeName = name, let buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else {
fatalError("Unsupported button name found.")
}
self.buttonIdentifier = buttonIdentifier
@ -158,14 +158,14 @@ class ButtonNode: SKSpriteNode {
}
// 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.
userInteractionEnabled = true
isUserInteractionEnabled = true
}
override func copyWithZone(zone: NSZone) -> AnyObject {
let newButton = super.copyWithZone(zone) as! ButtonNode
override func copy(with zone: NSZone? = nil) -> Any {
let newButton = super.copy(with: zone) as! ButtonNode
// Copy the `ButtonNode` specific properties.
newButton.buttonIdentifier = buttonIdentifier
@ -176,9 +176,9 @@ class ButtonNode: SKSpriteNode {
}
func buttonTriggered() {
if userInteractionEnabled {
if isUserInteractionEnabled {
// 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) {
let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey"
guard actionForKey(animationKey) == nil else { return }
guard action(forKey: animationKey) == nil else { return }
// Find the reference action from `ButtonFocusActions.sks`.
let action: SKAction
let theAction: 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")!
case .up: theAction = SKAction(named: "InvalidFocusChange_Up")!
case .down: theAction = SKAction(named: "InvalidFocusChange_Down")!
case .left: theAction = SKAction(named: "InvalidFocusChange_Left")!
case .right: theAction = SKAction(named: "InvalidFocusChange_Right")!
}
runAction(action, withKey: animationKey)
run(theAction, withKey: animationKey)
}
// MARK: Responder
#if os(iOS)
/// UIResponder touch handling.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
isHighlighted = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
isHighlighted = false
// Touch up inside behavior.
if containsTouches(touches) {
if containsTouches(touches: touches) {
buttonTriggered()
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches!, with: event)
isHighlighted = false
}
@ -235,22 +235,22 @@ class ButtonNode: SKSpriteNode {
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)
let touchPoint = touch.location(in: scene)
let touchedNode = scene.atPoint(touchPoint)
return touchedNode === self || touchedNode.inParentHierarchy(self)
}
}
#elseif os(OSX)
/// NSResponder mouse handling.
override func mouseDown(event: NSEvent) {
super.mouseDown(event)
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
isHighlighted = true
}
override func mouseUp(event: NSEvent) {
super.mouseUp(event)
override func mouseUp(with event: NSEvent) {
super.mouseUp(with: event)
isHighlighted = false
@ -261,11 +261,11 @@ class ButtonNode: SKSpriteNode {
}
/// 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.") }
let location = event.locationInNode(scene)
let clickedNode = scene.nodeAtPoint(location)
let location = event.location(in: scene)
let clickedNode = scene.atPoint(location)
return clickedNode === self || clickedNode.inParentHierarchy(self)
}
#endif

View File

@ -19,13 +19,13 @@ class ChargeBar: SKSpriteNode {
static let chargeLevelNodeSize = CGSize(width: 70.0, height: 6.0)
/// 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.
static let backgroundColor = SKColor.blackColor()
static let backgroundColor = SKColor.black
/// The charge level node color.
static let chargeLevelColor = SKColor.greenColor()
static let chargeLevelColor = SKColor.green
}
// MARK: Properties
@ -33,10 +33,10 @@ class ChargeBar: SKSpriteNode {
var level: Double = 1.0 {
didSet {
// Scale the level bar node based on the current health level.
let action = SKAction.scaleXTo(CGFloat(level), duration: Configuration.levelUpdateDuration)
action.timingMode = .EaseInEaseOut
let action = SKAction.scaleX(to: CGFloat(level), duration: Configuration.levelUpdateDuration)
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")
// `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
@ -69,25 +69,25 @@ class ThumbStickNode: SKSpriteNode {
// MARK: UIResponder
override func canBecomeFirstResponder() -> Bool {
override var canBecomeFirstResponder: Bool {
return true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Highlight that the control is being used by adjusting the alpha.
alpha = selectedAlpha
// 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?) {
super.touchesMoved(touches, withEvent: event)
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
// For each touch, calculate the movement of the touchPad.
for touch in touches {
let touchLocation = touch.locationInNode(self)
let touchLocation = touch.location(in: self)
var dx = touchLocation.x - center.x
var dy = touchLocation.y - center.y
@ -111,12 +111,12 @@ class ThumbStickNode: SKSpriteNode {
// Normalize the displacements between [-1.0, 1.0].
let normalizedDx = Float(dx / 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?) {
super.touchesEnded(touches, withEvent: event)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// If the touches set is empty, return immediately.
guard !touches.isEmpty else { return }
@ -124,8 +124,8 @@ class ThumbStickNode: SKSpriteNode {
resetTouchPad()
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches!, with: event)
resetTouchPad()
}
@ -133,10 +133,10 @@ class ThumbStickNode: SKSpriteNode {
func resetTouchPad() {
alpha = normalAlpha
let restoreToCenter = SKAction.moveTo(CGPoint.zero, duration: 0.2)
touchPad.runAction(restoreToCenter)
let restoreToCenter = SKAction.move(to: CGPoint.zero, duration: 0.2)
touchPad.run(restoreToCenter)
delegate?.thumbStickNode(self, isPressed: false)
delegate?.thumbStickNode(self, didUpdateXValue: 0, yValue: 0)
delegate?.thumbStickNode(thumbStickNode: self, isPressed: false)
delegate?.thumbStickNode(thumbStickNode: self, didUpdateXValue: 0, yValue: 0)
}
}

View File

@ -9,7 +9,7 @@
import SpriteKit
import GameplayKit
struct ColliderType: OptionSetType, Hashable, CustomDebugStringConvertible {
struct ColliderType: OptionSet, Hashable, CustomDebugStringConvertible {
// MARK: Static properties
/// 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
notified of contact with the passed `ColliderType`.
*/
func notifyOnContactWithColliderType(colliderType: ColliderType) -> Bool {
func notifyOnContactWith(_ colliderType: ColliderType) -> Bool {
if let requestedContacts = ColliderType.requestedContactNotifications[self] {
return requestedContacts.contains(colliderType)
}

View File

@ -19,15 +19,15 @@ class ProgressScene: BaseScene {
/// Returns the background node from the scene.
override var backgroundNode: SKSpriteNode? {
return childNodeWithName("backgroundNode") as? SKSpriteNode
return childNode(withName: "backgroundNode") as? SKSpriteNode
}
var labelNode: SKLabelNode {
return backgroundNode!.childNodeWithName("label") as! SKLabelNode
return backgroundNode!.childNode(withName: "label") as! SKLabelNode
}
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),
we need to make most of the properties `var` and implicitly unwrapped
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.
@ -45,13 +45,13 @@ class ProgressScene: BaseScene {
var progressBarInitialWidth: CGFloat!
/// Add child progress objects to track downloading and loading states.
var progress: NSProgress? {
var progress: Progress? {
didSet {
// Unregister as an observer on the old value for the "fractionCompleted" property.
oldValue?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressSceneKVOContext)
// 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
assets into memory.
*/
static func progressSceneWithSceneLoader(sceneLoader: SceneLoader) -> ProgressScene {
static func progressScene(withSceneLoader loader: SceneLoader) -> ProgressScene {
// Load the progress scene from its sks file.
let progressScene = ProgressScene(fileNamed: "ProgressScene")!
progressScene.createCamera()
progressScene.setupWithSceneLoader(sceneLoader)
progressScene.setup(withSceneLoader: loader)
// Return the setup progress scene.
return progressScene
}
func setupWithSceneLoader(sceneLoader: SceneLoader) {
func setup(withSceneLoader loader: SceneLoader) {
// 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.
if let progress = sceneLoader.progress {
@ -90,18 +90,18 @@ class ProgressScene: BaseScene {
}
// Register for notifications posted when the `SceneDownloader` fails.
let defaultCenter = NSNotificationCenter.defaultCenter()
downloadFailedObserver = defaultCenter.addObserverForName(SceneLoaderDidFailNotification, object: sceneLoader, queue: NSOperationQueue.mainQueue()) { [unowned self] notification in
guard let sceneLoader = notification.object as? SceneLoader, error = sceneLoader.error else { fatalError("The scene loader has no error to show.") }
let defaultCenter = NotificationCenter.default
downloadFailedObserver = defaultCenter.addObserver(forName: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader, queue: OperationQueue.main) { [unowned self] notification in
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 {
// Unregister as an observer of 'SceneLoaderDownloadFailedNotification' notifications.
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.
@ -110,17 +110,17 @@ class ProgressScene: BaseScene {
// MARK: Scene Life Cycle
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
override func didMove(to view: SKView) {
super.didMove(to: view)
centerCameraOnPoint(backgroundNode!.position)
centerCameraOnPoint(point: backgroundNode!.position)
// Remember the progress bar's initial width. It will change to indicate progress.
progressBarInitialWidth = progressBarNode.frame.width
if let error = sceneLoader.error {
// Show the scene loader's error.
showErrorStateForError(error)
showError(error as NSError)
}
else {
showDefaultState()
@ -128,12 +128,18 @@ class ProgressScene: BaseScene {
}
// 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.
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.
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
guard let progress = self.progress else { return }
// Update the progress bar to match the amount of progress completed.
@ -143,28 +149,25 @@ class ProgressScene: BaseScene {
self.labelNode.text = progress.localizedDescription
}
}
else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
// MARK: ButtonNodeResponderType
override func buttonTriggered(button: ButtonNode) {
switch button.buttonIdentifier! {
case .Retry:
case .retry:
// Set up the progress for a new preparation attempt.
progress = sceneLoader.asynchronouslyLoadSceneForPresentation()
sceneLoader.requestedForPresentation = true
showDefaultState()
case .Cancel:
case .cancel:
/*
Canceling the parent progress propagates the cancellation to the child
progress objects.
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
for more information.
@ -174,39 +177,39 @@ class ProgressScene: BaseScene {
default:
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
super.buttonTriggered(button)
super.buttonTriggered(button: button)
}
}
// MARK: Convenience
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? {
return backgroundNode?.childNodeWithName(identifier.rawValue) as? ButtonNode
func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
return backgroundNode?.childNode(withName: identifier.rawValue) as? ButtonNode
}
func showDefaultState() {
progressBarNode.hidden = false
progressBarNode.isHidden = false
// Only display the "Cancel" button.
buttonWithIdentifier(.Home)?.hidden = true
buttonWithIdentifier(.Retry)?.hidden = true
buttonWithIdentifier(.Cancel)?.hidden = false
button(withIdentifier: .home)?.isHidden = true
button(withIdentifier: .retry)?.isHidden = true
button(withIdentifier: .cancel)?.isHidden = false
// Reset the button focus.
resetFocus()
}
func showErrorStateForError(error: NSError) {
func showError(_ error: NSError) {
// A new progress object will have to be created for any subsequent loading attempts.
progress = nil
// Display "Quit" and "Retry" buttons.
buttonWithIdentifier(.Home)?.hidden = false
buttonWithIdentifier(.Retry)?.hidden = false
buttonWithIdentifier(.Cancel)?.hidden = true
button(withIdentifier: .home)?.isHidden = false
button(withIdentifier: .retry)?.isHidden = false
button(withIdentifier: .cancel)?.isHidden = true
// Hide normal state.
progressBarNode.hidden = true
progressBarNode.isHidden = true
progressBarNode.size.width = 0.0
// Reset the button focus.
@ -217,11 +220,11 @@ class ProgressScene: BaseScene {
labelNode.text = NSLocalizedString("Cancelled", comment: "Displayed when the user cancels loading.")
}
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.")
// 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.") }
let alert = NSAlert(error: error)
alert.beginSheetModalForWindow(window, completionHandler: nil)
alert.beginSheetModal(for: window, completionHandler: nil)
#else
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 alertAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .`default`, handler: nil)
alert.addAction(alertAction)
rootViewController.presentViewController(alert, animated: true, completion: nil)
rootViewController.present(alert, animated: true, completion: nil)
#endif
}
}

View File

@ -10,7 +10,7 @@ import GameplayKit
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 }
/// 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.
static func purgeResources()

View File

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

View File

@ -32,7 +32,7 @@ class LevelStateSnapshot {
/// Returns the `GKAgent2D` for a `PlayerBot` or `TaskBot`.
func agentForEntity(entity: GKEntity) -> GKAgent2D {
if let agent = entity.componentForClass(TaskBotAgent.self) {
if let agent = entity.component(ofType: TaskBotAgent.self) {
return agent
}
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,
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.
let sourceAgent = agentForEntity(sourceEntity)
let sourceAgent = agentForEntity(entity: sourceEntity)
// Iterate over the remaining entities to calculate their distance from the source agent.
for targetIndex in sourceIndex.successor() ..< scene.entities.endIndex {
// Retrieve the target entity for this index.
let targetEntity = scene.entities[targetIndex]
for targetEntity in scene.entities[scene.entities.index(after: sourceIndex) ..< scene.entities.endIndex] {
// Retrieve the `GKAgent` for the target entity.
let targetAgent = agentForEntity(targetEntity)
let targetAgent = agentForEntity(entity: targetEntity)
// Calculate the distance between the two agents.
let dx = targetAgent.position.x - sourceAgent.position.x
@ -140,7 +134,7 @@ class EntitySnapshot {
self.proximityFactor = proximityFactor
// 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
}
@ -152,10 +146,10 @@ class EntitySnapshot {
(if it is targetable) and the nearest "good" `TaskBot`.
*/
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)
}
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)
}

View File

@ -22,19 +22,19 @@ import GameplayKit
enum Fact: String {
// Fuzzy rules pertaining to the proportion of "bad" bots in the level.
case BadTaskBotPercentageLow = "BadTaskBotPercentageLow"
case BadTaskBotPercentageMedium = "BadTaskBotPercentageMedium"
case BadTaskBotPercentageHigh = "BadTaskBotPercentageHigh"
case badTaskBotPercentageLow = "BadTaskBotPercentageLow"
case badTaskBotPercentageMedium = "BadTaskBotPercentageMedium"
case badTaskBotPercentageHigh = "BadTaskBotPercentageHigh"
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the `PlayerBot`.
case PlayerBotNear = "PlayerBotNear"
case PlayerBotMedium = "PlayerBotMedium"
case PlayerBotFar = "PlayerBotFar"
case playerBotNear = "PlayerBotNear"
case playerBotMedium = "PlayerBotMedium"
case playerBotFar = "PlayerBotFar"
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the nearest "good" `TaskBot`.
case GoodTaskBotNear = "GoodTaskBotNear"
case GoodTaskBotMedium = "GoodTaskBotMedium"
case GoodTaskBotFar = "GoodTaskBotFar"
case goodTaskBotNear = "GoodTaskBotNear"
case goodTaskBotMedium = "GoodTaskBotMedium"
case goodTaskBotFar = "GoodTaskBotFar"
}
/// Asserts whether the number of "bad" `TaskBot`s is considered "low".
@ -47,7 +47,7 @@ class BadTaskBotPercentageLowRule: FuzzyTaskBotRule {
// MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageLow) }
init() { super.init(fact: .badTaskBotPercentageLow) }
}
/// Asserts whether the number of "bad" `TaskBot`s is considered "medium".
@ -65,7 +65,7 @@ class BadTaskBotPercentageMediumRule: FuzzyTaskBotRule {
// MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageMedium) }
init() { super.init(fact: .badTaskBotPercentageMedium) }
}
/// Asserts whether the number of "bad" `TaskBot`s is considered "high".
@ -78,7 +78,7 @@ class BadTaskBotPercentageHighRule: FuzzyTaskBotRule {
// MARK: Initializers
init() { super.init(fact: .BadTaskBotPercentageHigh) }
init() { super.init(fact: .badTaskBotPercentageHigh) }
}
/// Asserts whether the `PlayerBot` is considered to be "near" to this `TaskBot`.
@ -93,7 +93,7 @@ class PlayerBotNearRule: FuzzyTaskBotRule {
// 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`.
@ -108,7 +108,7 @@ class PlayerBotMediumRule: FuzzyTaskBotRule {
// MARK: Initializers
init() { super.init(fact: .PlayerBotMedium) }
init() { super.init(fact: .playerBotMedium) }
}
/// Asserts whether the `PlayerBot` is considered to be "far" from this `TaskBot`.
@ -123,7 +123,7 @@ class PlayerBotFarRule: FuzzyTaskBotRule {
// MARK: Initializers
init() { super.init(fact: .PlayerBotFar) }
init() { super.init(fact: .playerBotFar) }
}
// MARK: TaskBot Proximity Rules
@ -140,7 +140,7 @@ class GoodTaskBotNearRule: FuzzyTaskBotRule {
// 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`.
@ -155,7 +155,7 @@ class GoodTaskBotMediumRule: FuzzyTaskBotRule {
// 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`.
@ -170,5 +170,5 @@ class GoodTaskBotFarRule: FuzzyTaskBotRule {
// 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`.
*/
let SceneLoaderDidCompleteNotification = "SceneLoaderDidCompleteNotification"
let SceneLoaderDidFailNotification = "SceneLoaderDidFailNotification"
extension NSNotification.Name {
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.
class SceneLoader {
@ -47,7 +49,7 @@ class SceneLoader {
var scene: BaseScene?
/// The error, if one occurs, from fetching resources.
var error: NSError?
var error: Error?
/**
A parent progress, constructed when `prepareSceneForPresentation()`
@ -55,7 +57,7 @@ class SceneLoader {
`SceneLoaderDownloadingResourcesState` and `SceneLoaderPreparingResourcesState`
states.
*/
var progress: NSProgress? {
var progress: Progress? {
didSet {
guard let progress = progress else { return }
@ -65,7 +67,7 @@ class SceneLoader {
self.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
// 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 operation queue that is preparing the resources.
*/
preparingState.operationQueue.qualityOfService = .UserInteractive
preparingState.operationQueue.qualityOfService = .userInteractive
}
}
}
@ -131,7 +133,7 @@ class SceneLoader {
self.sceneMetadata = sceneMetadata
// Enter the initial state as soon as the scene loader is created.
stateMachine.enterState(SceneLoaderInitialState)
stateMachine.enter(SceneLoaderInitialState.self)
}
#if os(iOS) || os(tvOS)
@ -141,10 +143,10 @@ class SceneLoader {
*/
func downloadResourcesIfNecessary() {
if sceneMetadata.requiresOnDemandResources {
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self)
stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
}
else {
stateMachine.enterState(SceneLoaderResourcesAvailableState.self)
stateMachine.enter(SceneLoaderResourcesAvailableState.self)
}
}
#endif
@ -156,20 +158,20 @@ class SceneLoader {
Note: On iOS there are two distinct steps to loading: downloading on demand resources
-> 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 let progress = progress where !progress.cancelled {
if let progress = progress , !progress.isCancelled {
return progress
}
switch stateMachine.currentState {
case is SceneLoaderResourcesReadyState:
// No additional work needs to be done.
progress = NSProgress(totalUnitCount: 0)
progress = Progress(totalUnitCount: 0)
case is SceneLoaderResourcesAvailableState:
progress = NSProgress(totalUnitCount: 1)
progress = Progress(totalUnitCount: 1)
/*
Begin preparing the scene's resources.
@ -177,17 +179,17 @@ class SceneLoader {
The `SceneLoaderPreparingResourcesState`'s progress is added to the `SceneLoader`s
progress when the operation is started.
*/
stateMachine.enterState(SceneLoaderPreparingResourcesState.self)
stateMachine.enter(SceneLoaderPreparingResourcesState.self)
default:
#if os(iOS) || os(tvOS)
// 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
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self)
stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
guard let bundleResourceRequest = bundleResourceRequest else {
fatalError("In the `SceneLoaderDownloadingResourcesState`, but a valid resource request has not been created.")
@ -219,7 +221,7 @@ class SceneLoader {
progress?.cancel()
// Reset the state machine back to the initial state.
stateMachine.enterState(SceneLoaderInitialState)
stateMachine.enter(SceneLoaderInitialState.self)
// Unpin any on demand resources.
bundleResourceRequest = nil
@ -231,4 +233,4 @@ class SceneLoader {
error = nil
}
#endif
}
}

View File

@ -21,17 +21,17 @@ class SceneLoaderDownloadFailedState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Clear the `sceneLoader`'s progress.
sceneLoader.progress = nil
// 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
}
}
}

View File

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

View File

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

View File

@ -14,13 +14,13 @@ class SceneLoaderPreparingResourcesState: GKState {
unowned let sceneLoader: SceneLoader
/// 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
the resources being loaded. Also supports cancellation.
*/
var progress: NSProgress? {
var progress: Progress? {
didSet {
guard let progress = progress else { return }
@ -47,19 +47,19 @@ class SceneLoaderPreparingResourcesState: GKState {
state machine. Setting the `qualityOfService` as `.Utility` reflects the
fact that this is an important task, but is not blocking the user.
*/
operationQueue.qualityOfService = .Utility
operationQueue.qualityOfService = .utility
}
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Begin loading the scene and associated resources in the background.
loadResourcesAsynchronously()
}
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
// Only valid if the `sceneLoader`'s scene has been loaded.
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
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.
sceneLoader.progress?.addChild(loadingProgress, withPendingUnitCount: 1)
@ -105,10 +105,10 @@ class SceneLoaderPreparingResourcesState: GKState {
loadSceneOperation.completionBlock = { [unowned self] in
// Enter the next state on the main queue.
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
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.")
}
}
@ -142,11 +142,11 @@ class SceneLoaderPreparingResourcesState: GKState {
sceneLoader.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
// Enter the next state on the main queue.
dispatch_async(dispatch_get_main_queue()) {
self.stateMachine!.enterState(SceneLoaderResourcesAvailableState.self)
DispatchQueue.main.async {
self.stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
// 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
override func isValidNextState(stateClass: AnyClass) -> Bool {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is SceneLoaderInitialState.Type, is SceneLoaderPreparingResourcesState.Type:
return true
@ -31,4 +31,4 @@ class SceneLoaderResourcesAvailableState: GKState {
}
}
}
}

View File

@ -21,17 +21,17 @@ class SceneLoaderResourcesReadyState: GKState {
// MARK: GKState Life Cycle
override func didEnterWithPreviousState(previousState: GKState?) {
super.didEnterWithPreviousState(previousState)
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Clear the `sceneLoader`'s progress as loading is complete.
sceneLoader.progress = nil
// 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 {
case is SceneLoaderResourcesAvailableState.Type, is SceneLoaderInitialState.Type:
return true
@ -41,8 +41,8 @@ class SceneLoaderResourcesReadyState: GKState {
}
}
override func willExitWithNextState(nextState: GKState) {
super.willExitWithNextState(nextState)
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
/*
Presenting the scene is a one shot operation. Clear the scene when
@ -50,4 +50,4 @@ class SceneLoaderResourcesReadyState: GKState {
*/
sceneLoader.scene = nil
}
}
}

View File

@ -10,7 +10,7 @@ import SpriteKit
protocol SceneManagerDelegate: class {
// 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
enum SceneIdentifier {
case Home, End
case CurrentLevel, NextLevel
case Level(Int)
case home, end
case currentLevel, nextLevel
case level(Int)
}
// MARK: Properties
@ -49,7 +49,7 @@ final class SceneManager {
// If there is no current scene, we can only transition back to the home scene.
guard let currentSceneMetadata = currentSceneMetadata else { return homeScene }
let index = sceneConfigurationInfo.indexOf(currentSceneMetadata)!
let index = sceneConfigurationInfo.index(of: currentSceneMetadata)!
if index + 1 < sceneConfigurationInfo.count {
// 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
about every scene in the game, and the order in which they should be displayed.
*/
let url = NSBundle.mainBundle().URLForResource("SceneConfiguration", withExtension: "plist")!
let scenes = NSArray(contentsOfURL: url) as! [[String: AnyObject]]
let url = Bundle.main.url(forResource: "SceneConfiguration", withExtension: "plist")!
let scenes = NSArray(contentsOf: url) as! [[String: AnyObject]]
/*
Extract the configuration info dictionary for each possible scene,
@ -116,7 +116,7 @@ final class SceneManager {
deinit {
// Unregister for `SceneLoader` notifications if the observer is still around.
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
to the scene in order to minimize the amount of load time.
*/
func prepareSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) {
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier)
sceneLoader.asynchronouslyLoadSceneForPresentation()
func prepareScene(identifier sceneIdentifier: SceneIdentifier) {
let loader = sceneLoader(forSceneIdentifier: sceneIdentifier)
_ = loader.asynchronouslyLoadSceneForPresentation()
}
/**
@ -139,26 +139,25 @@ final class SceneManager {
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.
*/
func transitionToSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) {
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier)
if sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState {
func transitionToScene(identifier sceneIdentifier: SceneIdentifier) {
let loader = self.sceneLoader(forSceneIdentifier: sceneIdentifier)
if loader.stateMachine.currentState is SceneLoaderResourcesReadyState {
// The scene is ready to be displayed.
presentSceneForSceneLoader(sceneLoader)
presentScene(for: loader)
}
else {
sceneLoader.asynchronouslyLoadSceneForPresentation()
_ = loader.asynchronouslyLoadSceneForPresentation()
/*
Mark the `sceneLoader` as `requestedForPresentation` to automatically
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.
if sceneLoader.requiresProgressSceneForPreparing {
presentProgressScene(sceneLoader)
if loader.requiresProgressSceneForPreparing {
presentProgressScene(for: loader)
}
}
}
@ -166,17 +165,17 @@ final class SceneManager {
// MARK: Scene Presentation
/// Configures and presents a scene.
func presentSceneForSceneLoader(sceneLoader: SceneLoader) {
guard let scene = sceneLoader.scene else {
func presentScene(for loader: SceneLoader) {
guard let scene = loader.scene else {
assertionFailure("Requested presentation for a `sceneLoader` without a valid `scene`.")
return
}
// 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.
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
/*
Provide the scene with a reference to the `SceneLoadingManger`
so that it can coordinate the next scene that should be loaded.
@ -184,7 +183,7 @@ final class SceneManager {
scene.sceneManager = self
// 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)
/*
@ -199,23 +198,23 @@ final class SceneManager {
self.progressScene = nil
// 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.
sceneLoader.stateMachine.enterState(SceneLoaderInitialState.self)
loader.stateMachine.enter(SceneLoaderInitialState.self)
}
}
/// 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.
guard progressScene == nil else { return }
// Create a `ProgressScene` for the scene loader.
progressScene = ProgressScene.progressSceneWithSceneLoader(sceneLoader)
progressScene = ProgressScene.progressScene(withSceneLoader: loader)
progressScene!.sceneManager = self
let transition = SKTransition.doorsCloseHorizontalWithDuration(GameplayConfiguration.SceneManager.progressSceneTransitionDuration)
let transition = SKTransition.doorsCloseHorizontal(withDuration: GameplayConfiguration.SceneManager.progressSceneTransitionDuration)
presentingView.presentScene(progressScene!, transition: transition)
}
@ -233,9 +232,9 @@ final class SceneManager {
}
// Clean up scenes that are no longer accessible.
let allScenes = Set(sceneLoaderForMetadata.keys)
let unreachableScenes = allScenes.subtract(possibleScenes)
var unreachableScenes = Set(sceneLoaderForMetadata.keys)
unreachableScenes.subtract(possibleScenes)
for sceneMetadata in unreachableScenes {
let resourceRequest = sceneLoaderForMetadata[sceneMetadata]!
resourceRequest.purgeResources()
@ -269,11 +268,11 @@ final class SceneManager {
// Avoid reregistering for the notification.
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
// 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 {
fatalError("Received complete notification, but the `stateMachine`'s current state is not ready.")
@ -287,7 +286,7 @@ final class SceneManager {
a progress scene.
*/
if sceneLoader.requestedForPresentation {
self.presentSceneForSceneLoader(sceneLoader)
self.presentScene(for: sceneLoader)
}
// Reset the scene loader's presentation preference.
@ -298,25 +297,25 @@ final class SceneManager {
// MARK: Convenience
/// Returns the scene loader associated with the scene identifier.
func sceneLoaderForSceneIdentifier(sceneIdentifier: SceneIdentifier) -> SceneLoader {
func sceneLoader(forSceneIdentifier sceneIdentifier: SceneIdentifier) -> SceneLoader {
let sceneMetadata: SceneMetadata
switch sceneIdentifier {
case .Home:
case .home:
sceneMetadata = sceneConfigurationInfo.first!
case .CurrentLevel:
case .currentLevel:
guard let currentSceneMetadata = currentSceneMetadata else {
fatalError("Current scene doesn't exist.")
}
sceneMetadata = currentSceneMetadata
case .Level(let number):
case .level(let number):
sceneMetadata = sceneConfigurationInfo[number]
case .NextLevel:
case .nextLevel:
sceneMetadata = nextSceneMetadata
case .End:
case .end:
sceneMetadata = sceneConfigurationInfo.last!
}

View File

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

View File

@ -22,7 +22,7 @@ class SceneOverlay {
init(overlaySceneFileName fileName: String, zPosition: CGFloat) {
// Load the scene and get the overlay node from it.
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.
backgroundNode = SKSpriteNode(color: contentTemplateNode.color, size: contentTemplateNode.size)
@ -34,7 +34,7 @@ class SceneOverlay {
backgroundNode.addChild(contentNode)
// 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.
nativeContentSize = contentNode.size

View File

@ -32,8 +32,8 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
let centerDividerWidth: CGFloat
var hideThumbStickNodes: Bool = false {
didSet {
leftThumbStickNode.hidden = hideThumbStickNodes
rightThumbStickNode.hidden = hideThumbStickNodes
leftThumbStickNode.isHidden = hideThumbStickNodes
rightThumbStickNode.isHidden = hideThumbStickNodes
}
}
@ -59,10 +59,10 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
// Setup pause button.
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)
super.init(texture: nil, color: UIColor.clearColor(), size: frame.size)
super.init(texture: nil, color: UIColor.clear, size: frame.size)
rightThumbStickNode.delegate = self
leftThumbStickNode.delegate = self
@ -74,7 +74,7 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
A `TouchControlInputNode` is designed to receive all user interaction
and forwards it along to the child nodes.
*/
userInteractionEnabled = true
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
@ -121,11 +121,11 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
// MARK: UIResponder
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
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
@ -137,20 +137,20 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
}
if touchPoint.x < 0 {
leftControlTouches.unionInPlace([touch])
leftThumbStickNode.position = pointByCheckingControlOffset(touchPoint)
leftThumbStickNode.touchesBegan([touch], withEvent: event)
leftControlTouches.formUnion([touch])
leftThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
leftThumbStickNode.touchesBegan([touch], with: event)
}
else {
rightControlTouches.unionInPlace([touch])
rightThumbStickNode.position = pointByCheckingControlOffset(touchPoint)
rightThumbStickNode.touchesBegan([touch], withEvent: event)
rightControlTouches.formUnion([touch])
rightThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
rightThumbStickNode.touchesBegan([touch], with: event)
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesMoved(touches, withEvent: event)
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
/*
If the touch pertains to a `thumbStickNode`, pass the
touch along to be handled.
@ -160,44 +160,44 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
over the the `rightThumbStickNode`s zone or vice versa,
while ensuring it is handled by the correct thumb stick.
*/
let movedLeftTouches = touches.intersect(leftControlTouches)
leftThumbStickNode.touchesMoved(movedLeftTouches, withEvent: event)
let movedLeftTouches = touches.intersection(leftControlTouches)
leftThumbStickNode.touchesMoved(movedLeftTouches, with: event)
let movedRightTouches = touches.intersect(rightControlTouches)
rightThumbStickNode.touchesMoved(movedRightTouches, withEvent: event)
let movedRightTouches = touches.intersection(rightControlTouches)
rightThumbStickNode.touchesMoved(movedRightTouches, with: event)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
for touch in touches {
let touchPoint = touch.locationInNode(self)
let touchPoint = touch.location(in: self)
/// Toggle pause when touching in the pause node.
if pauseButton === nodeAtPoint(touchPoint) {
if pauseButton === atPoint(touchPoint) {
gameStateDelegate?.controlInputSourceDidTogglePauseState(self)
break
}
}
let endedLeftTouches = touches.intersect(leftControlTouches)
leftThumbStickNode.touchesEnded(endedLeftTouches, withEvent: event)
leftControlTouches.subtractInPlace(endedLeftTouches)
let endedLeftTouches = touches.intersection(leftControlTouches)
leftThumbStickNode.touchesEnded(endedLeftTouches, with: event)
leftControlTouches.subtract(endedLeftTouches)
let endedRightTouches = touches.intersect(rightControlTouches)
rightThumbStickNode.touchesEnded(endedRightTouches, withEvent: event)
rightControlTouches.subtractInPlace(endedRightTouches)
let endedRightTouches = touches.intersection(rightControlTouches)
rightThumbStickNode.touchesEnded(endedRightTouches, with: event)
rightControlTouches.subtract(endedRightTouches)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
super.touchesCancelled(touches!, with: event)
leftThumbStickNode.resetTouchPad()
rightThumbStickNode.resetTouchPad()
// Keep the set's capacity, because roughly the same number of touch events are being received.
leftControlTouches.removeAll(keepCapacity: true)
rightControlTouches.removeAll(keepCapacity: true)
leftControlTouches.removeAll(keepingCapacity: true)
rightControlTouches.removeAll(keepingCapacity: true)
}
// MARK: Convenience Methods
@ -228,4 +228,4 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
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
Version: 2.2
Version: 2.3
IMPORTANT: This Apple software is supplied to you by Apple
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 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
@ -12,11 +12,11 @@ DemoBots takes advantage of the Xcode 7 scene and actions editor to create detai
### 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
OS X 10.11, iOS 9.0, tvOS 9.0
OS X 10.12, iOS 10.0, tvOS 10.0
## About DemoBots