parent
ab88a73bc7
commit
4dd3ea358f
|
@ -10,7 +10,7 @@ import Cocoa
|
|||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
BIN
DemoBots/DemoBots/Assets/Images.xcassets/FlyingBot.imageset/FlyingBotGoodWalk@2x.png
vendored
Normal file
BIN
DemoBots/DemoBots/Assets/Images.xcassets/FlyingBot.imageset/FlyingBotGoodWalk@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
BIN
DemoBots/DemoBots/Assets/Images.xcassets/GroundBot.imageset/GroundBotBadWalk@2x.png
vendored
Normal file
BIN
DemoBots/DemoBots/Assets/Images.xcassets/GroundBot.imageset/GroundBotBadWalk@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
BIN
DemoBots/DemoBots/Assets/Images.xcassets/PlayerBot.imageset/PlayerBotWalk@2x.png
vendored
Normal file
BIN
DemoBots/DemoBots/Assets/Images.xcassets/PlayerBot.imageset/PlayerBotWalk@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
21
DemoBots/DemoBots/Assets/Images.xcassets/ResistorHorizontal.imageset/Contents.json
vendored
Normal file
21
DemoBots/DemoBots/Assets/Images.xcassets/ResistorHorizontal.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
BIN
DemoBots/DemoBots/Assets/Images.xcassets/ResistorHorizontal.imageset/resistor_horizontal@2x.png
vendored
Normal file
BIN
DemoBots/DemoBots/Assets/Images.xcassets/ResistorHorizontal.imageset/resistor_horizontal@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -30,4 +30,4 @@
|
|||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,272 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2016 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
See LICENSE.txt for this sample’s 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
/*
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2016 Apple Inc. All Rights Reserved.
|
||||
See LICENSE.txt for this sample’s 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!
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import GameplayKit
|
|||
|
||||
protocol ContactNotifiableType {
|
||||
|
||||
func contactWithEntityDidBegin(entity: GKEntity)
|
||||
func contactWithEntityDidBegin(_ entity: GKEntity)
|
||||
|
||||
func contactWithEntityDidEnd(entity: GKEntity)
|
||||
func contactWithEntityDidEnd(_ entity: GKEntity)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue