parent
ab88a73bc7
commit
4dd3ea358f
|
@ -10,7 +10,7 @@ import Cocoa
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,6 @@ class GameViewController: NSViewController {
|
||||||
let skView = view as! SKView
|
let skView = view as! SKView
|
||||||
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
||||||
|
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
|
sceneManager.transitionToScene(identifier: .home)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,18 +24,17 @@ class GameWindowController: NSWindowController, NSWindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: NSWindowDelegate
|
// MARK: NSWindowDelegate
|
||||||
|
func windowWillStartLiveResize(_ notification: Notification) {
|
||||||
func windowWillStartLiveResize(notification: NSNotification) {
|
|
||||||
// Pause the scene while the window resizes if the game is active.
|
// Pause the scene while the window resizes if the game is active.
|
||||||
if let levelScene = view.scene as? LevelScene where levelScene.stateMachine.currentState is LevelSceneActiveState {
|
if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
|
||||||
levelScene.paused = true
|
levelScene.isPaused = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowDidEndLiveResize(notification: NSNotification) {
|
func windowDidEndLiveResize(_ notification: Notification) {
|
||||||
// Un-pause the scene when the window stops resizing if the game is active.
|
// Un-pause the scene when the window stops resizing if the game is active.
|
||||||
if let levelScene = view.scene as? LevelScene where levelScene.stateMachine.currentState is LevelSceneActiveState {
|
if let levelScene = view.scene as? LevelScene, levelScene.stateMachine.currentState is LevelSceneActiveState {
|
||||||
levelScene.paused = false
|
levelScene.isPaused = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178t" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="ilw-vW-taU">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.11" systemVersion="16A201m" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ilw-vW-taU">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
|
||||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--View Controller-->
|
<!--View Controller-->
|
||||||
|
@ -16,25 +18,20 @@
|
||||||
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
|
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
|
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
|
||||||
<rect key="frame" x="20" y="0.0" width="560" height="115"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<animations/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
|
||||||
</view>
|
</view>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
|
||||||
<rect key="frame" x="49" y="115" width="502" height="322"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<animations/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
|
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<animations/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
|
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
|
||||||
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
|
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
|
||||||
|
@ -52,6 +49,6 @@
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="DemoBotsLogo" width="387" height="248"/>
|
<image name="DemoBotsLogo" width="904" height="579"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A178t" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="ilw-vW-taU">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11129.11" systemVersion="16A201m" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ilw-vW-taU">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11103.9"/>
|
||||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Game View Controller-->
|
<!--Game View Controller-->
|
||||||
|
@ -16,25 +18,20 @@
|
||||||
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
|
<viewControllerLayoutGuide type="bottom" id="VoE-AP-xAo"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
|
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="Daa-bP-j0y" customClass="SKView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-qp-hXh">
|
||||||
<rect key="frame" x="20" y="0.0" width="560" height="115"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<animations/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
|
||||||
</view>
|
</view>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="DemoBotsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="9xR-fI-aqA">
|
||||||
<rect key="frame" x="49" y="115" width="502" height="322"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<animations/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
|
<constraint firstAttribute="width" secondItem="9xR-fI-aqA" secondAttribute="height" multiplier="387:248" id="VBp-Dp-xMT"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<animations/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
|
<constraint firstItem="9xR-fI-aqA" firstAttribute="height" secondItem="Daa-bP-j0y" secondAttribute="height" multiplier="0.537" id="1dA-jV-Fmv"/>
|
||||||
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
|
<constraint firstItem="fcf-qp-hXh" firstAttribute="leading" secondItem="Daa-bP-j0y" secondAttribute="leadingMargin" id="RG1-YB-De7"/>
|
||||||
|
@ -55,6 +52,6 @@
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="DemoBotsLogo" width="387" height="248"/>
|
<image name="DemoBotsLogo" width="904" height="579"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -39,23 +39,22 @@ class GameViewController: UIViewController, SceneManagerDelegate {
|
||||||
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
||||||
sceneManager.delegate = self
|
sceneManager.delegate = self
|
||||||
|
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
|
sceneManager.transitionToScene(identifier: .home)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide status bar during game play.
|
// Hide status bar during game play.
|
||||||
override func prefersStatusBarHidden() -> Bool {
|
override var prefersStatusBarHidden: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: SceneManagerDelegate
|
// MARK: SceneManagerDelegate
|
||||||
|
|
||||||
func sceneManagerDidTransitionToScene(scene: SKScene) {
|
func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene) {
|
||||||
|
|
||||||
// Fade out the app's initial loading `logoView` if it is visible.
|
// Fade out the app's initial loading `logoView` if it is visible.
|
||||||
UIView.animateWithDuration(0.2, delay: 0.0, options: [], animations: {
|
UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
|
||||||
self.logoView.alpha = 0.0
|
self.logoView.alpha = 0.0
|
||||||
}, completion: { _ in
|
}, completion: { _ in
|
||||||
self.logoView.hidden = true
|
self.logoView.isHidden = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,12 @@ class GameViewController: GCEventViewController, SceneManagerDelegate {
|
||||||
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
sceneManager = SceneManager(presentingView: skView, gameInput: gameInput)
|
||||||
sceneManager.delegate = self
|
sceneManager.delegate = self
|
||||||
|
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
|
sceneManager.transitionToScene(identifier: .home)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: SceneManagerDelegate
|
// MARK: SceneManagerDelegate
|
||||||
|
|
||||||
func sceneManagerDidTransitionToScene(scene: SKScene) {
|
func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene) {
|
||||||
/*
|
/*
|
||||||
When transitioning to the `HomeEndScene` set
|
When transitioning to the `HomeEndScene` set
|
||||||
`controllerUserInteractionEnabled` to `true` to allow the
|
`controllerUserInteractionEnabled` to `true` to allow the
|
||||||
|
|
|
@ -73,7 +73,6 @@
|
||||||
0A38AC181B73BE0000AE9C43 /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEEA9B61AE1E3C300C519E7 /* GeometryExtensions.swift */; };
|
0A38AC181B73BE0000AE9C43 /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEEA9B61AE1E3C300C519E7 /* GeometryExtensions.swift */; };
|
||||||
0A38AC191B73BE0500AE9C43 /* BeamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */; };
|
0A38AC191B73BE0500AE9C43 /* BeamNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */; };
|
||||||
0A38AC1B1B73BE0500AE9C43 /* ChargeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168482F61AA66D2400EAB4C0 /* ChargeBar.swift */; };
|
0A38AC1B1B73BE0500AE9C43 /* ChargeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168482F61AA66D2400EAB4C0 /* ChargeBar.swift */; };
|
||||||
0A38AC1C1B73BE0500AE9C43 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
|
|
||||||
0A38AC1E1B73BE0A00AE9C43 /* ColliderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16950F6E1A9C08240074F3EC /* ColliderType.swift */; };
|
0A38AC1E1B73BE0A00AE9C43 /* ColliderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16950F6E1A9C08240074F3EC /* ColliderType.swift */; };
|
||||||
0A38AC1F1B73BE0A00AE9C43 /* ContactNotifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5FB611B1B7E3D007D0C86 /* ContactNotifiableType.swift */; };
|
0A38AC1F1B73BE0A00AE9C43 /* ContactNotifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5FB611B1B7E3D007D0C86 /* ContactNotifiableType.swift */; };
|
||||||
0A38AC201B73BE1400AE9C43 /* GameInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169B38AC1A73279B00B850B8 /* GameInput.swift */; };
|
0A38AC201B73BE1400AE9C43 /* GameInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169B38AC1A73279B00B850B8 /* GameInput.swift */; };
|
||||||
|
@ -89,7 +88,7 @@
|
||||||
0A38AC2A1B73BE2400AE9C43 /* SceneLoaderPreparingResourcesState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2619A81B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift */; };
|
0A38AC2A1B73BE2400AE9C43 /* SceneLoaderPreparingResourcesState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2619A81B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift */; };
|
||||||
0A38AC2B1B73BE2400AE9C43 /* SceneLoaderResourcesReadyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */; };
|
0A38AC2B1B73BE2400AE9C43 /* SceneLoaderResourcesReadyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */; };
|
||||||
0A38AC2C1B73BE2400AE9C43 /* ResourceLoadableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */; };
|
0A38AC2C1B73BE2400AE9C43 /* ResourceLoadableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */; };
|
||||||
0A38AC2D1B73BE2400AE9C43 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; };
|
0A38AC2D1B73BE2400AE9C43 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
|
||||||
0A38AC2E1B73BE2400AE9C43 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
0A38AC2E1B73BE2400AE9C43 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
||||||
0A38AC2F1B73BE2400AE9C43 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
0A38AC2F1B73BE2400AE9C43 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
||||||
0A38AC321B73BE3200AE9C43 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16A8FCA41A7062CC006FC06F /* Images.xcassets */; };
|
0A38AC321B73BE3200AE9C43 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16A8FCA41A7062CC006FC06F /* Images.xcassets */; };
|
||||||
|
@ -132,8 +131,8 @@
|
||||||
0A73CB411B1CDEB80030BB13 /* TaskBotAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */; };
|
0A73CB411B1CDEB80030BB13 /* TaskBotAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */; };
|
||||||
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
||||||
0A73CB441B1CE91C0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
0A73CB441B1CE91C0030BB13 /* LoadResourcesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */; };
|
||||||
0A788CD21B6AC873006B3799 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; };
|
0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
|
||||||
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* Operation.swift */; };
|
0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD11B6AC873006B3799 /* SceneOperation.swift */; };
|
||||||
0A788CD51B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
0A788CD51B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
||||||
0A788CD61B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
0A788CD61B6AD483006B3799 /* LoadSceneOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */; };
|
||||||
0A9F5F201B1150D8002D0238 /* BaseScene+Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A58DD331B114ACD000BEEB3 /* BaseScene+Buttons.swift */; };
|
0A9F5F201B1150D8002D0238 /* BaseScene+Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A58DD331B114ACD000BEEB3 /* BaseScene+Buttons.swift */; };
|
||||||
|
@ -192,8 +191,6 @@
|
||||||
16D800131AF4254F00979C5F /* TaskBotBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */; };
|
16D800131AF4254F00979C5F /* TaskBotBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D800111AF4254F00979C5F /* TaskBotBehavior.swift */; };
|
||||||
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
|
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
|
||||||
16E3D95B1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
|
16E3D95B1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */; };
|
||||||
16F685FC1A9FF32800B28C12 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
|
|
||||||
16F685FD1A9FF32800B28C12 /* EntityNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F685FB1A9FF32800B28C12 /* EntityNode.swift */; };
|
|
||||||
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */; };
|
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */; };
|
||||||
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */; };
|
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */; };
|
||||||
3E8513021AA0BDB400AF1B97 /* ControlInputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */; };
|
3E8513021AA0BDB400AF1B97 /* ControlInputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */; };
|
||||||
|
@ -353,7 +350,7 @@
|
||||||
0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneLoaderResourcesReadyState.swift; sourceTree = "<group>"; };
|
0A724CEC1B17C6DE00F64132 /* SceneLoaderResourcesReadyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneLoaderResourcesReadyState.swift; sourceTree = "<group>"; };
|
||||||
0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgent.swift; sourceTree = "<group>"; };
|
0A73CB3F1B1CDEB80030BB13 /* TaskBotAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgent.swift; sourceTree = "<group>"; };
|
||||||
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadResourcesOperation.swift; sourceTree = "<group>"; };
|
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadResourcesOperation.swift; sourceTree = "<group>"; };
|
||||||
0A788CD11B6AC873006B3799 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
0A788CD11B6AC873006B3799 /* SceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneOperation.swift; sourceTree = "<group>"; };
|
||||||
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadSceneOperation.swift; sourceTree = "<group>"; };
|
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadSceneOperation.swift; sourceTree = "<group>"; };
|
||||||
0AC33EAE1B7E91D600791074 /* ButtonFocusActions.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = ButtonFocusActions.sks; sourceTree = "<group>"; };
|
0AC33EAE1B7E91D600791074 /* ButtonFocusActions.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = ButtonFocusActions.sks; sourceTree = "<group>"; };
|
||||||
0ADAB8C71B1660E700F98E5A /* SceneMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneMetadata.swift; sourceTree = "<group>"; };
|
0ADAB8C71B1660E700F98E5A /* SceneMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneMetadata.swift; sourceTree = "<group>"; };
|
||||||
|
@ -385,7 +382,6 @@
|
||||||
16D8000B1AF4000000979C5F /* TaskBotAgentControlledState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgentControlledState.swift; sourceTree = "<group>"; };
|
16D8000B1AF4000000979C5F /* TaskBotAgentControlledState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskBotAgentControlledState.swift; sourceTree = "<group>"; };
|
||||||
16D800111AF4254F00979C5F /* TaskBotBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskBotBehavior.swift; path = Components/TaskBotBehavior.swift; sourceTree = "<group>"; };
|
16D800111AF4254F00979C5F /* TaskBotBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TaskBotBehavior.swift; path = Components/TaskBotBehavior.swift; sourceTree = "<group>"; };
|
||||||
16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsComponent.swift; sourceTree = "<group>"; };
|
16E3D9591AA4F3B80001E9D1 /* PhysicsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsComponent.swift; sourceTree = "<group>"; };
|
||||||
16F685FB1A9FF32800B28C12 /* EntityNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityNode.swift; sourceTree = "<group>"; };
|
|
||||||
3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+KeyboardEventForwarding.swift"; sourceTree = "<group>"; };
|
3E44A3161AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+KeyboardEventForwarding.swift"; sourceTree = "<group>"; };
|
||||||
3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+TouchEventForwarding.swift"; sourceTree = "<group>"; };
|
3E5AFC8A1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseScene+TouchEventForwarding.swift"; sourceTree = "<group>"; };
|
||||||
3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlInputSource.swift; sourceTree = "<group>"; };
|
3E8513011AA0BDB400AF1B97 /* ControlInputSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlInputSource.swift; sourceTree = "<group>"; };
|
||||||
|
@ -539,7 +535,7 @@
|
||||||
0A724CDF1B17949200F64132 /* SceneLoader.swift */,
|
0A724CDF1B17949200F64132 /* SceneLoader.swift */,
|
||||||
0A724CE51B17C53000F64132 /* SceneLoader States */,
|
0A724CE51B17C53000F64132 /* SceneLoader States */,
|
||||||
0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */,
|
0ADCE3361B1BE3F200E85CEB /* ResourceLoadableType.swift */,
|
||||||
0A788CD11B6AC873006B3799 /* Operation.swift */,
|
0A788CD11B6AC873006B3799 /* SceneOperation.swift */,
|
||||||
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */,
|
0A788CD41B6AD483006B3799 /* LoadSceneOperation.swift */,
|
||||||
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */,
|
0A73CB421B1CE6AE0030BB13 /* LoadResourcesOperation.swift */,
|
||||||
);
|
);
|
||||||
|
@ -608,7 +604,6 @@
|
||||||
B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */,
|
B5BC4A091AF263C700ABC4A6 /* BeamNode.swift */,
|
||||||
B5B2DA681BE0DD1A00DF201F /* ButtonNode.swift */,
|
B5B2DA681BE0DD1A00DF201F /* ButtonNode.swift */,
|
||||||
168482F61AA66D2400EAB4C0 /* ChargeBar.swift */,
|
168482F61AA66D2400EAB4C0 /* ChargeBar.swift */,
|
||||||
16F685FB1A9FF32800B28C12 /* EntityNode.swift */,
|
|
||||||
B5B2DA6C1BE0DD4200DF201F /* ThumbStickNode.swift */,
|
B5B2DA6C1BE0DD4200DF201F /* ThumbStickNode.swift */,
|
||||||
);
|
);
|
||||||
path = Nodes;
|
path = Nodes;
|
||||||
|
@ -951,8 +946,9 @@
|
||||||
Level2,
|
Level2,
|
||||||
Level3,
|
Level3,
|
||||||
);
|
);
|
||||||
|
LastSwiftMigration = 0800;
|
||||||
LastSwiftUpdateCheck = 0700;
|
LastSwiftUpdateCheck = 0700;
|
||||||
LastUpgradeCheck = 0700;
|
LastUpgradeCheck = 0800;
|
||||||
ORGANIZATIONNAME = "Apple, Inc.";
|
ORGANIZATIONNAME = "Apple, Inc.";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
0A38ABAF1B73013F00AE9C43 = {
|
0A38ABAF1B73013F00AE9C43 = {
|
||||||
|
@ -1131,7 +1127,6 @@
|
||||||
B5B2DA671BE0DD0400DF201F /* ProgressScene.swift in Sources */,
|
B5B2DA671BE0DD0400DF201F /* ProgressScene.swift in Sources */,
|
||||||
0A38AC0F1B73BDF800AE9C43 /* CompassDirection.swift in Sources */,
|
0A38AC0F1B73BDF800AE9C43 /* CompassDirection.swift in Sources */,
|
||||||
0A38AC121B73BDF800AE9C43 /* RulesComponent.swift in Sources */,
|
0A38AC121B73BDF800AE9C43 /* RulesComponent.swift in Sources */,
|
||||||
0A38AC1C1B73BE0500AE9C43 /* EntityNode.swift in Sources */,
|
|
||||||
0A38AC231B73BE1D00AE9C43 /* SceneManager.swift in Sources */,
|
0A38AC231B73BE1D00AE9C43 /* SceneManager.swift in Sources */,
|
||||||
B5B2DA6E1BE0DD4900DF201F /* ThumbStickNode.swift in Sources */,
|
B5B2DA6E1BE0DD4900DF201F /* ThumbStickNode.swift in Sources */,
|
||||||
0A38AC031B73BDF300AE9C43 /* PlayerBotHitState.swift in Sources */,
|
0A38AC031B73BDF300AE9C43 /* PlayerBotHitState.swift in Sources */,
|
||||||
|
@ -1148,7 +1143,7 @@
|
||||||
0A38ABFB1B73BDEB00AE9C43 /* BeamComponent.swift in Sources */,
|
0A38ABFB1B73BDEB00AE9C43 /* BeamComponent.swift in Sources */,
|
||||||
0A18365B1B82B11900177830 /* LevelScene+Pause.swift in Sources */,
|
0A18365B1B82B11900177830 /* LevelScene+Pause.swift in Sources */,
|
||||||
0A38AC211B73BE1400AE9C43 /* ControlInputSource.swift in Sources */,
|
0A38AC211B73BE1400AE9C43 /* ControlInputSource.swift in Sources */,
|
||||||
0A38AC2D1B73BE2400AE9C43 /* Operation.swift in Sources */,
|
0A38AC2D1B73BE2400AE9C43 /* SceneOperation.swift in Sources */,
|
||||||
0A38AC101B73BDF800AE9C43 /* PhysicsComponent.swift in Sources */,
|
0A38AC101B73BDF800AE9C43 /* PhysicsComponent.swift in Sources */,
|
||||||
0A38AC111B73BDF800AE9C43 /* RenderComponent.swift in Sources */,
|
0A38AC111B73BDF800AE9C43 /* RenderComponent.swift in Sources */,
|
||||||
0A38AC0C1B73BDF800AE9C43 /* TaskBotZappedState.swift in Sources */,
|
0A38AC0C1B73BDF800AE9C43 /* TaskBotZappedState.swift in Sources */,
|
||||||
|
@ -1221,7 +1216,7 @@
|
||||||
B5B2DA831BE0DDF200DF201F /* GameWindowController.swift in Sources */,
|
B5B2DA831BE0DDF200DF201F /* GameWindowController.swift in Sources */,
|
||||||
B545AF1E1AF0F422002BC931 /* BeamComponent.swift in Sources */,
|
B545AF1E1AF0F422002BC931 /* BeamComponent.swift in Sources */,
|
||||||
0A6F773E1B02EA060091C645 /* SceneManager.swift in Sources */,
|
0A6F773E1B02EA060091C645 /* SceneManager.swift in Sources */,
|
||||||
0A788CD31B6AC873006B3799 /* Operation.swift in Sources */,
|
0A788CD31B6AC873006B3799 /* SceneOperation.swift in Sources */,
|
||||||
B5CAD52D1AEBAE9D00D9B7A9 /* FlyingBotBlastState.swift in Sources */,
|
B5CAD52D1AEBAE9D00D9B7A9 /* FlyingBotBlastState.swift in Sources */,
|
||||||
16873A0C1A9E6DC2003FB425 /* RenderComponent.swift in Sources */,
|
16873A0C1A9E6DC2003FB425 /* RenderComponent.swift in Sources */,
|
||||||
B52D2DFB1AFD11E000469E3B /* PlayerBotAppearState.swift in Sources */,
|
B52D2DFB1AFD11E000469E3B /* PlayerBotAppearState.swift in Sources */,
|
||||||
|
@ -1245,7 +1240,6 @@
|
||||||
0ADCE3381B1BE3F200E85CEB /* ResourceLoadableType.swift in Sources */,
|
0ADCE3381B1BE3F200E85CEB /* ResourceLoadableType.swift in Sources */,
|
||||||
16109B641AF54D3800A780AE /* FuzzyTaskBotRule.swift in Sources */,
|
16109B641AF54D3800A780AE /* FuzzyTaskBotRule.swift in Sources */,
|
||||||
0A255E2B1B5EB9EF0085D218 /* BaseScene+Focus.swift in Sources */,
|
0A255E2B1B5EB9EF0085D218 /* BaseScene+Focus.swift in Sources */,
|
||||||
16F685FD1A9FF32800B28C12 /* EntityNode.swift in Sources */,
|
|
||||||
16D8000D1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
|
16D8000D1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
|
||||||
0ADAB8C91B1660E700F98E5A /* SceneMetadata.swift in Sources */,
|
0ADAB8C91B1660E700F98E5A /* SceneMetadata.swift in Sources */,
|
||||||
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */,
|
3E44A3171AA7FC300080AFBB /* BaseScene+KeyboardEventForwarding.swift in Sources */,
|
||||||
|
@ -1284,7 +1278,6 @@
|
||||||
3E91BAC41A89755500CE240A /* LevelScene.swift in Sources */,
|
3E91BAC41A89755500CE240A /* LevelScene.swift in Sources */,
|
||||||
B5CAD5251AEBA48E00D9B7A9 /* FlyingBot.swift in Sources */,
|
B5CAD5251AEBA48E00D9B7A9 /* FlyingBot.swift in Sources */,
|
||||||
B545AF1D1AF0F418002BC931 /* BeamComponent.swift in Sources */,
|
B545AF1D1AF0F418002BC931 /* BeamComponent.swift in Sources */,
|
||||||
16F685FC1A9FF32800B28C12 /* EntityNode.swift in Sources */,
|
|
||||||
16D8000C1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
|
16D8000C1AF4000000979C5F /* TaskBotAgentControlledState.swift in Sources */,
|
||||||
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */,
|
3E5AFC8B1AA81116001BA618 /* BaseScene+TouchEventForwarding.swift in Sources */,
|
||||||
55A24EF41AF033C70039A2DB /* LevelConfiguration.swift in Sources */,
|
55A24EF41AF033C70039A2DB /* LevelConfiguration.swift in Sources */,
|
||||||
|
@ -1316,7 +1309,7 @@
|
||||||
B54A81591B15140A00D33DF8 /* SceneOverlay.swift in Sources */,
|
B54A81591B15140A00D33DF8 /* SceneOverlay.swift in Sources */,
|
||||||
B543D7DD1B1B976000F0AA9D /* OrientationComponent.swift in Sources */,
|
B543D7DD1B1B976000F0AA9D /* OrientationComponent.swift in Sources */,
|
||||||
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */,
|
16E3D95A1AA4F3B80001E9D1 /* PhysicsComponent.swift in Sources */,
|
||||||
0A788CD21B6AC873006B3799 /* Operation.swift in Sources */,
|
0A788CD21B6AC873006B3799 /* SceneOperation.swift in Sources */,
|
||||||
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */,
|
0A73CB431B1CE6AE0030BB13 /* LoadResourcesOperation.swift in Sources */,
|
||||||
0A2619A91B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift in Sources */,
|
0A2619A91B17CD26001E4950 /* SceneLoaderPreparingResourcesState.swift in Sources */,
|
||||||
3E8513071AA0BEA800AF1B97 /* TouchControlInputNode.swift in Sources */,
|
3E8513071AA0BEA800AF1B97 /* TouchControlInputNode.swift in Sources */,
|
||||||
|
@ -1387,7 +1380,10 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
|
||||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
|
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
|
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
@ -1396,8 +1392,9 @@
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
PRODUCT_NAME = DemoBots;
|
PRODUCT_NAME = DemoBots;
|
||||||
SDKROOT = appletvos;
|
SDKROOT = appletvos;
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = 3;
|
TARGETED_DEVICE_FAMILY = 3;
|
||||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -1406,6 +1403,9 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "tvOS Icon";
|
||||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
|
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "tvOS LaunchImage";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer";
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
|
INFOPLIST_FILE = "DemoBots (tvOS)/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
@ -1414,8 +1414,9 @@
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
PRODUCT_NAME = DemoBots;
|
PRODUCT_NAME = DemoBots;
|
||||||
SDKROOT = appletvos;
|
SDKROOT = appletvos;
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = 3;
|
TARGETED_DEVICE_FAMILY = 3;
|
||||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
@ -1424,6 +1425,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPRESSION = lossless;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
@ -1455,14 +1457,14 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
OTHER_SWIFT_FLAGS = "";
|
OTHER_SWIFT_FLAGS = "";
|
||||||
PRODUCT_NAME = DemoBots;
|
PRODUCT_NAME = DemoBots;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
TOOLCHAINS = default;
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -1470,6 +1472,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPRESSION = "respect-asset-catalog";
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
@ -1495,11 +1498,11 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PRODUCT_NAME = DemoBots;
|
PRODUCT_NAME = DemoBots;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
TOOLCHAINS = default;
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -1508,20 +1511,22 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(BUILD_DIR)",
|
"$(BUILD_DIR)",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
|
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
|
||||||
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
|
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
|
||||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -1530,18 +1535,20 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(BUILD_DIR)",
|
"$(BUILD_DIR)",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
|
INFOPLIST_FILE = "$(SRCROOT)/DemoBots (OS X)/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
|
||||||
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
|
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue FlyingBot Green GroundBot Level1 Level2 Level3";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -1558,21 +1565,18 @@
|
||||||
"$(BUILD_DIR)",
|
"$(BUILD_DIR)",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
|
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
|
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
|
||||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
@ -1590,14 +1594,15 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
|
INFOPLIST_FILE = "DemoBots (iOS)/Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
|
ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS = "Blue GroundBot Level1";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_MODULE_NAME = DemoBots;
|
PRODUCT_MODULE_NAME = DemoBots;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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",
|
"filename" : "App Icon - Small.imagestack",
|
||||||
"role" : "primary-app-icon"
|
"role" : "primary-app-icon"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"size" : "2320x720",
|
||||||
|
"idiom" : "tv",
|
||||||
|
"filename" : "Top Shelf Image Wide.imageset",
|
||||||
|
"role" : "top-shelf-image-wide"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"size" : "1920x720",
|
"size" : "1920x720",
|
||||||
"idiom" : "tv",
|
"idiom" : "tv",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "tv",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,4 +30,4 @@
|
||||||
"version" : 1,
|
"version" : 1,
|
||||||
"author" : "xcode"
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ extension BaseScene: ButtonNodeResponderType {
|
||||||
/// Searches the scene for all `ButtonNode`s.
|
/// Searches the scene for all `ButtonNode`s.
|
||||||
func findAllButtonsInScene() -> [ButtonNode] {
|
func findAllButtonsInScene() -> [ButtonNode] {
|
||||||
return ButtonIdentifier.allButtonIdentifiers.flatMap { buttonIdentifier in
|
return ButtonIdentifier.allButtonIdentifiers.flatMap { buttonIdentifier in
|
||||||
childNodeWithName("//\(buttonIdentifier.rawValue)") as? ButtonNode
|
childNode(withName: "//\(buttonIdentifier.rawValue)") as? ButtonNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,27 +22,27 @@ extension BaseScene: ButtonNodeResponderType {
|
||||||
|
|
||||||
func buttonTriggered(button: ButtonNode) {
|
func buttonTriggered(button: ButtonNode) {
|
||||||
switch button.buttonIdentifier! {
|
switch button.buttonIdentifier! {
|
||||||
case .Home:
|
case .home:
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.Home)
|
sceneManager.transitionToScene(identifier: .home)
|
||||||
|
|
||||||
case .ProceedToNextScene:
|
case .proceedToNextScene:
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.NextLevel)
|
sceneManager.transitionToScene(identifier: .nextLevel)
|
||||||
|
|
||||||
case .Replay:
|
case .replay:
|
||||||
sceneManager.transitionToSceneWithSceneIdentifier(.CurrentLevel)
|
sceneManager.transitionToScene(identifier: .currentLevel)
|
||||||
|
|
||||||
case .ScreenRecorderToggle:
|
case .screenRecorderToggle:
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
toggleScreenRecording(button)
|
toggleScreenRecording(button: button)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case .ViewRecordedContent:
|
case .viewRecordedContent:
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
displayRecordedContent()
|
displayRecordedContent()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fatalError("Unsupported ButtonNode type in Scene.")
|
fatalError("Unsupported ButtonNode type in Scene.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ extension BaseScene {
|
||||||
|
|
||||||
/// A computed property to determine which buttons are focusable.
|
/// A computed property to determine which buttons are focusable.
|
||||||
var currentlyFocusableButtons: [ButtonNode] {
|
var currentlyFocusableButtons: [ButtonNode] {
|
||||||
return buttons.filter { !$0.hidden && $0.userInteractionEnabled }
|
return buttons.filter { !$0.isHidden && $0.isUserInteractionEnabled }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,14 +40,14 @@ extension BaseScene {
|
||||||
*/
|
*/
|
||||||
private var buttonIdentifiersOrderedByInitialFocusPriority: [ButtonIdentifier] {
|
private var buttonIdentifiersOrderedByInitialFocusPriority: [ButtonIdentifier] {
|
||||||
return [
|
return [
|
||||||
.Resume,
|
.resume,
|
||||||
.ProceedToNextScene,
|
.proceedToNextScene,
|
||||||
.Replay,
|
.replay,
|
||||||
.Retry,
|
.retry,
|
||||||
.Home,
|
.home,
|
||||||
.Cancel,
|
.cancel,
|
||||||
.ViewRecordedContent,
|
.viewRecordedContent,
|
||||||
.ScreenRecorderToggle
|
.screenRecorderToggle
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ extension BaseScene {
|
||||||
could be expanded to include horizontal navigation if necessary.
|
could be expanded to include horizontal navigation if necessary.
|
||||||
*/
|
*/
|
||||||
func createButtonFocusGraph() {
|
func createButtonFocusGraph() {
|
||||||
let sortedFocusableButtons = currentlyFocusableButtons.sort { $0.position.y > $1.position.y }
|
let sortedFocusableButtons = currentlyFocusableButtons.sorted { $0.position.y > $1.position.y }
|
||||||
|
|
||||||
// Clear any existing connections.
|
// Clear any existing connections.
|
||||||
sortedFocusableButtons.forEach { $0.focusableNeighbors.removeAll() }
|
sortedFocusableButtons.forEach { $0.focusableNeighbors.removeAll() }
|
||||||
|
@ -71,8 +71,8 @@ extension BaseScene {
|
||||||
let nextNode = sortedFocusableButtons[i + 1]
|
let nextNode = sortedFocusableButtons[i + 1]
|
||||||
|
|
||||||
// Create a bidirectional connection between the nodes.
|
// Create a bidirectional connection between the nodes.
|
||||||
node.focusableNeighbors[.Down] = nextNode
|
node.focusableNeighbors[.down] = nextNode
|
||||||
nextNode.focusableNeighbors[.Up] = node
|
nextNode.focusableNeighbors[.up] = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,14 +87,14 @@ extension BaseScene {
|
||||||
// On iOS, ensure a game controller is connected otherwise return without providing focus.
|
// On iOS, ensure a game controller is connected otherwise return without providing focus.
|
||||||
guard sceneManager.gameInput.isGameControllerConnected else { return }
|
guard sceneManager.gameInput.isGameControllerConnected else { return }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Reset focus to the `buttonNode` with the maximum initial focus priority.
|
// Reset focus to the `buttonNode` with the maximum initial focus priority.
|
||||||
focusedButton = currentlyFocusableButtons.maxElement { lhsButton, rhsButton in
|
focusedButton = currentlyFocusableButtons.max { lhsButton, rhsButton in
|
||||||
// The initial focus priority is the index within the `buttonIdentifiersOrderedByInitialFocusPriority` array.
|
// The initial focus priority is the index within the `buttonIdentifiersOrderedByInitialFocusPriority` array.
|
||||||
let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(lhsButton.buttonIdentifier)!
|
let lhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: lhsButton.buttonIdentifier)!
|
||||||
let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.indexOf(rhsButton.buttonIdentifier)!
|
let rhsPriority = buttonIdentifiersOrderedByInitialFocusPriority.index(of: rhsButton.buttonIdentifier)!
|
||||||
|
|
||||||
return lhsPriority > rhsPriority
|
return lhsPriority > rhsPriority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,27 +21,27 @@ extension BaseScene {
|
||||||
|
|
||||||
// MARK: NSResponder
|
// MARK: NSResponder
|
||||||
|
|
||||||
override func mouseDown(event: NSEvent) {
|
override func mouseDown(with event: NSEvent) {
|
||||||
keyboardControlInputSource.handleMouseDownEvent()
|
keyboardControlInputSource.handleMouseDownEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(theEvent: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
keyboardControlInputSource.handleMouseUpEvent()
|
keyboardControlInputSource.handleMouseUpEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func keyDown(event: NSEvent) {
|
override func keyDown(with event: NSEvent) {
|
||||||
guard let characters = event.charactersIgnoringModifiers?.characters else { return }
|
guard let characters = event.charactersIgnoringModifiers?.characters else { return }
|
||||||
|
|
||||||
for character in characters {
|
for character in characters {
|
||||||
keyboardControlInputSource.handleKeyDownForCharacter(character)
|
keyboardControlInputSource.handleKeyDown(forCharacter: character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func keyUp(event: NSEvent) {
|
override func keyUp(with event: NSEvent) {
|
||||||
guard let characters = event.charactersIgnoringModifiers?.characters else { return }
|
guard let characters = event.charactersIgnoringModifiers?.characters else { return }
|
||||||
|
|
||||||
for character in characters {
|
for character in characters {
|
||||||
keyboardControlInputSource.handleKeyUpForCharacter(character)
|
keyboardControlInputSource.handleKeyUp(forCharacter: character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
|
||||||
// MARK: Computed Properties
|
// MARK: Computed Properties
|
||||||
|
|
||||||
var screenRecordingToggleEnabled: Bool {
|
var screenRecordingToggleEnabled: Bool {
|
||||||
return NSUserDefaults.standardUserDefaults().boolForKey(screenRecorderEnabledKey)
|
return UserDefaults.standard.bool(forKey: screenRecorderEnabledKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Start/Stop Screen Recording
|
// MARK: Start/Stop Screen Recording
|
||||||
|
@ -21,25 +21,25 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
|
||||||
// Do nothing if screen recording hasn't been enabled.
|
// Do nothing if screen recording hasn't been enabled.
|
||||||
guard screenRecordingToggleEnabled else { return }
|
guard screenRecordingToggleEnabled else { return }
|
||||||
|
|
||||||
let sharedRecorder = RPScreenRecorder.sharedRecorder()
|
let sharedRecorder = RPScreenRecorder.shared()
|
||||||
|
|
||||||
// Register as the recorder's delegate to handle errors.
|
// Register as the recorder's delegate to handle errors.
|
||||||
sharedRecorder.delegate = self
|
sharedRecorder.delegate = self
|
||||||
|
|
||||||
sharedRecorder.startRecordingWithMicrophoneEnabled(true) { error in
|
sharedRecorder.startRecording() { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
self.showScreenRecordingAlert(error.localizedDescription)
|
self.showScreenRecordingAlert(message: error.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopScreenRecordingWithHandler(handler:(() -> Void)) {
|
func stopScreenRecording(withHandler handler:@escaping (() -> Void)) {
|
||||||
let sharedRecorder = RPScreenRecorder.sharedRecorder()
|
let sharedRecorder = RPScreenRecorder.shared()
|
||||||
|
|
||||||
sharedRecorder.stopRecordingWithHandler { (previewViewController: RPPreviewViewController?, error: NSError?) in
|
sharedRecorder.stopRecording { previewViewController, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
// If an error has occurred, display an alert to the user.
|
// If an error has occurred, display an alert to the user.
|
||||||
self.showScreenRecordingAlert(error.localizedDescription)
|
self.showScreenRecordingAlert(message: error.localizedDescription)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,13 +60,13 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
|
||||||
|
|
||||||
func showScreenRecordingAlert(message: String) {
|
func showScreenRecordingAlert(message: String) {
|
||||||
// Pause the scene and un-pause after the alert returns.
|
// Pause the scene and un-pause after the alert returns.
|
||||||
paused = true
|
isPaused = true
|
||||||
|
|
||||||
// Show an alert notifying the user that there was an issue with starting or stopping the recorder.
|
// Show an alert notifying the user that there was an issue with starting or stopping the recorder.
|
||||||
let alertController = UIAlertController(title: "ReplayKit Error", message: message, preferredStyle: .Alert)
|
let alertController = UIAlertController(title: "ReplayKit Error", message: message, preferredStyle: .alert)
|
||||||
|
|
||||||
let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { _ in
|
let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.`default`) { _ in
|
||||||
self.paused = false
|
self.isPaused = false
|
||||||
}
|
}
|
||||||
alertController.addAction(alertAction)
|
alertController.addAction(alertAction)
|
||||||
|
|
||||||
|
@ -74,23 +74,23 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
|
||||||
`ReplayKit` event handlers may be called on a background queue. Ensure
|
`ReplayKit` event handlers may be called on a background queue. Ensure
|
||||||
this alert is presented on the main queue.
|
this alert is presented on the main queue.
|
||||||
*/
|
*/
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async() {
|
||||||
self.view?.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
|
self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func discardRecording() {
|
func discardRecording() {
|
||||||
// When we no longer need the `previewViewController`, tell `ReplayKit` to discard the recording and nil out our reference
|
// When we no longer need the `previewViewController`, tell `ReplayKit` to discard the recording and nil out our reference
|
||||||
RPScreenRecorder.sharedRecorder().discardRecordingWithHandler {
|
RPScreenRecorder.shared().discardRecording {
|
||||||
self.previewViewController = nil
|
self.previewViewController = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: RPScreenRecorderDelegate
|
// MARK: RPScreenRecorderDelegate
|
||||||
|
|
||||||
func screenRecorder(screenRecorder: RPScreenRecorder, didStopRecordingWithError error: NSError, previewViewController: RPPreviewViewController?) {
|
func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWithError error: Error, previewViewController: RPPreviewViewController?) {
|
||||||
// Display the error the user to alert them that the recording failed.
|
// Display the error the user to alert them that the recording failed.
|
||||||
showScreenRecordingAlert(error.localizedDescription)
|
showScreenRecordingAlert(message: error.localizedDescription)
|
||||||
|
|
||||||
/// Hold onto a reference of the `previewViewController` if not nil.
|
/// Hold onto a reference of the `previewViewController` if not nil.
|
||||||
if previewViewController != nil {
|
if previewViewController != nil {
|
||||||
|
@ -101,6 +101,6 @@ extension BaseScene: RPPreviewViewControllerDelegate, RPScreenRecorderDelegate {
|
||||||
// MARK: RPPreviewViewControllerDelegate
|
// MARK: RPPreviewViewControllerDelegate
|
||||||
|
|
||||||
func previewControllerDidFinish(previewController: RPPreviewViewController) {
|
func previewControllerDidFinish(previewController: RPPreviewViewController) {
|
||||||
previewViewController?.dismissViewControllerAnimated(true, completion: nil)
|
previewViewController?.dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,13 @@ extension BaseScene {
|
||||||
touchControlInputNode.size = size
|
touchControlInputNode.size = size
|
||||||
|
|
||||||
// Center the control node on the camera.
|
// Center the control node on the camera.
|
||||||
touchControlInputNode.position = CGPointZero
|
touchControlInputNode.position = CGPoint.zero
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Assign a `zPosition` that is above in-game elements, but below the top
|
Assign a `zPosition` that is above in-game elements, but below the top
|
||||||
layer where buttons are added.
|
layer where buttons are added.
|
||||||
*/
|
*/
|
||||||
touchControlInputNode.zPosition = WorldLayer.Top.rawValue - CGFloat(1.0)
|
touchControlInputNode.zPosition = WorldLayer.top.rawValue - CGFloat(1.0)
|
||||||
|
|
||||||
// Add the control node to the camera node so the controls remain stationary as the camera moves.
|
// Add the control node to the camera node so the controls remain stationary as the camera moves.
|
||||||
camera.addChild(touchControlInputNode)
|
camera.addChild(touchControlInputNode)
|
||||||
|
@ -48,4 +48,4 @@ extension BaseScene {
|
||||||
touchControlInputNode.hideThumbStickNodes = false
|
touchControlInputNode.hideThumbStickNodes = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,13 +54,13 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
// Clear the `buttons` in preparation for new buttons in the overlay.
|
// Clear the `buttons` in preparation for new buttons in the overlay.
|
||||||
buttons = []
|
buttons = []
|
||||||
|
|
||||||
if let overlay = overlay, camera = camera {
|
if let overlay = overlay, let camera = camera {
|
||||||
overlay.backgroundNode.removeFromParent()
|
overlay.backgroundNode.removeFromParent()
|
||||||
camera.addChild(overlay.backgroundNode)
|
camera.addChild(overlay.backgroundNode)
|
||||||
|
|
||||||
// Animate the overlay in.
|
// Animate the overlay in.
|
||||||
overlay.backgroundNode.alpha = 0.0
|
overlay.backgroundNode.alpha = 0.0
|
||||||
overlay.backgroundNode.runAction(SKAction.fadeInWithDuration(0.25))
|
overlay.backgroundNode.run(SKAction.fadeIn(withDuration: 0.25))
|
||||||
overlay.updateScale()
|
overlay.updateScale()
|
||||||
|
|
||||||
buttons = findAllButtonsInScene()
|
buttons = findAllButtonsInScene()
|
||||||
|
@ -70,7 +70,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate the old overlay out.
|
// Animate the old overlay out.
|
||||||
oldValue?.backgroundNode.runAction(SKAction.fadeOutWithDuration(0.25)) {
|
oldValue?.backgroundNode.run(SKAction.fadeOut(withDuration: 0.25)) {
|
||||||
oldValue?.backgroundNode.removeFromParent()
|
oldValue?.backgroundNode.removeFromParent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
|
|
||||||
// MARK: SKScene Life Cycle
|
// MARK: SKScene Life Cycle
|
||||||
|
|
||||||
override func didMoveToView(view: SKView) {
|
override func didMove(to view: SKView) {
|
||||||
super.didMoveToView(view)
|
super.didMove(to: view)
|
||||||
|
|
||||||
updateCameraScale()
|
updateCameraScale()
|
||||||
overlay?.updateScale()
|
overlay?.updateScale()
|
||||||
|
@ -95,7 +95,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
resetFocus()
|
resetFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didChangeSize(oldSize: CGSize) {
|
override func didChangeSize(_ oldSize: CGSize) {
|
||||||
super.didChangeSize(oldSize)
|
super.didChangeSize(oldSize)
|
||||||
|
|
||||||
updateCameraScale()
|
updateCameraScale()
|
||||||
|
@ -122,11 +122,11 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
|
|
||||||
// MARK: ControlInputSourceGameStateDelegate
|
// MARK: ControlInputSourceGameStateDelegate
|
||||||
|
|
||||||
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType) {
|
||||||
focusedButton?.buttonTriggered()
|
focusedButton?.buttonTriggered()
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didSpecifyDirection direction: ControlInputDirection) {
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didSpecifyDirection direction: ControlInputDirection) {
|
||||||
// Check that this scene has focus changes enabled, otherwise ignore.
|
// Check that this scene has focus changes enabled, otherwise ignore.
|
||||||
guard focusChangesEnabled else { return }
|
guard focusChangesEnabled else { return }
|
||||||
|
|
||||||
|
@ -155,7 +155,8 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
constant input.
|
constant input.
|
||||||
*/
|
*/
|
||||||
focusChangesEnabled = false
|
focusChangesEnabled = false
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(200 * NSEC_PER_MSEC)), dispatch_get_main_queue()) {
|
let deadline = DispatchTime.now() + DispatchTimeInterval.microseconds(200)
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: deadline) {
|
||||||
self.focusChangesEnabled = true
|
self.focusChangesEnabled = true
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -167,7 +168,7 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Indicate that a neighboring button does not exist for the requested direction.
|
// Indicate that a neighboring button does not exist for the requested direction.
|
||||||
currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction)
|
currentFocusedButton.performInvalidFocusChangeAnimationForDirection(direction: direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -176,20 +177,20 @@ class BaseScene: SKScene, GameInputDelegate, ControlInputSourceGameStateDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
|
||||||
// Subclasses implement to toggle pause state.
|
// Subclasses implement to toggle pause state.
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
|
||||||
// Subclasses implement if necessary, to display useful debug info.
|
// Subclasses implement if necessary, to display useful debug info.
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType) {
|
||||||
// Implemented by subclasses to switch to next level while debugging.
|
// Implemented by subclasses to switch to next level while debugging.
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
|
||||||
// Implemented by subclasses to force failing the level while debugging.
|
// Implemented by subclasses to force failing the level while debugging.
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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
|
button.isSelected = !button.isSelected
|
||||||
|
|
||||||
NSUserDefaults.standardUserDefaults().setBool(button.isSelected, forKey: screenRecorderEnabledKey)
|
UserDefaults.standard.set(button.isSelected, forKey: screenRecorderEnabledKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayRecordedContent() {
|
func displayRecordedContent() {
|
||||||
|
@ -30,8 +30,8 @@ extension ButtonNodeResponderType where Self: BaseScene {
|
||||||
guard let rootViewController = view?.window?.rootViewController else { fatalError("The scene must be contained in a window with a root view controller.") }
|
guard let rootViewController = view?.window?.rootViewController else { fatalError("The scene must be contained in a window with a root view controller.") }
|
||||||
|
|
||||||
// `RPPreviewViewController` only supports full screen modal presentation.
|
// `RPPreviewViewController` only supports full screen modal presentation.
|
||||||
previewViewController.modalPresentationStyle = UIModalPresentationStyle.FullScreen
|
previewViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
|
||||||
|
|
||||||
rootViewController.presentViewController(previewViewController, animated: true, completion:nil)
|
rootViewController.present(previewViewController, animated: true, completion:nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,14 @@ import GameplayKit
|
||||||
|
|
||||||
/// The different animation states that an animated character can be in.
|
/// The different animation states that an animated character can be in.
|
||||||
enum AnimationState: String {
|
enum AnimationState: String {
|
||||||
case Idle = "Idle"
|
case idle = "Idle"
|
||||||
case WalkForward = "WalkForward"
|
case walkForward = "WalkForward"
|
||||||
case WalkBackward = "WalkBackward"
|
case walkBackward = "WalkBackward"
|
||||||
case PreAttack = "PreAttack"
|
case preAttack = "PreAttack"
|
||||||
case Attack = "Attack"
|
case attack = "Attack"
|
||||||
case Zapped = "Zapped"
|
case zapped = "Zapped"
|
||||||
case Hit = "Hit"
|
case hit = "Hit"
|
||||||
case Inactive = "Inactive"
|
case inactive = "Inactive"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +86,7 @@ class AnimationComponent: GKComponent {
|
||||||
static let textureActionKey = "textureAction"
|
static let textureActionKey = "textureAction"
|
||||||
|
|
||||||
/// The time to display each frame of a texture animation.
|
/// The time to display each frame of a texture animation.
|
||||||
static let timePerFrame = NSTimeInterval(1.0 / 10.0)
|
static let timePerFrame = TimeInterval(1.0 / 10.0)
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
|
@ -109,18 +109,23 @@ class AnimationComponent: GKComponent {
|
||||||
private(set) var currentAnimation: Animation?
|
private(set) var currentAnimation: Animation?
|
||||||
|
|
||||||
/// The length of time spent in the current animation state and direction.
|
/// The length of time spent in the current animation state and direction.
|
||||||
private var elapsedAnimationDuration: NSTimeInterval = 0.0
|
private var elapsedAnimationDuration: TimeInterval = 0.0
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init(textureSize: CGSize, animations: [AnimationState: [CompassDirection: Animation]]) {
|
init(textureSize: CGSize, animations: [AnimationState: [CompassDirection: Animation]]) {
|
||||||
node = SKSpriteNode(texture: nil, size: textureSize)
|
node = SKSpriteNode(texture: nil, size: textureSize)
|
||||||
self.animations = animations
|
self.animations = animations
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Character Animation
|
// MARK: Character Animation
|
||||||
|
|
||||||
private func runAnimationForAnimationState(animationState: AnimationState, compassDirection: CompassDirection, deltaTime: NSTimeInterval) {
|
private func runAnimationForAnimationState(animationState: AnimationState, compassDirection: CompassDirection, deltaTime: TimeInterval) {
|
||||||
|
|
||||||
// Update the tracking of how long we have been animating.
|
// Update the tracking of how long we have been animating.
|
||||||
elapsedAnimationDuration += deltaTime
|
elapsedAnimationDuration += deltaTime
|
||||||
|
@ -145,21 +150,21 @@ class AnimationComponent: GKComponent {
|
||||||
// Check if the action for the body node has changed.
|
// Check if the action for the body node has changed.
|
||||||
if currentAnimation?.bodyActionName != animation.bodyActionName {
|
if currentAnimation?.bodyActionName != animation.bodyActionName {
|
||||||
// Remove the existing body action if it exists.
|
// Remove the existing body action if it exists.
|
||||||
node.removeActionForKey(AnimationComponent.bodyActionKey)
|
node.removeAction(forKey: AnimationComponent.bodyActionKey)
|
||||||
|
|
||||||
// Reset the node's position in its parent (it may have been animating with a move action).
|
// Reset the node's position in its parent (it may have been animating with a move action).
|
||||||
node.position = CGPoint.zero
|
node.position = CGPoint.zero
|
||||||
|
|
||||||
// Add the new body action to the node if an action exists.
|
// Add the new body action to the node if an action exists.
|
||||||
if let bodyAction = animation.bodyAction {
|
if let bodyAction = animation.bodyAction {
|
||||||
node.runAction(SKAction.repeatActionForever(bodyAction), withKey: AnimationComponent.bodyActionKey)
|
node.run(SKAction.repeatForever(bodyAction), withKey: AnimationComponent.bodyActionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the action for the shadow node has changed.
|
// Check if the action for the shadow node has changed.
|
||||||
if currentAnimation?.shadowActionName != animation.shadowActionName {
|
if currentAnimation?.shadowActionName != animation.shadowActionName {
|
||||||
// Remove the existing shadow action if it exists.
|
// Remove the existing shadow action if it exists.
|
||||||
shadowNode?.removeActionForKey(AnimationComponent.shadowActionKey)
|
shadowNode?.removeAction(forKey: AnimationComponent.shadowActionKey)
|
||||||
|
|
||||||
// Reset the node's position in its parent (it may have been animating with a move action).
|
// Reset the node's position in its parent (it may have been animating with a move action).
|
||||||
shadowNode?.position = CGPoint.zero
|
shadowNode?.position = CGPoint.zero
|
||||||
|
@ -170,12 +175,12 @@ class AnimationComponent: GKComponent {
|
||||||
|
|
||||||
// Add the new shadow action to the shadow node if an action exists.
|
// Add the new shadow action to the shadow node if an action exists.
|
||||||
if let shadowAction = animation.shadowAction {
|
if let shadowAction = animation.shadowAction {
|
||||||
shadowNode?.runAction(SKAction.repeatActionForever(shadowAction), withKey: AnimationComponent.shadowActionKey)
|
shadowNode?.run(SKAction.repeatForever(shadowAction), withKey: AnimationComponent.shadowActionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the existing texture animation action if it exists.
|
// Remove the existing texture animation action if it exists.
|
||||||
node.removeActionForKey(AnimationComponent.textureActionKey)
|
node.removeAction(forKey: AnimationComponent.textureActionKey)
|
||||||
|
|
||||||
// Create a new action to display the appropriate animation textures.
|
// Create a new action to display the appropriate animation textures.
|
||||||
let texturesAction: SKAction
|
let texturesAction: SKAction
|
||||||
|
@ -210,15 +215,15 @@ class AnimationComponent: GKComponent {
|
||||||
|
|
||||||
// Create an appropriate action from the (possibly offset) animation frames.
|
// Create an appropriate action from the (possibly offset) animation frames.
|
||||||
if animation.repeatTexturesForever {
|
if animation.repeatTexturesForever {
|
||||||
texturesAction = SKAction.repeatActionForever(SKAction.animateWithTextures(animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame))
|
texturesAction = SKAction.repeatForever(SKAction.animate(with: animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
texturesAction = SKAction.animateWithTextures(animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame)
|
texturesAction = SKAction.animate(with: animation.offsetTextures, timePerFrame: AnimationComponent.timePerFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the textures animation to the body node.
|
// Add the textures animation to the body node.
|
||||||
node.runAction(texturesAction, withKey: AnimationComponent.textureActionKey)
|
node.run(texturesAction, withKey: AnimationComponent.textureActionKey)
|
||||||
|
|
||||||
// Remember the animation we are currently running.
|
// Remember the animation we are currently running.
|
||||||
currentAnimation = animation
|
currentAnimation = animation
|
||||||
|
@ -229,14 +234,14 @@ class AnimationComponent: GKComponent {
|
||||||
|
|
||||||
// MARK: GKComponent Life Cycle
|
// MARK: GKComponent Life Cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(deltaTime: NSTimeInterval) {
|
override func update(deltaTime: TimeInterval) {
|
||||||
super.updateWithDeltaTime(deltaTime)
|
super.update(deltaTime: deltaTime)
|
||||||
|
|
||||||
// If an animation has been requested, run the animation.
|
// If an animation has been requested, run the animation.
|
||||||
if let animationState = requestedAnimationState {
|
if let animationState = requestedAnimationState {
|
||||||
guard let orientationComponent = entity?.componentForClass(OrientationComponent.self) else { fatalError("An AnimationComponent's entity must have an OrientationComponent.") }
|
guard let orientationComponent = entity?.component(ofType: OrientationComponent.self) else { fatalError("An AnimationComponent's entity must have an OrientationComponent.") }
|
||||||
|
|
||||||
runAnimationForAnimationState(animationState, compassDirection: orientationComponent.compassDirection, deltaTime: deltaTime)
|
runAnimationForAnimationState(animationState: animationState, compassDirection: orientationComponent.compassDirection, deltaTime: deltaTime)
|
||||||
requestedAnimationState = nil
|
requestedAnimationState = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +253,7 @@ class AnimationComponent: GKComponent {
|
||||||
// Filter for this facing direction, and sort the resulting texture names alphabetically.
|
// Filter for this facing direction, and sort the resulting texture names alphabetically.
|
||||||
let textureNames = atlas.textureNames.filter {
|
let textureNames = atlas.textureNames.filter {
|
||||||
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
|
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
|
||||||
}.sort()
|
}.sorted()
|
||||||
|
|
||||||
// Find and return the first texture for this direction.
|
// Find and return the first texture for this direction.
|
||||||
return atlas.textureNamed(textureNames.first!)
|
return atlas.textureNamed(textureNames.first!)
|
||||||
|
@ -257,7 +262,7 @@ class AnimationComponent: GKComponent {
|
||||||
/// Creates a texture action from all textures in an atlas.
|
/// Creates a texture action from all textures in an atlas.
|
||||||
class func actionForAllTexturesInAtlas(atlas: SKTextureAtlas) -> SKAction {
|
class func actionForAllTexturesInAtlas(atlas: SKTextureAtlas) -> SKAction {
|
||||||
// Sort the texture names alphabetically, and map them to an array of actual textures.
|
// Sort the texture names alphabetically, and map them to an array of actual textures.
|
||||||
let textures = atlas.textureNames.sort().map {
|
let textures = atlas.textureNames.sorted().map {
|
||||||
atlas.textureNamed($0)
|
atlas.textureNamed($0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,8 +271,8 @@ class AnimationComponent: GKComponent {
|
||||||
return SKAction.setTexture(textures.first!)
|
return SKAction.setTexture(textures.first!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let texturesAction = SKAction.animateWithTextures(textures, timePerFrame: AnimationComponent.timePerFrame)
|
let texturesAction = SKAction.animate(with: textures, timePerFrame: AnimationComponent.timePerFrame)
|
||||||
return SKAction.repeatActionForever(texturesAction)
|
return SKAction.repeatForever(texturesAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +304,7 @@ class AnimationComponent: GKComponent {
|
||||||
// Find all matching texture names, sorted alphabetically, and map them to an array of actual textures.
|
// Find all matching texture names, sorted alphabetically, and map them to an array of actual textures.
|
||||||
let textures = atlas.textureNames.filter {
|
let textures = atlas.textureNames.filter {
|
||||||
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
|
$0.hasPrefix("\(identifier)_\(compassDirection.rawValue)_")
|
||||||
}.sort {
|
}.sorted {
|
||||||
playBackwards ? $0 > $1 : $0 < $1
|
playBackwards ? $0 > $1 : $0 < $1
|
||||||
}.map {
|
}.map {
|
||||||
atlas.textureNamed($0)
|
atlas.textureNamed($0)
|
||||||
|
|
|
@ -20,8 +20,8 @@ class BeamComponent: GKComponent {
|
||||||
let rotation: Float
|
let rotation: Float
|
||||||
|
|
||||||
init(entity: GKEntity, antennaOffset: CGPoint) {
|
init(entity: GKEntity, antennaOffset: CGPoint) {
|
||||||
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has a RenderComponent") }
|
guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has a RenderComponent") }
|
||||||
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has an OrientationComponent") }
|
guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("An AntennaInfo must be created with an entity that has an OrientationComponent") }
|
||||||
|
|
||||||
position = CGPoint(x: renderComponent.node.position.x + antennaOffset.x, y: renderComponent.node.position.y + antennaOffset.y)
|
position = CGPoint(x: renderComponent.node.position.x + antennaOffset.x, y: renderComponent.node.position.y + antennaOffset.y)
|
||||||
rotation = Float(orientationComponent.zRotation)
|
rotation = Float(orientationComponent.zRotation)
|
||||||
|
@ -71,7 +71,7 @@ class BeamComponent: GKComponent {
|
||||||
|
|
||||||
/// The `RenderComponent' for this component's 'entity'.
|
/// The `RenderComponent' for this component's 'entity'.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = entity?.componentForClass(RenderComponent.self) else { fatalError("A BeamComponent's entity must have a RenderComponent") }
|
guard let renderComponent = entity?.component(ofType: RenderComponent.self) else { fatalError("A BeamComponent's entity must have a RenderComponent") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,11 @@ class BeamComponent: GKComponent {
|
||||||
BeamCoolingState(beamComponent: self)
|
BeamCoolingState(beamComponent: self)
|
||||||
])
|
])
|
||||||
|
|
||||||
stateMachine.enterState(BeamIdleState.self)
|
stateMachine.enter(BeamIdleState.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -96,8 +100,8 @@ class BeamComponent: GKComponent {
|
||||||
|
|
||||||
// MARK: GKComponent Life Cycle
|
// MARK: GKComponent Life Cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
stateMachine.updateWithDeltaTime(seconds)
|
stateMachine.update(deltaTime: seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
@ -106,18 +110,18 @@ class BeamComponent: GKComponent {
|
||||||
Finds the nearest "bad" `TaskBot` that lies within the beam's arc.
|
Finds the nearest "bad" `TaskBot` that lies within the beam's arc.
|
||||||
Returns `nil` if no `TaskBot`s are within targeting range.
|
Returns `nil` if no `TaskBot`s are within targeting range.
|
||||||
*/
|
*/
|
||||||
func findTargetInBeamArcWithCurrentTarget(currentTarget: TaskBot?) -> TaskBot? {
|
func findTargetInBeamArc(withCurrentTarget currentTarget: TaskBot?) -> TaskBot? {
|
||||||
let playerBotNode = renderComponent.node
|
let playerBotNode = renderComponent.node
|
||||||
|
|
||||||
// Use the player's `EntitySnapshot` to build an array of targetable `TaskBot`s who's antennas are within the beam's arc.
|
// Use the player's `EntitySnapshot` to build an array of targetable `TaskBot`s who's antennas are within the beam's arc.
|
||||||
guard let level = playerBotNode.scene as? LevelScene else { return nil }
|
guard let level = playerBotNode.scene as? LevelScene else { return nil }
|
||||||
guard let snapshot = level.entitySnapshotForEntity(playerBot) else { return nil }
|
guard let snapshot = level.entitySnapshotForEntity(entity: playerBot) else { return nil }
|
||||||
|
|
||||||
let botsInArc = snapshot.entityDistances.filter { entityDistance in
|
let botsInArc = snapshot.entityDistances.filter { entityDistance in
|
||||||
guard let taskBot = entityDistance.target as? TaskBot else { return false }
|
guard let taskBot = entityDistance.target as? TaskBot else { return false }
|
||||||
|
|
||||||
// Filter out entities that aren't "bad" `TaskBot`s with a `RenderComponent`.
|
// Filter out entities that aren't "bad" `TaskBot`s with a `RenderComponent`.
|
||||||
guard let taskBotNode = taskBot.componentForClass(RenderComponent.self)?.node else { return false }
|
guard let taskBotNode = taskBot.component(ofType: RenderComponent.self)?.node else { return false }
|
||||||
if taskBot.isGood {
|
if taskBot.isGood {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -139,16 +143,16 @@ class BeamComponent: GKComponent {
|
||||||
This adjustment allows for easier aiming as the `PlayerBot` and `TaskBot`
|
This adjustment allows for easier aiming as the `PlayerBot` and `TaskBot`
|
||||||
get closer together.
|
get closer together.
|
||||||
*/
|
*/
|
||||||
let arcAngle = playerBotAntenna.angleTo(taskBotAntenna) * targetDistanceRatio
|
let arcAngle = playerBotAntenna.angleTo(target: taskBotAntenna) * targetDistanceRatio
|
||||||
if arcAngle > Float(GameplayConfiguration.Beam.maxArcAngle) {
|
if arcAngle > Float(GameplayConfiguration.Beam.maxArcAngle) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out `TaskBot`s where there is scenery between their antenna and the `PlayerBot`'s antenna.
|
// Filter out `TaskBot`s where there is scenery between their antenna and the `PlayerBot`'s antenna.
|
||||||
var hasLineOfSite = true
|
var hasLineOfSite = true
|
||||||
level.physicsWorld.enumerateBodiesAlongRayStart(playerBotAntenna.position, end: taskBotAntenna.position) { obstacleBody, _, _, stop in
|
level.physicsWorld.enumerateBodies(alongRayStart: playerBotAntenna.position, end: taskBotAntenna.position) { obstacleBody, _, _, stop in
|
||||||
// Ignore `EntityNode`s as they are not scenery.
|
// Ignore nodes that have an entity as they are not scenery.
|
||||||
if obstacleBody.node is EntityNode {
|
if obstacleBody.node?.entity != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +166,7 @@ class BeamComponent: GKComponent {
|
||||||
*/
|
*/
|
||||||
if obstacleLowestY < taskBotNode.position.y || obstacleLowestY < playerBotNode.position.y {
|
if obstacleLowestY < taskBotNode.position.y || obstacleLowestY < playerBotNode.position.y {
|
||||||
hasLineOfSite = false
|
hasLineOfSite = false
|
||||||
stop.memory = true
|
stop.pointee = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +178,7 @@ class BeamComponent: GKComponent {
|
||||||
let target: TaskBot?
|
let target: TaskBot?
|
||||||
|
|
||||||
// If the current target is still targetable, continue to target it.
|
// If the current target is still targetable, continue to target it.
|
||||||
if let currentTarget = currentTarget where botsInArc.contains(currentTarget) {
|
if let currentTarget = currentTarget, botsInArc.contains(currentTarget) {
|
||||||
target = currentTarget
|
target = currentTarget
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -15,7 +15,7 @@ class BeamCoolingState: GKState {
|
||||||
unowned var beamComponent: BeamComponent
|
unowned var beamComponent: BeamComponent
|
||||||
|
|
||||||
/// The amount of time the beam has been cooling down.
|
/// The amount of time the beam has been cooling down.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
|
@ -25,24 +25,24 @@ class BeamCoolingState: GKState {
|
||||||
|
|
||||||
// MARK: GKState life cycle
|
// MARK: GKState life cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
|
||||||
// If the beam has spent long enough cooling down, enter `BeamIdleState`.
|
// If the beam has spent long enough cooling down, enter `BeamIdleState`.
|
||||||
if elapsedTime >= GameplayConfiguration.Beam.coolDownDuration {
|
if elapsedTime >= GameplayConfiguration.Beam.coolDownDuration {
|
||||||
stateMachine?.enterState(BeamIdleState.self)
|
stateMachine?.enter(BeamIdleState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is BeamIdleState.Type, is BeamFiringState.Type:
|
case is BeamIdleState.Type, is BeamFiringState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -52,11 +52,11 @@ class BeamCoolingState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
if let playerBot = beamComponent.entity as? PlayerBot {
|
if let playerBot = beamComponent.entity as? PlayerBot {
|
||||||
beamComponent.beamNode.updateWithBeamState(nextState, source: playerBot)
|
beamComponent.beamNode.update(withBeamState: nextState, source: playerBot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class BeamFiringState: GKState {
|
||||||
var target: TaskBot?
|
var target: TaskBot?
|
||||||
|
|
||||||
/// The amount of time the beam has been in its "firing" state.
|
/// The amount of time the beam has been in its "firing" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `PlayerBot` associated with the `BeamComponent`'s `entity`.
|
/// The `PlayerBot` associated with the `BeamComponent`'s `entity`.
|
||||||
var playerBot: PlayerBot {
|
var playerBot: PlayerBot {
|
||||||
|
@ -28,7 +28,7 @@ class BeamFiringState: GKState {
|
||||||
|
|
||||||
/// The `RenderComponent` associated with the `BeamComponent`'s `entity`.
|
/// The `RenderComponent` associated with the `BeamComponent`'s `entity`.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = beamComponent.entity?.componentForClass(RenderComponent.self) else { fatalError("A BeamFiringState's entity must have a RenderComponent.") }
|
guard let renderComponent = beamComponent.entity?.component(ofType: RenderComponent.self) else { fatalError("A BeamFiringState's entity must have a RenderComponent.") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,8 @@ class BeamFiringState: GKState {
|
||||||
|
|
||||||
// MARK: GKState life cycle
|
// MARK: GKState life cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the "amount of time firing" tracker when we enter the "firing" state.
|
// Reset the "amount of time firing" tracker when we enter the "firing" state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
@ -63,7 +63,7 @@ class BeamFiringState: GKState {
|
||||||
*/
|
*/
|
||||||
beamComponent.beamNode.zPosition = -1.0
|
beamComponent.beamNode.zPosition = -1.0
|
||||||
|
|
||||||
let aboveCharactersNode = scene.worldLayerNodes[.AboveCharacters]!
|
let aboveCharactersNode = scene.worldLayerNodes[.aboveCharacters]!
|
||||||
aboveCharactersNode.addChild(beamComponent.beamNode)
|
aboveCharactersNode.addChild(beamComponent.beamNode)
|
||||||
|
|
||||||
// Constrain the `BeamNode` to the antenna position on the `PlayerBot`'s node.
|
// Constrain the `BeamNode` to the antenna position on the `PlayerBot`'s node.
|
||||||
|
@ -76,11 +76,11 @@ class BeamFiringState: GKState {
|
||||||
beamComponent.beamNode.constraints = [constraint]
|
beamComponent.beamNode.constraints = [constraint]
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBeamNodeWithDeltaTime(0.0)
|
updateBeamNode(withDeltaTime: 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the "amount of time firing" tracker.
|
// Update the "amount of time firing" tracker.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
@ -90,18 +90,18 @@ class BeamFiringState: GKState {
|
||||||
The player has been firing the beam for too long. Enter the `BeamCoolingState`
|
The player has been firing the beam for too long. Enter the `BeamCoolingState`
|
||||||
to disable firing until the beam has had time to cool down.
|
to disable firing until the beam has had time to cool down.
|
||||||
*/
|
*/
|
||||||
stateMachine?.enterState(BeamCoolingState.self)
|
stateMachine?.enter(BeamCoolingState.self)
|
||||||
}
|
}
|
||||||
else if !beamComponent.isTriggered {
|
else if !beamComponent.isTriggered {
|
||||||
// The beam is no longer being fired. Enter the `BeamIdleState`.
|
// The beam is no longer being fired. Enter the `BeamIdleState`.
|
||||||
stateMachine?.enterState(BeamIdleState.self)
|
stateMachine?.enter(BeamIdleState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
updateBeamNodeWithDeltaTime(seconds)
|
updateBeamNode(withDeltaTime: seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is BeamIdleState.Type, is BeamCoolingState.Type:
|
case is BeamIdleState.Type, is BeamCoolingState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -111,35 +111,35 @@ class BeamFiringState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
// Clear the current target.
|
// Clear the current target.
|
||||||
target = nil
|
target = nil
|
||||||
|
|
||||||
// Update the beam component with the next state.
|
// Update the beam component with the next state.
|
||||||
beamComponent.beamNode.updateWithBeamState(nextState, source: beamComponent.playerBot)
|
beamComponent.beamNode.update(withBeamState: nextState, source: beamComponent.playerBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func updateBeamNodeWithDeltaTime(seconds: NSTimeInterval) {
|
func updateBeamNode(withDeltaTime seconds: TimeInterval) {
|
||||||
// Find an appropriate target for the beam.
|
// Find an appropriate target for the beam.
|
||||||
target = beamComponent.findTargetInBeamArcWithCurrentTarget(target)
|
target = beamComponent.findTargetInBeamArc(withCurrentTarget: target)
|
||||||
|
|
||||||
// If the beam has a target with a charge component, drain charge from it.
|
// If the beam has a target with a charge component, drain charge from it.
|
||||||
if let chargeComponent = target?.componentForClass(ChargeComponent.self) {
|
if let chargeComponent = target?.component(ofType: ChargeComponent.self) {
|
||||||
let chargeToLose = GameplayConfiguration.Beam.chargeLossPerSecond * seconds
|
let chargeToLose = GameplayConfiguration.Beam.chargeLossPerSecond * seconds
|
||||||
chargeComponent.loseCharge(chargeToLose)
|
chargeComponent.loseCharge(chargeToLose: chargeToLose)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the appearance, position, size and orientation of the `BeamNode`.
|
// Update the appearance, position, size and orientation of the `BeamNode`.
|
||||||
beamComponent.beamNode.updateWithBeamState(self, source: playerBot, target: target)
|
beamComponent.beamNode.update(withBeamState: self, source: playerBot, target: target)
|
||||||
|
|
||||||
// If the current target has been turned good, deactivate the beam and move to the idle state.
|
// If the current target has been turned good, deactivate the beam and move to the idle state.
|
||||||
if let currentTarget = target where currentTarget.isGood {
|
if let currentTarget = target, currentTarget.isGood {
|
||||||
beamComponent.isTriggered = false
|
beamComponent.isTriggered = false
|
||||||
stateMachine?.enterState(BeamIdleState.self)
|
stateMachine?.enter(BeamIdleState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,16 +22,16 @@ class BeamIdleState: GKState {
|
||||||
|
|
||||||
// MARK: GKState life cycle
|
// MARK: GKState life cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// If the beam has been triggered, enter `BeamFiringState`.
|
// If the beam has been triggered, enter `BeamFiringState`.
|
||||||
if beamComponent.isTriggered {
|
if beamComponent.isTriggered {
|
||||||
stateMachine?.enterState(BeamFiringState.self)
|
stateMachine?.enter(BeamFiringState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return stateClass is BeamFiringState.Type
|
return stateClass is BeamFiringState.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,10 @@ class ChargeComponent: GKComponent {
|
||||||
|
|
||||||
chargeBar?.level = percentageCharge
|
chargeBar?.level = percentageCharge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Component actions
|
// MARK: Component actions
|
||||||
|
|
||||||
|
@ -78,7 +82,7 @@ class ChargeComponent: GKComponent {
|
||||||
if newCharge < charge {
|
if newCharge < charge {
|
||||||
charge = newCharge
|
charge = newCharge
|
||||||
chargeBar?.level = percentageCharge
|
chargeBar?.level = percentageCharge
|
||||||
delegate?.chargeComponentDidLoseCharge(self)
|
delegate?.chargeComponentDidLoseCharge(chargeComponent: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,18 +10,18 @@ import CoreGraphics
|
||||||
|
|
||||||
/// The different directions that an animated character can be facing.
|
/// The different directions that an animated character can be facing.
|
||||||
enum CompassDirection: Int {
|
enum CompassDirection: Int {
|
||||||
case East = 0, EastByNorthEast, NorthEast, NorthByNorthEast
|
case east = 0, eastByNorthEast, northEast, northByNorthEast
|
||||||
case North, NorthByNorthWest, NorthWest, WestByNorthWest
|
case north, northByNorthWest, northWest, westByNorthWest
|
||||||
case West, WestBySouthWest, SouthWest, SouthBySouthWest
|
case west, westBySouthWest, southWest, southBySouthWest
|
||||||
case South, SouthBySouthEast, SouthEast, EastBySouthEast
|
case south, southBySouthEast, southEast, eastBySouthEast
|
||||||
|
|
||||||
/// Convenience array of all available directions.
|
/// Convenience array of all available directions.
|
||||||
static let allDirections: [CompassDirection] =
|
static let allDirections: [CompassDirection] =
|
||||||
[
|
[
|
||||||
.East, .EastByNorthEast, .NorthEast, .NorthByNorthEast,
|
.east, .eastByNorthEast, .northEast, .northByNorthEast,
|
||||||
.North, .NorthByNorthWest, .NorthWest, .WestByNorthWest,
|
.north, .northByNorthWest, .northWest, .westByNorthWest,
|
||||||
.West, .WestBySouthWest, .SouthWest, .SouthBySouthWest,
|
.west, .westBySouthWest, .southWest, .southBySouthWest,
|
||||||
.South, .SouthBySouthEast, .SouthEast, .EastBySouthEast
|
.south, .southBySouthEast, .southEast, .eastBySouthEast
|
||||||
]
|
]
|
||||||
|
|
||||||
/// The angle of rotation that the orientation represents.
|
/// The angle of rotation that the orientation represents.
|
||||||
|
@ -37,13 +37,13 @@ enum CompassDirection: Int {
|
||||||
let twoPi = M_PI * 2
|
let twoPi = M_PI * 2
|
||||||
|
|
||||||
// Normalize the node's rotation.
|
// Normalize the node's rotation.
|
||||||
let rotation = (Double(zRotation) + twoPi) % twoPi
|
let rotation = (Double(zRotation) + twoPi).truncatingRemainder(dividingBy: twoPi)
|
||||||
|
|
||||||
// Convert the rotation of the node to a percentage of a circle.
|
// Convert the rotation of the node to a percentage of a circle.
|
||||||
let orientation = rotation / twoPi
|
let orientation = rotation / twoPi
|
||||||
|
|
||||||
// Scale the percentage to a value between 0 and 15.
|
// Scale the percentage to a value between 0 and 15.
|
||||||
let rawFacingValue = round(orientation * 16.0) % 16.0
|
let rawFacingValue = round(orientation * 16.0).truncatingRemainder(dividingBy: 16.0)
|
||||||
|
|
||||||
// Select the appropriate `CompassDirection` based on its members' raw values, which also run from 0 to 15.
|
// Select the appropriate `CompassDirection` based on its members' raw values, which also run from 0 to 15.
|
||||||
self = CompassDirection(rawValue: Int(rawFacingValue))!
|
self = CompassDirection(rawValue: Int(rawFacingValue))!
|
||||||
|
@ -52,28 +52,28 @@ enum CompassDirection: Int {
|
||||||
init(string: String) {
|
init(string: String) {
|
||||||
switch string {
|
switch string {
|
||||||
case "North":
|
case "North":
|
||||||
self = .North
|
self = .north
|
||||||
|
|
||||||
case "NorthEast":
|
case "NorthEast":
|
||||||
self = .NorthEast
|
self = .northEast
|
||||||
|
|
||||||
case "East":
|
case "East":
|
||||||
self = .East
|
self = .east
|
||||||
|
|
||||||
case "SouthEast":
|
case "SouthEast":
|
||||||
self = .SouthEast
|
self = .southEast
|
||||||
|
|
||||||
case "South":
|
case "South":
|
||||||
self = .South
|
self = .south
|
||||||
|
|
||||||
case "SouthWest":
|
case "SouthWest":
|
||||||
self = .SouthWest
|
self = .southWest
|
||||||
|
|
||||||
case "West":
|
case "West":
|
||||||
self = .West
|
self = .west
|
||||||
|
|
||||||
case "NorthWest":
|
case "NorthWest":
|
||||||
self = .NorthWest
|
self = .northWest
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fatalError("Unknown or unsupported string - \(string)")
|
fatalError("Unknown or unsupported string - \(string)")
|
||||||
|
|
|
@ -24,17 +24,17 @@ class FlyingBotBlastState: GKState {
|
||||||
var currentEmitterNode: SKEmitterNode?
|
var currentEmitterNode: SKEmitterNode?
|
||||||
|
|
||||||
/// The amount of time the `TaskBot` has been in the "blast" state.
|
/// The amount of time the `TaskBot` has been in the "blast" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A FlyingBotBlastState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A FlyingBotBlastState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `RenderComponent` associated with the `entity`.
|
/// The `RenderComponent` associated with the `entity`.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("A FlyingBotBlastState's entity must have a RenderComponent.") }
|
guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("A FlyingBotBlastState's entity must have a RenderComponent.") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ class FlyingBotBlastState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the "length of this blast" tracker when entering the "blast" state.
|
// Reset the "length of this blast" tracker when entering the "blast" state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
@ -81,17 +81,17 @@ class FlyingBotBlastState: GKState {
|
||||||
renderComponent.node.addChild(currentEmitterNode!)
|
renderComponent.node.addChild(currentEmitterNode!)
|
||||||
|
|
||||||
// Request the appropriate "attack" animation for this `TaskBot`.
|
// Request the appropriate "attack" animation for this `TaskBot`.
|
||||||
animationComponent.requestedAnimationState = .Attack
|
animationComponent.requestedAnimationState = .attack
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Check if the `FlyingBot` has reached the end of its blast duration.
|
// Check if the `FlyingBot` has reached the end of its blast duration.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
if elapsedTime >= GameplayConfiguration.FlyingBot.blastDuration {
|
if elapsedTime >= GameplayConfiguration.FlyingBot.blastDuration {
|
||||||
// Return to an agent-controlled state if the blast has completed.
|
// Return to an agent-controlled state if the blast has completed.
|
||||||
stateMachine?.enterState(TaskBotAgentControlledState.self)
|
stateMachine?.enter(TaskBotAgentControlledState.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
else if elapsedTime < GameplayConfiguration.FlyingBot.blastEffectDuration {
|
else if elapsedTime < GameplayConfiguration.FlyingBot.blastEffectDuration {
|
||||||
|
@ -100,12 +100,12 @@ class FlyingBotBlastState: GKState {
|
||||||
performGoodBlast()
|
performGoodBlast()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
performBadBlastWithDeltaTime(seconds)
|
performBadBlast(withDeltaTime: seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
|
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -115,8 +115,8 @@ class FlyingBotBlastState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
// Remove the blast effect emitter node from the `TaskBot` when leaving the blast state.
|
// Remove the blast effect emitter node from the `TaskBot` when leaving the blast state.
|
||||||
currentEmitterNode?.removeFromParent()
|
currentEmitterNode?.removeFromParent()
|
||||||
|
@ -129,7 +129,7 @@ class FlyingBotBlastState: GKState {
|
||||||
func entitiesInRange() -> [GKEntity] {
|
func entitiesInRange() -> [GKEntity] {
|
||||||
// Retrieve an entity snapshot containing the distances from this `TaskBot` to other entities in the `LevelScene`.
|
// Retrieve an entity snapshot containing the distances from this `TaskBot` to other entities in the `LevelScene`.
|
||||||
guard let level = renderComponent.node.scene as? LevelScene else { return [] }
|
guard let level = renderComponent.node.scene as? LevelScene else { return [] }
|
||||||
guard let entitySnapshot = level.entitySnapshotForEntity(entity) else { return [] }
|
guard let entitySnapshot = level.entitySnapshotForEntity(entity: entity) else { return [] }
|
||||||
|
|
||||||
// Convert the array of `EntityDistance`s to an array of `GKEntity`s where the distance to the entity is within the blast radius.
|
// Convert the array of `EntityDistance`s to an array of `GKEntity`s where the distance to the entity is within the blast radius.
|
||||||
let entitiesInRange: [GKEntity] = entitySnapshot.entityDistances.flatMap {
|
let entitiesInRange: [GKEntity] = entitySnapshot.entityDistances.flatMap {
|
||||||
|
@ -150,7 +150,7 @@ class FlyingBotBlastState: GKState {
|
||||||
// Iterate through the `TaskBot`s in range.
|
// Iterate through the `TaskBot`s in range.
|
||||||
for taskBot in taskBotsInRange {
|
for taskBot in taskBotsInRange {
|
||||||
// Retrieve the current intelligence state for the `TaskBot`.
|
// Retrieve the current intelligence state for the `TaskBot`.
|
||||||
guard let currentState = taskBot.componentForClass(IntelligenceComponent.self)?.stateMachine.currentState else { continue }
|
guard let currentState = taskBot.component(ofType: IntelligenceComponent.self)?.stateMachine.currentState else { continue }
|
||||||
|
|
||||||
// If the entity is a "bad" `TaskBot` that isn't currently attacking, turn it "good".
|
// If the entity is a "bad" `TaskBot` that isn't currently attacking, turn it "good".
|
||||||
if taskBot.isGood { continue }
|
if taskBot.isGood { continue }
|
||||||
|
@ -166,7 +166,7 @@ class FlyingBotBlastState: GKState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a "bad" blast that removes charge from the `PlayerBot` and turns "good" `TaskBot`s "bad".
|
/// Performs a "bad" blast that removes charge from the `PlayerBot` and turns "good" `TaskBot`s "bad".
|
||||||
func performBadBlastWithDeltaTime(seconds: NSTimeInterval) {
|
func performBadBlast(withDeltaTime seconds: TimeInterval) {
|
||||||
// Calculate how much charge `PlayerBot`s should lose if hit by this application of the blast attack.
|
// Calculate how much charge `PlayerBot`s should lose if hit by this application of the blast attack.
|
||||||
let chargeToLose = GameplayConfiguration.FlyingBot.blastChargeLossPerSecond * seconds
|
let chargeToLose = GameplayConfiguration.FlyingBot.blastChargeLossPerSecond * seconds
|
||||||
|
|
||||||
|
@ -174,12 +174,12 @@ class FlyingBotBlastState: GKState {
|
||||||
let entities = entitiesInRange()
|
let entities = entitiesInRange()
|
||||||
|
|
||||||
for entity in entities {
|
for entity in entities {
|
||||||
if let playerBot = entity as? PlayerBot where !playerBot.isPoweredDown,
|
if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown,
|
||||||
let chargeComponent = entity.componentForClass(ChargeComponent.self) {
|
let chargeComponent = entity.component(ofType: ChargeComponent.self) {
|
||||||
// Decrease the charge of a `PlayerBot` if it is in range and not powered down.
|
// Decrease the charge of a `PlayerBot` if it is in range and not powered down.
|
||||||
chargeComponent.loseCharge(chargeToLose)
|
chargeComponent.loseCharge(chargeToLose: chargeToLose)
|
||||||
}
|
}
|
||||||
else if let taskBot = entity as? TaskBot where taskBot.isGood {
|
else if let taskBot = entity as? TaskBot, taskBot.isGood {
|
||||||
// Turn a `TaskBot` "bad" if it is in range and "good".
|
// Turn a `TaskBot` "bad" if it is in range and "good".
|
||||||
taskBot.isGood = false
|
taskBot.isGood = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ class FlyingBotPreAttackState: GKState {
|
||||||
unowned var entity: FlyingBot
|
unowned var entity: FlyingBot
|
||||||
|
|
||||||
/// The amount of time the `FlyingBot` has been in its "pre-attack" state.
|
/// The amount of time the `FlyingBot` has been in its "pre-attack" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A FlyingBotPreAttackState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A FlyingBotPreAttackState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,18 +31,18 @@ class FlyingBotPreAttackState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the tracking of how long the `TaskBot` has been in a "pre-attack" state.
|
// Reset the tracking of how long the `TaskBot` has been in a "pre-attack" state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
|
||||||
// Request the "attack" animation for this `FlyingBot`.
|
// Request the "attack" animation for this `FlyingBot`.
|
||||||
animationComponent.requestedAnimationState = .Attack
|
animationComponent.requestedAnimationState = .attack
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the time that the `TaskBot` has been in its "pre-attack" state.
|
// Update the time that the `TaskBot` has been in its "pre-attack" state.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
@ -52,11 +52,11 @@ class FlyingBotPreAttackState: GKState {
|
||||||
move to the attack state.
|
move to the attack state.
|
||||||
*/
|
*/
|
||||||
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
|
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
|
||||||
stateMachine?.enterState(FlyingBotBlastState.self)
|
stateMachine?.enter(FlyingBotBlastState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotAgentControlledState.Type, is FlyingBotBlastState.Type, is TaskBotZappedState.Type:
|
case is TaskBotAgentControlledState.Type, is FlyingBotBlastState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -19,13 +19,13 @@ class GroundBotAttackState: GKState {
|
||||||
|
|
||||||
/// The `MovementComponent` associated with the `entity`.
|
/// The `MovementComponent` associated with the `entity`.
|
||||||
var movementComponent: MovementComponent {
|
var movementComponent: MovementComponent {
|
||||||
guard let movementComponent = entity.componentForClass(MovementComponent.self) else { fatalError("A GroundBotAttackState's entity must have a MovementComponent.") }
|
guard let movementComponent = entity.component(ofType: MovementComponent.self) else { fatalError("A GroundBotAttackState's entity must have a MovementComponent.") }
|
||||||
return movementComponent
|
return movementComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `PhysicsComponent` associated with the `entity`.
|
/// The `PhysicsComponent` associated with the `entity`.
|
||||||
var physicsComponent: PhysicsComponent {
|
var physicsComponent: PhysicsComponent {
|
||||||
guard let physicsComponent = entity.componentForClass(PhysicsComponent.self) else { fatalError("A GroundBotAttackState's entity must have a PhysicsComponent.") }
|
guard let physicsComponent = entity.component(ofType: PhysicsComponent.self) else { fatalError("A GroundBotAttackState's entity must have a PhysicsComponent.") }
|
||||||
return physicsComponent
|
return physicsComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,14 +43,14 @@ class GroundBotAttackState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Apply damage to any entities the `GroundBot` is already in contact with.
|
// Apply damage to any entities the `GroundBot` is already in contact with.
|
||||||
let contactedBodies = physicsComponent.physicsBody.allContactedBodies()
|
let contactedBodies = physicsComponent.physicsBody.allContactedBodies()
|
||||||
for contactedBody in contactedBodies {
|
for contactedBody in contactedBodies {
|
||||||
guard let entity = (contactedBody.node as? EntityNode)?.entity else { continue }
|
guard let entity = contactedBody.node?.entity else { continue }
|
||||||
applyDamageToEntity(entity)
|
applyDamageToEntity(entity: entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
|
@ -74,8 +74,8 @@ class GroundBotAttackState: GKState {
|
||||||
movementComponent.nextRotation = nil
|
movementComponent.nextRotation = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `targetPosition` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
let targetPosition = self.targetPosition
|
let targetPosition = self.targetPosition
|
||||||
|
@ -86,7 +86,7 @@ class GroundBotAttackState: GKState {
|
||||||
|
|
||||||
let currentDistanceToTarget = hypot(dx, dy)
|
let currentDistanceToTarget = hypot(dx, dy)
|
||||||
if currentDistanceToTarget < GameplayConfiguration.GroundBot.attackEndProximity {
|
if currentDistanceToTarget < GameplayConfiguration.GroundBot.attackEndProximity {
|
||||||
stateMachine?.enterState(TaskBotAgentControlledState.self)
|
stateMachine?.enter(TaskBotAgentControlledState.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class GroundBotAttackState: GKState {
|
||||||
its target because it has been knocked off course.
|
its target because it has been knocked off course.
|
||||||
*/
|
*/
|
||||||
if currentDistanceToTarget > lastDistanceToTarget {
|
if currentDistanceToTarget > lastDistanceToTarget {
|
||||||
stateMachine?.enterState(TaskBotAgentControlledState.self)
|
stateMachine?.enter(TaskBotAgentControlledState.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ class GroundBotAttackState: GKState {
|
||||||
lastDistanceToTarget = currentDistanceToTarget
|
lastDistanceToTarget = currentDistanceToTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
|
case is TaskBotAgentControlledState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -113,8 +113,8 @@ class GroundBotAttackState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
let movementComponent = self.movementComponent
|
let movementComponent = self.movementComponent
|
||||||
|
@ -129,11 +129,11 @@ class GroundBotAttackState: GKState {
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func applyDamageToEntity(entity: GKEntity) {
|
func applyDamageToEntity(entity: GKEntity) {
|
||||||
if let playerBot = entity as? PlayerBot, chargeComponent = playerBot.componentForClass(ChargeComponent.self) where !playerBot.isPoweredDown {
|
if let playerBot = entity as? PlayerBot, let chargeComponent = playerBot.component(ofType: ChargeComponent.self), !playerBot.isPoweredDown {
|
||||||
// If the other entity is a `PlayerBot` that isn't powered down, reduce its charge.
|
// If the other entity is a `PlayerBot` that isn't powered down, reduce its charge.
|
||||||
chargeComponent.loseCharge(GameplayConfiguration.GroundBot.chargeLossPerContact)
|
chargeComponent.loseCharge(chargeToLose: GameplayConfiguration.GroundBot.chargeLossPerContact)
|
||||||
}
|
}
|
||||||
else if let taskBot = entity as? TaskBot where taskBot.isGood {
|
else if let taskBot = entity as? TaskBot, taskBot.isGood {
|
||||||
// If the other entity is a good `TaskBot`, turn it bad.
|
// If the other entity is a good `TaskBot`, turn it bad.
|
||||||
taskBot.isGood = false
|
taskBot.isGood = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ class GroundBotPreAttackState: GKState {
|
||||||
unowned var entity: GroundBot
|
unowned var entity: GroundBot
|
||||||
|
|
||||||
/// The amount of time the `GroundBot` has been in its "pre-attack" state.
|
/// The amount of time the `GroundBot` has been in its "pre-attack" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A GroundBotPreAttackState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A GroundBotPreAttackState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,18 +31,18 @@ class GroundBotPreAttackState: GKState {
|
||||||
|
|
||||||
// MARK: GPState Life Cycle
|
// MARK: GPState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the tracking of how long the `GroundBot` has been in a "pre-attack" state.
|
// Reset the tracking of how long the `GroundBot` has been in a "pre-attack" state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
|
||||||
// Request the "attack" animation for this state's `GroundBot`.
|
// Request the "attack" animation for this state's `GroundBot`.
|
||||||
animationComponent.requestedAnimationState = .Attack
|
animationComponent.requestedAnimationState = .attack
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
|
||||||
|
@ -51,11 +51,11 @@ class GroundBotPreAttackState: GKState {
|
||||||
move to the attack state.
|
move to the attack state.
|
||||||
*/
|
*/
|
||||||
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
|
if elapsedTime >= GameplayConfiguration.TaskBot.preAttackStateDuration {
|
||||||
stateMachine?.enterState(GroundBotAttackState.self)
|
stateMachine?.enter(GroundBotAttackState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotAgentControlledState.Type, is GroundBotAttackState.Type, is TaskBotZappedState.Type:
|
case is TaskBotAgentControlledState.Type, is GroundBotAttackState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -16,13 +16,13 @@ class GroundBotRotateToAttackState: GKState {
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `OrientationComponent` associated with the `entity`.
|
/// The `OrientationComponent` associated with the `entity`.
|
||||||
var orientationComponent: OrientationComponent {
|
var orientationComponent: OrientationComponent {
|
||||||
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an OrientationComponent.") }
|
guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("A GroundBotRotateToAttackState's entity must have an OrientationComponent.") }
|
||||||
return orientationComponent
|
return orientationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,21 +40,21 @@ class GroundBotRotateToAttackState: GKState {
|
||||||
|
|
||||||
// MARK: GPState Life Cycle
|
// MARK: GPState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Request the "walk forward" animation for this `GroundBot`.
|
// Request the "walk forward" animation for this `GroundBot`.
|
||||||
animationComponent.requestedAnimationState = .WalkForward
|
animationComponent.requestedAnimationState = .walkForward
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// `orientationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `orientationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
let orientationComponent = self.orientationComponent
|
let orientationComponent = self.orientationComponent
|
||||||
|
|
||||||
// Calculate the angle the `GroundBot` needs to turn to face the `targetPosition`.
|
// Calculate the angle the `GroundBot` needs to turn to face the `targetPosition`.
|
||||||
let angleDeltaToTarget = shortestAngleDeltaToTargetFromRotation(Float(orientationComponent.zRotation))
|
let angleDeltaToTarget = shortestAngleDeltaToTargetFromRotation(entityRotation: Float(orientationComponent.zRotation))
|
||||||
|
|
||||||
// Calculate the amount of rotation that should be applied during this update.
|
// Calculate the amount of rotation that should be applied during this update.
|
||||||
var delta = CGFloat(seconds * GameplayConfiguration.GroundBot.preAttackRotationSpeed)
|
var delta = CGFloat(seconds * GameplayConfiguration.GroundBot.preAttackRotationSpeed)
|
||||||
|
@ -66,7 +66,7 @@ class GroundBotRotateToAttackState: GKState {
|
||||||
if abs(delta) >= abs(angleDeltaToTarget) {
|
if abs(delta) >= abs(angleDeltaToTarget) {
|
||||||
// Finish the rotation and enter `GroundBotPreAttackState`.
|
// Finish the rotation and enter `GroundBotPreAttackState`.
|
||||||
orientationComponent.zRotation += angleDeltaToTarget
|
orientationComponent.zRotation += angleDeltaToTarget
|
||||||
stateMachine?.enterState(GroundBotPreAttackState.self)
|
stateMachine?.enter(GroundBotPreAttackState.self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,10 +74,10 @@ class GroundBotRotateToAttackState: GKState {
|
||||||
orientationComponent.zRotation += delta
|
orientationComponent.zRotation += delta
|
||||||
|
|
||||||
// The `GroundBot` may have rotated into a new `FacingDirection`, so re-request the "walk forward" animation.
|
// The `GroundBot` may have rotated into a new `FacingDirection`, so re-request the "walk forward" animation.
|
||||||
animationComponent.requestedAnimationState = .WalkForward
|
animationComponent.requestedAnimationState = .walkForward
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotAgentControlledState.Type, is GroundBotPreAttackState.Type, is TaskBotZappedState.Type:
|
case is TaskBotAgentControlledState.Type, is GroundBotPreAttackState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -33,11 +33,11 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
|
||||||
didSet {
|
didSet {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
// Apply the current input state to the movement and beam components.
|
// Apply the current input state to the movement and beam components.
|
||||||
applyInputState(state)
|
applyInputState(state: state)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Apply a state of no input to the movement and beam components.
|
// Apply a state of no input to the movement and beam components.
|
||||||
applyInputState(InputState.noInput)
|
applyInputState(state: InputState.noInput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,22 +45,22 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
|
||||||
var state = InputState() {
|
var state = InputState() {
|
||||||
didSet {
|
didSet {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
applyInputState(state)
|
applyInputState(state: state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ControlInputSourceDelegate
|
// MARK: ControlInputSourceDelegate
|
||||||
|
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) {
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2) {
|
||||||
state.translation = MovementKind(displacement: displacement)
|
state.translation = MovementKind(displacement: displacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) {
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2) {
|
||||||
state.rotation = MovementKind(displacement: angularDisplacement)
|
state.rotation = MovementKind(displacement: angularDisplacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2) {
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2) {
|
||||||
/*
|
/*
|
||||||
Create a `MovementKind` instance indicating whether the displacement
|
Create a `MovementKind` instance indicating whether the displacement
|
||||||
should translate the entity forwards or backwards from the direction
|
should translate the entity forwards or backwards from the direction
|
||||||
|
@ -69,7 +69,7 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
|
||||||
state.translation = MovementKind(displacement: relativeDisplacement, relativeToOrientation: true)
|
state.translation = MovementKind(displacement: relativeDisplacement, relativeToOrientation: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) {
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2) {
|
||||||
/*
|
/*
|
||||||
Create a `MovementKind` instance indicating whether the displacement
|
Create a `MovementKind` instance indicating whether the displacement
|
||||||
should rotate the entity clockwise or counter-clockwise from the direction
|
should rotate the entity clockwise or counter-clockwise from the direction
|
||||||
|
@ -78,26 +78,26 @@ class InputComponent: GKComponent, ControlInputSourceDelegate {
|
||||||
state.rotation = MovementKind(displacement: relativeAngularDisplacement, relativeToOrientation: true)
|
state.rotation = MovementKind(displacement: relativeAngularDisplacement, relativeToOrientation: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType) {
|
||||||
state.allowsStrafing = controlInputSource.allowsStrafing
|
state.allowsStrafing = controlInputSource.allowsStrafing
|
||||||
state.beamIsTriggered = true
|
state.beamIsTriggered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func controlInputSourceDidFinishAttacking(controlInputSource: ControlInputSourceType) {
|
func controlInputSourceDidFinishAttacking(_ controlInputSource: ControlInputSourceType) {
|
||||||
state.beamIsTriggered = false
|
state.beamIsTriggered = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func applyInputState(state: InputState) {
|
func applyInputState(state: InputState) {
|
||||||
if let movementComponent = entity?.componentForClass(MovementComponent.self) {
|
if let movementComponent = entity?.component(ofType: MovementComponent.self) {
|
||||||
movementComponent.allowsStrafing = state.allowsStrafing
|
movementComponent.allowsStrafing = state.allowsStrafing
|
||||||
movementComponent.nextRotation = state.rotation
|
movementComponent.nextRotation = state.rotation
|
||||||
movementComponent.nextTranslation = state.translation
|
movementComponent.nextTranslation = state.translation
|
||||||
}
|
}
|
||||||
|
|
||||||
if let beamComponent = entity?.componentForClass(BeamComponent.self) {
|
if let beamComponent = entity?.component(ofType: BeamComponent.self) {
|
||||||
beamComponent.isTriggered = state.beamIsTriggered
|
beamComponent.isTriggered = state.beamIsTriggered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,20 +21,26 @@ class IntelligenceComponent: GKComponent {
|
||||||
|
|
||||||
init(states: [GKState]) {
|
init(states: [GKState]) {
|
||||||
stateMachine = GKStateMachine(states: states)
|
stateMachine = GKStateMachine(states: states)
|
||||||
initialStateClass = states.first!.dynamicType
|
let firstState = states.first!
|
||||||
|
initialStateClass = type(of: firstState)
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GKComponent Life Cycle
|
// MARK: GKComponent Life Cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
stateMachine.updateWithDeltaTime(seconds)
|
stateMachine.update(deltaTime: seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Actions
|
// MARK: Actions
|
||||||
|
|
||||||
func enterInitialState() {
|
func enterInitialState() {
|
||||||
stateMachine.enterState(initialStateClass)
|
stateMachine.enter(initialStateClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,19 +55,19 @@ class MovementComponent: GKComponent {
|
||||||
|
|
||||||
/// The `RenderComponent` for this component's entity.
|
/// The `RenderComponent` for this component's entity.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = entity?.componentForClass(RenderComponent.self) else { fatalError("A MovementComponent's entity must have a RenderComponent") }
|
guard let renderComponent = entity?.component(ofType: RenderComponent.self) else { fatalError("A MovementComponent's entity must have a RenderComponent") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `AnimationComponent` for this component's entity.
|
/// The `AnimationComponent` for this component's entity.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity?.componentForClass(AnimationComponent.self) else { fatalError("A MovementComponent's entity must have an AnimationComponent") }
|
guard let animationComponent = entity?.component(ofType: AnimationComponent.self) else { fatalError("A MovementComponent's entity must have an AnimationComponent") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `OrientationComponent` for this component's entity.
|
/// The `OrientationComponent` for this component's entity.
|
||||||
var orientationComponent: OrientationComponent {
|
var orientationComponent: OrientationComponent {
|
||||||
guard let orientationComponent = entity?.componentForClass(OrientationComponent.self) else { fatalError("A MovementComponent's entity must have an OrientationComponent") }
|
guard let orientationComponent = entity?.component(ofType: OrientationComponent.self) else { fatalError("A MovementComponent's entity must have an OrientationComponent") }
|
||||||
return orientationComponent
|
return orientationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +82,17 @@ class MovementComponent: GKComponent {
|
||||||
override init() {
|
override init() {
|
||||||
movementSpeed = GameplayConfiguration.PlayerBot.movementSpeed
|
movementSpeed = GameplayConfiguration.PlayerBot.movementSpeed
|
||||||
angularSpeed = GameplayConfiguration.PlayerBot.angularSpeed
|
angularSpeed = GameplayConfiguration.PlayerBot.angularSpeed
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GKComponent Life Cycle
|
// MARK: GKComponent Life Cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(deltaTime: NSTimeInterval) {
|
override func update(deltaTime: TimeInterval) {
|
||||||
super.updateWithDeltaTime(deltaTime)
|
super.update(deltaTime: deltaTime)
|
||||||
|
|
||||||
// Declare local versions of computed properties so we don't compute them multiple times.
|
// Declare local versions of computed properties so we don't compute them multiple times.
|
||||||
let node = renderComponent.node
|
let node = renderComponent.node
|
||||||
|
@ -104,10 +109,10 @@ class MovementComponent: GKComponent {
|
||||||
nextRotation = MovementKind(displacement: targetVector)
|
nextRotation = MovementKind(displacement: targetVector)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let movement = nextRotation, newRotation = angleForRotatingNode(node, withRotationalMovement: movement, duration: deltaTime) {
|
if let movement = nextRotation, let newRotation = angleForRotatingNode(node: node, withRotationalMovement: movement, duration: deltaTime) {
|
||||||
// Update the node's `zRotation` with new rotation information.
|
// Update the node's `zRotation` with new rotation information.
|
||||||
orientationComponent.zRotation = newRotation
|
orientationComponent.zRotation = newRotation
|
||||||
animationState = .Idle
|
animationState = .idle
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Clear the rotation if a valid angle could not be created.
|
// Clear the rotation if a valid angle could not be created.
|
||||||
|
@ -115,7 +120,7 @@ class MovementComponent: GKComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the node's `position` with new displacement information.
|
// Update the node's `position` with new displacement information.
|
||||||
if let movement = nextTranslation, newPosition = pointForTranslatingNode(node, withTranslationalMovement: movement, duration: deltaTime) {
|
if let movement = nextTranslation, let newPosition = pointForTranslatingNode(node: node, withTranslationalMovement: movement, duration: deltaTime) {
|
||||||
node.position = newPosition
|
node.position = newPosition
|
||||||
|
|
||||||
// If no explicit rotation is being provided, orient in the direction of movement.
|
// If no explicit rotation is being provided, orient in the direction of movement.
|
||||||
|
@ -127,7 +132,7 @@ class MovementComponent: GKComponent {
|
||||||
Always request a walking animation, but distinguish between walking
|
Always request a walking animation, but distinguish between walking
|
||||||
forward and backwards based on node's `zRotation`.
|
forward and backwards based on node's `zRotation`.
|
||||||
*/
|
*/
|
||||||
animationState = animationStateForDestination(node, destination: newPosition)
|
animationState = animationStateForDestination(node: node, destination: newPosition)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Clear the translation if a valid point could not be created.
|
// Clear the translation if a valid point could not be created.
|
||||||
|
@ -143,7 +148,7 @@ class MovementComponent: GKComponent {
|
||||||
// `animationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `animationComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
let animationComponent = self.animationComponent
|
let animationComponent = self.animationComponent
|
||||||
|
|
||||||
if animationStateCanBeOverwritten(animationComponent.currentAnimation?.animationState) && animationStateCanBeOverwritten(animationComponent.requestedAnimationState) {
|
if animationStateCanBeOverwritten(animationState: animationComponent.currentAnimation?.animationState) && animationStateCanBeOverwritten(animationState: animationComponent.requestedAnimationState) {
|
||||||
animationComponent.requestedAnimationState = animationState
|
animationComponent.requestedAnimationState = animationState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,10 +158,10 @@ class MovementComponent: GKComponent {
|
||||||
|
|
||||||
/// Creates a vector towards the current target of a `BeamComponent` attack (if one exists).
|
/// Creates a vector towards the current target of a `BeamComponent` attack (if one exists).
|
||||||
func vectorForBeamTowardsCurrentTarget() -> float2? {
|
func vectorForBeamTowardsCurrentTarget() -> float2? {
|
||||||
guard let beamComponent = entity?.componentForClass(BeamComponent.self) else { return nil }
|
guard let beamComponent = entity?.component(ofType: BeamComponent.self) else { return nil }
|
||||||
|
|
||||||
let target = (beamComponent.stateMachine.currentState as? BeamFiringState)?.target
|
let target = (beamComponent.stateMachine.currentState as? BeamFiringState)?.target
|
||||||
guard let taskBotPosition = target?.componentForClass(RenderComponent)?.node.position else { return nil }
|
guard let taskBotPosition = target?.component(ofType: RenderComponent.self)?.node.position else { return nil }
|
||||||
let playerBotPosition = beamComponent.playerBotAntenna.position
|
let playerBotPosition = beamComponent.playerBotAntenna.position
|
||||||
|
|
||||||
// Return a vector translating from the `taskBotPosition` to the `playerBotPosition`.
|
// Return a vector translating from the `taskBotPosition` to the `playerBotPosition`.
|
||||||
|
@ -164,7 +169,7 @@ class MovementComponent: GKComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces the destination point for the node, based on the provided translation.
|
/// Produces the destination point for the node, based on the provided translation.
|
||||||
func pointForTranslatingNode(node: SKNode, withTranslationalMovement translation: MovementKind, duration: NSTimeInterval) -> CGPoint? {
|
func pointForTranslatingNode(node: SKNode, withTranslationalMovement translation: MovementKind, duration: TimeInterval) -> CGPoint? {
|
||||||
// No translation if the vector is a zeroVector.
|
// No translation if the vector is a zeroVector.
|
||||||
guard translation.displacement != float2() else { return nil }
|
guard translation.displacement != float2() else { return nil }
|
||||||
|
|
||||||
|
@ -176,7 +181,7 @@ class MovementComponent: GKComponent {
|
||||||
if translation.isRelativeToOrientation {
|
if translation.isRelativeToOrientation {
|
||||||
// Ensure the relative displacement component is non-zero.
|
// Ensure the relative displacement component is non-zero.
|
||||||
guard displacement.x != 0 else { return nil }
|
guard displacement.x != 0 else { return nil }
|
||||||
displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(displacement)
|
displacement = calculateAbsoluteDisplacementFromRelativeDisplacement(relativeDisplacement: displacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
let angle = CGFloat(atan2(displacement.y, displacement.x))
|
let angle = CGFloat(atan2(displacement.y, displacement.x))
|
||||||
|
@ -210,7 +215,7 @@ class MovementComponent: GKComponent {
|
||||||
return CGPoint(x: node.position.x + dx, y: node.position.y + dy)
|
return CGPoint(x: node.position.x + dx, y: node.position.y + dy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func angleForRotatingNode(node: SKNode, withRotationalMovement rotation: MovementKind, duration: NSTimeInterval) -> CGFloat? {
|
func angleForRotatingNode(node: SKNode, withRotationalMovement rotation: MovementKind, duration: TimeInterval) -> CGFloat? {
|
||||||
// No rotation if the vector is a zeroVector.
|
// No rotation if the vector is a zeroVector.
|
||||||
guard rotation.displacement != float2() else { return nil }
|
guard rotation.displacement != float2() else { return nil }
|
||||||
|
|
||||||
|
@ -251,7 +256,7 @@ class MovementComponent: GKComponent {
|
||||||
private func animationStateForDestination(node: SKNode, destination: CGPoint) -> AnimationState {
|
private func animationStateForDestination(node: SKNode, destination: CGPoint) -> AnimationState {
|
||||||
// Ensures nodes rotation is the same direction as the destination point.
|
// Ensures nodes rotation is the same direction as the destination point.
|
||||||
let isMovingWithOrientation = (orientationComponent.zRotation * atan2(destination.y, destination.x)) > 0
|
let isMovingWithOrientation = (orientationComponent.zRotation * atan2(destination.y, destination.x)) > 0
|
||||||
return isMovingWithOrientation ? .WalkForward : .WalkBackward
|
return isMovingWithOrientation ? .walkForward : .walkBackward
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,7 +293,7 @@ class MovementComponent: GKComponent {
|
||||||
*/
|
*/
|
||||||
private func animationStateCanBeOverwritten(animationState: AnimationState?) -> Bool {
|
private func animationStateCanBeOverwritten(animationState: AnimationState?) -> Bool {
|
||||||
switch animationState {
|
switch animationState {
|
||||||
case nil, .Idle?, .WalkForward?, .WalkBackward?:
|
case .idle?, .walkForward?, .walkBackward?:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -15,7 +15,7 @@ class OrientationComponent: GKComponent {
|
||||||
var zRotation: CGFloat = 0.0 {
|
var zRotation: CGFloat = 0.0 {
|
||||||
didSet {
|
didSet {
|
||||||
let twoPi = CGFloat(M_PI * 2)
|
let twoPi = CGFloat(M_PI * 2)
|
||||||
zRotation = (zRotation + twoPi) % twoPi
|
zRotation = (zRotation + twoPi).truncatingRemainder(dividingBy: twoPi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,4 +28,4 @@ class OrientationComponent: GKComponent {
|
||||||
zRotation = newValue.zRotation
|
zRotation = newValue.zRotation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,10 @@ class PhysicsComponent: GKComponent {
|
||||||
self.physicsBody.categoryBitMask = colliderType.categoryMask
|
self.physicsBody.categoryBitMask = colliderType.categoryMask
|
||||||
self.physicsBody.collisionBitMask = colliderType.collisionMask
|
self.physicsBody.collisionBitMask = colliderType.collisionMask
|
||||||
self.physicsBody.contactTestBitMask = colliderType.contactMask
|
self.physicsBody.contactTestBitMask = colliderType.contactMask
|
||||||
|
super.init()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,29 +15,29 @@ class PlayerBotAppearState: GKState {
|
||||||
unowned var entity: PlayerBot
|
unowned var entity: PlayerBot
|
||||||
|
|
||||||
/// The amount of time the `PlayerBot` has been in the "appear" state.
|
/// The amount of time the `PlayerBot` has been in the "appear" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `RenderComponent` associated with the `entity`.
|
/// The `RenderComponent` associated with the `entity`.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = entity.componentForClass(RenderComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an RenderComponent.") }
|
guard let renderComponent = entity.component(ofType: RenderComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an RenderComponent.") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `OrientationComponent` associated with the `entity`.
|
/// The `OrientationComponent` associated with the `entity`.
|
||||||
var orientationComponent: OrientationComponent {
|
var orientationComponent: OrientationComponent {
|
||||||
guard let orientationComponent = entity.componentForClass(OrientationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an OrientationComponent.") }
|
guard let orientationComponent = entity.component(ofType: OrientationComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an OrientationComponent.") }
|
||||||
return orientationComponent
|
return orientationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `InputComponent` associated with the `entity`.
|
/// The `InputComponent` associated with the `entity`.
|
||||||
var inputComponent: InputComponent {
|
var inputComponent: InputComponent {
|
||||||
guard let inputComponent = entity.componentForClass(InputComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an InputComponent.") }
|
guard let inputComponent = entity.component(ofType: InputComponent.self) else { fatalError("A PlayerBotAppearState's entity must have an InputComponent.") }
|
||||||
return inputComponent
|
return inputComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ class PlayerBotAppearState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the elapsed time.
|
// Reset the elapsed time.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
@ -78,14 +78,14 @@ class PlayerBotAppearState: GKState {
|
||||||
renderComponent.node.addChild(node)
|
renderComponent.node.addChild(node)
|
||||||
|
|
||||||
// Hide the animation component node until the `PlayerBot` exits this state.
|
// Hide the animation component node until the `PlayerBot` exits this state.
|
||||||
animationComponent.node.hidden = true
|
animationComponent.node.isHidden = true
|
||||||
|
|
||||||
// Disable the input component while the `PlayerBot` appears.
|
// Disable the input component while the `PlayerBot` appears.
|
||||||
inputComponent.isEnabled = false
|
inputComponent.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the amount of time that the `PlayerBot` has been teleporting in to the level.
|
// Update the amount of time that the `PlayerBot` has been teleporting in to the level.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
@ -96,19 +96,19 @@ class PlayerBotAppearState: GKState {
|
||||||
node.removeFromParent()
|
node.removeFromParent()
|
||||||
|
|
||||||
// Switch the `PlayerBot` over to a "player controlled" state.
|
// Switch the `PlayerBot` over to a "player controlled" state.
|
||||||
stateMachine?.enterState(PlayerBotPlayerControlledState.self)
|
stateMachine?.enter(PlayerBotPlayerControlledState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return stateClass is PlayerBotPlayerControlledState.Type
|
return stateClass is PlayerBotPlayerControlledState.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
// Un-hide the animation component node.
|
// Un-hide the animation component node.
|
||||||
animationComponent.node.hidden = false
|
animationComponent.node.isHidden = false
|
||||||
|
|
||||||
// Re-enable the input component
|
// Re-enable the input component
|
||||||
inputComponent.isEnabled = true
|
inputComponent.isEnabled = true
|
||||||
|
|
|
@ -15,11 +15,11 @@ class PlayerBotHitState: GKState {
|
||||||
unowned var entity: PlayerBot
|
unowned var entity: PlayerBot
|
||||||
|
|
||||||
/// The amount of time the `PlayerBot` has been in the "hit" state.
|
/// The amount of time the `PlayerBot` has been in the "hit" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotHitState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotHitState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,18 +31,18 @@ class PlayerBotHitState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the elapsed "hit" duration on entering this state.
|
// Reset the elapsed "hit" duration on entering this state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
|
||||||
// Request the "hit" animation for this `PlayerBot`.
|
// Request the "hit" animation for this `PlayerBot`.
|
||||||
animationComponent.requestedAnimationState = .Hit
|
animationComponent.requestedAnimationState = .hit
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the amount of time the `PlayerBot` has been in the "hit" state.
|
// Update the amount of time the `PlayerBot` has been in the "hit" state.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
@ -50,15 +50,15 @@ class PlayerBotHitState: GKState {
|
||||||
// When the `PlayerBot` has been in this state for long enough, transition to the appropriate next state.
|
// When the `PlayerBot` has been in this state for long enough, transition to the appropriate next state.
|
||||||
if elapsedTime >= GameplayConfiguration.PlayerBot.hitStateDuration {
|
if elapsedTime >= GameplayConfiguration.PlayerBot.hitStateDuration {
|
||||||
if entity.isPoweredDown {
|
if entity.isPoweredDown {
|
||||||
stateMachine?.enterState(PlayerBotRechargingState.self)
|
stateMachine?.enter(PlayerBotRechargingState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
stateMachine?.enterState(PlayerBotPlayerControlledState.self)
|
stateMachine?.enter(PlayerBotPlayerControlledState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is PlayerBotPlayerControlledState.Type, is PlayerBotRechargingState.Type:
|
case is PlayerBotPlayerControlledState.Type, is PlayerBotRechargingState.Type:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -16,19 +16,19 @@ class PlayerBotPlayerControlledState: GKState {
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `MovementComponent` associated with the `entity`.
|
/// The `MovementComponent` associated with the `entity`.
|
||||||
var movementComponent: MovementComponent {
|
var movementComponent: MovementComponent {
|
||||||
guard let movementComponent = entity.componentForClass(MovementComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have a MovementComponent.") }
|
guard let movementComponent = entity.component(ofType: MovementComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have a MovementComponent.") }
|
||||||
return movementComponent
|
return movementComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `InputComponent` associated with the `entity`.
|
/// The `InputComponent` associated with the `entity`.
|
||||||
var inputComponent: InputComponent {
|
var inputComponent: InputComponent {
|
||||||
guard let inputComponent = entity.componentForClass(InputComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an InputComponent.") }
|
guard let inputComponent = entity.component(ofType: InputComponent.self) else { fatalError("A PlayerBotPlayerControlledState's entity must have an InputComponent.") }
|
||||||
return inputComponent
|
return inputComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,24 +40,24 @@ class PlayerBotPlayerControlledState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Turn on controller input for the `PlayerBot` when entering the player-controlled state.
|
// Turn on controller input for the `PlayerBot` when entering the player-controlled state.
|
||||||
inputComponent.isEnabled = true
|
inputComponent.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Assume an animation of "idle" that can then be overwritten by the movement
|
Assume an animation of "idle" that can then be overwritten by the movement
|
||||||
component in response to user input.
|
component in response to user input.
|
||||||
*/
|
*/
|
||||||
animationComponent.requestedAnimationState = .Idle
|
animationComponent.requestedAnimationState = .idle
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is PlayerBotHitState.Type, is PlayerBotRechargingState.Type:
|
case is PlayerBotHitState.Type, is PlayerBotRechargingState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -67,11 +67,11 @@ class PlayerBotPlayerControlledState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
// Turn off controller input for the `PlayerBot` when leaving the player-controlled state.
|
// Turn off controller input for the `PlayerBot` when leaving the player-controlled state.
|
||||||
entity.componentForClass(InputComponent.self)?.isEnabled = false
|
entity.component(ofType: InputComponent.self)?.isEnabled = false
|
||||||
|
|
||||||
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
// `movementComponent` is a computed property. Declare a local version so we don't compute it multiple times.
|
||||||
let movementComponent = self.movementComponent
|
let movementComponent = self.movementComponent
|
||||||
|
|
|
@ -15,17 +15,17 @@ class PlayerBotRechargingState: GKState {
|
||||||
unowned var entity: PlayerBot
|
unowned var entity: PlayerBot
|
||||||
|
|
||||||
/// The amount of time the `PlayerBot` has been in the "recharging" state.
|
/// The amount of time the `PlayerBot` has been in the "recharging" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `ChargeComponent` associated with the `entity`.
|
/// The `ChargeComponent` associated with the `entity`.
|
||||||
var chargeComponent: ChargeComponent {
|
var chargeComponent: ChargeComponent {
|
||||||
guard let chargeComponent = entity.componentForClass(ChargeComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have a ChargeComponent.") }
|
guard let chargeComponent = entity.component(ofType: ChargeComponent.self) else { fatalError("A PlayerBotRechargingState's entity must have a ChargeComponent.") }
|
||||||
return chargeComponent
|
return chargeComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,18 +37,18 @@ class PlayerBotRechargingState: GKState {
|
||||||
|
|
||||||
// MARK: GKState life cycle
|
// MARK: GKState life cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the recharge duration when entering this state.
|
// Reset the recharge duration when entering this state.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
|
||||||
// Request the "inactive" animation for the `PlayerBot`.
|
// Request the "inactive" animation for the `PlayerBot`.
|
||||||
animationComponent.requestedAnimationState = .Inactive
|
animationComponent.requestedAnimationState = .inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the elapsed recharge duration.
|
// Update the elapsed recharge duration.
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
@ -64,16 +64,16 @@ class PlayerBotRechargingState: GKState {
|
||||||
|
|
||||||
// Add charge to the `PlayerBot`.
|
// Add charge to the `PlayerBot`.
|
||||||
let amountToRecharge = GameplayConfiguration.PlayerBot.rechargeAmountPerSecond * seconds
|
let amountToRecharge = GameplayConfiguration.PlayerBot.rechargeAmountPerSecond * seconds
|
||||||
chargeComponent.addCharge(amountToRecharge)
|
chargeComponent.addCharge(chargeToAdd: amountToRecharge)
|
||||||
|
|
||||||
// If the `PlayerBot` is fully charged it can become player controlled again.
|
// If the `PlayerBot` is fully charged it can become player controlled again.
|
||||||
if chargeComponent.isFullyCharged {
|
if chargeComponent.isFullyCharged {
|
||||||
entity.isPoweredDown = false
|
entity.isPoweredDown = false
|
||||||
stateMachine?.enterState(PlayerBotPlayerControlledState.self)
|
stateMachine?.enter(PlayerBotPlayerControlledState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return stateClass is PlayerBotPlayerControlledState.Type
|
return stateClass is PlayerBotPlayerControlledState.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
See LICENSE.txt for this sample’s licensing information
|
See LICENSE.txt for this sample’s licensing information
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
A `GKComponent` that provides an `SKNode` for an entity. This enables it to be represented in the SpriteKit world. The node is provided as an `EntityNode` instance, so that the entity can be discovered from the node.
|
A `GKComponent` that provides an `SKNode` for an entity. This enables it to be represented in the SpriteKit world.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SpriteKit
|
import SpriteKit
|
||||||
|
@ -13,9 +13,15 @@ class RenderComponent: GKComponent {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
// The `RenderComponent` vends a node allowing an entity to be rendered in a scene.
|
// The `RenderComponent` vends a node allowing an entity to be rendered in a scene.
|
||||||
let node = EntityNode()
|
let node = SKNode()
|
||||||
|
|
||||||
|
// MARK: GKComponent
|
||||||
|
|
||||||
init(entity: GKEntity) {
|
override func didAddToEntity() {
|
||||||
node.entity = entity
|
node.entity = entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func willRemoveFromEntity() {
|
||||||
|
node.entity = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,22 +21,28 @@ class RulesComponent: GKComponent {
|
||||||
var ruleSystem: GKRuleSystem
|
var ruleSystem: GKRuleSystem
|
||||||
|
|
||||||
/// The amount of time that has passed since the `TaskBot` last evaluated its rules.
|
/// The amount of time that has passed since the `TaskBot` last evaluated its rules.
|
||||||
private var timeSinceRulesUpdate: NSTimeInterval = 0.0
|
private var timeSinceRulesUpdate: TimeInterval = 0.0
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
ruleSystem = GKRuleSystem()
|
ruleSystem = GKRuleSystem()
|
||||||
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(rules: [GKRule]) {
|
init(rules: [GKRule]) {
|
||||||
ruleSystem = GKRuleSystem()
|
ruleSystem = GKRuleSystem()
|
||||||
ruleSystem.addRulesFromArray(rules)
|
ruleSystem.add(rules)
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GKComponent Life Cycle
|
// MARK: GKComponent Life Cycle
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
timeSinceRulesUpdate += seconds
|
timeSinceRulesUpdate += seconds
|
||||||
|
|
||||||
if timeSinceRulesUpdate < GameplayConfiguration.TaskBot.rulesUpdateWaitDuration { return }
|
if timeSinceRulesUpdate < GameplayConfiguration.TaskBot.rulesUpdateWaitDuration { return }
|
||||||
|
@ -44,8 +50,9 @@ class RulesComponent: GKComponent {
|
||||||
timeSinceRulesUpdate = 0.0
|
timeSinceRulesUpdate = 0.0
|
||||||
|
|
||||||
if let taskBot = entity as? TaskBot,
|
if let taskBot = entity as? TaskBot,
|
||||||
level = taskBot.componentForClass(RenderComponent)?.node.scene as? LevelScene,
|
let level = taskBot.component(ofType: RenderComponent.self)?.node.scene as? LevelScene,
|
||||||
entitySnapshot = level.entitySnapshotForEntity(taskBot) where !taskBot.isGood {
|
let entitySnapshot = level.entitySnapshotForEntity(entity: taskBot),
|
||||||
|
!taskBot.isGood {
|
||||||
|
|
||||||
ruleSystem.reset()
|
ruleSystem.reset()
|
||||||
|
|
||||||
|
@ -53,7 +60,7 @@ class RulesComponent: GKComponent {
|
||||||
|
|
||||||
ruleSystem.evaluate()
|
ruleSystem.evaluate()
|
||||||
|
|
||||||
delegate?.rulesComponent(self, didFinishEvaluatingRuleSystem: ruleSystem)
|
delegate?.rulesComponent(rulesComponent: self, didFinishEvaluatingRuleSystem: ruleSystem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,11 @@ class ShadowComponent: GKComponent {
|
||||||
node.alpha = 0.25
|
node.alpha = 0.25
|
||||||
node.size = size
|
node.size = size
|
||||||
node.position = offset
|
node.position = offset
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,10 @@ class TaskBotAgentControlledState: GKState {
|
||||||
unowned var entity: TaskBot
|
unowned var entity: TaskBot
|
||||||
|
|
||||||
/// The amount of time that has passed since the `TaskBot` became agent-controlled.
|
/// The amount of time that has passed since the `TaskBot` became agent-controlled.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The amount of time that has passed since the `TaskBot` last determined an appropriate behavior.
|
/// The amount of time that has passed since the `TaskBot` last determined an appropriate behavior.
|
||||||
var timeSinceBehaviorUpdate: NSTimeInterval = 0.0
|
var timeSinceBehaviorUpdate: TimeInterval = 0.0
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ class TaskBotAgentControlledState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the amount of time since the last behavior update.
|
// Reset the amount of time since the last behavior update.
|
||||||
timeSinceBehaviorUpdate = 0.0
|
timeSinceBehaviorUpdate = 0.0
|
||||||
|
@ -42,14 +42,14 @@ class TaskBotAgentControlledState: GKState {
|
||||||
`TaskBot`s recover to a full charge if they're hit with the beam but don't become "good".
|
`TaskBot`s recover to a full charge if they're hit with the beam but don't become "good".
|
||||||
If this `TaskBot` has any charge, restore it to the full amount.
|
If this `TaskBot` has any charge, restore it to the full amount.
|
||||||
*/
|
*/
|
||||||
if let chargeComponent = entity.componentForClass(ChargeComponent.self) where chargeComponent.hasCharge {
|
if let chargeComponent = entity.component(ofType: ChargeComponent.self), chargeComponent.hasCharge {
|
||||||
let chargeToAdd = chargeComponent.maximumCharge - chargeComponent.charge
|
let chargeToAdd = chargeComponent.maximumCharge - chargeComponent.charge
|
||||||
chargeComponent.addCharge(chargeToAdd)
|
chargeComponent.addCharge(chargeToAdd: chargeToAdd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Update the "time since last behavior update" tracker.
|
// Update the "time since last behavior update" tracker.
|
||||||
timeSinceBehaviorUpdate += seconds
|
timeSinceBehaviorUpdate += seconds
|
||||||
|
@ -59,8 +59,8 @@ class TaskBotAgentControlledState: GKState {
|
||||||
if timeSinceBehaviorUpdate >= GameplayConfiguration.TaskBot.behaviorUpdateWaitDuration {
|
if timeSinceBehaviorUpdate >= GameplayConfiguration.TaskBot.behaviorUpdateWaitDuration {
|
||||||
|
|
||||||
// When a `TaskBot` is returning to its path patrol start, and gets near enough, it should start to patrol.
|
// When a `TaskBot` is returning to its path patrol start, and gets near enough, it should start to patrol.
|
||||||
if case let .ReturnToPositionOnPath(position) = entity.mandate where entity.distanceToPoint(position) <= GameplayConfiguration.TaskBot.thresholdProximityToPatrolPathStartPoint {
|
if case let .returnToPositionOnPath(position) = entity.mandate, entity.distanceToPoint(otherPoint: position) <= GameplayConfiguration.TaskBot.thresholdProximityToPatrolPathStartPoint {
|
||||||
entity.mandate = entity.isGood ? .FollowGoodPatrolPath : .FollowBadPatrolPath
|
entity.mandate = entity.isGood ? .followGoodPatrolPath : .followBadPatrolPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the agent's behavior is the appropriate behavior for its current mandate.
|
// Ensure the agent's behavior is the appropriate behavior for its current mandate.
|
||||||
|
@ -71,7 +71,7 @@ class TaskBotAgentControlledState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is FlyingBotPreAttackState.Type, is GroundBotRotateToAttackState.Type, is TaskBotZappedState.Type:
|
case is FlyingBotPreAttackState.Type, is GroundBotRotateToAttackState.Type, is TaskBotZappedState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -81,8 +81,8 @@ class TaskBotAgentControlledState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The `TaskBot` will no longer be controlled by an agent in the steering simulation
|
The `TaskBot` will no longer be controlled by an agent in the steering simulation
|
||||||
|
|
|
@ -14,16 +14,16 @@ class TaskBotBehavior: GKBehavior {
|
||||||
// MARK: Behavior factory methods
|
// MARK: Behavior factory methods
|
||||||
|
|
||||||
/// Constructs a behavior to hunt a `TaskBot` or `PlayerBot` via a computed path.
|
/// Constructs a behavior to hunt a `TaskBot` or `PlayerBot` via a computed path.
|
||||||
static func behaviorAndPathPointsForAgent(agent: GKAgent2D, huntingAgent target: GKAgent2D, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
|
static func behaviorAndPathPoints(forAgent agent: GKAgent2D, huntingAgent target: GKAgent2D, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
|
||||||
let behavior = TaskBotBehavior()
|
let behavior = TaskBotBehavior()
|
||||||
|
|
||||||
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
||||||
behavior.addTargetSpeedGoal(agent.maxSpeed)
|
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
|
||||||
behavior.addAvoidObstaclesGoalForScene(scene)
|
behavior.addAvoidObstaclesGoal(forScene: scene)
|
||||||
|
|
||||||
// Find any nearby "bad" TaskBots to flock with.
|
// Find any nearby "bad" TaskBots to flock with.
|
||||||
let agentsToFlockWith: [GKAgent2D] = scene.entities.flatMap { entity in
|
let agentsToFlockWith: [GKAgent2D] = scene.entities.flatMap { entity in
|
||||||
if let taskBot = entity as? TaskBot where !taskBot.isGood && taskBot.agent !== agent && taskBot.distanceToAgent(agent) <= GameplayConfiguration.Flocking.agentSearchDistanceForFlocking {
|
if let taskBot = entity as? TaskBot, !taskBot.isGood && taskBot.agent !== agent && taskBot.distanceToAgent(otherAgent: agent) <= GameplayConfiguration.Flocking.agentSearchDistanceForFlocking {
|
||||||
return taskBot.agent
|
return taskBot.agent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,54 +32,56 @@ class TaskBotBehavior: GKBehavior {
|
||||||
|
|
||||||
if !agentsToFlockWith.isEmpty {
|
if !agentsToFlockWith.isEmpty {
|
||||||
// Add flocking goals for any nearby "bad" `TaskBot`s.
|
// Add flocking goals for any nearby "bad" `TaskBot`s.
|
||||||
let separationGoal = GKGoal(toSeparateFromAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle)
|
let separationGoal = GKGoal(toSeparateFrom: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.separationRadius, maxAngle: GameplayConfiguration.Flocking.separationAngle)
|
||||||
behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, forGoal: separationGoal)
|
behavior.setWeight(GameplayConfiguration.Flocking.separationWeight, for: separationGoal)
|
||||||
|
|
||||||
let alignmentGoal = GKGoal(toAlignWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle)
|
let alignmentGoal = GKGoal(toAlignWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.alignmentRadius, maxAngle: GameplayConfiguration.Flocking.alignmentAngle)
|
||||||
behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, forGoal: alignmentGoal)
|
behavior.setWeight(GameplayConfiguration.Flocking.alignmentWeight, for: alignmentGoal)
|
||||||
|
|
||||||
let cohesionGoal = GKGoal(toCohereWithAgents: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle)
|
let cohesionGoal = GKGoal(toCohereWith: agentsToFlockWith, maxDistance: GameplayConfiguration.Flocking.cohesionRadius, maxAngle: GameplayConfiguration.Flocking.cohesionAngle)
|
||||||
behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, forGoal: cohesionGoal)
|
behavior.setWeight(GameplayConfiguration.Flocking.cohesionWeight, for: cohesionGoal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add goals to follow a calculated path from the `TaskBot` to its target.
|
// Add goals to follow a calculated path from the `TaskBot` to its target.
|
||||||
let pathPoints = behavior.addGoalsToFollowPathFromStartPoint(agent.position, toEndPoint: target.position, pathRadius: pathRadius, inScene: scene)
|
let pathPoints = behavior.addGoalsToFollowPath(from: agent.position, to: target.position, pathRadius: pathRadius, inScene: scene)
|
||||||
|
|
||||||
// Return a tuple containing the new behavior, and the found path points for debug drawing.
|
// Return a tuple containing the new behavior, and the found path points for debug drawing.
|
||||||
return (behavior, pathPoints)
|
return (behavior, pathPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a behavior to return to the start of a `TaskBot` patrol path.
|
/// Constructs a behavior to return to the start of a `TaskBot` patrol path.
|
||||||
static func behaviorAndPathPointsForAgent(agent: GKAgent2D, returningToPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
|
static func behaviorAndPathPoints(forAgent agent: GKAgent2D, returningToPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> (behavior: GKBehavior, pathPoints: [CGPoint]) {
|
||||||
let behavior = TaskBotBehavior()
|
let behavior = TaskBotBehavior()
|
||||||
|
|
||||||
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
||||||
behavior.addTargetSpeedGoal(agent.maxSpeed)
|
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
|
||||||
behavior.addAvoidObstaclesGoalForScene(scene)
|
behavior.addAvoidObstaclesGoal(forScene: scene)
|
||||||
|
|
||||||
// Add goals to follow a calculated path from the `TaskBot` to the start of its patrol path.
|
// Add goals to follow a calculated path from the `TaskBot` to the start of its patrol path.
|
||||||
let pathPoints = behavior.addGoalsToFollowPathFromStartPoint(agent.position, toEndPoint: endPoint, pathRadius: pathRadius, inScene: scene)
|
let pathPoints = behavior.addGoalsToFollowPath(from: agent.position, to: endPoint, pathRadius: pathRadius, inScene: scene)
|
||||||
|
|
||||||
// Return a tuple containing the new behavior, and the found path points for debug drawing.
|
// Return a tuple containing the new behavior, and the found path points for debug drawing.
|
||||||
return (behavior, pathPoints)
|
return (behavior, pathPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a behavior to patrol a path of points, avoiding obstacles along the way.
|
/// Constructs a behavior to patrol a path of points, avoiding obstacles along the way.
|
||||||
static func behaviorForAgent(agent: GKAgent2D, patrollingPathWithPoints patrolPathPoints: [CGPoint], pathRadius: Float, inScene scene: LevelScene) -> GKBehavior {
|
static func behavior(forAgent agent: GKAgent2D, patrollingPathWithPoints patrolPathPoints: [CGPoint], pathRadius: Float, inScene scene: LevelScene) -> GKBehavior {
|
||||||
let behavior = TaskBotBehavior()
|
let behavior = TaskBotBehavior()
|
||||||
|
|
||||||
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
// Add basic goals to reach the `TaskBot`'s maximum speed and avoid obstacles.
|
||||||
behavior.addTargetSpeedGoal(agent.maxSpeed)
|
behavior.addTargetSpeedGoal(speed: agent.maxSpeed)
|
||||||
behavior.addAvoidObstaclesGoalForScene(scene)
|
behavior.addAvoidObstaclesGoal(forScene: scene)
|
||||||
|
|
||||||
// Convert the patrol path to an array of `float2`s.
|
// Convert the patrol path to an array of `float2`s.
|
||||||
let pathVectorPoints = patrolPathPoints.map(float2.init)
|
|
||||||
|
let pathVectorPoints = patrolPathPoints.map { float2($0) }
|
||||||
|
|
||||||
// Create a cyclical (closed) `GKPath` from the provided path points with the requested path radius.
|
// Create a cyclical (closed) `GKPath` from the provided path points with the requested path radius.
|
||||||
let path = GKPath(points: UnsafeMutablePointer<float2>(pathVectorPoints), count: pathVectorPoints.count, radius: pathRadius, cyclical: true)
|
// GKPath(points: &pathVectorPoints, radius: <#T##Float#>, cyclical: <#T##Bool#>)
|
||||||
|
let path = GKPath(points: pathVectorPoints, radius: pathRadius, cyclical: true)
|
||||||
|
|
||||||
// Add "follow path" and "stay on path" goals for this path.
|
// Add "follow path" and "stay on path" goals for this path.
|
||||||
behavior.addFollowAndStayOnPathGoalsForPath(path)
|
behavior.addFollowAndStayOnPathGoals(for: path)
|
||||||
|
|
||||||
return behavior
|
return behavior
|
||||||
}
|
}
|
||||||
|
@ -90,7 +92,7 @@ class TaskBotBehavior: GKBehavior {
|
||||||
Calculates all of the extruded obstacles that the provided point resides near.
|
Calculates all of the extruded obstacles that the provided point resides near.
|
||||||
The extrusion is based on the buffer radius of the pathfinding graph.
|
The extrusion is based on the buffer radius of the pathfinding graph.
|
||||||
*/
|
*/
|
||||||
private func extrudedObstaclesContainingPoint(point: float2, inScene scene: LevelScene) -> [GKPolygonObstacle] {
|
private func extrudedObstaclesContaining(point: float2, inScene scene: LevelScene) -> [GKPolygonObstacle] {
|
||||||
/*
|
/*
|
||||||
Add a small fudge factor (+5) to the extrusion radius to make sure
|
Add a small fudge factor (+5) to the extrusion radius to make sure
|
||||||
we're including all obstacles.
|
we're including all obstacles.
|
||||||
|
@ -108,14 +110,14 @@ class TaskBotBehavior: GKBehavior {
|
||||||
// Retrieve all vertices for the polygon obstacle.
|
// Retrieve all vertices for the polygon obstacle.
|
||||||
let range = 0..<obstacle.vertexCount
|
let range = 0..<obstacle.vertexCount
|
||||||
|
|
||||||
let polygonVertices = range.map { obstacle.vertexAtIndex($0) }
|
let polygonVertices = range.map { obstacle.vertex(at: $0) }
|
||||||
guard !polygonVertices.isEmpty else { return false }
|
guard !polygonVertices.isEmpty else { return false }
|
||||||
|
|
||||||
let maxX = polygonVertices.maxElement { $0.x < $1.x }!.x + extrusionRadius
|
let maxX = polygonVertices.max { $0.x < $1.x }!.x + extrusionRadius
|
||||||
let maxY = polygonVertices.maxElement { $0.y < $1.y }!.y + extrusionRadius
|
let maxY = polygonVertices.max { $0.y < $1.y }!.y + extrusionRadius
|
||||||
|
|
||||||
let minX = polygonVertices.minElement { $0.x < $1.x }!.x - extrusionRadius
|
let minX = polygonVertices.min { $0.x < $1.x }!.x - extrusionRadius
|
||||||
let minY = polygonVertices.minElement { $0.y < $1.y }!.y - extrusionRadius
|
let minY = polygonVertices.min { $0.y < $1.y }!.y - extrusionRadius
|
||||||
|
|
||||||
return (point.x > minX && point.x < maxX) && (point.y > minY && point.y < maxY)
|
return (point.x > minX && point.x < maxX) && (point.y > minY && point.y < maxY)
|
||||||
}
|
}
|
||||||
|
@ -127,12 +129,12 @@ class TaskBotBehavior: GKBehavior {
|
||||||
|
|
||||||
Returns `nil` if a valid connection could not be made.
|
Returns `nil` if a valid connection could not be made.
|
||||||
*/
|
*/
|
||||||
private func connectedNodeForPoint(point: float2, onObstacleGraphInScene scene: LevelScene) -> GKGraphNode2D? {
|
private func connectedNode(forPoint point: float2, onObstacleGraphInScene scene: LevelScene) -> GKGraphNode2D? {
|
||||||
// Create a graph node for this point.
|
// Create a graph node for this point.
|
||||||
let pointNode = GKGraphNode2D(point: point)
|
let pointNode = GKGraphNode2D(point: point)
|
||||||
|
|
||||||
// Try to connect this node to the graph.
|
// Try to connect this node to the graph.
|
||||||
scene.graph.connectNodeUsingObstacles(pointNode)
|
scene.graph.connectUsingObstacles(node: pointNode)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check to see if we were able to connect the node to the graph.
|
Check to see if we were able to connect the node to the graph.
|
||||||
|
@ -143,20 +145,20 @@ class TaskBotBehavior: GKBehavior {
|
||||||
*/
|
*/
|
||||||
if pointNode.connectedNodes.isEmpty {
|
if pointNode.connectedNodes.isEmpty {
|
||||||
// The previous connection attempt failed, so remove the node from the graph.
|
// The previous connection attempt failed, so remove the node from the graph.
|
||||||
scene.graph.removeNodes([pointNode])
|
scene.graph.remove([pointNode])
|
||||||
|
|
||||||
// Search the graph for all intersecting obstacles.
|
// Search the graph for all intersecting obstacles.
|
||||||
let intersectingObstacles = extrudedObstaclesContainingPoint(point, inScene: scene)
|
let intersectingObstacles = extrudedObstaclesContaining(point: point, inScene: scene)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Connect this node to the graph ignoring the buffer radius of any
|
Connect this node to the graph ignoring the buffer radius of any
|
||||||
obstacles that the point is currently intersecting.
|
obstacles that the point is currently intersecting.
|
||||||
*/
|
*/
|
||||||
scene.graph.connectNodeUsingObstacles(pointNode, ignoringBufferRadiusOfObstacles: intersectingObstacles)
|
scene.graph.connectUsingObstacles(node: pointNode, ignoringBufferRadiusOf: intersectingObstacles)
|
||||||
|
|
||||||
// If still no connection could be made, return `nil`.
|
// If still no connection could be made, return `nil`.
|
||||||
if pointNode.connectedNodes.isEmpty {
|
if pointNode.connectedNodes.isEmpty {
|
||||||
scene.graph.removeNodes([pointNode])
|
scene.graph.remove([pointNode])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,16 +167,16 @@ class TaskBotBehavior: GKBehavior {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pathfinds around obstacles to create a path between two points, and adds goals to follow that path.
|
/// Pathfinds around obstacles to create a path between two points, and adds goals to follow that path.
|
||||||
private func addGoalsToFollowPathFromStartPoint(startPoint: float2, toEndPoint endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> [CGPoint] {
|
private func addGoalsToFollowPath(from startPoint: float2, to endPoint: float2, pathRadius: Float, inScene scene: LevelScene) -> [CGPoint] {
|
||||||
// Convert the provided `CGPoint`s into nodes for the `GPGraph`.
|
// Convert the provided `CGPoint`s into nodes for the `GPGraph`.
|
||||||
guard let startNode = connectedNodeForPoint(startPoint, onObstacleGraphInScene: scene),
|
guard let startNode = connectedNode(forPoint: startPoint, onObstacleGraphInScene: scene),
|
||||||
endNode = connectedNodeForPoint(endPoint, onObstacleGraphInScene: scene) else { return [] }
|
let endNode = connectedNode(forPoint: endPoint, onObstacleGraphInScene: scene) else { return [] }
|
||||||
|
|
||||||
// Remove the "start" and "end" nodes when exiting this scope.
|
// Remove the "start" and "end" nodes when exiting this scope.
|
||||||
defer { scene.graph.removeNodes([startNode, endNode]) }
|
defer { scene.graph.remove([startNode, endNode]) }
|
||||||
|
|
||||||
// Find a path between these two nodes.
|
// Find a path between these two nodes.
|
||||||
let pathNodes = scene.graph.findPathFromNode(startNode, toNode: endNode) as! [GKGraphNode2D]
|
let pathNodes = scene.graph.findPath(from: startNode, to: endNode) as! [GKGraphNode2D]
|
||||||
|
|
||||||
// A valid `GKPath` can not be created if fewer than 2 path nodes were found, return.
|
// A valid `GKPath` can not be created if fewer than 2 path nodes were found, return.
|
||||||
guard pathNodes.count > 1 else { return [] }
|
guard pathNodes.count > 1 else { return [] }
|
||||||
|
@ -183,7 +185,7 @@ class TaskBotBehavior: GKBehavior {
|
||||||
let path = GKPath(graphNodes: pathNodes, radius: pathRadius)
|
let path = GKPath(graphNodes: pathNodes, radius: pathRadius)
|
||||||
|
|
||||||
// Add "follow path" and "stay on path" goals for this path.
|
// Add "follow path" and "stay on path" goals for this path.
|
||||||
addFollowAndStayOnPathGoalsForPath(path)
|
addFollowAndStayOnPathGoals(for: path)
|
||||||
|
|
||||||
// Convert the `GKGraphNode2D` nodes into `CGPoint`s for debug drawing.
|
// Convert the `GKGraphNode2D` nodes into `CGPoint`s for debug drawing.
|
||||||
let pathPoints = pathNodes.map { CGPoint($0.position) }
|
let pathPoints = pathNodes.map { CGPoint($0.position) }
|
||||||
|
@ -191,21 +193,21 @@ class TaskBotBehavior: GKBehavior {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a goal to avoid all polygon obstacles in the scene.
|
/// Adds a goal to avoid all polygon obstacles in the scene.
|
||||||
private func addAvoidObstaclesGoalForScene(scene: LevelScene) {
|
private func addAvoidObstaclesGoal(forScene scene: LevelScene) {
|
||||||
setWeight(1.0, forGoal: GKGoal(toAvoidObstacles: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance))
|
setWeight(1.0, for: GKGoal(toAvoid: scene.polygonObstacles, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeForObstacleAvoidance))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a goal to attain a target speed.
|
/// Adds a goal to attain a target speed.
|
||||||
private func addTargetSpeedGoal(speed: Float) {
|
private func addTargetSpeedGoal(speed: Float) {
|
||||||
setWeight(0.5, forGoal: GKGoal(toReachTargetSpeed: speed))
|
setWeight(0.5, for: GKGoal(toReachTargetSpeed: speed))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds goals to follow and stay on a path.
|
/// Adds goals to follow and stay on a path.
|
||||||
private func addFollowAndStayOnPathGoalsForPath(path: GKPath) {
|
private func addFollowAndStayOnPathGoals(for path: GKPath) {
|
||||||
// The "follow path" goal tries to keep the agent facing in a forward direction when it is on this path.
|
// The "follow path" goal tries to keep the agent facing in a forward direction when it is on this path.
|
||||||
setWeight(1.0, forGoal: GKGoal(toFollowPath: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath, forward: true))
|
setWeight(1.0, for: GKGoal(toFollow: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath, forward: true))
|
||||||
|
|
||||||
// The "stay on path" goal tries to keep the agent on the path within the path's radius.
|
// The "stay on path" goal tries to keep the agent on the path within the path's radius.
|
||||||
setWeight(1.0, forGoal: GKGoal(toStayOnPath: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath))
|
setWeight(1.0, for: GKGoal(toStayOn: path, maxPredictionTime: GameplayConfiguration.TaskBot.maxPredictionTimeWhenFollowingPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ class TaskBotZappedState: GKState {
|
||||||
unowned var entity: TaskBot
|
unowned var entity: TaskBot
|
||||||
|
|
||||||
/// The amount of time the `TaskBot` has been in its "zapped" state.
|
/// The amount of time the `TaskBot` has been in its "zapped" state.
|
||||||
var elapsedTime: NSTimeInterval = 0.0
|
var elapsedTime: TimeInterval = 0.0
|
||||||
|
|
||||||
/// The `AnimationComponent` associated with the `entity`.
|
/// The `AnimationComponent` associated with the `entity`.
|
||||||
var animationComponent: AnimationComponent {
|
var animationComponent: AnimationComponent {
|
||||||
guard let animationComponent = entity.componentForClass(AnimationComponent.self) else { fatalError("A TaskBotZappedState's entity must have an AnimationComponent.") }
|
guard let animationComponent = entity.component(ofType: AnimationComponent.self) else { fatalError("A TaskBotZappedState's entity must have an AnimationComponent.") }
|
||||||
return animationComponent
|
return animationComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +31,14 @@ class TaskBotZappedState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Reset the elapsed time.
|
// Reset the elapsed time.
|
||||||
elapsedTime = 0.0
|
elapsedTime = 0.0
|
||||||
|
|
||||||
// Check if the `TaskBot` has a movement component. (`GroundBot`s do, `FlyingBot`s do not.)
|
// Check if the `TaskBot` has a movement component. (`GroundBot`s do, `FlyingBot`s do not.)
|
||||||
if let movementComponent = entity.componentForClass(MovementComponent.self) {
|
if let movementComponent = entity.component(ofType: MovementComponent.self) {
|
||||||
// Clear any pending movement.
|
// Clear any pending movement.
|
||||||
movementComponent.nextTranslation = nil
|
movementComponent.nextTranslation = nil
|
||||||
movementComponent.nextRotation = nil
|
movementComponent.nextRotation = nil
|
||||||
|
@ -46,11 +46,11 @@ class TaskBotZappedState: GKState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the "zapped" animation for this `TaskBot`.
|
// Request the "zapped" animation for this `TaskBot`.
|
||||||
animationComponent.requestedAnimationState = .Zapped
|
animationComponent.requestedAnimationState = .zapped
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
elapsedTime += seconds
|
elapsedTime += seconds
|
||||||
|
|
||||||
|
@ -59,11 +59,11 @@ class TaskBotZappedState: GKState {
|
||||||
re-enter `TaskBotAgentControlledState`.
|
re-enter `TaskBotAgentControlledState`.
|
||||||
*/
|
*/
|
||||||
if entity.isGood || elapsedTime >= GameplayConfiguration.TaskBot.zappedStateDuration {
|
if entity.isGood || elapsedTime >= GameplayConfiguration.TaskBot.zappedStateDuration {
|
||||||
stateMachine?.enterState(TaskBotAgentControlledState.self)
|
stateMachine?.enter(TaskBotAgentControlledState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is TaskBotZappedState.Type:
|
case is TaskBotZappedState.Type:
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import simd
|
import simd
|
||||||
|
|
||||||
enum ControlInputDirection: Int {
|
enum ControlInputDirection: Int {
|
||||||
case Up = 0, Down, Left, Right
|
case up = 0, down, left, right
|
||||||
|
|
||||||
init?(vector: float2) {
|
init?(vector: float2) {
|
||||||
// Require sufficient displacement to specify direction.
|
// Require sufficient displacement to specify direction.
|
||||||
|
@ -17,25 +17,25 @@ enum ControlInputDirection: Int {
|
||||||
|
|
||||||
// Take the max displacement as the specified axis.
|
// Take the max displacement as the specified axis.
|
||||||
if abs(vector.x) > abs(vector.y) {
|
if abs(vector.x) > abs(vector.y) {
|
||||||
self = vector.x > 0 ? .Right : .Left
|
self = vector.x > 0 ? .right : .left
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self = vector.y > 0 ? .Up : .Down
|
self = vector.y > 0 ? .up : .down
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delegate methods for responding to control input that applies to the game as a whole.
|
/// Delegate methods for responding to control input that applies to the game as a whole.
|
||||||
protocol ControlInputSourceGameStateDelegate: class {
|
protocol ControlInputSourceGameStateDelegate: class {
|
||||||
func controlInputSourceDidSelect(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidSelect(_ controlInputSource: ControlInputSourceType)
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection)
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didSpecifyDirection: ControlInputDirection)
|
||||||
func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType)
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType)
|
||||||
|
|
||||||
func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType)
|
||||||
func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +49,14 @@ protocol ControlInputSourceDelegate: class {
|
||||||
Left: (-1.0, 0.0)
|
Left: (-1.0, 0.0)
|
||||||
Right: (1.0, 0.0)
|
Right: (1.0, 0.0)
|
||||||
*/
|
*/
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2)
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateDisplacement displacement: float2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update the `ControlInputSourceDelegate` with new angular displacement
|
Update the `ControlInputSourceDelegate` with new angular displacement
|
||||||
denoting both the requested angle, and magnitude with which to rotate.
|
denoting both the requested angle, and magnitude with which to rotate.
|
||||||
Measured in radians.
|
Measured in radians.
|
||||||
*/
|
*/
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2)
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateAngularDisplacement angularDisplacement: float2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update the `ControlInputSourceDelegate` to move forward or backward
|
Update the `ControlInputSourceDelegate` to move forward or backward
|
||||||
|
@ -64,7 +64,7 @@ protocol ControlInputSourceDelegate: class {
|
||||||
Forward: (0.0, 1.0)
|
Forward: (0.0, 1.0)
|
||||||
Backward: (0.0, -1.0)
|
Backward: (0.0, -1.0)
|
||||||
*/
|
*/
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2)
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeDisplacement relativeDisplacement: float2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update the `ControlInputSourceDelegate` with new angular displacement
|
Update the `ControlInputSourceDelegate` with new angular displacement
|
||||||
|
@ -72,13 +72,13 @@ protocol ControlInputSourceDelegate: class {
|
||||||
Clockwise: (-1.0, 0.0)
|
Clockwise: (-1.0, 0.0)
|
||||||
CounterClockwise: (1.0, 0.0)
|
CounterClockwise: (1.0, 0.0)
|
||||||
*/
|
*/
|
||||||
func controlInputSource(controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2)
|
func controlInputSource(_ controlInputSource: ControlInputSourceType, didUpdateWithRelativeAngularDisplacement relativeAngularDisplacement: float2)
|
||||||
|
|
||||||
/// Instructs the `ControlInputSourceDelegate` to cause the player to attack.
|
/// Instructs the `ControlInputSourceDelegate` to cause the player to attack.
|
||||||
func controlInputSourceDidBeginAttacking(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidBeginAttacking(_ controlInputSource: ControlInputSourceType)
|
||||||
|
|
||||||
/// Instructs the `ControlInputSourceDelegate` to end the player's attack.
|
/// Instructs the `ControlInputSourceDelegate` to end the player's attack.
|
||||||
func controlInputSourceDidFinishAttacking(controlInputSource: ControlInputSourceType)
|
func controlInputSourceDidFinishAttacking(_ controlInputSource: ControlInputSourceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A protocol to be adopted by classes that provide control input and notify their delegates when input is available.
|
/// A protocol to be adopted by classes that provide control input and notify their delegates when input is available.
|
||||||
|
@ -92,4 +92,4 @@ protocol ControlInputSourceType: class {
|
||||||
var allowsStrafing: Bool { get }
|
var allowsStrafing: Bool { get }
|
||||||
|
|
||||||
func resetControlState()
|
func resetControlState()
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create components that define how the entity looks and behaves.
|
// Create components that define how the entity looks and behaves.
|
||||||
let renderComponent = RenderComponent(entity: self)
|
let renderComponent = RenderComponent()
|
||||||
addComponent(renderComponent)
|
addComponent(renderComponent)
|
||||||
|
|
||||||
let orientationComponent = OrientationComponent()
|
let orientationComponent = OrientationComponent()
|
||||||
|
@ -107,35 +107,39 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset
|
beamTargetOffset = GameplayConfiguration.FlyingBot.beamTargetOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: ContactableType
|
// MARK: ContactableType
|
||||||
|
|
||||||
override func contactWithEntityDidBegin(entity: GKEntity) {
|
override func contactWithEntityDidBegin(_ entity: GKEntity) {
|
||||||
super.contactWithEntityDidBegin(entity)
|
super.contactWithEntityDidBegin(entity)
|
||||||
|
|
||||||
guard !isGood else { return }
|
guard !isGood else { return }
|
||||||
|
|
||||||
var shouldStartAttack = false
|
var shouldStartAttack = false
|
||||||
|
|
||||||
if let otherTaskBot = entity as? TaskBot where otherTaskBot.isGood {
|
if let otherTaskBot = entity as? TaskBot, otherTaskBot.isGood {
|
||||||
// Contact with good task bot will trigger an attack.
|
// Contact with good task bot will trigger an attack.
|
||||||
shouldStartAttack = true
|
shouldStartAttack = true
|
||||||
}
|
}
|
||||||
else if let playerBot = entity as? PlayerBot where !playerBot.isPoweredDown {
|
else if let playerBot = entity as? PlayerBot, !playerBot.isPoweredDown {
|
||||||
// Contact with an active `PlayerBot` will trigger an attack.
|
// Contact with an active `PlayerBot` will trigger an attack.
|
||||||
shouldStartAttack = true
|
shouldStartAttack = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if let stateMachine = componentForClass(IntelligenceComponent)?.stateMachine where shouldStartAttack {
|
if let stateMachine = component(ofType: IntelligenceComponent.self)?.stateMachine, shouldStartAttack {
|
||||||
stateMachine.enterState(FlyingBotPreAttackState.self)
|
stateMachine.enter(FlyingBotPreAttackState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ChargeComponentDelegate
|
// MARK: ChargeComponentDelegate
|
||||||
|
|
||||||
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
||||||
guard let intelligenceComponent = componentForClass(IntelligenceComponent) else { return }
|
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
|
||||||
|
|
||||||
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self)
|
intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
|
||||||
isGood = !chargeComponent.hasCharge
|
isGood = !chargeComponent.hasCharge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +149,7 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
return goodAnimations == nil || badAnimations == nil
|
return goodAnimations == nil || badAnimations == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
|
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
|
||||||
// Load `TaskBot`s shared assets.
|
// Load `TaskBot`s shared assets.
|
||||||
super.loadSharedAssets()
|
super.loadSharedAssets()
|
||||||
|
|
||||||
|
@ -170,13 +174,13 @@ class FlyingBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
after the `FlyingBot` texture atlases have finished preloading.
|
after the `FlyingBot` texture atlases have finished preloading.
|
||||||
*/
|
*/
|
||||||
goodAnimations = [:]
|
goodAnimations = [:]
|
||||||
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .WalkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
|
goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[0], withImageIdentifier: "FlyingBotGoodWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
|
||||||
goodAnimations![.Attack] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
goodAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[1], withImageIdentifier: "FlyingBotGoodAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
||||||
|
|
||||||
badAnimations = [:]
|
badAnimations = [:]
|
||||||
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .WalkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
|
badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[2], withImageIdentifier: "FlyingBotBadWalk", forAnimationState: .walkForward, bodyActionName: "FlyingBotBob", shadowActionName: "FlyingBotShadowScale")
|
||||||
badAnimations![.Attack] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[3], withImageIdentifier: "FlyingBotBadAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
||||||
badAnimations![.Zapped] = AnimationComponent.animationsFromAtlas(flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .Zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
badAnimations![.zapped] = AnimationComponent.animationsFromAtlas(atlas: flyingBotAtlases[4], withImageIdentifier: "FlyingBotZapped", forAnimationState: .zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
||||||
|
|
||||||
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
|
|
@ -73,7 +73,7 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create components that define how the entity looks and behaves.
|
// Create components that define how the entity looks and behaves.
|
||||||
let renderComponent = RenderComponent(entity: self)
|
let renderComponent = RenderComponent()
|
||||||
addComponent(renderComponent)
|
addComponent(renderComponent)
|
||||||
|
|
||||||
let orientationComponent = OrientationComponent()
|
let orientationComponent = OrientationComponent()
|
||||||
|
@ -116,22 +116,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
beamTargetOffset = GameplayConfiguration.GroundBot.beamTargetOffset
|
beamTargetOffset = GameplayConfiguration.GroundBot.beamTargetOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: ContactableType
|
// MARK: ContactableType
|
||||||
|
|
||||||
override func contactWithEntityDidBegin(entity: GKEntity) {
|
override func contactWithEntityDidBegin(_ entity: GKEntity) {
|
||||||
super.contactWithEntityDidBegin(entity)
|
super.contactWithEntityDidBegin(entity)
|
||||||
|
|
||||||
// Retrieve the current state from this `GroundBot` as a `GroundBotAttackState`.
|
// Retrieve the current state from this `GroundBot` as a `GroundBotAttackState`.
|
||||||
guard let attackState = componentForClass(IntelligenceComponent)?.stateMachine.currentState as? GroundBotAttackState else { return }
|
guard let attackState = component(ofType: IntelligenceComponent.self)?.stateMachine.currentState as? GroundBotAttackState else { return }
|
||||||
|
|
||||||
// Use the `GroundBotAttackState` to apply the appropriate damage to the contacted entity.
|
// Use the `GroundBotAttackState` to apply the appropriate damage to the contacted entity.
|
||||||
attackState.applyDamageToEntity(entity)
|
attackState.applyDamageToEntity(entity: entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: RulesComponentDelegate
|
// MARK: RulesComponentDelegate
|
||||||
|
|
||||||
override func rulesComponent(rulesComponent: RulesComponent, didFinishEvaluatingRuleSystem ruleSystem: GKRuleSystem) {
|
override func rulesComponent(rulesComponent: RulesComponent, didFinishEvaluatingRuleSystem ruleSystem: GKRuleSystem) {
|
||||||
super.rulesComponent(rulesComponent, didFinishEvaluatingRuleSystem: ruleSystem)
|
super.rulesComponent(rulesComponent: rulesComponent, didFinishEvaluatingRuleSystem: ruleSystem)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A `GroundBot` will attack a location in the scene if the following conditions are met:
|
A `GroundBot` will attack a location in the scene if the following conditions are met:
|
||||||
|
@ -140,26 +144,26 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
3) The target is within the `GroundBot`'s attack range.
|
3) The target is within the `GroundBot`'s attack range.
|
||||||
4) There is no scenery between the `GroundBot` and the target.
|
4) There is no scenery between the `GroundBot` and the target.
|
||||||
*/
|
*/
|
||||||
guard let scene = componentForClass(RenderComponent.self)?.node.scene else { return }
|
guard let scene = component(ofType: RenderComponent.self)?.node.scene else { return }
|
||||||
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return }
|
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
|
||||||
guard let agentControlledState = intelligenceComponent.stateMachine.currentState as? TaskBotAgentControlledState else { return }
|
guard let agentControlledState = intelligenceComponent.stateMachine.currentState as? TaskBotAgentControlledState else { return }
|
||||||
|
|
||||||
// 1) Check if enough time has passed since the `GroundBot`'s last attack.
|
// 1) Check if enough time has passed since the `GroundBot`'s last attack.
|
||||||
guard agentControlledState.elapsedTime >= GameplayConfiguration.GroundBot.delayBetweenAttacks else { return }
|
guard agentControlledState.elapsedTime >= GameplayConfiguration.GroundBot.delayBetweenAttacks else { return }
|
||||||
|
|
||||||
// 2) Check if the current mandate is to hunt an agent.
|
// 2) Check if the current mandate is to hunt an agent.
|
||||||
guard case let .HuntAgent(targetAgent) = mandate else { return }
|
guard case let .huntAgent(targetAgent) = mandate else { return }
|
||||||
|
|
||||||
// 3) Check if the target is within the `GroundBot`'s attack range.
|
// 3) Check if the target is within the `GroundBot`'s attack range.
|
||||||
guard distanceToAgent(targetAgent) <= GameplayConfiguration.GroundBot.maximumAttackDistance else { return }
|
guard distanceToAgent(otherAgent: targetAgent) <= GameplayConfiguration.GroundBot.maximumAttackDistance else { return }
|
||||||
|
|
||||||
// 4) Check if any walls or obstacles are between the `GroundBot` and its hunt target position.
|
// 4) Check if any walls or obstacles are between the `GroundBot` and its hunt target position.
|
||||||
var hasLineOfSight = true
|
var hasLineOfSight = true
|
||||||
|
|
||||||
scene.physicsWorld.enumerateBodiesAlongRayStart(CGPoint(agent.position), end: CGPoint(targetAgent.position)) { body, _, _, stop in
|
scene.physicsWorld.enumerateBodies(alongRayStart: CGPoint(agent.position), end: CGPoint(targetAgent.position)) { body, _, _, stop in
|
||||||
if ColliderType(rawValue: body.categoryBitMask).contains(.Obstacle) {
|
if ColliderType(rawValue: body.categoryBitMask).contains(.Obstacle) {
|
||||||
hasLineOfSight = false
|
hasLineOfSight = false
|
||||||
stop.memory = true
|
stop.pointee = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,18 +171,18 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
|
|
||||||
// The `GroundBot` is ready to attack the `targetAgent`'s current position.
|
// The `GroundBot` is ready to attack the `targetAgent`'s current position.
|
||||||
targetPosition = targetAgent.position
|
targetPosition = targetAgent.position
|
||||||
intelligenceComponent.stateMachine.enterState(GroundBotRotateToAttackState.self)
|
intelligenceComponent.stateMachine.enter(GroundBotRotateToAttackState.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ChargeComponentDelegate
|
// MARK: ChargeComponentDelegate
|
||||||
|
|
||||||
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
||||||
guard let intelligenceComponent = componentForClass(IntelligenceComponent) else { return }
|
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
|
||||||
|
|
||||||
isGood = !chargeComponent.hasCharge
|
isGood = !chargeComponent.hasCharge
|
||||||
|
|
||||||
if !isGood {
|
if !isGood {
|
||||||
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self)
|
intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +192,7 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
return goodAnimations == nil || badAnimations == nil
|
return goodAnimations == nil || badAnimations == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
|
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
|
||||||
// Load `TaskBot`s shared assets.
|
// Load `TaskBot`s shared assets.
|
||||||
super.loadSharedAssets()
|
super.loadSharedAssets()
|
||||||
|
|
||||||
|
@ -213,12 +217,12 @@ class GroundBot: TaskBot, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
after the `GroundBot` texture atlases have finished preloading.
|
after the `GroundBot` texture atlases have finished preloading.
|
||||||
*/
|
*/
|
||||||
goodAnimations = [:]
|
goodAnimations = [:]
|
||||||
goodAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .WalkForward)
|
goodAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[0], withImageIdentifier: "GroundBotGoodWalk", forAnimationState: .walkForward)
|
||||||
|
|
||||||
badAnimations = [:]
|
badAnimations = [:]
|
||||||
badAnimations![.WalkForward] = AnimationComponent.animationsFromAtlas(groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .WalkForward)
|
badAnimations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[1], withImageIdentifier: "GroundBotBadWalk", forAnimationState: .walkForward)
|
||||||
badAnimations![.Attack] = AnimationComponent.animationsFromAtlas(groundBotAtlases[2], withImageIdentifier: "GroundBotAttack", forAnimationState: .Attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake", repeatTexturesForever: false)
|
badAnimations![.attack] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[2], withImageIdentifier: "GroundBotAttack", forAnimationState: .attack, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake", repeatTexturesForever: false)
|
||||||
badAnimations![.Zapped] = AnimationComponent.animationsFromAtlas(groundBotAtlases[3], withImageIdentifier: "GroundBotZapped", forAnimationState: .Zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
badAnimations![.zapped] = AnimationComponent.animationsFromAtlas(atlas: groundBotAtlases[3], withImageIdentifier: "GroundBotZapped", forAnimationState: .zapped, bodyActionName: "ZappedShake", shadowActionName: "ZappedShadowShake")
|
||||||
|
|
||||||
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
|
|
@ -48,7 +48,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
It is not targetable when appearing or recharging.
|
It is not targetable when appearing or recharging.
|
||||||
*/
|
*/
|
||||||
var isTargetable: Bool {
|
var isTargetable: Bool {
|
||||||
guard let currentState = componentForClass(IntelligenceComponent.self)?.stateMachine.currentState else { return false }
|
guard let currentState = component(ofType: IntelligenceComponent.self)?.stateMachine.currentState else { return false }
|
||||||
|
|
||||||
switch currentState {
|
switch currentState {
|
||||||
case is PlayerBotPlayerControlledState, is PlayerBotHitState:
|
case is PlayerBotPlayerControlledState, is PlayerBotHitState:
|
||||||
|
@ -64,7 +64,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
|
|
||||||
/// The `RenderComponent` associated with this `PlayerBot`.
|
/// The `RenderComponent` associated with this `PlayerBot`.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = componentForClass(RenderComponent.self) else { fatalError("A PlayerBot must have an RenderComponent.") }
|
guard let renderComponent = component(ofType: RenderComponent.self) else { fatalError("A PlayerBot must have an RenderComponent.") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
so that they have the render node available to them when first entered
|
so that they have the render node available to them when first entered
|
||||||
(e.g. so that `PlayerBotAppearState` can add a shader to the render node).
|
(e.g. so that `PlayerBotAppearState` can add a shader to the render node).
|
||||||
*/
|
*/
|
||||||
let renderComponent = RenderComponent(entity: self)
|
let renderComponent = RenderComponent()
|
||||||
addComponent(renderComponent)
|
addComponent(renderComponent)
|
||||||
|
|
||||||
let orientationComponent = OrientationComponent()
|
let orientationComponent = OrientationComponent()
|
||||||
|
@ -133,16 +133,20 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
addComponent(intelligenceComponent)
|
addComponent(intelligenceComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Charge component delegate
|
// MARK: Charge component delegate
|
||||||
|
|
||||||
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
func chargeComponentDidLoseCharge(chargeComponent: ChargeComponent) {
|
||||||
if let intelligenceComponent = componentForClass(IntelligenceComponent.self) {
|
if let intelligenceComponent = component(ofType: IntelligenceComponent.self) {
|
||||||
if !chargeComponent.hasCharge {
|
if !chargeComponent.hasCharge {
|
||||||
isPoweredDown = true
|
isPoweredDown = true
|
||||||
intelligenceComponent.stateMachine.enterState(PlayerBotRechargingState.self)
|
intelligenceComponent.stateMachine.enter(PlayerBotRechargingState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
intelligenceComponent.stateMachine.enterState(PlayerBotHitState.self)
|
intelligenceComponent.stateMachine.enter(PlayerBotHitState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +157,7 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
return appearTextures == nil || animations == nil
|
return appearTextures == nil || animations == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
|
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
|
||||||
loadMiscellaneousAssets()
|
loadMiscellaneousAssets()
|
||||||
|
|
||||||
let playerBotAtlasNames = [
|
let playerBotAtlasNames = [
|
||||||
|
@ -181,16 +185,16 @@ class PlayerBot: GKEntity, ChargeComponentDelegate, ResourceLoadableType {
|
||||||
*/
|
*/
|
||||||
appearTextures = [:]
|
appearTextures = [:]
|
||||||
for orientation in CompassDirection.allDirections {
|
for orientation in CompassDirection.allDirections {
|
||||||
appearTextures![orientation] = AnimationComponent.firstTextureForOrientation(orientation, inAtlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle")
|
appearTextures![orientation] = AnimationComponent.firstTextureForOrientation(compassDirection: orientation, inAtlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up all of the `PlayerBot`s animations.
|
// Set up all of the `PlayerBot`s animations.
|
||||||
animations = [:]
|
animations = [:]
|
||||||
animations![.Idle] = AnimationComponent.animationsFromAtlas(playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .Idle)
|
animations![.idle] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[0], withImageIdentifier: "PlayerBotIdle", forAnimationState: .idle)
|
||||||
animations![.WalkForward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkForward)
|
animations![.walkForward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkForward)
|
||||||
animations![.WalkBackward] = AnimationComponent.animationsFromAtlas(playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .WalkBackward, playBackwards: true)
|
animations![.walkBackward] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[1], withImageIdentifier: "PlayerBotWalk", forAnimationState: .walkBackward, playBackwards: true)
|
||||||
animations![.Inactive] = AnimationComponent.animationsFromAtlas(playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .Inactive)
|
animations![.inactive] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[2], withImageIdentifier: "PlayerBotInactive", forAnimationState: .inactive)
|
||||||
animations![.Hit] = AnimationComponent.animationsFromAtlas(playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .Hit, repeatTexturesForever: false)
|
animations![.hit] = AnimationComponent.animationsFromAtlas(atlas: playerBotAtlases[3], withImageIdentifier: "PlayerBotHit", forAnimationState: .hit, repeatTexturesForever: false)
|
||||||
|
|
||||||
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
|
|
@ -15,16 +15,16 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
/// Encapsulates a `TaskBot`'s current mandate, i.e. the aim that the `TaskBot` is setting out to achieve.
|
/// Encapsulates a `TaskBot`'s current mandate, i.e. the aim that the `TaskBot` is setting out to achieve.
|
||||||
enum TaskBotMandate {
|
enum TaskBotMandate {
|
||||||
// Hunt another agent (either a `PlayerBot` or a "good" `TaskBot`).
|
// Hunt another agent (either a `PlayerBot` or a "good" `TaskBot`).
|
||||||
case HuntAgent(GKAgent2D)
|
case huntAgent(GKAgent2D)
|
||||||
|
|
||||||
// Follow the `TaskBot`'s "good" patrol path.
|
// Follow the `TaskBot`'s "good" patrol path.
|
||||||
case FollowGoodPatrolPath
|
case followGoodPatrolPath
|
||||||
|
|
||||||
// Follow the `TaskBot`'s "bad" patrol path.
|
// Follow the `TaskBot`'s "bad" patrol path.
|
||||||
case FollowBadPatrolPath
|
case followBadPatrolPath
|
||||||
|
|
||||||
// Return to a given position on a patrol path.
|
// Return to a given position on a patrol path.
|
||||||
case ReturnToPositionOnPath(float2)
|
case returnToPositionOnPath(float2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
@ -36,12 +36,12 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
guard isGood != oldValue else { return }
|
guard isGood != oldValue else { return }
|
||||||
|
|
||||||
// Get the components we will need to access in response to the value changing.
|
// Get the components we will need to access in response to the value changing.
|
||||||
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { fatalError("TaskBots must have an intelligence component.") }
|
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { fatalError("TaskBots must have an intelligence component.") }
|
||||||
guard let animationComponent = componentForClass(AnimationComponent.self) else { fatalError("TaskBots must have an animation component.") }
|
guard let animationComponent = component(ofType: AnimationComponent.self) else { fatalError("TaskBots must have an animation component.") }
|
||||||
guard let chargeComponent = componentForClass(ChargeComponent.self) else { fatalError("TaskBots must have a charge component.") }
|
guard let chargeComponent = component(ofType: ChargeComponent.self) else { fatalError("TaskBots must have a charge component.") }
|
||||||
|
|
||||||
// Update the `TaskBot`'s speed and acceleration to suit the new value of `isGood`.
|
// Update the `TaskBot`'s speed and acceleration to suit the new value of `isGood`.
|
||||||
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood)
|
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood: isGood)
|
||||||
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
|
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
|
||||||
|
|
||||||
if isGood {
|
if isGood {
|
||||||
|
@ -49,16 +49,16 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
The `TaskBot` just turned from "bad" to "good".
|
The `TaskBot` just turned from "bad" to "good".
|
||||||
Set its mandate to `.ReturnToPositionOnPath` for the closest point on its "good" patrol path.
|
Set its mandate to `.ReturnToPositionOnPath` for the closest point on its "good" patrol path.
|
||||||
*/
|
*/
|
||||||
let closestPointOnGoodPath = closestPointOnPath(goodPathPoints)
|
let closestPointOnGoodPath = closestPointOnPath(path: goodPathPoints)
|
||||||
mandate = .ReturnToPositionOnPath(float2(closestPointOnGoodPath))
|
mandate = .returnToPositionOnPath(float2(closestPointOnGoodPath))
|
||||||
|
|
||||||
if self is FlyingBot {
|
if self is FlyingBot {
|
||||||
// Enter the `FlyingBotBlastState` so it performs a curing blast.
|
// Enter the `FlyingBotBlastState` so it performs a curing blast.
|
||||||
intelligenceComponent.stateMachine.enterState(FlyingBotBlastState.self)
|
intelligenceComponent.stateMachine.enter(FlyingBotBlastState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Make sure the `TaskBot`s state is `TaskBotAgentControlledState` so that it follows its mandate.
|
// Make sure the `TaskBot`s state is `TaskBotAgentControlledState` so that it follows its mandate.
|
||||||
intelligenceComponent.stateMachine.enterState(TaskBotAgentControlledState.self)
|
intelligenceComponent.stateMachine.enter(TaskBotAgentControlledState.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the animation component to use the "good" animations.
|
// Update the animation component to use the "good" animations.
|
||||||
|
@ -73,8 +73,8 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
Default to a `.ReturnToPositionOnPath` mandate for the closest point on its "bad" patrol path.
|
Default to a `.ReturnToPositionOnPath` mandate for the closest point on its "bad" patrol path.
|
||||||
This may be overridden by a `.HuntAgent` mandate when the `TaskBot`'s rules are next evaluated.
|
This may be overridden by a `.HuntAgent` mandate when the `TaskBot`'s rules are next evaluated.
|
||||||
*/
|
*/
|
||||||
let closestPointOnBadPath = closestPointOnPath(badPathPoints)
|
let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
|
||||||
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath))
|
mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
|
||||||
|
|
||||||
// Update the animation component to use the "bad" animations.
|
// Update the animation component to use the "bad" animations.
|
||||||
animationComponent.animations = badAnimations
|
animationComponent.animations = badAnimations
|
||||||
|
@ -83,7 +83,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
chargeComponent.charge = chargeComponent.maximumCharge
|
chargeComponent.charge = chargeComponent.maximumCharge
|
||||||
|
|
||||||
// Enter the "zapped" state.
|
// Enter the "zapped" state.
|
||||||
intelligenceComponent.stateMachine.enterState(TaskBotZappedState.self)
|
intelligenceComponent.stateMachine.enter(TaskBotZappedState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
/// The appropriate `GKBehavior` for the `TaskBot`, based on its current `mandate`.
|
/// The appropriate `GKBehavior` for the `TaskBot`, based on its current `mandate`.
|
||||||
var behaviorForCurrentMandate: GKBehavior {
|
var behaviorForCurrentMandate: GKBehavior {
|
||||||
// Return an empty behavior if this `TaskBot` is not yet in a `LevelScene`.
|
// Return an empty behavior if this `TaskBot` is not yet in a `LevelScene`.
|
||||||
guard let levelScene = componentForClass(RenderComponent.self)?.node.scene as? LevelScene else {
|
guard let levelScene = component(ofType: RenderComponent.self)?.node.scene as? LevelScene else {
|
||||||
return GKBehavior()
|
return GKBehavior()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,28 +113,28 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
let debugColor: SKColor
|
let debugColor: SKColor
|
||||||
|
|
||||||
switch mandate {
|
switch mandate {
|
||||||
case .FollowGoodPatrolPath, .FollowBadPatrolPath:
|
case .followGoodPatrolPath, .followBadPatrolPath:
|
||||||
let pathPoints = isGood ? goodPathPoints : badPathPoints
|
let pathPoints = isGood ? goodPathPoints : badPathPoints
|
||||||
radius = GameplayConfiguration.TaskBot.patrolPathRadius
|
radius = GameplayConfiguration.TaskBot.patrolPathRadius
|
||||||
agentBehavior = TaskBotBehavior.behaviorForAgent(agent, patrollingPathWithPoints: pathPoints, pathRadius: radius, inScene: levelScene)
|
agentBehavior = TaskBotBehavior.behavior(forAgent: agent, patrollingPathWithPoints: pathPoints, pathRadius: radius, inScene: levelScene)
|
||||||
debugPathPoints = pathPoints
|
debugPathPoints = pathPoints
|
||||||
// Patrol paths are always closed loops, so the debug drawing of the path should cycle back round to the start.
|
// Patrol paths are always closed loops, so the debug drawing of the path should cycle back round to the start.
|
||||||
debugPathShouldCycle = true
|
debugPathShouldCycle = true
|
||||||
debugColor = isGood ? SKColor.greenColor() : SKColor.purpleColor()
|
debugColor = isGood ? SKColor.green : SKColor.purple
|
||||||
|
|
||||||
case let .HuntAgent(targetAgent):
|
case let .huntAgent(targetAgent):
|
||||||
radius = GameplayConfiguration.TaskBot.huntPathRadius
|
radius = GameplayConfiguration.TaskBot.huntPathRadius
|
||||||
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene)
|
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, huntingAgent: targetAgent, pathRadius: radius, inScene: levelScene)
|
||||||
debugColor = SKColor.redColor()
|
debugColor = SKColor.red
|
||||||
|
|
||||||
case let .ReturnToPositionOnPath(position):
|
case let .returnToPositionOnPath(position):
|
||||||
radius = GameplayConfiguration.TaskBot.returnToPatrolPathRadius
|
radius = GameplayConfiguration.TaskBot.returnToPatrolPathRadius
|
||||||
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPointsForAgent(agent, returningToPoint: position, pathRadius: radius, inScene: levelScene)
|
(agentBehavior, debugPathPoints) = TaskBotBehavior.behaviorAndPathPoints(forAgent: agent, returningToPoint: position, pathRadius: radius, inScene: levelScene)
|
||||||
debugColor = SKColor.yellowColor()
|
debugColor = SKColor.yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
if levelScene.debugDrawingEnabled {
|
if levelScene.debugDrawingEnabled {
|
||||||
drawDebugPath(debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius)
|
drawDebugPath(path: debugPathPoints, cycle: debugPathShouldCycle, color: debugColor, radius: radius)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
debugNode.removeAllChildren()
|
debugNode.removeAllChildren()
|
||||||
|
@ -155,13 +155,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
|
|
||||||
/// The `GKAgent` associated with this `TaskBot`.
|
/// The `GKAgent` associated with this `TaskBot`.
|
||||||
var agent: TaskBotAgent {
|
var agent: TaskBotAgent {
|
||||||
guard let agent = componentForClass(TaskBotAgent.self) else { fatalError("A TaskBot entity must have a GKAgent2D component.") }
|
guard let agent = component(ofType: TaskBotAgent.self) else { fatalError("A TaskBot entity must have a GKAgent2D component.") }
|
||||||
return agent
|
return agent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `RenderComponent` associated with this `TaskBot`.
|
/// The `RenderComponent` associated with this `TaskBot`.
|
||||||
var renderComponent: RenderComponent {
|
var renderComponent: RenderComponent {
|
||||||
guard let renderComponent = componentForClass(RenderComponent.self) else { fatalError("A TaskBot must have an RenderComponent.") }
|
guard let renderComponent = component(ofType: RenderComponent.self) else { fatalError("A TaskBot must have an RenderComponent.") }
|
||||||
return renderComponent
|
return renderComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
Because a `TaskBot` is positioned at the appropriate path's start point when the level is created,
|
Because a `TaskBot` is positioned at the appropriate path's start point when the level is created,
|
||||||
there is no need for it to pathfind to the start of its path, and it can patrol immediately.
|
there is no need for it to pathfind to the start of its path, and it can patrol immediately.
|
||||||
*/
|
*/
|
||||||
mandate = isGood ? .FollowGoodPatrolPath : .FollowBadPatrolPath
|
mandate = isGood ? .followGoodPatrolPath : .followBadPatrolPath
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
agent.delegate = self
|
agent.delegate = self
|
||||||
|
|
||||||
// Configure the agent's characteristics for the steering physics simulation.
|
// Configure the agent's characteristics for the steering physics simulation.
|
||||||
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood)
|
agent.maxSpeed = GameplayConfiguration.TaskBot.maximumSpeedForIsGood(isGood: isGood)
|
||||||
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
|
agent.maxAcceleration = GameplayConfiguration.TaskBot.maximumAcceleration
|
||||||
agent.mass = GameplayConfiguration.TaskBot.agentMass
|
agent.mass = GameplayConfiguration.TaskBot.agentMass
|
||||||
agent.radius = GameplayConfiguration.TaskBot.agentRadius
|
agent.radius = GameplayConfiguration.TaskBot.agentRadius
|
||||||
|
@ -224,6 +224,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
rulesComponent.delegate = self
|
rulesComponent.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: GKAgentDelegate
|
// MARK: GKAgentDelegate
|
||||||
|
|
||||||
func agentWillUpdate(_: GKAgent) {
|
func agentWillUpdate(_: GKAgent) {
|
||||||
|
@ -243,13 +247,13 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
}
|
}
|
||||||
|
|
||||||
func agentDidUpdate(_: GKAgent) {
|
func agentDidUpdate(_: GKAgent) {
|
||||||
guard let intelligenceComponent = componentForClass(IntelligenceComponent.self) else { return }
|
guard let intelligenceComponent = component(ofType: IntelligenceComponent.self) else { return }
|
||||||
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return }
|
guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
|
||||||
|
|
||||||
if intelligenceComponent.stateMachine.currentState is TaskBotAgentControlledState {
|
if intelligenceComponent.stateMachine.currentState is TaskBotAgentControlledState {
|
||||||
|
|
||||||
// `TaskBot`s always move in a forward direction when they are agent-controlled.
|
// `TaskBot`s always move in a forward direction when they are agent-controlled.
|
||||||
componentForClass(AnimationComponent.self)?.requestedAnimationState = .WalkForward
|
component(ofType: AnimationComponent.self)?.requestedAnimationState = .walkForward
|
||||||
|
|
||||||
// When the `TaskBot` is agent-controlled, the node position follows the agent position.
|
// When the `TaskBot` is agent-controlled, the node position follows the agent position.
|
||||||
updateNodePositionToMatchAgentPosition()
|
updateNodePositionToMatchAgentPosition()
|
||||||
|
@ -288,9 +292,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
// A series of situations in which we prefer this `TaskBot` to hunt the player.
|
// A series of situations in which we prefer this `TaskBot` to hunt the player.
|
||||||
let huntPlayerBotRaw = [
|
let huntPlayerBotRaw = [
|
||||||
// "Number of bad TaskBots is high" AND "Player is nearby".
|
// "Number of bad TaskBots is high" AND "Player is nearby".
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageHigh.rawValue,
|
Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
|
||||||
Fact.PlayerBotNear.rawValue
|
Fact.playerBotNear.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -299,9 +303,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// "Number of bad `TaskBot`s is medium" AND "Player is nearby".
|
// "Number of bad `TaskBot`s is medium" AND "Player is nearby".
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageMedium.rawValue,
|
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
|
||||||
Fact.PlayerBotNear.rawValue
|
Fact.playerBotNear.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are already a reasonable number of bad `TaskBots` on the level,
|
There are already a reasonable number of bad `TaskBots` on the level,
|
||||||
|
@ -312,10 +316,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
"Number of bad TaskBots is high" AND "Player is at medium proximity"
|
"Number of bad TaskBots is high" AND "Player is at medium proximity"
|
||||||
AND "nearest good `TaskBot` is at medium proximity".
|
AND "nearest good `TaskBot` is at medium proximity".
|
||||||
*/
|
*/
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageHigh.rawValue,
|
Fact.badTaskBotPercentageHigh.rawValue as AnyObject,
|
||||||
Fact.PlayerBotMedium.rawValue,
|
Fact.playerBotMedium.rawValue as AnyObject,
|
||||||
Fact.GoodTaskBotMedium.rawValue
|
Fact.goodTaskBotMedium.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are already a lot of bad `TaskBot`s on the level, so even though
|
There are already a lot of bad `TaskBot`s on the level, so even though
|
||||||
|
@ -325,15 +329,15 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
]
|
]
|
||||||
|
|
||||||
// Find the maximum of the minima from above.
|
// Find the maximum of the minima from above.
|
||||||
let huntPlayerBot = huntPlayerBotRaw.reduce(0.0, combine: max)
|
let huntPlayerBot = huntPlayerBotRaw.reduce(0.0, max)
|
||||||
|
|
||||||
// A series of situations in which we prefer this `TaskBot` to hunt the nearest "good" TaskBot.
|
// A series of situations in which we prefer this `TaskBot` to hunt the nearest "good" TaskBot.
|
||||||
let huntTaskBotRaw = [
|
let huntTaskBotRaw = [
|
||||||
|
|
||||||
// "Number of bad TaskBots is low" AND "Nearest good `TaskBot` is nearby".
|
// "Number of bad TaskBots is low" AND "Nearest good `TaskBot` is nearby".
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageLow.rawValue,
|
Fact.badTaskBotPercentageLow.rawValue as AnyObject,
|
||||||
Fact.GoodTaskBotNear.rawValue
|
Fact.goodTaskBotNear.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are not many bad `TaskBot`s on the level, and a good `TaskBot`
|
There are not many bad `TaskBot`s on the level, and a good `TaskBot`
|
||||||
|
@ -341,9 +345,9 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// "Number of bad TaskBots is medium" AND "Nearest good TaskBot is nearby".
|
// "Number of bad TaskBots is medium" AND "Nearest good TaskBot is nearby".
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageMedium.rawValue,
|
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
|
||||||
Fact.GoodTaskBotNear.rawValue
|
Fact.goodTaskBotNear.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are a reasonable number of `TaskBot`s on the level, but a good
|
There are a reasonable number of `TaskBot`s on the level, but a good
|
||||||
|
@ -354,10 +358,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
"Number of bad TaskBots is low" AND "Player is at medium proximity"
|
"Number of bad TaskBots is low" AND "Player is at medium proximity"
|
||||||
AND "Nearest good TaskBot is at medium proximity".
|
AND "Nearest good TaskBot is at medium proximity".
|
||||||
*/
|
*/
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageLow.rawValue,
|
Fact.badTaskBotPercentageLow.rawValue as AnyObject,
|
||||||
Fact.PlayerBotMedium.rawValue,
|
Fact.playerBotMedium.rawValue as AnyObject,
|
||||||
Fact.GoodTaskBotMedium.rawValue
|
Fact.goodTaskBotMedium.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are not many bad `TaskBot`s on the level, so even though both
|
There are not many bad `TaskBot`s on the level, so even though both
|
||||||
|
@ -369,10 +373,10 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
"Number of bad `TaskBot`s is medium" AND "Player is far away" AND
|
"Number of bad `TaskBot`s is medium" AND "Player is far away" AND
|
||||||
"Nearest good `TaskBot` is at medium proximity".
|
"Nearest good `TaskBot` is at medium proximity".
|
||||||
*/
|
*/
|
||||||
ruleSystem.minimumGradeForFacts([
|
ruleSystem.minimumGrade(forFacts: [
|
||||||
Fact.BadTaskBotPercentageMedium.rawValue,
|
Fact.badTaskBotPercentageMedium.rawValue as AnyObject,
|
||||||
Fact.PlayerBotFar.rawValue,
|
Fact.playerBotFar.rawValue as AnyObject,
|
||||||
Fact.GoodTaskBotMedium.rawValue
|
Fact.goodTaskBotMedium.rawValue as AnyObject
|
||||||
]),
|
]),
|
||||||
/*
|
/*
|
||||||
There are a reasonable number of bad `TaskBot`s on the level, the
|
There are a reasonable number of bad `TaskBot`s on the level, the
|
||||||
|
@ -382,36 +386,36 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
]
|
]
|
||||||
|
|
||||||
// Find the maximum of the minima from above.
|
// Find the maximum of the minima from above.
|
||||||
let huntTaskBot = huntTaskBotRaw.reduce(0.0, combine: max)
|
let huntTaskBot = huntTaskBotRaw.reduce(0.0, max)
|
||||||
|
|
||||||
if huntPlayerBot >= huntTaskBot && huntPlayerBot > 0.0 {
|
if huntPlayerBot >= huntTaskBot && huntPlayerBot > 0.0 {
|
||||||
// The rules provided greater motivation to hunt the PlayerBot. Ignore any motivation to hunt the nearest good TaskBot.
|
// The rules provided greater motivation to hunt the PlayerBot. Ignore any motivation to hunt the nearest good TaskBot.
|
||||||
guard let playerBotAgent = state.playerBotTarget?.target.agent else { return }
|
guard let playerBotAgent = state.playerBotTarget?.target.agent else { return }
|
||||||
mandate = .HuntAgent(playerBotAgent)
|
mandate = .huntAgent(playerBotAgent)
|
||||||
}
|
}
|
||||||
else if huntTaskBot > huntPlayerBot {
|
else if huntTaskBot > huntPlayerBot {
|
||||||
// The rules provided greater motivation to hunt the nearest good TaskBot. Ignore any motivation to hunt the PlayerBot.
|
// The rules provided greater motivation to hunt the nearest good TaskBot. Ignore any motivation to hunt the PlayerBot.
|
||||||
mandate = .HuntAgent(state.nearestGoodTaskBotTarget!.target.agent)
|
mandate = .huntAgent(state.nearestGoodTaskBotTarget!.target.agent)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// The rules provided no motivation to hunt, so patrol in the "bad" state.
|
// The rules provided no motivation to hunt, so patrol in the "bad" state.
|
||||||
switch mandate {
|
switch mandate {
|
||||||
case .FollowBadPatrolPath:
|
case .followBadPatrolPath:
|
||||||
// The `TaskBot` is already on its "bad" patrol path, so no update is needed.
|
// The `TaskBot` is already on its "bad" patrol path, so no update is needed.
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
// Send the `TaskBot` to the closest point on its "bad" patrol path.
|
// Send the `TaskBot` to the closest point on its "bad" patrol path.
|
||||||
let closestPointOnBadPath = closestPointOnPath(badPathPoints)
|
let closestPointOnBadPath = closestPointOnPath(path: badPathPoints)
|
||||||
mandate = .ReturnToPositionOnPath(float2(closestPointOnBadPath))
|
mandate = .returnToPositionOnPath(float2(closestPointOnBadPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ContactableType
|
// MARK: ContactableType
|
||||||
|
|
||||||
func contactWithEntityDidBegin(entity: GKEntity) {}
|
func contactWithEntityDidBegin(_ entity: GKEntity) {}
|
||||||
|
|
||||||
func contactWithEntityDidEnd(entity: GKEntity) {}
|
func contactWithEntityDidEnd(_ entity: GKEntity) {}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
|
@ -433,7 +437,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
func closestPointOnPath(path: [CGPoint]) -> CGPoint {
|
func closestPointOnPath(path: [CGPoint]) -> CGPoint {
|
||||||
// Find the closest point to the `TaskBot`.
|
// Find the closest point to the `TaskBot`.
|
||||||
let taskBotPosition = agent.position
|
let taskBotPosition = agent.position
|
||||||
let closestPoint = path.minElement {
|
let closestPoint = path.min {
|
||||||
return distance_squared(taskBotPosition, float2($0)) < distance_squared(taskBotPosition, float2($1))
|
return distance_squared(taskBotPosition, float2($0)) < distance_squared(taskBotPosition, float2($1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +455,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
|
|
||||||
/// Sets the `TaskBot` `GKAgent` rotation to match the `TaskBot`'s orientation.
|
/// Sets the `TaskBot` `GKAgent` rotation to match the `TaskBot`'s orientation.
|
||||||
func updateAgentRotationToMatchTaskBotOrientation() {
|
func updateAgentRotationToMatchTaskBotOrientation() {
|
||||||
guard let orientationComponent = componentForClass(OrientationComponent.self) else { return }
|
guard let orientationComponent = component(ofType: OrientationComponent.self) else { return }
|
||||||
agent.rotation = Float(orientationComponent.zRotation)
|
agent.rotation = Float(orientationComponent.zRotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +504,7 @@ class TaskBot: GKEntity, ContactNotifiableType, GKAgentDelegate, RulesComponentD
|
||||||
|
|
||||||
let deltaX = next.x - current.x
|
let deltaX = next.x - current.x
|
||||||
let deltaY = next.y - current.y
|
let deltaY = next.y - current.y
|
||||||
let rectNode = SKShapeNode(rectOfSize: CGSize(width: hypot(deltaX, deltaY), height: CGFloat(radius) * 2))
|
let rectNode = SKShapeNode(rectOf: CGSize(width: hypot(deltaX, deltaY), height: CGFloat(radius) * 2))
|
||||||
rectNode.strokeColor = strokeColor
|
rectNode.strokeColor = strokeColor
|
||||||
rectNode.fillColor = fillColor
|
rectNode.fillColor = fillColor
|
||||||
rectNode.zRotation = atan(deltaY / deltaX)
|
rectNode.zRotation = atan(deltaY / deltaX)
|
||||||
|
|
|
@ -46,11 +46,11 @@ class GameControllerInputSource: ControlInputSourceType {
|
||||||
self.delegate?.controlInputSourceDidBeginAttacking(self)
|
self.delegate?.controlInputSourceDidBeginAttacking(self)
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
if let microGamepad = self.gameController.microGamepad where button == microGamepad.buttonA || button == microGamepad.buttonX {
|
if let microGamepad = self.gameController.microGamepad, button == microGamepad.buttonA || button == microGamepad.buttonX {
|
||||||
self.gameStateDelegate?.controlInputSourceDidSelect(self)
|
self.gameStateDelegate?.controlInputSourceDidSelect(self)
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if let gamepad = self.gameController.gamepad where button == gamepad.buttonA || button == gamepad.buttonX {
|
if let gamepad = self.gameController.gamepad, button == gamepad.buttonA || button == gamepad.buttonX {
|
||||||
self.gameStateDelegate?.controlInputSourceDidSelect(self)
|
self.gameStateDelegate?.controlInputSourceDidSelect(self)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class GameInput {
|
||||||
|
|
||||||
var isGameControllerConnected: Bool {
|
var isGameControllerConnected: Bool {
|
||||||
var isGameControllerConnected = false
|
var isGameControllerConnected = false
|
||||||
dispatch_sync(controlsQueue) {
|
controlsQueue.sync {
|
||||||
isGameControllerConnected = (self.secondaryControlInputSource != nil) || (self.nativeControlInputSource is GameControllerInputSource)
|
isGameControllerConnected = (self.secondaryControlInputSource != nil) || (self.nativeControlInputSource is GameControllerInputSource)
|
||||||
}
|
}
|
||||||
return isGameControllerConnected
|
return isGameControllerConnected
|
||||||
|
@ -49,13 +49,13 @@ final class GameInput {
|
||||||
weak var delegate: GameInputDelegate? {
|
weak var delegate: GameInputDelegate? {
|
||||||
didSet {
|
didSet {
|
||||||
// Ensure the delegate is aware of the player's current controls.
|
// Ensure the delegate is aware of the player's current controls.
|
||||||
delegate?.gameInputDidUpdateControlInputSources(self)
|
delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An internal queue to protect accessing the player's control input sources.
|
/// An internal queue to protect accessing the player's control input sources.
|
||||||
private let controlsQueue = dispatch_queue_create("com.example.apple-samplecode.player.controlsqueue", DISPATCH_QUEUE_SERIAL)
|
private let controlsQueue = DispatchQueue(label: "com.example.apple-samplecode.player.controlsqueue")
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(nativeControlInputSource: ControlInputSourceType) {
|
init(nativeControlInputSource: ControlInputSourceType) {
|
||||||
|
@ -68,7 +68,7 @@ final class GameInput {
|
||||||
init() {
|
init() {
|
||||||
// Search for paired game controllers.
|
// Search for paired game controllers.
|
||||||
for pairedController in GCController.controllers() {
|
for pairedController in GCController.controllers() {
|
||||||
updateWithGameController(pairedController)
|
update(withGameController: pairedController)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerForGameControllerNotifications()
|
registerForGameControllerNotifications()
|
||||||
|
@ -77,17 +77,17 @@ final class GameInput {
|
||||||
|
|
||||||
/// Register for `GCGameController` pairing notifications.
|
/// Register for `GCGameController` pairing notifications.
|
||||||
func registerForGameControllerNotifications() {
|
func registerForGameControllerNotifications() {
|
||||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(_:)), name: GCControllerDidConnectNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidConnectNotification(notification:)), name: NSNotification.Name.GCControllerDidConnect, object: nil)
|
||||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(_:)), name: GCControllerDidDisconnectNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(GameInput.handleControllerDidDisconnectNotification(notification:)), name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidConnectNotification, object: nil)
|
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidConnect, object: nil)
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(self, name: GCControllerDidDisconnectNotification, object: nil)
|
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateWithGameController(gameController: GCController) {
|
func update(withGameController gameController: GCController) {
|
||||||
dispatch_sync(controlsQueue) {
|
controlsQueue.sync {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
// Assign a controller to the `nativeControlInputSource` if one does not already exist.
|
// Assign a controller to the `nativeControlInputSource` if one does not already exist.
|
||||||
if self.nativeControlInputSource == nil {
|
if self.nativeControlInputSource == nil {
|
||||||
|
@ -103,7 +103,7 @@ final class GameInput {
|
||||||
if self.secondaryControlInputSource == nil {
|
if self.secondaryControlInputSource == nil {
|
||||||
let gameControllerInputSource = GameControllerInputSource(gameController: gameController)
|
let gameControllerInputSource = GameControllerInputSource(gameController: gameController)
|
||||||
self.secondaryControlInputSource = gameControllerInputSource
|
self.secondaryControlInputSource = gameControllerInputSource
|
||||||
gameController.playerIndex = .Index1
|
gameController.playerIndex = .index1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,8 +113,8 @@ final class GameInput {
|
||||||
@objc func handleControllerDidConnectNotification(notification: NSNotification) {
|
@objc func handleControllerDidConnectNotification(notification: NSNotification) {
|
||||||
let connectedGameController = notification.object as! GCController
|
let connectedGameController = notification.object as! GCController
|
||||||
|
|
||||||
updateWithGameController(connectedGameController)
|
update(withGameController: connectedGameController)
|
||||||
delegate?.gameInputDidUpdateControlInputSources(self)
|
delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleControllerDidDisconnectNotification(notification: NSNotification) {
|
@objc func handleControllerDidDisconnectNotification(notification: NSNotification) {
|
||||||
|
@ -122,16 +122,16 @@ final class GameInput {
|
||||||
|
|
||||||
// Check if the player was being controlled by the disconnected controller.
|
// Check if the player was being controlled by the disconnected controller.
|
||||||
if secondaryControlInputSource?.gameController == disconnectedGameController {
|
if secondaryControlInputSource?.gameController == disconnectedGameController {
|
||||||
dispatch_sync(controlsQueue) {
|
controlsQueue.sync {
|
||||||
self.secondaryControlInputSource = nil
|
self.secondaryControlInputSource = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any other connected controllers.
|
// Check for any other connected controllers.
|
||||||
if let gameController = GCController.controllers().first {
|
if let gameController = GCController.controllers().first {
|
||||||
updateWithGameController(gameController)
|
update(withGameController: gameController)
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate?.gameInputDidUpdateControlInputSources(self)
|
delegate?.gameInputDidUpdateControlInputSources(gameInput: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ struct GameplayConfiguration {
|
||||||
static let maxArcAngle = CGFloat(0.35)
|
static let maxArcAngle = CGFloat(0.35)
|
||||||
|
|
||||||
/// The maximum number of seconds for which the beam can be fired before recharging.
|
/// The maximum number of seconds for which the beam can be fired before recharging.
|
||||||
static let maximumFireDuration: NSTimeInterval = 2.0
|
static let maximumFireDuration: TimeInterval = 2.0
|
||||||
|
|
||||||
/// The amount of charge points the beam drains from `TaskBot`s per second.
|
/// The amount of charge points the beam drains from `TaskBot`s per second.
|
||||||
static let chargeLossPerSecond = 90.0
|
static let chargeLossPerSecond = 90.0
|
||||||
|
|
||||||
/// The length of time that the beam takes to recharge when it is fully depleted.
|
/// The length of time that the beam takes to recharge when it is fully depleted.
|
||||||
static let coolDownDuration: NSTimeInterval = 1.0
|
static let coolDownDuration: TimeInterval = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PlayerBot {
|
struct PlayerBot {
|
||||||
|
@ -62,24 +62,24 @@ struct GameplayConfiguration {
|
||||||
static let maximumCharge = 100.0
|
static let maximumCharge = 100.0
|
||||||
|
|
||||||
/// The length of time for which the `PlayerBot` remains in its "hit" state.
|
/// The length of time for which the `PlayerBot` remains in its "hit" state.
|
||||||
static let hitStateDuration: NSTimeInterval = 0.75
|
static let hitStateDuration: TimeInterval = 0.75
|
||||||
|
|
||||||
/// The length of time that it takes the `PlayerBot` to recharge when deactivated.
|
/// The length of time that it takes the `PlayerBot` to recharge when deactivated.
|
||||||
static let rechargeDelayWhenInactive: NSTimeInterval = 2.0
|
static let rechargeDelayWhenInactive: TimeInterval = 2.0
|
||||||
|
|
||||||
/// The amount of charge that the `PlayerBot` gains per second when recharging.
|
/// The amount of charge that the `PlayerBot` gains per second when recharging.
|
||||||
static let rechargeAmountPerSecond = 10.0
|
static let rechargeAmountPerSecond = 10.0
|
||||||
|
|
||||||
/// The amount of time it takes the `PlayerBot` to appear in a level before becoming controllable by the player.
|
/// The amount of time it takes the `PlayerBot` to appear in a level before becoming controllable by the player.
|
||||||
static let appearDuration: NSTimeInterval = 0.50
|
static let appearDuration: TimeInterval = 0.50
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TaskBot {
|
struct TaskBot {
|
||||||
/// The length of time a `TaskBot` waits before re-evaluating its rules.
|
/// The length of time a `TaskBot` waits before re-evaluating its rules.
|
||||||
static let rulesUpdateWaitDuration: NSTimeInterval = 1.0
|
static let rulesUpdateWaitDuration: TimeInterval = 1.0
|
||||||
|
|
||||||
/// The length of time a `TaskBot` waits before re-checking for an appropriate behavior.
|
/// The length of time a `TaskBot` waits before re-checking for an appropriate behavior.
|
||||||
static let behaviorUpdateWaitDuration: NSTimeInterval = 0.25
|
static let behaviorUpdateWaitDuration: TimeInterval = 0.25
|
||||||
|
|
||||||
/// How close a `TaskBot` has to be to a patrol path start point in order to start patrolling.
|
/// How close a `TaskBot` has to be to a patrol path start point in order to start patrolling.
|
||||||
static let thresholdProximityToPatrolPathStartPoint: Float = 50.0
|
static let thresholdProximityToPatrolPathStartPoint: Float = 50.0
|
||||||
|
@ -118,10 +118,10 @@ struct GameplayConfiguration {
|
||||||
static let agentOffset = physicsBodyOffset
|
static let agentOffset = physicsBodyOffset
|
||||||
|
|
||||||
/// The maximum time to look ahead when following a path.
|
/// The maximum time to look ahead when following a path.
|
||||||
static let maxPredictionTimeWhenFollowingPath: NSTimeInterval = 1.0
|
static let maxPredictionTimeWhenFollowingPath: TimeInterval = 1.0
|
||||||
|
|
||||||
/// The maximum time to look ahead for obstacles to be avoided.
|
/// The maximum time to look ahead for obstacles to be avoided.
|
||||||
static let maxPredictionTimeForObstacleAvoidance: NSTimeInterval = 1.0
|
static let maxPredictionTimeForObstacleAvoidance: TimeInterval = 1.0
|
||||||
|
|
||||||
/// The radius of the path along which an agent patrols.
|
/// The radius of the path along which an agent patrols.
|
||||||
static let patrolPathRadius: Float = 10.0
|
static let patrolPathRadius: Float = 10.0
|
||||||
|
@ -136,10 +136,10 @@ struct GameplayConfiguration {
|
||||||
static let pathfindingGraphBufferRadius: Float = 30.0
|
static let pathfindingGraphBufferRadius: Float = 30.0
|
||||||
|
|
||||||
/// The duration of a `TaskBot`'s pre-attack state.
|
/// The duration of a `TaskBot`'s pre-attack state.
|
||||||
static let preAttackStateDuration: NSTimeInterval = 0.8
|
static let preAttackStateDuration: TimeInterval = 0.8
|
||||||
|
|
||||||
/// The duration of a `TaskBot`'s zapped state.
|
/// The duration of a `TaskBot`'s zapped state.
|
||||||
static let zappedStateDuration: NSTimeInterval = 0.75
|
static let zappedStateDuration: TimeInterval = 0.75
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FlyingBot {
|
struct FlyingBot {
|
||||||
|
@ -153,10 +153,10 @@ struct GameplayConfiguration {
|
||||||
static let blastChargeLossPerSecond = 25.0
|
static let blastChargeLossPerSecond = 25.0
|
||||||
|
|
||||||
/// The duration of a `FlyingBot` blast.
|
/// The duration of a `FlyingBot` blast.
|
||||||
static let blastDuration: NSTimeInterval = 1.25
|
static let blastDuration: TimeInterval = 1.25
|
||||||
|
|
||||||
/// The duration over which a `FlyingBot` blast affects entities in its blast radius.
|
/// The duration over which a `FlyingBot` blast affects entities in its blast radius.
|
||||||
static let blastEffectDuration: NSTimeInterval = 0.75
|
static let blastEffectDuration: TimeInterval = 0.75
|
||||||
|
|
||||||
/// The offset from the `FlyingBot`'s position for the blast particle emitter node.
|
/// The offset from the `FlyingBot`'s position for the blast particle emitter node.
|
||||||
static let blastEmitterOffset = CGPoint(x: 0.0, y: 20.0)
|
static let blastEmitterOffset = CGPoint(x: 0.0, y: 20.0)
|
||||||
|
@ -188,7 +188,7 @@ struct GameplayConfiguration {
|
||||||
static let angularSpeedMultiplierWhenAttacking: CGFloat = 2.5
|
static let angularSpeedMultiplierWhenAttacking: CGFloat = 2.5
|
||||||
|
|
||||||
/// The amount of time to wait between `GroundBot` attacks.
|
/// The amount of time to wait between `GroundBot` attacks.
|
||||||
static let delayBetweenAttacks: NSTimeInterval = 2.0
|
static let delayBetweenAttacks: TimeInterval = 2.0
|
||||||
|
|
||||||
/// The offset from the `GroundBot`'s position that should be used for beam targeting.
|
/// The offset from the `GroundBot`'s position that should be used for beam targeting.
|
||||||
static let beamTargetOffset = CGPoint(x: 0.0, y: 40.0)
|
static let beamTargetOffset = CGPoint(x: 0.0, y: 40.0)
|
||||||
|
@ -224,10 +224,10 @@ struct GameplayConfiguration {
|
||||||
|
|
||||||
struct SceneManager {
|
struct SceneManager {
|
||||||
/// The duration of a transition between loaded scenes.
|
/// The duration of a transition between loaded scenes.
|
||||||
static let transitionDuration: NSTimeInterval = 2.0
|
static let transitionDuration: TimeInterval = 2.0
|
||||||
|
|
||||||
/// The duration of a transition from the progress scene to its loaded scene.
|
/// The duration of a transition from the progress scene to its loaded scene.
|
||||||
static let progressSceneTransitionDuration: NSTimeInterval = 0.5
|
static let progressSceneTransitionDuration: TimeInterval = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Timer {
|
struct Timer {
|
||||||
|
@ -245,4 +245,4 @@ struct GameplayConfiguration {
|
||||||
static let paddingSize: CGFloat = 0.2
|
static let paddingSize: CGFloat = 0.2
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,60 +13,60 @@ class HomeEndScene: BaseScene {
|
||||||
|
|
||||||
/// Returns the background node from the scene.
|
/// Returns the background node from the scene.
|
||||||
override var backgroundNode: SKSpriteNode? {
|
override var backgroundNode: SKSpriteNode? {
|
||||||
return childNodeWithName("backgroundNode") as? SKSpriteNode
|
return childNode(withName: "backgroundNode") as? SKSpriteNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The screen recorder button for the scene (if it has one).
|
/// The screen recorder button for the scene (if it has one).
|
||||||
var screenRecorderButton: ButtonNode? {
|
var screenRecorderButton: ButtonNode? {
|
||||||
return backgroundNode?.childNodeWithName(ButtonIdentifier.ScreenRecorderToggle.rawValue) as? ButtonNode
|
return backgroundNode?.childNode(withName: ButtonIdentifier.screenRecorderToggle.rawValue) as? ButtonNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The "NEW GAME" button which allows the player to proceed to the first level.
|
/// The "NEW GAME" button which allows the player to proceed to the first level.
|
||||||
var proceedButton: ButtonNode? {
|
var proceedButton: ButtonNode? {
|
||||||
return backgroundNode?.childNodeWithName(ButtonIdentifier.ProceedToNextScene.rawValue) as? ButtonNode
|
return backgroundNode?.childNode(withName: ButtonIdentifier.proceedToNextScene.rawValue) as? ButtonNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An array of objects for `SceneLoader` notifications.
|
/// An array of objects for `SceneLoader` notifications.
|
||||||
private var sceneLoaderNotificationObservers = [AnyObject]()
|
private var sceneLoaderNotificationObservers = [Any]()
|
||||||
|
|
||||||
// MARK: Deinitialization
|
// MARK: Deinitialization
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
// Deregister for scene loader notifications.
|
// Deregister for scene loader notifications.
|
||||||
for observer in sceneLoaderNotificationObservers {
|
for observer in sceneLoaderNotificationObservers {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(observer)
|
NotificationCenter.default.removeObserver(observer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Scene Life Cycle
|
// MARK: Scene Life Cycle
|
||||||
|
|
||||||
override func didMoveToView(view: SKView) {
|
override func didMove(to view: SKView) {
|
||||||
super.didMoveToView(view)
|
super.didMove(to: view)
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
screenRecorderButton?.isSelected = screenRecordingToggleEnabled
|
screenRecorderButton?.isSelected = screenRecordingToggleEnabled
|
||||||
#else
|
#else
|
||||||
screenRecorderButton?.hidden = true
|
screenRecorderButton?.isHidden = true
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Enable focus based navigation.
|
// Enable focus based navigation.
|
||||||
focusChangesEnabled = true
|
focusChangesEnabled = true
|
||||||
|
|
||||||
registerForNotifications()
|
registerForNotifications()
|
||||||
centerCameraOnPoint(backgroundNode!.position)
|
centerCameraOnPoint(point: backgroundNode!.position)
|
||||||
|
|
||||||
// Begin loading the first level as soon as the view appears.
|
// Begin loading the first level as soon as the view appears.
|
||||||
sceneManager.prepareSceneWithSceneIdentifier(.Level(1))
|
sceneManager.prepareScene(identifier: .level(1))
|
||||||
|
|
||||||
let levelLoader = sceneManager.sceneLoaderForSceneIdentifier(.Level(1))
|
let levelLoader = sceneManager.sceneLoader(forSceneIdentifier: .level(1))
|
||||||
|
|
||||||
// If the first level is not ready, hide the buttons until we are notified.
|
// If the first level is not ready, hide the buttons until we are notified.
|
||||||
if !(levelLoader.stateMachine.currentState is SceneLoaderResourcesReadyState) {
|
if !(levelLoader.stateMachine.currentState is SceneLoaderResourcesReadyState) {
|
||||||
proceedButton?.alpha = 0.0
|
proceedButton?.alpha = 0.0
|
||||||
proceedButton?.userInteractionEnabled = false
|
proceedButton?.isUserInteractionEnabled = false
|
||||||
|
|
||||||
screenRecorderButton?.alpha = 0.0
|
screenRecorderButton?.alpha = 0.0
|
||||||
screenRecorderButton?.userInteractionEnabled = false
|
screenRecorderButton?.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,21 +75,21 @@ class HomeEndScene: BaseScene {
|
||||||
guard sceneLoaderNotificationObservers.isEmpty else { return }
|
guard sceneLoaderNotificationObservers.isEmpty else { return }
|
||||||
|
|
||||||
// Create a closure to pass as a notification handler for when loading completes or fails.
|
// Create a closure to pass as a notification handler for when loading completes or fails.
|
||||||
let handleSceneLoaderNotification: (NSNotification) -> () = { [unowned self] notification in
|
let handleSceneLoaderNotification: (Notification) -> () = { [unowned self] notification in
|
||||||
let sceneLoader = notification.object as! SceneLoader
|
let sceneLoader = notification.object as! SceneLoader
|
||||||
|
|
||||||
// Show the proceed button if the `sceneLoader` pertains to a `LevelScene`.
|
// Show the proceed button if the `sceneLoader` pertains to a `LevelScene`.
|
||||||
if sceneLoader.sceneMetadata.sceneType is LevelScene.Type {
|
if sceneLoader.sceneMetadata.sceneType is LevelScene.Type {
|
||||||
// Allow the proceed and screen to be tapped or clicked.
|
// Allow the proceed and screen to be tapped or clicked.
|
||||||
self.proceedButton?.userInteractionEnabled = true
|
self.proceedButton?.isUserInteractionEnabled = true
|
||||||
self.screenRecorderButton?.userInteractionEnabled = true
|
self.screenRecorderButton?.isUserInteractionEnabled = true
|
||||||
|
|
||||||
// Fade in the proceed and screen recorder buttons.
|
// Fade in the proceed and screen recorder buttons.
|
||||||
self.screenRecorderButton?.runAction(SKAction.fadeInWithDuration(1.0))
|
self.screenRecorderButton?.run(SKAction.fadeIn(withDuration: 1.0))
|
||||||
|
|
||||||
// Clear the initial `proceedButton` focus.
|
// Clear the initial `proceedButton` focus.
|
||||||
self.proceedButton?.isFocused = false
|
self.proceedButton?.isFocused = false
|
||||||
self.proceedButton?.runAction(SKAction.fadeInWithDuration(1.0)) {
|
self.proceedButton?.run(SKAction.fadeIn(withDuration: 1.0)) {
|
||||||
// Indicate that the `proceedButton` is focused.
|
// Indicate that the `proceedButton` is focused.
|
||||||
self.resetFocus()
|
self.resetFocus()
|
||||||
}
|
}
|
||||||
|
@ -97,8 +97,8 @@ class HomeEndScene: BaseScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register for scene loader notifications.
|
// Register for scene loader notifications.
|
||||||
let completeNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidCompleteNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: handleSceneLoaderNotification)
|
let completeNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil, queue: OperationQueue.main, using: handleSceneLoaderNotification)
|
||||||
let failNotification = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidFailNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: handleSceneLoaderNotification)
|
let failNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidFailNotification, object: nil, queue: OperationQueue.main, using: handleSceneLoaderNotification)
|
||||||
|
|
||||||
// Keep track of the notifications we are registered to so we can remove them in `deinit`.
|
// Keep track of the notifications we are registered to so we can remove them in `deinit`.
|
||||||
sceneLoaderNotificationObservers += [completeNotification, failNotification]
|
sceneLoaderNotificationObservers += [completeNotification, failNotification]
|
||||||
|
|
|
@ -51,7 +51,7 @@ class KeyboardControlInputSource: ControlInputSourceType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logic matching a key press to `ControlInputSourceDelegate` calls.
|
/// The logic matching a key press to `ControlInputSourceDelegate` calls.
|
||||||
func handleKeyDownForCharacter(character: Character) {
|
func handleKeyDown(forCharacter character: Character) {
|
||||||
// Ignore repeat input.
|
// Ignore repeat input.
|
||||||
if downKeys.contains(character) {
|
if downKeys.contains(character) {
|
||||||
return
|
return
|
||||||
|
@ -110,8 +110,8 @@ class KeyboardControlInputSource: ControlInputSourceType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the logic matching when a key is released to `ControlInputSource` delegate calls.
|
// Handle the logic matching when a key is released to `ControlInputSource` delegate calls.
|
||||||
func handleKeyUpForCharacter(character: Character) {
|
func handleKeyUp(forCharacter character: Character) {
|
||||||
// Ensure the character was accounted for by `handleKeyDownForCharacter(_:)`.
|
// Ensure the character was accounted for by `handleKeyDown(forCharacter:)`.
|
||||||
guard downKeys.remove(character) != nil else { return }
|
guard downKeys.remove(character) != nil else { return }
|
||||||
|
|
||||||
if let relativeDisplacement = relativeDisplacementForCharacter(character) {
|
if let relativeDisplacement = relativeDisplacementForCharacter(character) {
|
||||||
|
@ -158,35 +158,35 @@ class KeyboardControlInputSource: ControlInputSourceType {
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
private func isDirectionalDisplacementVector(displacement: float2) -> Bool {
|
private func isDirectionalDisplacementVector(_ displacement: float2) -> Bool {
|
||||||
return displacement == KeyboardControlInputSource.forwardVector
|
return displacement == KeyboardControlInputSource.forwardVector
|
||||||
|| displacement == KeyboardControlInputSource.backwardVector
|
|| displacement == KeyboardControlInputSource.backwardVector
|
||||||
}
|
}
|
||||||
|
|
||||||
private func relativeDisplacementForCharacter(character: Character) -> float2? {
|
private func relativeDisplacementForCharacter(_ character: Character) -> float2? {
|
||||||
let mapping: [Character: float2] = [
|
let mapping: [Character: float2] = [
|
||||||
// Up arrow.
|
// Up arrow.
|
||||||
Character(UnicodeScalar(0xF700)): KeyboardControlInputSource.forwardVector,
|
Character(UnicodeScalar(0xF700)!): KeyboardControlInputSource.forwardVector,
|
||||||
"w": KeyboardControlInputSource.forwardVector,
|
"w": KeyboardControlInputSource.forwardVector,
|
||||||
|
|
||||||
// Down arrow.
|
// Down arrow.
|
||||||
Character(UnicodeScalar(0xF701)): KeyboardControlInputSource.backwardVector,
|
Character(UnicodeScalar(0xF701)!): KeyboardControlInputSource.backwardVector,
|
||||||
"s": KeyboardControlInputSource.backwardVector,
|
"s": KeyboardControlInputSource.backwardVector,
|
||||||
|
|
||||||
// Left arrow.
|
// Left arrow.
|
||||||
Character(UnicodeScalar(0xF702)): KeyboardControlInputSource.counterClockwiseVector,
|
Character(UnicodeScalar(0xF702)!): KeyboardControlInputSource.counterClockwiseVector,
|
||||||
"a": KeyboardControlInputSource.counterClockwiseVector,
|
"a": KeyboardControlInputSource.counterClockwiseVector,
|
||||||
|
|
||||||
// Right arrow.
|
// Right arrow.
|
||||||
Character(UnicodeScalar(0xF703)): KeyboardControlInputSource.clockwiseVector,
|
Character(UnicodeScalar(0xF703)!): KeyboardControlInputSource.clockwiseVector,
|
||||||
"d": KeyboardControlInputSource.clockwiseVector
|
"d": KeyboardControlInputSource.clockwiseVector
|
||||||
]
|
]
|
||||||
|
|
||||||
return mapping[character]
|
return mapping[character]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates if the provided character should trigger an attack.
|
/// Indicates if the provided character should trigger an attack.
|
||||||
private func isAttackCharacter(character: Character) -> Bool {
|
private func isAttackCharacter(_ character: Character) -> Bool {
|
||||||
return ["f", " ", "\r"].contains(character)
|
return ["f", " ", "\r"].contains(character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ struct LevelConfiguration {
|
||||||
|
|
||||||
/// The different types of `TaskBot` that can exist in a level.
|
/// The different types of `TaskBot` that can exist in a level.
|
||||||
enum Locomotion {
|
enum Locomotion {
|
||||||
case Ground
|
case ground
|
||||||
case Flying
|
case flying
|
||||||
}
|
}
|
||||||
|
|
||||||
let locomotion: Locomotion
|
let locomotion: Locomotion
|
||||||
|
@ -41,10 +41,10 @@ struct LevelConfiguration {
|
||||||
init(botConfigurationInfo: [String: AnyObject]) {
|
init(botConfigurationInfo: [String: AnyObject]) {
|
||||||
switch botConfigurationInfo["locomotion"] as! String {
|
switch botConfigurationInfo["locomotion"] as! String {
|
||||||
case "ground":
|
case "ground":
|
||||||
locomotion = .Ground
|
locomotion = .ground
|
||||||
|
|
||||||
case "flying":
|
case "flying":
|
||||||
locomotion = .Flying
|
locomotion = .flying
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fatalError("Unknown locomotion found while parsing `taskBot` data")
|
fatalError("Unknown locomotion found while parsing `taskBot` data")
|
||||||
|
@ -81,8 +81,8 @@ struct LevelConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The time limit (in seconds) for this level.
|
/// The time limit (in seconds) for this level.
|
||||||
var timeLimit: NSTimeInterval {
|
var timeLimit: TimeInterval {
|
||||||
return configurationInfo["timeLimit"] as! NSTimeInterval
|
return configurationInfo["timeLimit"] as! TimeInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The factor used to normalize distances between characters for 'fuzzy' logic.
|
/// The factor used to normalize distances between characters for 'fuzzy' logic.
|
||||||
|
@ -95,9 +95,9 @@ struct LevelConfiguration {
|
||||||
init(fileName: String) {
|
init(fileName: String) {
|
||||||
self.fileName = fileName
|
self.fileName = fileName
|
||||||
|
|
||||||
let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: "plist")!
|
let url = Bundle.main.url(forResource: fileName, withExtension: "plist")
|
||||||
|
|
||||||
configurationInfo = NSDictionary(contentsOfURL: url) as! [String: AnyObject]
|
configurationInfo = NSDictionary(contentsOf: url!) as! [String: AnyObject]
|
||||||
|
|
||||||
// Extract the data for every `TaskBot` in this level as an array of `TaskBotConfiguration` values.
|
// Extract the data for every `TaskBot` in this level as an array of `TaskBotConfiguration` values.
|
||||||
let botConfigurations = configurationInfo["taskBotConfigurations"] as! [[String: AnyObject]]
|
let botConfigurations = configurationInfo["taskBotConfigurations"] as! [[String: AnyObject]]
|
||||||
|
@ -107,4 +107,4 @@ struct LevelConfiguration {
|
||||||
|
|
||||||
initialPlayerBotOrientation = CompassDirection(string: configurationInfo["initialPlayerBotOrientation"] as! String)
|
initialPlayerBotOrientation = CompassDirection(string: configurationInfo["initialPlayerBotOrientation"] as! String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ extension LevelScene {
|
||||||
for destination in node.connectedNodes as! [GKGraphNode2D] {
|
for destination in node.connectedNodes as! [GKGraphNode2D] {
|
||||||
let points = [CGPoint(node.position), CGPoint(destination.position)]
|
let points = [CGPoint(node.position), CGPoint(destination.position)]
|
||||||
|
|
||||||
let shapeNode = SKShapeNode(points: UnsafeMutablePointer<CGPoint>(points), count: 2)
|
let shapeNode = SKShapeNode(points: UnsafeMutablePointer<CGPoint>(mutating: points), count: 2)
|
||||||
shapeNode.strokeColor = SKColor(white: 1.0, alpha: 0.1)
|
shapeNode.strokeColor = SKColor(white: 1.0, alpha: 0.1)
|
||||||
shapeNode.lineWidth = 2.0
|
shapeNode.lineWidth = 2.0
|
||||||
shapeNode.zPosition = -1
|
shapeNode.zPosition = -1
|
||||||
|
@ -76,22 +76,22 @@ extension SKSpriteNode {
|
||||||
if newValue == true {
|
if newValue == true {
|
||||||
let bufferRadius = CGFloat(GameplayConfiguration.TaskBot.pathfindingGraphBufferRadius)
|
let bufferRadius = CGFloat(GameplayConfiguration.TaskBot.pathfindingGraphBufferRadius)
|
||||||
let bufferFrame = frame.insetBy(dx: -bufferRadius, dy: -bufferRadius)
|
let bufferFrame = frame.insetBy(dx: -bufferRadius, dy: -bufferRadius)
|
||||||
let bufferedShape = SKShapeNode(rectOfSize: bufferFrame.size)
|
let bufferedShape = SKShapeNode(rectOf: bufferFrame.size)
|
||||||
bufferedShape.fillColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.2)
|
bufferedShape.fillColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.2))
|
||||||
bufferedShape.strokeColor = SKColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.4)
|
bufferedShape.strokeColor = SKColor(red: CGFloat(1.0), green: CGFloat(0.5), blue: CGFloat(0.0), alpha: CGFloat(0.4))
|
||||||
bufferedShape.name = debugBufferShapeName
|
bufferedShape.name = debugBufferShapeName
|
||||||
addChild(bufferedShape)
|
addChild(bufferedShape)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Remove any existing debug shape layer if we are turning off debug drawing for this node.
|
// Remove any existing debug shape layer if we are turning off debug drawing for this node.
|
||||||
guard let debugBufferShape = childNodeWithName(debugBufferShapeName) else { return }
|
guard let debugBufferShape = childNode(withName: debugBufferShapeName) else { return }
|
||||||
removeChildrenInArray([debugBufferShape])
|
removeChildren(in: [debugBufferShape])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get {
|
get {
|
||||||
// Debug drawing is considered "enabled" if we have the debug node as a child.
|
// Debug drawing is considered "enabled" if we have the debug node as a child.
|
||||||
return childNodeWithName(debugBufferShapeName) != nil
|
return childNode(withName: debugBufferShapeName) != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,24 +20,24 @@ extension LevelScene {
|
||||||
app enters the background. Override to check if an `overlay` node is
|
app enters the background. Override to check if an `overlay` node is
|
||||||
being presented to determine if the game should be paused.
|
being presented to determine if the game should be paused.
|
||||||
*/
|
*/
|
||||||
override var paused: Bool {
|
override var isPaused: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
if overlay != nil {
|
if overlay != nil {
|
||||||
worldNode.paused = true
|
worldNode.isPaused = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Platform specific notifications about the app becoming inactive.
|
/// Platform specific notifications about the app becoming inactive.
|
||||||
var pauseNotificationNames: [String] {
|
private var pauseNotificationNames: [NSNotification.Name] {
|
||||||
#if os(OSX)
|
#if os(OSX)
|
||||||
return [
|
return [
|
||||||
NSApplicationWillResignActiveNotification,
|
.NSApplicationWillResignActive,
|
||||||
NSWindowDidMiniaturizeNotification
|
.NSWindowDidMiniaturize
|
||||||
]
|
]
|
||||||
#else
|
#else
|
||||||
return [
|
return [
|
||||||
UIApplicationWillResignActiveNotification
|
NSNotification.Name.UIApplicationWillResignActive
|
||||||
]
|
]
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -50,17 +50,17 @@ extension LevelScene {
|
||||||
*/
|
*/
|
||||||
func registerForPauseNotifications() {
|
func registerForPauseNotifications() {
|
||||||
for notificationName in pauseNotificationNames {
|
for notificationName in pauseNotificationNames {
|
||||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(LevelScene.pauseGame), name: notificationName, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(LevelScene.pauseGame), name: notificationName, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pauseGame() {
|
func pauseGame() {
|
||||||
stateMachine.enterState(LevelScenePauseState.self)
|
stateMachine.enter(LevelScenePauseState.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unregisterForPauseNotifications() {
|
func unregisterForPauseNotifications() {
|
||||||
for notificationName in pauseNotificationNames {
|
for notificationName in pauseNotificationNames {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(self, name: notificationName, object: nil)
|
NotificationCenter.default.removeObserver(self, name: notificationName, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,27 @@ enum WorldLayer: CGFloat {
|
||||||
static let zSpacePerCharacter: CGFloat = 100
|
static let zSpacePerCharacter: CGFloat = 100
|
||||||
|
|
||||||
// Specifying `AboveCharacters` as 1000 gives room for 9 enemies on a level.
|
// Specifying `AboveCharacters` as 1000 gives room for 9 enemies on a level.
|
||||||
case Board = -100, Debug = -75, Shadows = -50, Obstacles = -25, Characters = 0, AboveCharacters = 1000, Top = 1100
|
case board = -100, debug = -75, shadows = -50, obstacles = -25, characters = 0, aboveCharacters = 1000, top = 1100
|
||||||
|
|
||||||
// The expected name for this node in the scene file.
|
// The expected name for this node in the scene file.
|
||||||
var nodeName: String {
|
var nodeName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .Board: return "board"
|
case .board: return "board"
|
||||||
case .Debug: return "debug"
|
case .debug: return "debug"
|
||||||
case .Shadows: return "shadows"
|
case .shadows: return "shadows"
|
||||||
case .Obstacles: return "obstacles"
|
case .obstacles: return "obstacles"
|
||||||
case .Characters: return "characters"
|
case .characters: return "characters"
|
||||||
case .AboveCharacters: return "above_characters"
|
case .aboveCharacters: return "above_characters"
|
||||||
case .Top: return "top"
|
case .top: return "top"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The full path to this node, for use with `childNodeWithName(_:)`.
|
// The full path to this node, for use with `childNode(withName name:)`.
|
||||||
var nodePath: String {
|
var nodePath: String {
|
||||||
return "/world/\(nodeName)"
|
return "/world/\(nodeName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allLayers = [Board, Debug, Shadows, Obstacles, Characters, AboveCharacters, Top]
|
static var allLayers = [board, debug, shadows, obstacles, characters, aboveCharacters, top]
|
||||||
}
|
}
|
||||||
|
|
||||||
class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
@ -45,14 +45,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
var worldLayerNodes = [WorldLayer: SKNode]()
|
var worldLayerNodes = [WorldLayer: SKNode]()
|
||||||
|
|
||||||
var worldNode: SKNode {
|
var worldNode: SKNode {
|
||||||
return childNodeWithName("world")!
|
return childNode(withName: "world")!
|
||||||
}
|
}
|
||||||
|
|
||||||
let playerBot = PlayerBot()
|
let playerBot = PlayerBot()
|
||||||
var entities = Set<GKEntity>()
|
var entities = Set<GKEntity>()
|
||||||
|
|
||||||
var lastUpdateTimeInterval: NSTimeInterval = 0
|
var lastUpdateTimeInterval: TimeInterval = 0
|
||||||
let maximumUpdateDeltaTime: NSTimeInterval = 1.0 / 60.0
|
let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
|
||||||
|
|
||||||
var levelConfiguration: LevelConfiguration!
|
var levelConfiguration: LevelConfiguration!
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
lazy var obstacleSpriteNodes: [SKSpriteNode] = self["world/obstacles/*"] as! [SKSpriteNode]
|
lazy var obstacleSpriteNodes: [SKSpriteNode] = self["world/obstacles/*"] as! [SKSpriteNode]
|
||||||
|
|
||||||
lazy var polygonObstacles: [GKPolygonObstacle] = SKNode.obstaclesFromNodePhysicsBodies(self.obstacleSpriteNodes)
|
lazy var polygonObstacles: [GKPolygonObstacle] = SKNode.obstacles(fromNodePhysicsBodies: self.obstacleSpriteNodes)
|
||||||
|
|
||||||
// MARK: Pathfinding Debug
|
// MARK: Pathfinding Debug
|
||||||
|
|
||||||
|
@ -127,8 +127,8 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
// MARK: Scene Life Cycle
|
// MARK: Scene Life Cycle
|
||||||
|
|
||||||
override func didMoveToView(view: SKView) {
|
override func didMove(to view: SKView) {
|
||||||
super.didMoveToView(view)
|
super.didMove(to: view)
|
||||||
|
|
||||||
// Load the level's configuration from the level data file.
|
// Load the level's configuration from the level data file.
|
||||||
levelConfiguration = LevelConfiguration(fileName: sceneManager.currentSceneMetadata!.fileName)
|
levelConfiguration = LevelConfiguration(fileName: sceneManager.currentSceneMetadata!.fileName)
|
||||||
|
@ -152,24 +152,24 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
physicsWorld.contactDelegate = self
|
physicsWorld.contactDelegate = self
|
||||||
|
|
||||||
// Move to the active state, starting the level timer.
|
// Move to the active state, starting the level timer.
|
||||||
stateMachine.enterState(LevelSceneActiveState.self)
|
stateMachine.enter(LevelSceneActiveState.self)
|
||||||
|
|
||||||
// Add the debug layers to the scene.
|
// Add the debug layers to the scene.
|
||||||
addNode(graphLayer, toWorldLayer: .Debug)
|
addNode(node: graphLayer, toWorldLayer: .debug)
|
||||||
addNode(debugObstacleLayer, toWorldLayer: .Debug)
|
addNode(node: debugObstacleLayer, toWorldLayer: .debug)
|
||||||
|
|
||||||
// Configure the `timerNode` and add it to the camera node.
|
// Configure the `timerNode` and add it to the camera node.
|
||||||
timerNode.zPosition = WorldLayer.AboveCharacters.rawValue
|
timerNode.zPosition = WorldLayer.aboveCharacters.rawValue
|
||||||
timerNode.fontColor = SKColor.whiteColor()
|
timerNode.fontColor = SKColor.white
|
||||||
timerNode.fontName = GameplayConfiguration.Timer.fontName
|
timerNode.fontName = GameplayConfiguration.Timer.fontName
|
||||||
timerNode.horizontalAlignmentMode = .Center
|
timerNode.horizontalAlignmentMode = .center
|
||||||
timerNode.verticalAlignmentMode = .Top
|
timerNode.verticalAlignmentMode = .top
|
||||||
scaleTimerNode()
|
scaleTimerNode()
|
||||||
camera!.addChild(timerNode)
|
camera!.addChild(timerNode)
|
||||||
|
|
||||||
// A convenience function to find node locations given a set of node names.
|
// A convenience function to find node locations given a set of node names.
|
||||||
func nodePointsFromNodeNames(nodeNames: [String]) -> [CGPoint] {
|
func nodePointsFromNodeNames(nodeNames: [String]) -> [CGPoint] {
|
||||||
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)!
|
let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
|
||||||
return nodeNames.map {
|
return nodeNames.map {
|
||||||
charactersNode[$0].first!.position
|
charactersNode[$0].first!.position
|
||||||
}
|
}
|
||||||
|
@ -180,20 +180,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
let taskBot: TaskBot
|
let taskBot: TaskBot
|
||||||
|
|
||||||
// Find the locations of the nodes that define the `TaskBot`'s "good" and "bad" patrol paths.
|
// Find the locations of the nodes that define the `TaskBot`'s "good" and "bad" patrol paths.
|
||||||
let goodPathPoints = nodePointsFromNodeNames(taskBotConfiguration.goodPathNodeNames)
|
let goodPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.goodPathNodeNames)
|
||||||
let badPathPoints = nodePointsFromNodeNames(taskBotConfiguration.badPathNodeNames)
|
let badPathPoints = nodePointsFromNodeNames(nodeNames: taskBotConfiguration.badPathNodeNames)
|
||||||
|
|
||||||
// Create the appropriate type `TaskBot` (ground or flying).
|
// Create the appropriate type `TaskBot` (ground or flying).
|
||||||
switch taskBotConfiguration.locomotion {
|
switch taskBotConfiguration.locomotion {
|
||||||
case .Flying:
|
case .flying:
|
||||||
taskBot = FlyingBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
|
taskBot = FlyingBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
|
||||||
|
|
||||||
case .Ground:
|
case .ground:
|
||||||
taskBot = GroundBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
|
taskBot = GroundBot(isGood: !taskBotConfiguration.startsBad, goodPathPoints: goodPathPoints, badPathPoints: badPathPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the `TaskBot`'s initial orientation so that it is facing the correct way.
|
// Set the `TaskBot`'s initial orientation so that it is facing the correct way.
|
||||||
guard let orientationComponent = taskBot.componentForClass(OrientationComponent.self) else {
|
guard let orientationComponent = taskBot.component(ofType: OrientationComponent.self) else {
|
||||||
fatalError("A task bot must have an orientation component to be able to be added to a level")
|
fatalError("A task bot must have an orientation component to be able to be added to a level")
|
||||||
}
|
}
|
||||||
orientationComponent.compassDirection = taskBotConfiguration.initialOrientation
|
orientationComponent.compassDirection = taskBotConfiguration.initialOrientation
|
||||||
|
@ -204,10 +204,10 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
taskBot.updateAgentPositionToMatchNodePosition()
|
taskBot.updateAgentPositionToMatchNodePosition()
|
||||||
|
|
||||||
// Add the `TaskBot` to the scene and the component systems.
|
// Add the `TaskBot` to the scene and the component systems.
|
||||||
addEntity(taskBot)
|
addEntity(entity: taskBot)
|
||||||
|
|
||||||
// Add the `TaskBot`'s debug drawing node beneath all characters.
|
// Add the `TaskBot`'s debug drawing node beneath all characters.
|
||||||
addNode(taskBot.debugNode, toWorldLayer: .Debug)
|
addNode(node: taskBot.debugNode, toWorldLayer: .debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -223,7 +223,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didChangeSize(oldSize: CGSize) {
|
override func didChangeSize(_ oldSize: CGSize) {
|
||||||
super.didChangeSize(oldSize)
|
super.didChangeSize(oldSize)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -239,7 +239,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
// MARK: SKScene Processing
|
// MARK: SKScene Processing
|
||||||
|
|
||||||
/// Called before each frame is rendered.
|
/// Called before each frame is rendered.
|
||||||
override func update(currentTime: NSTimeInterval) {
|
override func update(_ currentTime: TimeInterval) {
|
||||||
super.update(currentTime)
|
super.update(currentTime)
|
||||||
|
|
||||||
// Don't perform any updates if the scene isn't in a view.
|
// Don't perform any updates if the scene isn't in a view.
|
||||||
|
@ -262,10 +262,10 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
Pausing a subsection of the node tree allows the `camera`
|
Pausing a subsection of the node tree allows the `camera`
|
||||||
and `overlay` nodes to remain interactive.
|
and `overlay` nodes to remain interactive.
|
||||||
*/
|
*/
|
||||||
if worldNode.paused { return }
|
if worldNode.isPaused { return }
|
||||||
|
|
||||||
// Update the level's state machine.
|
// Update the level's state machine.
|
||||||
stateMachine.updateWithDeltaTime(deltaTime)
|
stateMachine.update(deltaTime: deltaTime)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Update each component system.
|
Update each component system.
|
||||||
|
@ -273,13 +273,13 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
and was determined when the `componentSystems` array was instantiated.
|
and was determined when the `componentSystems` array was instantiated.
|
||||||
*/
|
*/
|
||||||
for componentSystem in componentSystems {
|
for componentSystem in componentSystems {
|
||||||
componentSystem.updateWithDeltaTime(deltaTime)
|
componentSystem.update(deltaTime: deltaTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didFinishUpdate() {
|
override func didFinishUpdate() {
|
||||||
// Check if the `playerBot` has been added to this scene.
|
// Check if the `playerBot` has been added to this scene.
|
||||||
if let playerBotNode = playerBot.componentForClass(RenderComponent.self)?.node where playerBotNode.scene == self {
|
if let playerBotNode = playerBot.component(ofType: RenderComponent.self)?.node, playerBotNode.scene == self {
|
||||||
/*
|
/*
|
||||||
Update the `PlayerBot`'s agent position to match its node position.
|
Update the `PlayerBot`'s agent position to match its node position.
|
||||||
This makes sure that the agent is in a valid location in the SpriteKit
|
This makes sure that the agent is in a valid location in the SpriteKit
|
||||||
|
@ -289,9 +289,9 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the entities in the scene by ascending y-position.
|
// Sort the entities in the scene by ascending y-position.
|
||||||
let ySortedEntities = entities.sort {
|
let ySortedEntities = entities.sorted {
|
||||||
let nodeA = $0.0.componentForClass(RenderComponent.self)!.node
|
let nodeA = $0.0.component(ofType: RenderComponent.self)!.node
|
||||||
let nodeB = $0.1.componentForClass(RenderComponent.self)!.node
|
let nodeB = $0.1.component(ofType: RenderComponent.self)!.node
|
||||||
|
|
||||||
return nodeA.position.y > nodeB.position.y
|
return nodeA.position.y > nodeB.position.y
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
// Set the `zPosition` of each entity so that entities with a higher y-position are rendered above those with a lower y-position.
|
// Set the `zPosition` of each entity so that entities with a higher y-position are rendered above those with a lower y-position.
|
||||||
var characterZPosition = WorldLayer.zSpacePerCharacter
|
var characterZPosition = WorldLayer.zSpacePerCharacter
|
||||||
for entity in ySortedEntities {
|
for entity in ySortedEntities {
|
||||||
let node = entity.componentForClass(RenderComponent.self)!.node
|
let node = entity.component(ofType: RenderComponent.self)!.node
|
||||||
node.zPosition = characterZPosition
|
node.zPosition = characterZPosition
|
||||||
|
|
||||||
// Use a large enough z-position increment to leave space for emitter effects.
|
// Use a large enough z-position increment to leave space for emitter effects.
|
||||||
|
@ -309,14 +309,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
// MARK: SKPhysicsContactDelegate
|
// MARK: SKPhysicsContactDelegate
|
||||||
|
|
||||||
func didBeginContact(contact: SKPhysicsContact) {
|
@objc(didBeginContact:) func didBegin(_ contact: SKPhysicsContact) {
|
||||||
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
|
handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
|
||||||
ContactNotifiableType.contactWithEntityDidBegin(otherEntity)
|
ContactNotifiableType.contactWithEntityDidBegin(otherEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func didEndContact(contact: SKPhysicsContact) {
|
@objc(didEndContact:) func didEnd(_ contact: SKPhysicsContact) {
|
||||||
handleContact(contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
|
handleContact(contact: contact) { (ContactNotifiableType: ContactNotifiableType, otherEntity: GKEntity) in
|
||||||
ContactNotifiableType.contactWithEntityDidEnd(otherEntity)
|
ContactNotifiableType.contactWithEntityDidEnd(otherEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,20 +329,20 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
let colliderTypeB = ColliderType(rawValue: contact.bodyB.categoryBitMask)
|
let colliderTypeB = ColliderType(rawValue: contact.bodyB.categoryBitMask)
|
||||||
|
|
||||||
// Determine which `ColliderType` should be notified of the contact.
|
// Determine which `ColliderType` should be notified of the contact.
|
||||||
let aWantsCallback = colliderTypeA.notifyOnContactWithColliderType(colliderTypeB)
|
let aWantsCallback = colliderTypeA.notifyOnContactWith(colliderTypeB)
|
||||||
let bWantsCallback = colliderTypeB.notifyOnContactWithColliderType(colliderTypeA)
|
let bWantsCallback = colliderTypeB.notifyOnContactWith(colliderTypeA)
|
||||||
|
|
||||||
// Make sure that at least one of the entities wants to handle this contact.
|
// Make sure that at least one of the entities wants to handle this contact.
|
||||||
assert(aWantsCallback || bWantsCallback, "Unhandled physics contact - A = \(colliderTypeA), B = \(colliderTypeB)")
|
assert(aWantsCallback || bWantsCallback, "Unhandled physics contact - A = \(colliderTypeA), B = \(colliderTypeB)")
|
||||||
|
|
||||||
let entityA = (contact.bodyA.node as? EntityNode)?.entity
|
let entityA = contact.bodyA.node?.entity
|
||||||
let entityB = (contact.bodyB.node as? EntityNode)?.entity
|
let entityB = contact.bodyB.node?.entity
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If `entityA` is a notifiable type and `colliderTypeA` specifies that it should be notified
|
If `entityA` is a notifiable type and `colliderTypeA` specifies that it should be notified
|
||||||
of contact with `colliderTypeB`, call the callback on `entityA`.
|
of contact with `colliderTypeB`, call the callback on `entityA`.
|
||||||
*/
|
*/
|
||||||
if let notifiableEntity = entityA as? ContactNotifiableType, otherEntity = entityB where aWantsCallback {
|
if let notifiableEntity = entityA as? ContactNotifiableType, let otherEntity = entityB, aWantsCallback {
|
||||||
contactCallback(notifiableEntity, otherEntity)
|
contactCallback(notifiableEntity, otherEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
If `entityB` is a notifiable type and `colliderTypeB` specifies that it should be notified
|
If `entityB` is a notifiable type and `colliderTypeB` specifies that it should be notified
|
||||||
of contact with `colliderTypeA`, call the callback on `entityB`.
|
of contact with `colliderTypeA`, call the callback on `entityB`.
|
||||||
*/
|
*/
|
||||||
if let notifiableEntity = entityB as? ContactNotifiableType, otherEntity = entityA where bWantsCallback {
|
if let notifiableEntity = entityB as? ContactNotifiableType, let otherEntity = entityA, bWantsCallback {
|
||||||
contactCallback(notifiableEntity, otherEntity)
|
contactCallback(notifiableEntity, otherEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,19 +380,19 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
entities.insert(entity)
|
entities.insert(entity)
|
||||||
|
|
||||||
for componentSystem in self.componentSystems {
|
for componentSystem in self.componentSystems {
|
||||||
componentSystem.addComponentWithEntity(entity)
|
componentSystem.addComponent(foundIn: entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entity has a `RenderComponent`, add its node to the scene.
|
// If the entity has a `RenderComponent`, add its node to the scene.
|
||||||
if let renderNode = entity.componentForClass(RenderComponent.self)?.node {
|
if let renderNode = entity.component(ofType: RenderComponent.self)?.node {
|
||||||
addNode(renderNode, toWorldLayer: .Characters)
|
addNode(node: renderNode, toWorldLayer: .characters)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If the entity has a `ShadowComponent`, add its shadow node to the scene.
|
If the entity has a `ShadowComponent`, add its shadow node to the scene.
|
||||||
Constrain the `ShadowComponent`'s node to the `RenderComponent`'s node.
|
Constrain the `ShadowComponent`'s node to the `RenderComponent`'s node.
|
||||||
*/
|
*/
|
||||||
if let shadowNode = entity.componentForClass(ShadowComponent.self)?.node {
|
if let shadowNode = entity.component(ofType: ShadowComponent.self)?.node {
|
||||||
addNode(shadowNode, toWorldLayer: .Shadows)
|
addNode(node: shadowNode, toWorldLayer: .shadows)
|
||||||
|
|
||||||
// Constrain the shadow node's position to the render node.
|
// Constrain the shadow node's position to the render node.
|
||||||
let xRange = SKRange(constantValue: shadowNode.position.x)
|
let xRange = SKRange(constantValue: shadowNode.position.x)
|
||||||
|
@ -408,8 +408,8 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
If the entity has a `ChargeComponent` with a `ChargeBar`, add the `ChargeBar`
|
If the entity has a `ChargeComponent` with a `ChargeBar`, add the `ChargeBar`
|
||||||
to the scene. Constrain the `ChargeBar` to the `RenderComponent`'s node.
|
to the scene. Constrain the `ChargeBar` to the `RenderComponent`'s node.
|
||||||
*/
|
*/
|
||||||
if let chargeBar = entity.componentForClass(ChargeComponent.self)?.chargeBar {
|
if let chargeBar = entity.component(ofType: ChargeComponent.self)?.chargeBar {
|
||||||
addNode(chargeBar, toWorldLayer: .AboveCharacters)
|
addNode(node: chargeBar, toWorldLayer: .aboveCharacters)
|
||||||
|
|
||||||
// Constrain the `ChargeBar`'s node position to the render node.
|
// Constrain the `ChargeBar`'s node position to the render node.
|
||||||
let xRange = SKRange(constantValue: GameplayConfiguration.PlayerBot.chargeBarOffset.x)
|
let xRange = SKRange(constantValue: GameplayConfiguration.PlayerBot.chargeBarOffset.x)
|
||||||
|
@ -423,7 +423,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entity has an `IntelligenceComponent`, enter its initial state.
|
// If the entity has an `IntelligenceComponent`, enter its initial state.
|
||||||
if let intelligenceComponent = entity.componentForClass(IntelligenceComponent.self) {
|
if let intelligenceComponent = entity.component(ofType: IntelligenceComponent.self) {
|
||||||
intelligenceComponent.enterInitialState()
|
intelligenceComponent.enterInitialState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,14 +437,14 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
// MARK: GameInputDelegate
|
// MARK: GameInputDelegate
|
||||||
|
|
||||||
override func gameInputDidUpdateControlInputSources(gameInput: GameInput) {
|
override func gameInputDidUpdateControlInputSources(gameInput: GameInput) {
|
||||||
super.gameInputDidUpdateControlInputSources(gameInput)
|
super.gameInputDidUpdateControlInputSources(gameInput: gameInput)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Update the player's `controlInputSources` to delegate input
|
Update the player's `controlInputSources` to delegate input
|
||||||
to the playerBot's `InputComponent`.
|
to the playerBot's `InputComponent`.
|
||||||
*/
|
*/
|
||||||
for controlInputSource in gameInput.controlInputSources {
|
for controlInputSource in gameInput.controlInputSources {
|
||||||
controlInputSource.delegate = playerBot.componentForClass(InputComponent.self)
|
controlInputSource.delegate = playerBot.component(ofType: InputComponent.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -455,17 +455,17 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
// MARK: ControlInputSourceGameStateDelegate
|
// MARK: ControlInputSourceGameStateDelegate
|
||||||
|
|
||||||
override func controlInputSourceDidTogglePauseState(controlInputSource: ControlInputSourceType) {
|
override func controlInputSourceDidTogglePauseState(_ controlInputSource: ControlInputSourceType) {
|
||||||
if stateMachine.currentState is LevelSceneActiveState {
|
if stateMachine.currentState is LevelSceneActiveState {
|
||||||
stateMachine.enterState(LevelScenePauseState.self)
|
stateMachine.enter(LevelScenePauseState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
stateMachine.enterState(LevelSceneActiveState.self)
|
stateMachine.enter(LevelSceneActiveState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
override func controlInputSourceDidToggleDebugInfo(controlInputSource: ControlInputSourceType) {
|
override func controlInputSourceDidToggleDebugInfo(_ controlInputSource: ControlInputSourceType) {
|
||||||
debugDrawingEnabled = !debugDrawingEnabled
|
debugDrawingEnabled = !debugDrawingEnabled
|
||||||
|
|
||||||
if let view = view {
|
if let view = view {
|
||||||
|
@ -476,15 +476,15 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func controlInputSourceDidTriggerLevelSuccess(controlInputSource: ControlInputSourceType) {
|
override func controlInputSourceDidTriggerLevelSuccess(_ controlInputSource: ControlInputSourceType) {
|
||||||
if stateMachine.currentState is LevelSceneActiveState {
|
if stateMachine.currentState is LevelSceneActiveState {
|
||||||
stateMachine.enterState(LevelSceneSuccessState.self)
|
stateMachine.enter(LevelSceneSuccessState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func controlInputSourceDidTriggerLevelFailure(controlInputSource: ControlInputSourceType) {
|
override func controlInputSourceDidTriggerLevelFailure(_ controlInputSource: ControlInputSourceType) {
|
||||||
if stateMachine.currentState is LevelSceneActiveState {
|
if stateMachine.currentState is LevelSceneActiveState {
|
||||||
stateMachine.enterState(LevelSceneFailState.self)
|
stateMachine.enter(LevelSceneFailState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,12 +494,12 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
override func buttonTriggered(button: ButtonNode) {
|
override func buttonTriggered(button: ButtonNode) {
|
||||||
switch button.buttonIdentifier! {
|
switch button.buttonIdentifier! {
|
||||||
case .Resume:
|
case .resume:
|
||||||
stateMachine.enterState(LevelSceneActiveState.self)
|
stateMachine.enter(LevelSceneActiveState.self)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
|
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
|
||||||
super.buttonTriggered(button)
|
super.buttonTriggered(button: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +513,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
// Constrain the camera to stay a constant distance of 0 points from the player node.
|
// Constrain the camera to stay a constant distance of 0 points from the player node.
|
||||||
let zeroRange = SKRange(constantValue: 0.0)
|
let zeroRange = SKRange(constantValue: 0.0)
|
||||||
let playerNode = playerBot.renderComponent.node
|
let playerNode = playerBot.renderComponent.node
|
||||||
let playerBotLocationConstraint = SKConstraint.distance(zeroRange, toNode: playerNode)
|
let playerBotLocationConstraint = SKConstraint.distance(zeroRange, to: playerNode)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Also constrain the camera to avoid it moving to the very edges of the scene.
|
Also constrain the camera to avoid it moving to the very edges of the scene.
|
||||||
|
@ -527,7 +527,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
Find the root "board" node in the scene (the container node for
|
Find the root "board" node in the scene (the container node for
|
||||||
the level's background tiles).
|
the level's background tiles).
|
||||||
*/
|
*/
|
||||||
let boardNode = childNodeWithName(WorldLayer.Board.nodePath)!
|
let boardNode = childNode(withName: WorldLayer.board.nodePath)!
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Calculate the accumulated frame of this node.
|
Calculate the accumulated frame of this node.
|
||||||
|
@ -583,11 +583,11 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
|
|
||||||
private func beamInPlayerBot() {
|
private func beamInPlayerBot() {
|
||||||
// Find the location of the player's initial position.
|
// Find the location of the player's initial position.
|
||||||
let charactersNode = childNodeWithName(WorldLayer.Characters.nodePath)!
|
let charactersNode = childNode(withName: WorldLayer.characters.nodePath)!
|
||||||
let transporterCoordinate = charactersNode.childNodeWithName("transporter_coordinate")!
|
let transporterCoordinate = charactersNode.childNode(withName: "transporter_coordinate")!
|
||||||
|
|
||||||
// Set the initial orientation.
|
// Set the initial orientation.
|
||||||
guard let orientationComponent = playerBot.componentForClass(OrientationComponent.self) else {
|
guard let orientationComponent = playerBot.component(ofType: OrientationComponent.self) else {
|
||||||
fatalError("A player bot must have an orientation component to be able to be added to a level")
|
fatalError("A player bot must have an orientation component to be able to be added to a level")
|
||||||
}
|
}
|
||||||
orientationComponent.compassDirection = levelConfiguration.initialPlayerBotOrientation
|
orientationComponent.compassDirection = levelConfiguration.initialPlayerBotOrientation
|
||||||
|
@ -601,7 +601,7 @@ class LevelScene: BaseScene, SKPhysicsContactDelegate {
|
||||||
setCameraConstraints()
|
setCameraConstraints()
|
||||||
|
|
||||||
// Add the `PlayerBot` to the scene and component systems.
|
// Add the `PlayerBot` to the scene and component systems.
|
||||||
addEntity(playerBot)
|
addEntity(entity: playerBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,16 @@ class LevelSceneActiveState: GKState {
|
||||||
|
|
||||||
unowned let levelScene: LevelScene
|
unowned let levelScene: LevelScene
|
||||||
|
|
||||||
var timeRemaining: NSTimeInterval = 0.0
|
var timeRemaining: TimeInterval = 0.0
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A formatter for individual date components used to provide an appropriate
|
A formatter for individual date components used to provide an appropriate
|
||||||
display value for the timer.
|
display value for the timer.
|
||||||
*/
|
*/
|
||||||
let timeRemainingFormatter: NSDateComponentsFormatter = {
|
let timeRemainingFormatter: DateComponentsFormatter = {
|
||||||
let formatter = NSDateComponentsFormatter()
|
let formatter = DateComponentsFormatter()
|
||||||
formatter.zeroFormattingBehavior = .Pad
|
formatter.zeroFormattingBehavior = .pad
|
||||||
formatter.allowedUnits = [.Minute, .Second]
|
formatter.allowedUnits = [.minute, .second]
|
||||||
|
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
@ -33,7 +33,7 @@ class LevelSceneActiveState: GKState {
|
||||||
let components = NSDateComponents()
|
let components = NSDateComponents()
|
||||||
components.second = Int(max(0.0, timeRemaining))
|
components.second = Int(max(0.0, timeRemaining))
|
||||||
|
|
||||||
return timeRemainingFormatter.stringFromDateComponents(components)!
|
return timeRemainingFormatter.string(from: components as DateComponents)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
@ -46,14 +46,14 @@ class LevelSceneActiveState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
levelScene.timerNode.text = timeRemainingString
|
levelScene.timerNode.text = timeRemainingString
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateWithDeltaTime(seconds: NSTimeInterval) {
|
override func update(deltaTime seconds: TimeInterval) {
|
||||||
super.updateWithDeltaTime(seconds)
|
super.update(deltaTime: seconds)
|
||||||
|
|
||||||
// Subtract the elapsed time from the remaining time.
|
// Subtract the elapsed time from the remaining time.
|
||||||
timeRemaining -= seconds
|
timeRemaining -= seconds
|
||||||
|
@ -72,15 +72,15 @@ class LevelSceneActiveState: GKState {
|
||||||
|
|
||||||
if allTaskBotsAreGood {
|
if allTaskBotsAreGood {
|
||||||
// If all the TaskBots are good, the player has completed the level.
|
// If all the TaskBots are good, the player has completed the level.
|
||||||
stateMachine?.enterState(LevelSceneSuccessState.self)
|
stateMachine?.enter(LevelSceneSuccessState.self)
|
||||||
}
|
}
|
||||||
else if timeRemaining <= 0.0 {
|
else if timeRemaining <= 0.0 {
|
||||||
// If there is no time remaining, the player has failed to complete the level.
|
// If there is no time remaining, the player has failed to complete the level.
|
||||||
stateMachine?.enterState(LevelSceneFailState.self)
|
stateMachine?.enter(LevelSceneFailState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is LevelScenePauseState.Type, is LevelSceneFailState.Type, is LevelSceneSuccessState.Type:
|
case is LevelScenePauseState.Type, is LevelSceneFailState.Type, is LevelSceneSuccessState.Type:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -18,15 +18,15 @@ class LevelSceneFailState: LevelSceneOverlayState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
if let inputComponent = levelScene.playerBot.componentForClass(InputComponent.self) {
|
if let inputComponent = levelScene.playerBot.component(ofType: InputComponent.self) {
|
||||||
inputComponent.isEnabled = false
|
inputComponent.isEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,50 +27,50 @@ class LevelSceneOverlayState: GKState {
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
overlay = SceneOverlay(overlaySceneFileName: overlaySceneFileName, zPosition: WorldLayer.Top.rawValue)
|
overlay = SceneOverlay(overlaySceneFileName: overlaySceneFileName, zPosition: WorldLayer.top.rawValue)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set the level preview image to the image for this state's level if this state
|
Set the level preview image to the image for this state's level if this state
|
||||||
has a "view recorded content" button, with a child node called "levelPreview".
|
has a "view recorded content" button, with a child node called "levelPreview".
|
||||||
*/
|
*/
|
||||||
if let viewRecordedContentButton = buttonWithIdentifier(.ViewRecordedContent) , levelPreviewNode = viewRecordedContentButton.childNodeWithName("levelPreview") as? SKSpriteNode {
|
if let viewRecordedContentButton = button(withIdentifier: .viewRecordedContent), let levelPreviewNode = viewRecordedContentButton.childNode(withName: "levelPreview") as? SKSpriteNode {
|
||||||
levelPreviewNode.texture = SKTexture(imageNamed: levelScene.levelConfiguration.fileName)
|
levelPreviewNode.texture = SKTexture(imageNamed: levelScene.levelConfiguration.fileName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
// Show the appropriate state for the recording buttons.
|
// Show the appropriate state for the recording buttons.
|
||||||
buttonWithIdentifier(.ScreenRecorderToggle)?.isSelected = levelScene.screenRecordingToggleEnabled
|
button(withIdentifier: .screenRecorderToggle)?.isSelected = levelScene.screenRecordingToggleEnabled
|
||||||
|
|
||||||
if self is LevelSceneSuccessState || self is LevelSceneFailState {
|
if self is LevelSceneSuccessState || self is LevelSceneFailState {
|
||||||
if let viewRecordedContentButton = buttonWithIdentifier(.ViewRecordedContent) {
|
if let viewRecordedContentButton = button(withIdentifier: .viewRecordedContent) {
|
||||||
viewRecordedContentButton.hidden = true
|
viewRecordedContentButton.isHidden = true
|
||||||
|
|
||||||
// Stop screen recording and update view recorded content button when complete.
|
// Stop screen recording and update view recorded content button when complete.
|
||||||
levelScene.stopScreenRecordingWithHandler {
|
levelScene.stopScreenRecording() {
|
||||||
// Only show the view button if the recording is enabled and there's a valid `previewViewController` to present.
|
// Only show the view button if the recording is enabled and there's a valid `previewViewController` to present.
|
||||||
let recordingEnabledAndPreviewAvailable = self.levelScene.screenRecordingToggleEnabled && self.levelScene.previewViewController != nil
|
let recordingEnabledAndPreviewAvailable = self.levelScene.screenRecordingToggleEnabled && self.levelScene.previewViewController != nil
|
||||||
viewRecordedContentButton.hidden = !recordingEnabledAndPreviewAvailable
|
viewRecordedContentButton.isHidden = !recordingEnabledAndPreviewAvailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Hide replay buttons on OSX and tvOS.
|
// Hide replay buttons on OSX and tvOS.
|
||||||
buttonWithIdentifier(.ScreenRecorderToggle)?.hidden = true
|
button(withIdentifier: .screenRecorderToggle)?.isHidden = true
|
||||||
buttonWithIdentifier(.ViewRecordedContent)?.hidden = true
|
button(withIdentifier: .viewRecordedContent)?.isHidden = true
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Provide the levelScene with a reference to the overlay node.
|
// Provide the levelScene with a reference to the overlay node.
|
||||||
levelScene.overlay = overlay
|
levelScene.overlay = overlay
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
levelScene.overlay = nil
|
levelScene.overlay = nil
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class LevelSceneOverlayState: GKState {
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? {
|
func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
|
||||||
return overlay.contentNode.childNodeWithName("//\(identifier.rawValue)") as? ButtonNode
|
return overlay.contentNode.childNode(withName: "//\(identifier.rawValue)") as? ButtonNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,19 +18,19 @@ class LevelScenePauseState: LevelSceneOverlayState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
levelScene.worldNode.paused = true
|
levelScene.worldNode.isPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return stateClass is LevelSceneActiveState.Type
|
return stateClass is LevelSceneActiveState.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
levelScene.worldNode.paused = false
|
levelScene.worldNode.isPaused = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,18 @@ class LevelSceneSuccessState: LevelSceneOverlayState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
if let inputComponent = levelScene.playerBot.componentForClass(InputComponent.self) {
|
if let inputComponent = levelScene.playerBot.component(ofType: InputComponent.self) {
|
||||||
inputComponent.isEnabled = false
|
inputComponent.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin preloading the next scene in preparation for the user to advance.
|
// Begin preloading the next scene in preparation for the user to advance.
|
||||||
levelScene.sceneManager.prepareSceneWithSceneIdentifier(.NextLevel)
|
levelScene.sceneManager.prepareScene(identifier: .nextLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,20 +9,20 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LoadResourcesOperation: Operation, NSProgressReporting {
|
class LoadResourcesOperation: SceneOperation, ProgressReporting {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
/// A class that conforms to the `ResourceLoadableType` protocol.
|
/// A class that conforms to the `ResourceLoadableType` protocol.
|
||||||
let loadableType: ResourceLoadableType.Type
|
let loadableType: ResourceLoadableType.Type
|
||||||
|
|
||||||
let progress: NSProgress
|
let progress: Progress
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(loadableType: ResourceLoadableType.Type) {
|
init(loadableType: ResourceLoadableType.Type) {
|
||||||
self.loadableType = loadableType
|
self.loadableType = loadableType
|
||||||
|
|
||||||
progress = NSProgress(totalUnitCount: 1)
|
progress = Progress(totalUnitCount: 1)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
|
||||||
|
|
||||||
override func start() {
|
override func start() {
|
||||||
// If the operation is cancelled there's nothing to do.
|
// If the operation is cancelled there's nothing to do.
|
||||||
guard !cancelled else { return }
|
guard !isCancelled else { return }
|
||||||
|
|
||||||
if progress.cancelled {
|
if progress.isCancelled {
|
||||||
// Ensure the operation is marked as `cancelled`.
|
// Ensure the operation is marked as `cancelled`.
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
|
@ -45,10 +45,10 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the operation as executing.
|
// Mark the operation as executing.
|
||||||
state = .Executing
|
state = .executing
|
||||||
|
|
||||||
// Begin loading the resources.
|
// Begin loading the resources.
|
||||||
loadableType.loadResourcesWithCompletionHandler { [unowned self] in
|
loadableType.loadResources() { [unowned self] in
|
||||||
// Mark the operation as complete once the resources are loaded.
|
// Mark the operation as complete once the resources are loaded.
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,6 @@ class LoadResourcesOperation: Operation, NSProgressReporting {
|
||||||
|
|
||||||
func finish() {
|
func finish() {
|
||||||
progress.completedUnitCount = 1
|
progress.completedUnitCount = 1
|
||||||
state = .Finished
|
state = .finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LoadSceneOperation: Operation, NSProgressReporting {
|
class LoadSceneOperation: SceneOperation, ProgressReporting {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
/// The metadata for the scene to load.
|
/// The metadata for the scene to load.
|
||||||
|
@ -19,14 +19,14 @@ class LoadSceneOperation: Operation, NSProgressReporting {
|
||||||
var scene: BaseScene?
|
var scene: BaseScene?
|
||||||
|
|
||||||
/// Progress used to report on the status of this operation.
|
/// Progress used to report on the status of this operation.
|
||||||
let progress: NSProgress
|
let progress: Progress
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(sceneMetadata: SceneMetadata) {
|
init(sceneMetadata: SceneMetadata) {
|
||||||
self.sceneMetadata = sceneMetadata
|
self.sceneMetadata = sceneMetadata
|
||||||
|
|
||||||
progress = NSProgress(totalUnitCount: 1)
|
progress = Progress(totalUnitCount: 1)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,16 +34,16 @@ class LoadSceneOperation: Operation, NSProgressReporting {
|
||||||
|
|
||||||
override func start() {
|
override func start() {
|
||||||
// If the operation is cancelled there's nothing to do.
|
// If the operation is cancelled there's nothing to do.
|
||||||
guard !cancelled else { return }
|
guard !isCancelled else { return }
|
||||||
|
|
||||||
if progress.cancelled {
|
if progress.isCancelled {
|
||||||
// Ensure the operation is marked as `cancelled`.
|
// Ensure the operation is marked as `cancelled`.
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the operation as executing.
|
// Mark the operation as executing.
|
||||||
state = .Executing
|
state = .executing
|
||||||
|
|
||||||
// Load the scene into memory using `SKNode(fileNamed:)`.
|
// Load the scene into memory using `SKNode(fileNamed:)`.
|
||||||
let scene = sceneMetadata.sceneType.init(fileNamed: sceneMetadata.fileName)!
|
let scene = sceneMetadata.sceneType.init(fileNamed: sceneMetadata.fileName)!
|
||||||
|
@ -55,6 +55,6 @@ class LoadSceneOperation: Operation, NSProgressReporting {
|
||||||
// Update the progress object's completed unit count.
|
// Update the progress object's completed unit count.
|
||||||
progress.completedUnitCount = 1
|
progress.completedUnitCount = 1
|
||||||
|
|
||||||
state = .Finished
|
state = .finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
|
|
||||||
static let lineNodeTemplate: SKSpriteNode = {
|
static let lineNodeTemplate: SKSpriteNode = {
|
||||||
let templateScene = SKScene(fileNamed: "BeamLine.sks")!
|
let templateScene = SKScene(fileNamed: "BeamLine.sks")!
|
||||||
return templateScene.childNodeWithName("BeamLine") as! SKSpriteNode
|
return templateScene.childNode(withName: "BeamLine") as! SKSpriteNode
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
@ -48,21 +48,22 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
override init() {
|
override init() {
|
||||||
sourceNode = SKSpriteNode()
|
sourceNode = SKSpriteNode()
|
||||||
sourceNode.size = BeamNode.dotTextureSize
|
sourceNode.size = BeamNode.dotTextureSize
|
||||||
sourceNode.hidden = true
|
sourceNode.isHidden = true
|
||||||
|
|
||||||
destinationNode = SKSpriteNode()
|
destinationNode = SKSpriteNode()
|
||||||
destinationNode.size = BeamNode.dotTextureSize
|
destinationNode.size = BeamNode.dotTextureSize
|
||||||
destinationNode.hidden = true
|
destinationNode.isHidden = true
|
||||||
|
|
||||||
let arcPath = CGPathCreateMutable()
|
let arcPath = CGMutablePath.init()
|
||||||
CGPathAddArc(arcPath, nil, 0.0, 0.0, GameplayConfiguration.Beam.arcLength, GameplayConfiguration.Beam.arcAngle * 0.5, GameplayConfiguration.Beam.arcAngle * -0.5, true)
|
let center = CGPoint(x: 0.0, y: 0.0)
|
||||||
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0)
|
arcPath.addArc(center: center, radius: GameplayConfiguration.Beam.arcLength, startAngle: GameplayConfiguration.Beam.arcAngle * 0.5, endAngle: GameplayConfiguration.Beam.arcAngle * -0.5, clockwise: true)
|
||||||
|
arcPath.addLine(to: center)
|
||||||
|
|
||||||
debugNode = SKShapeNode(path: arcPath)
|
debugNode = SKShapeNode(path: arcPath)
|
||||||
debugNode.fillColor = SKColor.blueColor()
|
debugNode.fillColor = SKColor.blue
|
||||||
debugNode.lineWidth = 0.0
|
debugNode.lineWidth = 0.0
|
||||||
debugNode.alpha = 0.5
|
debugNode.alpha = 0.5
|
||||||
debugNode.hidden = true
|
debugNode.isHidden = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -77,9 +78,9 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
|
|
||||||
// MARK: Actions
|
// MARK: Actions
|
||||||
|
|
||||||
func updateWithBeamState(state: GKState, source: PlayerBot, target: TaskBot? = nil) {
|
func update(withBeamState state: GKState, source: PlayerBot, target: TaskBot? = nil) {
|
||||||
// Constrain the position of the target's antenna if it's not already constrained to it.
|
// Constrain the position of the target's antenna if it's not already constrained to it.
|
||||||
if let target = target, targetNode = target.componentForClass(RenderComponent.self)?.node where destinationNode.constraints?.first?.referenceNode != targetNode {
|
if let target = target, let targetNode = target.component(ofType: RenderComponent.self)?.node, destinationNode.constraints?.first?.referenceNode != targetNode {
|
||||||
let xRange = SKRange(constantValue: target.beamTargetOffset.x)
|
let xRange = SKRange(constantValue: target.beamTargetOffset.x)
|
||||||
let yRange = SKRange(constantValue: target.beamTargetOffset.y)
|
let yRange = SKRange(constantValue: target.beamTargetOffset.y)
|
||||||
|
|
||||||
|
@ -92,14 +93,14 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
switch state {
|
switch state {
|
||||||
case is BeamIdleState:
|
case is BeamIdleState:
|
||||||
// Hide the source and destination nodes.
|
// Hide the source and destination nodes.
|
||||||
sourceNode.hidden = true
|
sourceNode.isHidden = true
|
||||||
destinationNode.hidden = true
|
destinationNode.isHidden = true
|
||||||
|
|
||||||
// Remove the `lineNode` from the scene.
|
// Remove the `lineNode` from the scene.
|
||||||
lineNode?.removeFromParent()
|
lineNode?.removeFromParent()
|
||||||
lineNode = nil
|
lineNode = nil
|
||||||
|
|
||||||
debugNode.hidden = true
|
debugNode.isHidden = true
|
||||||
|
|
||||||
case is BeamFiringState:
|
case is BeamFiringState:
|
||||||
/*
|
/*
|
||||||
|
@ -109,38 +110,38 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
*/
|
*/
|
||||||
if lineNode == nil {
|
if lineNode == nil {
|
||||||
lineNode = BeamNode.lineNodeTemplate.copy() as? SKSpriteNode
|
lineNode = BeamNode.lineNodeTemplate.copy() as? SKSpriteNode
|
||||||
lineNode!.hidden = true
|
lineNode!.isHidden = true
|
||||||
addChild(lineNode!)
|
addChild(lineNode!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let target = target {
|
if let target = target {
|
||||||
// Show the `sourceNode` with the its firing animation.
|
// Show the `sourceNode` with the its firing animation.
|
||||||
sourceNode.hidden = false
|
sourceNode.isHidden = false
|
||||||
animateNode(sourceNode, withAction: AnimationActions.source)
|
animate(sourceNode, withAction: AnimationActions.source)
|
||||||
|
|
||||||
// Show the `destinationNode` with its animation.
|
// Show the `destinationNode` with its animation.
|
||||||
destinationNode.hidden = false
|
destinationNode.isHidden = false
|
||||||
animateNode(destinationNode, withAction: AnimationActions.destination)
|
animate(destinationNode, withAction: AnimationActions.destination)
|
||||||
|
|
||||||
// Position the `lineNode` and make sure it's visible.
|
// Position the `lineNode` and make sure it's visible.
|
||||||
positionLineNodeFrom(source, to: target)
|
positionLineNode(from: source, to: target)
|
||||||
lineNode?.hidden = false
|
lineNode?.isHidden = false
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Show the `sourceNode` with the its untargeted animation.
|
// Show the `sourceNode` with the its untargeted animation.
|
||||||
sourceNode.hidden = false
|
sourceNode.isHidden = false
|
||||||
animateNode(sourceNode, withAction: AnimationActions.untargetedSource)
|
animate(sourceNode, withAction: AnimationActions.untargetedSource)
|
||||||
|
|
||||||
// Hide the `destinationNode` and `lineNode`.
|
// Hide the `destinationNode` and `lineNode`.
|
||||||
destinationNode.hidden = true
|
destinationNode.isHidden = true
|
||||||
lineNode?.hidden = true
|
lineNode?.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the debug node if debug drawing is enabled.
|
// Update the debug node if debug drawing is enabled.
|
||||||
debugNode.hidden = !debugDrawingEnabled
|
debugNode.isHidden = !debugDrawingEnabled
|
||||||
|
|
||||||
if debugDrawingEnabled {
|
if debugDrawingEnabled {
|
||||||
guard let sourceOrientation = source.componentForClass(OrientationComponent.self) else {
|
guard let sourceOrientation = source.component(ofType: OrientationComponent.self) else {
|
||||||
fatalError("BeamNodees must be associated with entities that have an orientation node")
|
fatalError("BeamNodees must be associated with entities that have an orientation node")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,15 +152,17 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
This allows for easier aiming the closer the source is to
|
This allows for easier aiming the closer the source is to
|
||||||
the target.
|
the target.
|
||||||
*/
|
*/
|
||||||
let arcPath = CGPathCreateMutable()
|
let arcPath = CGMutablePath.init()
|
||||||
|
|
||||||
// Only draw beam arc if there is a target.
|
// Only draw beam arc if there is a target.
|
||||||
if let target = target {
|
if let target = target {
|
||||||
let distanceRatio = GameplayConfiguration.Beam.arcLength / CGFloat(distance(source.agent.position, target.agent.position))
|
let distanceRatio = GameplayConfiguration.Beam.arcLength / CGFloat(distance(source.agent.position, target.agent.position))
|
||||||
let arcAngle = min(GameplayConfiguration.Beam.arcAngle * distanceRatio, 1 / GameplayConfiguration.Beam.maxArcAngle)
|
let arcAngle = min(GameplayConfiguration.Beam.arcAngle * distanceRatio, 1 / GameplayConfiguration.Beam.maxArcAngle)
|
||||||
|
|
||||||
CGPathAddArc(arcPath, nil, 0.0, 0.0, GameplayConfiguration.Beam.arcLength, arcAngle * 0.5, -arcAngle * 0.5, true)
|
let center = CGPoint(x: 0, y: 0)
|
||||||
CGPathAddLineToPoint(arcPath, nil, 0.0, 0.0)
|
|
||||||
|
arcPath.addArc(center: center, radius: GameplayConfiguration.Beam.arcLength, startAngle: arcAngle * 0.5, endAngle: -arcAngle * 0.5, clockwise: true)
|
||||||
|
arcPath.addLine(to: center)
|
||||||
}
|
}
|
||||||
debugNode.path = arcPath
|
debugNode.path = arcPath
|
||||||
|
|
||||||
|
@ -168,17 +171,17 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
|
|
||||||
case is BeamCoolingState:
|
case is BeamCoolingState:
|
||||||
// Show the `sourceNode` with the "cooling" animation.
|
// Show the `sourceNode` with the "cooling" animation.
|
||||||
sourceNode.hidden = false
|
sourceNode.isHidden = false
|
||||||
animateNode(sourceNode, withAction: AnimationActions.cooling)
|
animate(sourceNode, withAction: AnimationActions.cooling)
|
||||||
|
|
||||||
// Hide the `destinationNode`.
|
// Hide the `destinationNode`.
|
||||||
destinationNode.hidden = true
|
destinationNode.isHidden = true
|
||||||
|
|
||||||
// Remove the `lineNode` from the scene.
|
// Remove the `lineNode` from the scene.
|
||||||
lineNode?.removeFromParent()
|
lineNode?.removeFromParent()
|
||||||
lineNode = nil
|
lineNode = nil
|
||||||
|
|
||||||
debugNode.hidden = true
|
debugNode.isHidden = true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -187,23 +190,23 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func animateNode(node: SKSpriteNode, withAction action: SKAction) {
|
func animate(_ node: SKSpriteNode, withAction action: SKAction) {
|
||||||
if runningNodeAnimations[node] != action {
|
if runningNodeAnimations[node] != action {
|
||||||
node.runAction(action, withKey: BeamNode.animationActionKey)
|
node.run(action, withKey: BeamNode.animationActionKey)
|
||||||
runningNodeAnimations[node] = action
|
runningNodeAnimations[node] = action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func positionLineNodeFrom(source: PlayerBot, to target: TaskBot) {
|
func positionLineNode(from source: PlayerBot, to target: TaskBot) {
|
||||||
guard let lineNode = lineNode else { fatalError("positionLineNodeFrom(_: to:) requires a lineNode to have been created.") }
|
guard let lineNode = lineNode else { fatalError("positionLineNodeFrom(_: to:) requires a lineNode to have been created.") }
|
||||||
|
|
||||||
// Calculate the source and destination positions.
|
// Calculate the source and destination positions.
|
||||||
let sourcePosition: CGPoint = {
|
let sourcePosition: CGPoint = {
|
||||||
guard let node = source.componentForClass(RenderComponent.self)?.node, nodeParent = node.parent else {
|
guard let node = source.component(ofType: RenderComponent.self)?.node, let nodeParent = node.parent else {
|
||||||
fatalError("positionLineNodeFrom(_: to:) requires the source to have a node with a parent.")
|
fatalError("positionLineNodeFrom(_: to:) requires the source to have a node with a parent.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var position = convertPoint(node.position, fromNode: nodeParent)
|
var position = convert(node.position, from: nodeParent)
|
||||||
position.x += source.antennaOffset.x
|
position.x += source.antennaOffset.x
|
||||||
position.y += source.antennaOffset.y
|
position.y += source.antennaOffset.y
|
||||||
|
|
||||||
|
@ -211,11 +214,11 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let destinationPosition: CGPoint = {
|
let destinationPosition: CGPoint = {
|
||||||
guard let node = target.componentForClass(RenderComponent.self)?.node, nodeParent = node.parent else {
|
guard let node = target.component(ofType: RenderComponent.self)?.node, let nodeParent = node.parent else {
|
||||||
fatalError("positionLineNodeFrom(_: to:) requires the destination to have a node with a parent.")
|
fatalError("positionLineNodeFrom(_: to:) requires the destination to have a node with a parent.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var position = convertPoint(node.position, fromNode: nodeParent)
|
var position = convert(node.position, from: nodeParent)
|
||||||
position.x += target.beamTargetOffset.x
|
position.x += target.beamTargetOffset.x
|
||||||
position.y += target.beamTargetOffset.y
|
position.y += target.beamTargetOffset.y
|
||||||
|
|
||||||
|
@ -240,7 +243,7 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
return AnimationActions.source == nil || AnimationActions.untargetedSource == nil || AnimationActions.destination == nil || AnimationActions.cooling == nil
|
return AnimationActions.source == nil || AnimationActions.untargetedSource == nil || AnimationActions.destination == nil || AnimationActions.cooling == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadResourcesWithCompletionHandler(completionHandler: () -> ()) {
|
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ()) {
|
||||||
let beamAtlasNames = [
|
let beamAtlasNames = [
|
||||||
"BeamDot",
|
"BeamDot",
|
||||||
"BeamCharging"
|
"BeamCharging"
|
||||||
|
@ -255,11 +258,11 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
fatalError("One or more texture atlases could not be found: \(error)")
|
fatalError("One or more texture atlases could not be found: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
let beamDotAction = AnimationComponent.actionForAllTexturesInAtlas(beamAtlases[0])
|
let beamDotAction = AnimationComponent.actionForAllTexturesInAtlas(atlas: beamAtlases[0])
|
||||||
AnimationActions.source = beamDotAction
|
AnimationActions.source = beamDotAction
|
||||||
AnimationActions.untargetedSource = beamDotAction
|
AnimationActions.untargetedSource = beamDotAction
|
||||||
AnimationActions.destination = beamDotAction
|
AnimationActions.destination = beamDotAction
|
||||||
AnimationActions.cooling = AnimationComponent.actionForAllTexturesInAtlas(beamAtlases[1])
|
AnimationActions.cooling = AnimationComponent.actionForAllTexturesInAtlas(atlas: beamAtlases[1])
|
||||||
|
|
||||||
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
// Invoke the passed `completionHandler` to indicate that loading has completed.
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
@ -272,4 +275,4 @@ class BeamNode: SKNode, ResourceLoadableType {
|
||||||
AnimationActions.untargetedSource = nil
|
AnimationActions.untargetedSource = nil
|
||||||
AnimationActions.cooling = nil
|
AnimationActions.cooling = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,24 +16,24 @@ protocol ButtonNodeResponderType: class {
|
||||||
|
|
||||||
/// The complete set of button identifiers supported in the app.
|
/// The complete set of button identifiers supported in the app.
|
||||||
enum ButtonIdentifier: String {
|
enum ButtonIdentifier: String {
|
||||||
case Resume
|
case resume = "Resume"
|
||||||
case Home
|
case home = "Home"
|
||||||
case ProceedToNextScene
|
case proceedToNextScene = "ProceedToNextScene"
|
||||||
case Replay
|
case replay = "Replay"
|
||||||
case Retry
|
case retry = "Retry"
|
||||||
case Cancel
|
case cancel = "Cancel"
|
||||||
case ScreenRecorderToggle
|
case screenRecorderToggle = "ScreenRecorderToggle"
|
||||||
case ViewRecordedContent
|
case viewRecordedContent = "ViewRecordedContent"
|
||||||
|
|
||||||
/// Convenience array of all available button identifiers.
|
/// Convenience array of all available button identifiers.
|
||||||
static let allButtonIdentifiers: [ButtonIdentifier] = [
|
static let allButtonIdentifiers: [ButtonIdentifier] = [
|
||||||
.Resume, .Home, .ProceedToNextScene, .Replay, .Retry, .Cancel, .ScreenRecorderToggle, .ViewRecordedContent
|
.resume, .home, .proceedToNextScene, .replay, .retry, .cancel, .screenRecorderToggle, .viewRecordedContent
|
||||||
]
|
]
|
||||||
|
|
||||||
/// The name of the texture to use for a button when the button is selected.
|
/// The name of the texture to use for a button when the button is selected.
|
||||||
var selectedTextureName: String? {
|
var selectedTextureName: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .ScreenRecorderToggle:
|
case .screenRecorderToggle:
|
||||||
return "ButtonAutoRecordOn"
|
return "ButtonAutoRecordOn"
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
@ -71,14 +71,14 @@ class ButtonNode: SKSpriteNode {
|
||||||
|
|
||||||
// Create a scale action to make the button look like it is slightly depressed.
|
// Create a scale action to make the button look like it is slightly depressed.
|
||||||
let newScale: CGFloat = isHighlighted ? 0.99 : 1.01
|
let newScale: CGFloat = isHighlighted ? 0.99 : 1.01
|
||||||
let scaleAction = SKAction.scaleBy(newScale, duration: 0.15)
|
let scaleAction = SKAction.scale(by: newScale, duration: 0.15)
|
||||||
|
|
||||||
// Create a color blend action to darken the button slightly when it is depressed.
|
// Create a color blend action to darken the button slightly when it is depressed.
|
||||||
let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0
|
let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0
|
||||||
let colorBlendAction = SKAction.colorizeWithColorBlendFactor(newColorBlendFactor, duration: 0.15)
|
let colorBlendAction = SKAction.colorize(withColorBlendFactor: newColorBlendFactor, duration: 0.15)
|
||||||
|
|
||||||
// Run the two actions at the same time.
|
// Run the two actions at the same time.
|
||||||
runAction(SKAction.group([scaleAction, colorBlendAction]))
|
run(SKAction.group([scaleAction, colorBlendAction]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,26 +112,26 @@ class ButtonNode: SKSpriteNode {
|
||||||
var isFocused = false {
|
var isFocused = false {
|
||||||
didSet {
|
didSet {
|
||||||
if isFocused {
|
if isFocused {
|
||||||
runAction(SKAction.scaleTo(1.08, duration: 0.20))
|
run(SKAction.scale(to: 1.08, duration: 0.20))
|
||||||
|
|
||||||
focusRing.alpha = 0.0
|
focusRing.alpha = 0.0
|
||||||
focusRing.hidden = false
|
focusRing.isHidden = false
|
||||||
focusRing.runAction(SKAction.fadeInWithDuration(0.2))
|
focusRing.run(SKAction.fadeIn(withDuration: 0.2))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
runAction(SKAction.scaleTo(1.0, duration: 0.20))
|
run(SKAction.scale(to: 1.0, duration: 0.20))
|
||||||
|
|
||||||
focusRing.hidden = true
|
focusRing.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node to indicate when a button has the input focus.
|
/// A node to indicate when a button has the input focus.
|
||||||
lazy var focusRing: SKNode = self.childNodeWithName("focusRing")!
|
lazy var focusRing: SKNode = self.childNode(withName: "focusRing")!
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
/// Overridden to support `copyWithZone(_:)`.
|
/// Overridden to support `copy(with zone:)`.
|
||||||
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
|
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
|
||||||
super.init(texture: texture, color: color, size: size)
|
super.init(texture: texture, color: color, size: size)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ class ButtonNode: SKSpriteNode {
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
// Ensure that the node has a supported button identifier as its name.
|
// Ensure that the node has a supported button identifier as its name.
|
||||||
guard let nodeName = name, buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else {
|
guard let nodeName = name, let buttonIdentifier = ButtonIdentifier(rawValue: nodeName) else {
|
||||||
fatalError("Unsupported button name found.")
|
fatalError("Unsupported button name found.")
|
||||||
}
|
}
|
||||||
self.buttonIdentifier = buttonIdentifier
|
self.buttonIdentifier = buttonIdentifier
|
||||||
|
@ -158,14 +158,14 @@ class ButtonNode: SKSpriteNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The focus ring should be hidden until the button is given the input focus.
|
// The focus ring should be hidden until the button is given the input focus.
|
||||||
focusRing.hidden = true
|
focusRing.isHidden = true
|
||||||
|
|
||||||
// Enable user interaction on the button node to detect tap and click events.
|
// Enable user interaction on the button node to detect tap and click events.
|
||||||
userInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func copyWithZone(zone: NSZone) -> AnyObject {
|
override func copy(with zone: NSZone? = nil) -> Any {
|
||||||
let newButton = super.copyWithZone(zone) as! ButtonNode
|
let newButton = super.copy(with: zone) as! ButtonNode
|
||||||
|
|
||||||
// Copy the `ButtonNode` specific properties.
|
// Copy the `ButtonNode` specific properties.
|
||||||
newButton.buttonIdentifier = buttonIdentifier
|
newButton.buttonIdentifier = buttonIdentifier
|
||||||
|
@ -176,9 +176,9 @@ class ButtonNode: SKSpriteNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buttonTriggered() {
|
func buttonTriggered() {
|
||||||
if userInteractionEnabled {
|
if isUserInteractionEnabled {
|
||||||
// Forward the button press event through to the responder.
|
// Forward the button press event through to the responder.
|
||||||
responder.buttonTriggered(self)
|
responder.buttonTriggered(button: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,43 +189,43 @@ class ButtonNode: SKSpriteNode {
|
||||||
*/
|
*/
|
||||||
func performInvalidFocusChangeAnimationForDirection(direction: ControlInputDirection) {
|
func performInvalidFocusChangeAnimationForDirection(direction: ControlInputDirection) {
|
||||||
let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey"
|
let animationKey = "ButtonNode.InvalidFocusChangeAnimationKey"
|
||||||
guard actionForKey(animationKey) == nil else { return }
|
guard action(forKey: animationKey) == nil else { return }
|
||||||
|
|
||||||
// Find the reference action from `ButtonFocusActions.sks`.
|
// Find the reference action from `ButtonFocusActions.sks`.
|
||||||
let action: SKAction
|
let theAction: SKAction
|
||||||
switch direction {
|
switch direction {
|
||||||
case .Up: action = SKAction(named: "InvalidFocusChange_Up")!
|
case .up: theAction = SKAction(named: "InvalidFocusChange_Up")!
|
||||||
case .Down: action = SKAction(named: "InvalidFocusChange_Down")!
|
case .down: theAction = SKAction(named: "InvalidFocusChange_Down")!
|
||||||
case .Left: action = SKAction(named: "InvalidFocusChange_Left")!
|
case .left: theAction = SKAction(named: "InvalidFocusChange_Left")!
|
||||||
case .Right: action = SKAction(named: "InvalidFocusChange_Right")!
|
case .right: theAction = SKAction(named: "InvalidFocusChange_Right")!
|
||||||
}
|
}
|
||||||
|
|
||||||
runAction(action, withKey: animationKey)
|
run(theAction, withKey: animationKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Responder
|
// MARK: Responder
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
/// UIResponder touch handling.
|
/// UIResponder touch handling.
|
||||||
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesBegan(touches, withEvent: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
isHighlighted = true
|
isHighlighted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesEnded(touches, withEvent: event)
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
isHighlighted = false
|
isHighlighted = false
|
||||||
|
|
||||||
// Touch up inside behavior.
|
// Touch up inside behavior.
|
||||||
if containsTouches(touches) {
|
if containsTouches(touches: touches) {
|
||||||
buttonTriggered()
|
buttonTriggered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
|
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
||||||
super.touchesCancelled(touches, withEvent: event)
|
super.touchesCancelled(touches!, with: event)
|
||||||
|
|
||||||
isHighlighted = false
|
isHighlighted = false
|
||||||
}
|
}
|
||||||
|
@ -235,22 +235,22 @@ class ButtonNode: SKSpriteNode {
|
||||||
guard let scene = scene else { fatalError("Button must be used within a scene.") }
|
guard let scene = scene else { fatalError("Button must be used within a scene.") }
|
||||||
|
|
||||||
return touches.contains { touch in
|
return touches.contains { touch in
|
||||||
let touchPoint = touch.locationInNode(scene)
|
let touchPoint = touch.location(in: scene)
|
||||||
let touchedNode = scene.nodeAtPoint(touchPoint)
|
let touchedNode = scene.atPoint(touchPoint)
|
||||||
return touchedNode === self || touchedNode.inParentHierarchy(self)
|
return touchedNode === self || touchedNode.inParentHierarchy(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#elseif os(OSX)
|
#elseif os(OSX)
|
||||||
/// NSResponder mouse handling.
|
/// NSResponder mouse handling.
|
||||||
override func mouseDown(event: NSEvent) {
|
override func mouseDown(with event: NSEvent) {
|
||||||
super.mouseDown(event)
|
super.mouseDown(with: event)
|
||||||
|
|
||||||
isHighlighted = true
|
isHighlighted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
super.mouseUp(event)
|
super.mouseUp(with: event)
|
||||||
|
|
||||||
isHighlighted = false
|
isHighlighted = false
|
||||||
|
|
||||||
|
@ -261,11 +261,11 @@ class ButtonNode: SKSpriteNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if the event location is within the `ButtonNode`.
|
/// Determine if the event location is within the `ButtonNode`.
|
||||||
private func containsLocationForEvent(event: NSEvent) -> Bool {
|
private func containsLocationForEvent(_ event: NSEvent) -> Bool {
|
||||||
guard let scene = scene else { fatalError("Button must be used within a scene.") }
|
guard let scene = scene else { fatalError("Button must be used within a scene.") }
|
||||||
|
|
||||||
let location = event.locationInNode(scene)
|
let location = event.location(in: scene)
|
||||||
let clickedNode = scene.nodeAtPoint(location)
|
let clickedNode = scene.atPoint(location)
|
||||||
return clickedNode === self || clickedNode.inParentHierarchy(self)
|
return clickedNode === self || clickedNode.inParentHierarchy(self)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -19,13 +19,13 @@ class ChargeBar: SKSpriteNode {
|
||||||
static let chargeLevelNodeSize = CGSize(width: 70.0, height: 6.0)
|
static let chargeLevelNodeSize = CGSize(width: 70.0, height: 6.0)
|
||||||
|
|
||||||
/// The duration used for actions to update the level indicator.
|
/// The duration used for actions to update the level indicator.
|
||||||
static let levelUpdateDuration: NSTimeInterval = 0.1
|
static let levelUpdateDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
/// The background color.
|
/// The background color.
|
||||||
static let backgroundColor = SKColor.blackColor()
|
static let backgroundColor = SKColor.black
|
||||||
|
|
||||||
/// The charge level node color.
|
/// The charge level node color.
|
||||||
static let chargeLevelColor = SKColor.greenColor()
|
static let chargeLevelColor = SKColor.green
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
@ -33,10 +33,10 @@ class ChargeBar: SKSpriteNode {
|
||||||
var level: Double = 1.0 {
|
var level: Double = 1.0 {
|
||||||
didSet {
|
didSet {
|
||||||
// Scale the level bar node based on the current health level.
|
// Scale the level bar node based on the current health level.
|
||||||
let action = SKAction.scaleXTo(CGFloat(level), duration: Configuration.levelUpdateDuration)
|
let action = SKAction.scaleX(to: CGFloat(level), duration: Configuration.levelUpdateDuration)
|
||||||
action.timingMode = .EaseInEaseOut
|
action.timingMode = .easeInEaseOut
|
||||||
|
|
||||||
chargeLevelNode.runAction(action)
|
chargeLevelNode.run(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
let touchPadTexture = SKTexture(imageNamed: "ControlPad")
|
||||||
|
|
||||||
// `touchPad` is the inner touch pad that follows the user's thumb.
|
// `touchPad` is the inner touch pad that follows the user's thumb.
|
||||||
touchPad = SKSpriteNode(texture: touchPadTexture, color: UIColor.clearColor(), size: touchPadSize)
|
touchPad = SKSpriteNode(texture: touchPadTexture, color: UIColor.clear, size: touchPadSize)
|
||||||
|
|
||||||
super.init(texture: touchPadTexture, color: UIColor.clearColor(), size: size)
|
super.init(texture: touchPadTexture, color: UIColor.clear, size: size)
|
||||||
|
|
||||||
alpha = normalAlpha
|
alpha = normalAlpha
|
||||||
|
|
||||||
|
@ -69,25 +69,25 @@ class ThumbStickNode: SKSpriteNode {
|
||||||
|
|
||||||
// MARK: UIResponder
|
// MARK: UIResponder
|
||||||
|
|
||||||
override func canBecomeFirstResponder() -> Bool {
|
override var canBecomeFirstResponder: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesBegan(touches, withEvent: event)
|
super.touchesBegan(touches, with: event)
|
||||||
// Highlight that the control is being used by adjusting the alpha.
|
// Highlight that the control is being used by adjusting the alpha.
|
||||||
alpha = selectedAlpha
|
alpha = selectedAlpha
|
||||||
|
|
||||||
// Inform the delegate that the control is being pressed.
|
// Inform the delegate that the control is being pressed.
|
||||||
delegate?.thumbStickNode(self, isPressed: true)
|
delegate?.thumbStickNode(thumbStickNode: self, isPressed: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesMoved(touches, withEvent: event)
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
// For each touch, calculate the movement of the touchPad.
|
// For each touch, calculate the movement of the touchPad.
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
let touchLocation = touch.locationInNode(self)
|
let touchLocation = touch.location(in: self)
|
||||||
|
|
||||||
var dx = touchLocation.x - center.x
|
var dx = touchLocation.x - center.x
|
||||||
var dy = touchLocation.y - center.y
|
var dy = touchLocation.y - center.y
|
||||||
|
@ -111,12 +111,12 @@ class ThumbStickNode: SKSpriteNode {
|
||||||
// Normalize the displacements between [-1.0, 1.0].
|
// Normalize the displacements between [-1.0, 1.0].
|
||||||
let normalizedDx = Float(dx / trackingDistance)
|
let normalizedDx = Float(dx / trackingDistance)
|
||||||
let normalizedDy = Float(dy / trackingDistance)
|
let normalizedDy = Float(dy / trackingDistance)
|
||||||
delegate?.thumbStickNode(self, didUpdateXValue: normalizedDx, yValue: normalizedDy)
|
delegate?.thumbStickNode(thumbStickNode: self, didUpdateXValue: normalizedDx, yValue: normalizedDy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesEnded(touches, withEvent: event)
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
// If the touches set is empty, return immediately.
|
// If the touches set is empty, return immediately.
|
||||||
guard !touches.isEmpty else { return }
|
guard !touches.isEmpty else { return }
|
||||||
|
@ -124,8 +124,8 @@ class ThumbStickNode: SKSpriteNode {
|
||||||
resetTouchPad()
|
resetTouchPad()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
|
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
||||||
super.touchesCancelled(touches, withEvent: event)
|
super.touchesCancelled(touches!, with: event)
|
||||||
resetTouchPad()
|
resetTouchPad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,10 +133,10 @@ class ThumbStickNode: SKSpriteNode {
|
||||||
func resetTouchPad() {
|
func resetTouchPad() {
|
||||||
alpha = normalAlpha
|
alpha = normalAlpha
|
||||||
|
|
||||||
let restoreToCenter = SKAction.moveTo(CGPoint.zero, duration: 0.2)
|
let restoreToCenter = SKAction.move(to: CGPoint.zero, duration: 0.2)
|
||||||
touchPad.runAction(restoreToCenter)
|
touchPad.run(restoreToCenter)
|
||||||
|
|
||||||
delegate?.thumbStickNode(self, isPressed: false)
|
delegate?.thumbStickNode(thumbStickNode: self, isPressed: false)
|
||||||
delegate?.thumbStickNode(self, didUpdateXValue: 0, yValue: 0)
|
delegate?.thumbStickNode(thumbStickNode: self, didUpdateXValue: 0, yValue: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import SpriteKit
|
import SpriteKit
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
|
|
||||||
struct ColliderType: OptionSetType, Hashable, CustomDebugStringConvertible {
|
struct ColliderType: OptionSet, Hashable, CustomDebugStringConvertible {
|
||||||
// MARK: Static properties
|
// MARK: Static properties
|
||||||
|
|
||||||
/// A dictionary to specify which `ColliderType`s should be notified of contacts with other `ColliderType`s.
|
/// A dictionary to specify which `ColliderType`s should be notified of contacts with other `ColliderType`s.
|
||||||
|
@ -87,7 +87,7 @@ struct ColliderType: OptionSetType, Hashable, CustomDebugStringConvertible {
|
||||||
Returns `true` if the `ContactNotifiableType` associated with this `ColliderType` should be
|
Returns `true` if the `ContactNotifiableType` associated with this `ColliderType` should be
|
||||||
notified of contact with the passed `ColliderType`.
|
notified of contact with the passed `ColliderType`.
|
||||||
*/
|
*/
|
||||||
func notifyOnContactWithColliderType(colliderType: ColliderType) -> Bool {
|
func notifyOnContactWith(_ colliderType: ColliderType) -> Bool {
|
||||||
if let requestedContacts = ColliderType.requestedContactNotifications[self] {
|
if let requestedContacts = ColliderType.requestedContactNotifications[self] {
|
||||||
return requestedContacts.contains(colliderType)
|
return requestedContacts.contains(colliderType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,15 @@ class ProgressScene: BaseScene {
|
||||||
|
|
||||||
/// Returns the background node from the scene.
|
/// Returns the background node from the scene.
|
||||||
override var backgroundNode: SKSpriteNode? {
|
override var backgroundNode: SKSpriteNode? {
|
||||||
return childNodeWithName("backgroundNode") as? SKSpriteNode
|
return childNode(withName: "backgroundNode") as? SKSpriteNode
|
||||||
}
|
}
|
||||||
|
|
||||||
var labelNode: SKLabelNode {
|
var labelNode: SKLabelNode {
|
||||||
return backgroundNode!.childNodeWithName("label") as! SKLabelNode
|
return backgroundNode!.childNode(withName: "label") as! SKLabelNode
|
||||||
}
|
}
|
||||||
|
|
||||||
var progressBarNode: SKSpriteNode {
|
var progressBarNode: SKSpriteNode {
|
||||||
return backgroundNode!.childNodeWithName("progressBar") as! SKSpriteNode
|
return backgroundNode!.childNode(withName: "progressBar") as! SKSpriteNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -35,7 +35,7 @@ class ProgressScene: BaseScene {
|
||||||
the scene from a file, but `init(fileNamed:)` is not a designated init),
|
the scene from a file, but `init(fileNamed:)` is not a designated init),
|
||||||
we need to make most of the properties `var` and implicitly unwrapped
|
we need to make most of the properties `var` and implicitly unwrapped
|
||||||
optional so we can set the properties after creating the scene with
|
optional so we can set the properties after creating the scene with
|
||||||
`progressSceneWithSceneLoader(sceneLoader:)`.
|
`progressScene(withSceneLoader sceneLoader:)`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// The scene loader currently handling the requested scene.
|
/// The scene loader currently handling the requested scene.
|
||||||
|
@ -45,13 +45,13 @@ class ProgressScene: BaseScene {
|
||||||
var progressBarInitialWidth: CGFloat!
|
var progressBarInitialWidth: CGFloat!
|
||||||
|
|
||||||
/// Add child progress objects to track downloading and loading states.
|
/// Add child progress objects to track downloading and loading states.
|
||||||
var progress: NSProgress? {
|
var progress: Progress? {
|
||||||
didSet {
|
didSet {
|
||||||
// Unregister as an observer on the old value for the "fractionCompleted" property.
|
// Unregister as an observer on the old value for the "fractionCompleted" property.
|
||||||
oldValue?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressSceneKVOContext)
|
oldValue?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressSceneKVOContext)
|
||||||
|
|
||||||
// Register as an observer on the initial and for changes to the "fractionCompleted" property.
|
// Register as an observer on the initial and for changes to the "fractionCompleted" property.
|
||||||
progress?.addObserver(self, forKeyPath: "fractionCompleted", options: [.New, .Initial], context: &progressSceneKVOContext)
|
progress?.addObserver(self, forKeyPath: "fractionCompleted", options: [.new, .initial], context: &progressSceneKVOContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,20 +65,20 @@ class ProgressScene: BaseScene {
|
||||||
progress of on demand resources and the loading progress of bringing
|
progress of on demand resources and the loading progress of bringing
|
||||||
assets into memory.
|
assets into memory.
|
||||||
*/
|
*/
|
||||||
static func progressSceneWithSceneLoader(sceneLoader: SceneLoader) -> ProgressScene {
|
static func progressScene(withSceneLoader loader: SceneLoader) -> ProgressScene {
|
||||||
// Load the progress scene from its sks file.
|
// Load the progress scene from its sks file.
|
||||||
let progressScene = ProgressScene(fileNamed: "ProgressScene")!
|
let progressScene = ProgressScene(fileNamed: "ProgressScene")!
|
||||||
|
|
||||||
progressScene.createCamera()
|
progressScene.createCamera()
|
||||||
progressScene.setupWithSceneLoader(sceneLoader)
|
progressScene.setup(withSceneLoader: loader)
|
||||||
|
|
||||||
// Return the setup progress scene.
|
// Return the setup progress scene.
|
||||||
return progressScene
|
return progressScene
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupWithSceneLoader(sceneLoader: SceneLoader) {
|
func setup(withSceneLoader loader: SceneLoader) {
|
||||||
// Set the sceneLoader. This may be in the downloading or preparing state.
|
// Set the sceneLoader. This may be in the downloading or preparing state.
|
||||||
self.sceneLoader = sceneLoader
|
self.sceneLoader = loader
|
||||||
|
|
||||||
// Grab the `sceneLoader`'s progress if it is already loading.
|
// Grab the `sceneLoader`'s progress if it is already loading.
|
||||||
if let progress = sceneLoader.progress {
|
if let progress = sceneLoader.progress {
|
||||||
|
@ -90,18 +90,18 @@ class ProgressScene: BaseScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register for notifications posted when the `SceneDownloader` fails.
|
// Register for notifications posted when the `SceneDownloader` fails.
|
||||||
let defaultCenter = NSNotificationCenter.defaultCenter()
|
let defaultCenter = NotificationCenter.default
|
||||||
downloadFailedObserver = defaultCenter.addObserverForName(SceneLoaderDidFailNotification, object: sceneLoader, queue: NSOperationQueue.mainQueue()) { [unowned self] notification in
|
downloadFailedObserver = defaultCenter.addObserver(forName: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader, queue: OperationQueue.main) { [unowned self] notification in
|
||||||
guard let sceneLoader = notification.object as? SceneLoader, error = sceneLoader.error else { fatalError("The scene loader has no error to show.") }
|
guard let loader = notification.object as? SceneLoader, let error = loader.error else { fatalError("The scene loader has no error to show.") }
|
||||||
|
|
||||||
self.showErrorStateForError(error)
|
self.showError(error as NSError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
// Unregister as an observer of 'SceneLoaderDownloadFailedNotification' notifications.
|
// Unregister as an observer of 'SceneLoaderDownloadFailedNotification' notifications.
|
||||||
if let downloadFailedObserver = downloadFailedObserver {
|
if let downloadFailedObserver = downloadFailedObserver {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(downloadFailedObserver, name: SceneLoaderDidFailNotification, object: sceneLoader)
|
NotificationCenter.default.removeObserver(downloadFailedObserver, name: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the progress property to nil which will remove this object as an observer.
|
// Set the progress property to nil which will remove this object as an observer.
|
||||||
|
@ -110,17 +110,17 @@ class ProgressScene: BaseScene {
|
||||||
|
|
||||||
// MARK: Scene Life Cycle
|
// MARK: Scene Life Cycle
|
||||||
|
|
||||||
override func didMoveToView(view: SKView) {
|
override func didMove(to view: SKView) {
|
||||||
super.didMoveToView(view)
|
super.didMove(to: view)
|
||||||
|
|
||||||
centerCameraOnPoint(backgroundNode!.position)
|
centerCameraOnPoint(point: backgroundNode!.position)
|
||||||
|
|
||||||
// Remember the progress bar's initial width. It will change to indicate progress.
|
// Remember the progress bar's initial width. It will change to indicate progress.
|
||||||
progressBarInitialWidth = progressBarNode.frame.width
|
progressBarInitialWidth = progressBarNode.frame.width
|
||||||
|
|
||||||
if let error = sceneLoader.error {
|
if let error = sceneLoader.error {
|
||||||
// Show the scene loader's error.
|
// Show the scene loader's error.
|
||||||
showErrorStateForError(error)
|
showError(error as NSError)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showDefaultState()
|
showDefaultState()
|
||||||
|
@ -128,12 +128,18 @@ class ProgressScene: BaseScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Key Value Observing (KVO) for NSProgress
|
// MARK: Key Value Observing (KVO) for NSProgress
|
||||||
|
|
||||||
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String: AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
@nonobjc override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
// Check if this is the KVO notification we need.
|
// Check if this is the KVO notification we need.
|
||||||
if context == &progressSceneKVOContext && keyPath == "fractionCompleted" && object === progress {
|
|
||||||
|
guard context == &progressSceneKVOContext else {
|
||||||
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let changedProgress = object as? Progress, changedProgress == progress, keyPath == "fractionCompleted" {
|
||||||
// Update the progress UI on the main queue.
|
// Update the progress UI on the main queue.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
guard let progress = self.progress else { return }
|
guard let progress = self.progress else { return }
|
||||||
|
|
||||||
// Update the progress bar to match the amount of progress completed.
|
// Update the progress bar to match the amount of progress completed.
|
||||||
|
@ -143,28 +149,25 @@ class ProgressScene: BaseScene {
|
||||||
self.labelNode.text = progress.localizedDescription
|
self.labelNode.text = progress.localizedDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ButtonNodeResponderType
|
// MARK: ButtonNodeResponderType
|
||||||
|
|
||||||
override func buttonTriggered(button: ButtonNode) {
|
override func buttonTriggered(button: ButtonNode) {
|
||||||
switch button.buttonIdentifier! {
|
switch button.buttonIdentifier! {
|
||||||
case .Retry:
|
case .retry:
|
||||||
// Set up the progress for a new preparation attempt.
|
// Set up the progress for a new preparation attempt.
|
||||||
progress = sceneLoader.asynchronouslyLoadSceneForPresentation()
|
progress = sceneLoader.asynchronouslyLoadSceneForPresentation()
|
||||||
sceneLoader.requestedForPresentation = true
|
sceneLoader.requestedForPresentation = true
|
||||||
showDefaultState()
|
showDefaultState()
|
||||||
|
|
||||||
case .Cancel:
|
case .cancel:
|
||||||
/*
|
/*
|
||||||
Canceling the parent progress propagates the cancellation to the child
|
Canceling the parent progress propagates the cancellation to the child
|
||||||
progress objects.
|
progress objects.
|
||||||
|
|
||||||
In `SceneLoaderDownloadingResourcesState` this will cause the completionHandler to
|
In `SceneLoaderDownloadingResourcesState` this will cause the completionHandler to
|
||||||
be invoked on `beginAccessingResourcesWithCompletionHandler(_:)`
|
be invoked on `beginAccessingResources(withCompletionHandler completionHandler:)`
|
||||||
with an NSUserCancelledError. See the NSBundleResourceRequest documentation
|
with an NSUserCancelledError. See the NSBundleResourceRequest documentation
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
@ -174,39 +177,39 @@ class ProgressScene: BaseScene {
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
|
// Allow `BaseScene` to handle the event in `BaseScene+Buttons`.
|
||||||
super.buttonTriggered(button)
|
super.buttonTriggered(button: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
func buttonWithIdentifier(identifier: ButtonIdentifier) -> ButtonNode? {
|
func button(withIdentifier identifier: ButtonIdentifier) -> ButtonNode? {
|
||||||
return backgroundNode?.childNodeWithName(identifier.rawValue) as? ButtonNode
|
return backgroundNode?.childNode(withName: identifier.rawValue) as? ButtonNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func showDefaultState() {
|
func showDefaultState() {
|
||||||
progressBarNode.hidden = false
|
progressBarNode.isHidden = false
|
||||||
|
|
||||||
// Only display the "Cancel" button.
|
// Only display the "Cancel" button.
|
||||||
buttonWithIdentifier(.Home)?.hidden = true
|
button(withIdentifier: .home)?.isHidden = true
|
||||||
buttonWithIdentifier(.Retry)?.hidden = true
|
button(withIdentifier: .retry)?.isHidden = true
|
||||||
buttonWithIdentifier(.Cancel)?.hidden = false
|
button(withIdentifier: .cancel)?.isHidden = false
|
||||||
|
|
||||||
// Reset the button focus.
|
// Reset the button focus.
|
||||||
resetFocus()
|
resetFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func showErrorStateForError(error: NSError) {
|
func showError(_ error: NSError) {
|
||||||
// A new progress object will have to be created for any subsequent loading attempts.
|
// A new progress object will have to be created for any subsequent loading attempts.
|
||||||
progress = nil
|
progress = nil
|
||||||
|
|
||||||
// Display "Quit" and "Retry" buttons.
|
// Display "Quit" and "Retry" buttons.
|
||||||
buttonWithIdentifier(.Home)?.hidden = false
|
button(withIdentifier: .home)?.isHidden = false
|
||||||
buttonWithIdentifier(.Retry)?.hidden = false
|
button(withIdentifier: .retry)?.isHidden = false
|
||||||
buttonWithIdentifier(.Cancel)?.hidden = true
|
button(withIdentifier: .cancel)?.isHidden = true
|
||||||
|
|
||||||
// Hide normal state.
|
// Hide normal state.
|
||||||
progressBarNode.hidden = true
|
progressBarNode.isHidden = true
|
||||||
progressBarNode.size.width = 0.0
|
progressBarNode.size.width = 0.0
|
||||||
|
|
||||||
// Reset the button focus.
|
// Reset the button focus.
|
||||||
|
@ -217,11 +220,11 @@ class ProgressScene: BaseScene {
|
||||||
labelNode.text = NSLocalizedString("Cancelled", comment: "Displayed when the user cancels loading.")
|
labelNode.text = NSLocalizedString("Cancelled", comment: "Displayed when the user cancels loading.")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
showErrorAlert(error)
|
showAlert(for: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showErrorAlert(error: NSError) {
|
func showAlert(for error: NSError) {
|
||||||
labelNode.text = NSLocalizedString("Failed", comment: "Displayed when the scene loader fails to load a scene.")
|
labelNode.text = NSLocalizedString("Failed", comment: "Displayed when the scene loader fails to load a scene.")
|
||||||
|
|
||||||
// Display the error description in a native alert.
|
// Display the error description in a native alert.
|
||||||
|
@ -229,15 +232,15 @@ class ProgressScene: BaseScene {
|
||||||
guard let window = view?.window else { fatalError("Attempting to present an error when the scene is not in a window.") }
|
guard let window = view?.window else { fatalError("Attempting to present an error when the scene is not in a window.") }
|
||||||
|
|
||||||
let alert = NSAlert(error: error)
|
let alert = NSAlert(error: error)
|
||||||
alert.beginSheetModalForWindow(window, completionHandler: nil)
|
alert.beginSheetModal(for: window, completionHandler: nil)
|
||||||
#else
|
#else
|
||||||
guard let rootViewController = view?.window?.rootViewController else { fatalError("Attempting to present an error when the scene is not in a view controller.") }
|
guard let rootViewController = view?.window?.rootViewController else { fatalError("Attempting to present an error when the scene is not in a view controller.") }
|
||||||
|
|
||||||
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .Alert)
|
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .alert)
|
||||||
let alertAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
|
let alertAction = UIAlertAction(title: "OK", style: .`default`, handler: nil)
|
||||||
alert.addAction(alertAction)
|
alert.addAction(alertAction)
|
||||||
|
|
||||||
rootViewController.presentViewController(alert, animated: true, completion: nil)
|
rootViewController.present(alert, animated: true, completion: nil)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import GameplayKit
|
||||||
|
|
||||||
protocol ContactNotifiableType {
|
protocol ContactNotifiableType {
|
||||||
|
|
||||||
func contactWithEntityDidBegin(entity: GKEntity)
|
func contactWithEntityDidBegin(_ entity: GKEntity)
|
||||||
|
|
||||||
func contactWithEntityDidEnd(entity: GKEntity)
|
func contactWithEntityDidEnd(_ entity: GKEntity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ protocol ResourceLoadableType: class {
|
||||||
static var resourcesNeedLoading: Bool { get }
|
static var resourcesNeedLoading: Bool { get }
|
||||||
|
|
||||||
/// Loads static resources into memory.
|
/// Loads static resources into memory.
|
||||||
static func loadResourcesWithCompletionHandler(completionHandler: () -> ())
|
static func loadResources(withCompletionHandler completionHandler: @escaping () -> ())
|
||||||
|
|
||||||
/// Releases any static resources that can be loaded again later.
|
/// Releases any static resources that can be loaded again later.
|
||||||
static func purgeResources()
|
static func purgeResources()
|
||||||
|
|
|
@ -30,7 +30,7 @@ class FuzzyTaskBotRule: GKRule {
|
||||||
|
|
||||||
// MARK: GPRule Overrides
|
// MARK: GPRule Overrides
|
||||||
|
|
||||||
override func evaluatePredicateWithSystem(system: GKRuleSystem) -> Bool {
|
override func evaluatePredicate(in system: GKRuleSystem) -> Bool {
|
||||||
snapshot = system.state["snapshot"] as! EntitySnapshot
|
snapshot = system.state["snapshot"] as! EntitySnapshot
|
||||||
|
|
||||||
if grade() >= 0.0 {
|
if grade() >= 0.0 {
|
||||||
|
@ -40,7 +40,7 @@ class FuzzyTaskBotRule: GKRule {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func performActionWithSystem(system: GKRuleSystem) {
|
override func performAction(in system: GKRuleSystem) {
|
||||||
system.assertFact(fact.rawValue, grade: grade())
|
system.assertFact(fact.rawValue as NSObject, grade: grade())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class LevelStateSnapshot {
|
||||||
|
|
||||||
/// Returns the `GKAgent2D` for a `PlayerBot` or `TaskBot`.
|
/// Returns the `GKAgent2D` for a `PlayerBot` or `TaskBot`.
|
||||||
func agentForEntity(entity: GKEntity) -> GKAgent2D {
|
func agentForEntity(entity: GKEntity) -> GKAgent2D {
|
||||||
if let agent = entity.componentForClass(TaskBotAgent.self) {
|
if let agent = entity.component(ofType: TaskBotAgent.self) {
|
||||||
return agent
|
return agent
|
||||||
}
|
}
|
||||||
else if let playerBot = entity as? PlayerBot {
|
else if let playerBot = entity as? PlayerBot {
|
||||||
|
@ -56,22 +56,16 @@ class LevelStateSnapshot {
|
||||||
Because we want to use the current index value from the outer loop as the seed for the inner loop,
|
Because we want to use the current index value from the outer loop as the seed for the inner loop,
|
||||||
we work with the `Set` index values directly.
|
we work with the `Set` index values directly.
|
||||||
*/
|
*/
|
||||||
for sourceIndex in scene.entities.startIndex ..< scene.entities.endIndex {
|
for sourceEntity in scene.entities {
|
||||||
|
let sourceIndex = scene.entities.index(of: sourceEntity)!
|
||||||
|
|
||||||
// Retrieve the source entity for this index.
|
|
||||||
let sourceEntity = scene.entities[sourceIndex]
|
|
||||||
|
|
||||||
// Retrieve the `GKAgent` for the source entity.
|
// Retrieve the `GKAgent` for the source entity.
|
||||||
let sourceAgent = agentForEntity(sourceEntity)
|
let sourceAgent = agentForEntity(entity: sourceEntity)
|
||||||
|
|
||||||
// Iterate over the remaining entities to calculate their distance from the source agent.
|
// Iterate over the remaining entities to calculate their distance from the source agent.
|
||||||
for targetIndex in sourceIndex.successor() ..< scene.entities.endIndex {
|
for targetEntity in scene.entities[scene.entities.index(after: sourceIndex) ..< scene.entities.endIndex] {
|
||||||
|
|
||||||
// Retrieve the target entity for this index.
|
|
||||||
let targetEntity = scene.entities[targetIndex]
|
|
||||||
|
|
||||||
// Retrieve the `GKAgent` for the target entity.
|
// Retrieve the `GKAgent` for the target entity.
|
||||||
let targetAgent = agentForEntity(targetEntity)
|
let targetAgent = agentForEntity(entity: targetEntity)
|
||||||
|
|
||||||
// Calculate the distance between the two agents.
|
// Calculate the distance between the two agents.
|
||||||
let dx = targetAgent.position.x - sourceAgent.position.x
|
let dx = targetAgent.position.x - sourceAgent.position.x
|
||||||
|
@ -140,7 +134,7 @@ class EntitySnapshot {
|
||||||
self.proximityFactor = proximityFactor
|
self.proximityFactor = proximityFactor
|
||||||
|
|
||||||
// Sort the `entityDistances` array by distance (nearest first), and store the sorted version.
|
// Sort the `entityDistances` array by distance (nearest first), and store the sorted version.
|
||||||
self.entityDistances = entityDistances.sort {
|
self.entityDistances = entityDistances.sorted {
|
||||||
return $0.distance < $1.distance
|
return $0.distance < $1.distance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,10 +146,10 @@ class EntitySnapshot {
|
||||||
(if it is targetable) and the nearest "good" `TaskBot`.
|
(if it is targetable) and the nearest "good" `TaskBot`.
|
||||||
*/
|
*/
|
||||||
for entityDistance in self.entityDistances {
|
for entityDistance in self.entityDistances {
|
||||||
if let target = entityDistance.target as? PlayerBot where playerBotTarget == nil && target.isTargetable {
|
if let target = entityDistance.target as? PlayerBot, playerBotTarget == nil && target.isTargetable {
|
||||||
playerBotTarget = (target: target, distance: entityDistance.distance)
|
playerBotTarget = (target: target, distance: entityDistance.distance)
|
||||||
}
|
}
|
||||||
else if let target = entityDistance.target as? TaskBot where nearestGoodTaskBotTarget == nil && target.isGood {
|
else if let target = entityDistance.target as? TaskBot, nearestGoodTaskBotTarget == nil && target.isGood {
|
||||||
nearestGoodTaskBotTarget = (target: target, distance: entityDistance.distance)
|
nearestGoodTaskBotTarget = (target: target, distance: entityDistance.distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,19 +22,19 @@ import GameplayKit
|
||||||
|
|
||||||
enum Fact: String {
|
enum Fact: String {
|
||||||
// Fuzzy rules pertaining to the proportion of "bad" bots in the level.
|
// Fuzzy rules pertaining to the proportion of "bad" bots in the level.
|
||||||
case BadTaskBotPercentageLow = "BadTaskBotPercentageLow"
|
case badTaskBotPercentageLow = "BadTaskBotPercentageLow"
|
||||||
case BadTaskBotPercentageMedium = "BadTaskBotPercentageMedium"
|
case badTaskBotPercentageMedium = "BadTaskBotPercentageMedium"
|
||||||
case BadTaskBotPercentageHigh = "BadTaskBotPercentageHigh"
|
case badTaskBotPercentageHigh = "BadTaskBotPercentageHigh"
|
||||||
|
|
||||||
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the `PlayerBot`.
|
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the `PlayerBot`.
|
||||||
case PlayerBotNear = "PlayerBotNear"
|
case playerBotNear = "PlayerBotNear"
|
||||||
case PlayerBotMedium = "PlayerBotMedium"
|
case playerBotMedium = "PlayerBotMedium"
|
||||||
case PlayerBotFar = "PlayerBotFar"
|
case playerBotFar = "PlayerBotFar"
|
||||||
|
|
||||||
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the nearest "good" `TaskBot`.
|
// Fuzzy rules pertaining to this `TaskBot`'s proximity to the nearest "good" `TaskBot`.
|
||||||
case GoodTaskBotNear = "GoodTaskBotNear"
|
case goodTaskBotNear = "GoodTaskBotNear"
|
||||||
case GoodTaskBotMedium = "GoodTaskBotMedium"
|
case goodTaskBotMedium = "GoodTaskBotMedium"
|
||||||
case GoodTaskBotFar = "GoodTaskBotFar"
|
case goodTaskBotFar = "GoodTaskBotFar"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the number of "bad" `TaskBot`s is considered "low".
|
/// Asserts whether the number of "bad" `TaskBot`s is considered "low".
|
||||||
|
@ -47,7 +47,7 @@ class BadTaskBotPercentageLowRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .BadTaskBotPercentageLow) }
|
init() { super.init(fact: .badTaskBotPercentageLow) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the number of "bad" `TaskBot`s is considered "medium".
|
/// Asserts whether the number of "bad" `TaskBot`s is considered "medium".
|
||||||
|
@ -65,7 +65,7 @@ class BadTaskBotPercentageMediumRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .BadTaskBotPercentageMedium) }
|
init() { super.init(fact: .badTaskBotPercentageMedium) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the number of "bad" `TaskBot`s is considered "high".
|
/// Asserts whether the number of "bad" `TaskBot`s is considered "high".
|
||||||
|
@ -78,7 +78,7 @@ class BadTaskBotPercentageHighRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .BadTaskBotPercentageHigh) }
|
init() { super.init(fact: .badTaskBotPercentageHigh) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the `PlayerBot` is considered to be "near" to this `TaskBot`.
|
/// Asserts whether the `PlayerBot` is considered to be "near" to this `TaskBot`.
|
||||||
|
@ -93,7 +93,7 @@ class PlayerBotNearRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .PlayerBotNear) }
|
init() { super.init(fact: .playerBotNear) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the `PlayerBot` is considered to be at a "medium" distance from this `TaskBot`.
|
/// Asserts whether the `PlayerBot` is considered to be at a "medium" distance from this `TaskBot`.
|
||||||
|
@ -108,7 +108,7 @@ class PlayerBotMediumRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .PlayerBotMedium) }
|
init() { super.init(fact: .playerBotMedium) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the `PlayerBot` is considered to be "far" from this `TaskBot`.
|
/// Asserts whether the `PlayerBot` is considered to be "far" from this `TaskBot`.
|
||||||
|
@ -123,7 +123,7 @@ class PlayerBotFarRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .PlayerBotFar) }
|
init() { super.init(fact: .playerBotFar) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: TaskBot Proximity Rules
|
// MARK: TaskBot Proximity Rules
|
||||||
|
@ -140,7 +140,7 @@ class GoodTaskBotNearRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .GoodTaskBotNear) }
|
init() { super.init(fact: .goodTaskBotNear) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the nearest "good" `TaskBot` is considered to be at a "medium" distance from this `TaskBot`.
|
/// Asserts whether the nearest "good" `TaskBot` is considered to be at a "medium" distance from this `TaskBot`.
|
||||||
|
@ -155,7 +155,7 @@ class GoodTaskBotMediumRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .GoodTaskBotMedium) }
|
init() { super.init(fact: .goodTaskBotMedium) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts whether the nearest "good" `TaskBot` is considered to be "far" from this `TaskBot`.
|
/// Asserts whether the nearest "good" `TaskBot` is considered to be "far" from this `TaskBot`.
|
||||||
|
@ -170,5 +170,5 @@ class GoodTaskBotFarRule: FuzzyTaskBotRule {
|
||||||
|
|
||||||
// MARK: Initializers
|
// MARK: Initializers
|
||||||
|
|
||||||
init() { super.init(fact: .GoodTaskBotFar) }
|
init() { super.init(fact: .goodTaskBotFar) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,10 @@ import GameplayKit
|
||||||
|
|
||||||
The `object` property of the notification will contain the `SceneLoader`.
|
The `object` property of the notification will contain the `SceneLoader`.
|
||||||
*/
|
*/
|
||||||
let SceneLoaderDidCompleteNotification = "SceneLoaderDidCompleteNotification"
|
extension NSNotification.Name {
|
||||||
let SceneLoaderDidFailNotification = "SceneLoaderDidFailNotification"
|
public static let SceneLoaderDidCompleteNotification = NSNotification.Name(rawValue: "SceneLoaderDidCompleteNotification")
|
||||||
|
public static let SceneLoaderDidFailNotification = NSNotification.Name(rawValue: "SceneLoaderDidFailNotification")
|
||||||
|
}
|
||||||
|
|
||||||
/// A class encapsulating the work necessary to load a scene and its resources based on a given `SceneMetadata` instance.
|
/// A class encapsulating the work necessary to load a scene and its resources based on a given `SceneMetadata` instance.
|
||||||
class SceneLoader {
|
class SceneLoader {
|
||||||
|
@ -47,7 +49,7 @@ class SceneLoader {
|
||||||
var scene: BaseScene?
|
var scene: BaseScene?
|
||||||
|
|
||||||
/// The error, if one occurs, from fetching resources.
|
/// The error, if one occurs, from fetching resources.
|
||||||
var error: NSError?
|
var error: Error?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A parent progress, constructed when `prepareSceneForPresentation()`
|
A parent progress, constructed when `prepareSceneForPresentation()`
|
||||||
|
@ -55,7 +57,7 @@ class SceneLoader {
|
||||||
`SceneLoaderDownloadingResourcesState` and `SceneLoaderPreparingResourcesState`
|
`SceneLoaderDownloadingResourcesState` and `SceneLoaderPreparingResourcesState`
|
||||||
states.
|
states.
|
||||||
*/
|
*/
|
||||||
var progress: NSProgress? {
|
var progress: Progress? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let progress = progress else { return }
|
guard let progress = progress else { return }
|
||||||
|
|
||||||
|
@ -65,7 +67,7 @@ class SceneLoader {
|
||||||
self.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
self.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||||
|
|
||||||
// Notify any interested objects that the download was not completed.
|
// Notify any interested objects that the download was not completed.
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: self)
|
NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +122,7 @@ class SceneLoader {
|
||||||
the scene's resources, so bump up the quality of service of
|
the scene's resources, so bump up the quality of service of
|
||||||
the operation queue that is preparing the resources.
|
the operation queue that is preparing the resources.
|
||||||
*/
|
*/
|
||||||
preparingState.operationQueue.qualityOfService = .UserInteractive
|
preparingState.operationQueue.qualityOfService = .userInteractive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +133,7 @@ class SceneLoader {
|
||||||
self.sceneMetadata = sceneMetadata
|
self.sceneMetadata = sceneMetadata
|
||||||
|
|
||||||
// Enter the initial state as soon as the scene loader is created.
|
// Enter the initial state as soon as the scene loader is created.
|
||||||
stateMachine.enterState(SceneLoaderInitialState)
|
stateMachine.enter(SceneLoaderInitialState.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
|
@ -141,10 +143,10 @@ class SceneLoader {
|
||||||
*/
|
*/
|
||||||
func downloadResourcesIfNecessary() {
|
func downloadResourcesIfNecessary() {
|
||||||
if sceneMetadata.requiresOnDemandResources {
|
if sceneMetadata.requiresOnDemandResources {
|
||||||
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self)
|
stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
stateMachine.enterState(SceneLoaderResourcesAvailableState.self)
|
stateMachine.enter(SceneLoaderResourcesAvailableState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -156,20 +158,20 @@ class SceneLoader {
|
||||||
Note: On iOS there are two distinct steps to loading: downloading on demand resources
|
Note: On iOS there are two distinct steps to loading: downloading on demand resources
|
||||||
-> loading assets into memory.
|
-> loading assets into memory.
|
||||||
*/
|
*/
|
||||||
func asynchronouslyLoadSceneForPresentation() -> NSProgress {
|
func asynchronouslyLoadSceneForPresentation() -> Progress {
|
||||||
// If a valid progress already exists it means the scene is already being prepared.
|
// If a valid progress already exists it means the scene is already being prepared.
|
||||||
if let progress = progress where !progress.cancelled {
|
if let progress = progress , !progress.isCancelled {
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
switch stateMachine.currentState {
|
switch stateMachine.currentState {
|
||||||
case is SceneLoaderResourcesReadyState:
|
case is SceneLoaderResourcesReadyState:
|
||||||
// No additional work needs to be done.
|
// No additional work needs to be done.
|
||||||
progress = NSProgress(totalUnitCount: 0)
|
progress = Progress(totalUnitCount: 0)
|
||||||
|
|
||||||
|
|
||||||
case is SceneLoaderResourcesAvailableState:
|
case is SceneLoaderResourcesAvailableState:
|
||||||
progress = NSProgress(totalUnitCount: 1)
|
progress = Progress(totalUnitCount: 1)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Begin preparing the scene's resources.
|
Begin preparing the scene's resources.
|
||||||
|
@ -177,17 +179,17 @@ class SceneLoader {
|
||||||
The `SceneLoaderPreparingResourcesState`'s progress is added to the `SceneLoader`s
|
The `SceneLoaderPreparingResourcesState`'s progress is added to the `SceneLoader`s
|
||||||
progress when the operation is started.
|
progress when the operation is started.
|
||||||
*/
|
*/
|
||||||
stateMachine.enterState(SceneLoaderPreparingResourcesState.self)
|
stateMachine.enter(SceneLoaderPreparingResourcesState.self)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
// Set two units of progress to account for both downloading and then loading into memory.
|
// Set two units of progress to account for both downloading and then loading into memory.
|
||||||
progress = NSProgress(totalUnitCount: 2)
|
progress = Progress(totalUnitCount: 2)
|
||||||
|
|
||||||
let downloadingState = stateMachine.stateForClass(SceneLoaderDownloadingResourcesState)!
|
let downloadingState = stateMachine.state(forClass: SceneLoaderDownloadingResourcesState.self)!
|
||||||
downloadingState.enterPreparingStateWhenFinished = true
|
downloadingState.enterPreparingStateWhenFinished = true
|
||||||
|
|
||||||
stateMachine.enterState(SceneLoaderDownloadingResourcesState.self)
|
stateMachine.enter(SceneLoaderDownloadingResourcesState.self)
|
||||||
|
|
||||||
guard let bundleResourceRequest = bundleResourceRequest else {
|
guard let bundleResourceRequest = bundleResourceRequest else {
|
||||||
fatalError("In the `SceneLoaderDownloadingResourcesState`, but a valid resource request has not been created.")
|
fatalError("In the `SceneLoaderDownloadingResourcesState`, but a valid resource request has not been created.")
|
||||||
|
@ -219,7 +221,7 @@ class SceneLoader {
|
||||||
progress?.cancel()
|
progress?.cancel()
|
||||||
|
|
||||||
// Reset the state machine back to the initial state.
|
// Reset the state machine back to the initial state.
|
||||||
stateMachine.enterState(SceneLoaderInitialState)
|
stateMachine.enter(SceneLoaderInitialState.self)
|
||||||
|
|
||||||
// Unpin any on demand resources.
|
// Unpin any on demand resources.
|
||||||
bundleResourceRequest = nil
|
bundleResourceRequest = nil
|
||||||
|
@ -231,4 +233,4 @@ class SceneLoader {
|
||||||
error = nil
|
error = nil
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,17 @@ class SceneLoaderDownloadFailedState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Clear the `sceneLoader`'s progress.
|
// Clear the `sceneLoader`'s progress.
|
||||||
sceneLoader.progress = nil
|
sceneLoader.progress = nil
|
||||||
|
|
||||||
// Notify any interested objects that the download has failed.
|
// Notify any interested objects that the download has failed.
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: sceneLoader)
|
NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: sceneLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
return stateClass is SceneLoaderDownloadingResourcesState.Type
|
return stateClass is SceneLoaderDownloadingResourcesState.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,15 @@ class SceneLoaderDownloadingResourcesState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Clear any previous errors, and begin downloading the scene's resources.
|
// Clear any previous errors, and begin downloading the scene's resources.
|
||||||
sceneLoader.error = nil
|
sceneLoader.error = nil
|
||||||
beginDownloadingScene()
|
beginDownloadingScene()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is SceneLoaderDownloadFailedState.Type, is SceneLoaderResourcesAvailableState.Type, is SceneLoaderPreparingResourcesState.Type:
|
case is SceneLoaderDownloadFailedState.Type, is SceneLoaderResourcesAvailableState.Type, is SceneLoaderPreparingResourcesState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -56,10 +56,10 @@ class SceneLoaderDownloadingResourcesState: GKState {
|
||||||
sceneLoader.bundleResourceRequest = bundleResourceRequest
|
sceneLoader.bundleResourceRequest = bundleResourceRequest
|
||||||
|
|
||||||
// Begin downloading the on demand resources.
|
// Begin downloading the on demand resources.
|
||||||
bundleResourceRequest.beginAccessingResourcesWithCompletionHandler { error in
|
bundleResourceRequest.beginAccessingResources { error in
|
||||||
|
|
||||||
// Progress to the next appropriate state from the main queue.
|
// Progress to the next appropriate state from the main queue.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
if let error = error {
|
if let error = error {
|
||||||
// Release the resources because we'll need to start a new request.
|
// Release the resources because we'll need to start a new request.
|
||||||
bundleResourceRequest.endAccessingResources()
|
bundleResourceRequest.endAccessingResources()
|
||||||
|
@ -67,16 +67,16 @@ class SceneLoaderDownloadingResourcesState: GKState {
|
||||||
// Set the error on the sceneLoader.
|
// Set the error on the sceneLoader.
|
||||||
self.sceneLoader.error = error
|
self.sceneLoader.error = error
|
||||||
|
|
||||||
self.stateMachine!.enterState(SceneLoaderDownloadFailedState.self)
|
self.stateMachine!.enter(SceneLoaderDownloadFailedState.self)
|
||||||
}
|
}
|
||||||
else if self.enterPreparingStateWhenFinished {
|
else if self.enterPreparingStateWhenFinished {
|
||||||
// If requested, proceed to the preparing state immediately.
|
// If requested, proceed to the preparing state immediately.
|
||||||
self.stateMachine!.enterState(SceneLoaderPreparingResourcesState.self)
|
self.stateMachine!.enter(SceneLoaderPreparingResourcesState.self)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.stateMachine!.enterState(SceneLoaderResourcesAvailableState.self)
|
self.stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,19 +21,19 @@ class SceneLoaderInitialState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
// Move the `stateMachine` to the available state if no on-demand resources are required.
|
// Move the `stateMachine` to the available state if no on-demand resources are required.
|
||||||
if !sceneLoader.sceneMetadata.requiresOnDemandResources {
|
if !sceneLoader.sceneMetadata.requiresOnDemandResources {
|
||||||
stateMachine!.enterState(SceneLoaderResourcesAvailableState.self)
|
stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
|
||||||
}
|
}
|
||||||
#elseif os(OSX)
|
#elseif os(OSX)
|
||||||
// On OS X the resources will always be in local storage available for download.
|
// On OS X the resources will always be in local storage available for download.
|
||||||
stateMachine!.enterState(SceneLoaderResourcesAvailableState.self)
|
_ = stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
if stateClass is SceneLoaderDownloadingResourcesState.Type {
|
if stateClass is SceneLoaderDownloadingResourcesState.Type {
|
||||||
return true
|
return true
|
||||||
|
@ -42,4 +42,4 @@ class SceneLoaderInitialState: GKState {
|
||||||
|
|
||||||
return stateClass is SceneLoaderResourcesAvailableState.Type
|
return stateClass is SceneLoaderResourcesAvailableState.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,13 @@ class SceneLoaderPreparingResourcesState: GKState {
|
||||||
unowned let sceneLoader: SceneLoader
|
unowned let sceneLoader: SceneLoader
|
||||||
|
|
||||||
/// An internal operation queue for loading scene resources in the background.
|
/// An internal operation queue for loading scene resources in the background.
|
||||||
let operationQueue = NSOperationQueue()
|
let operationQueue = OperationQueue()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An NSProgress object that can be used to query and monitor progress of
|
An NSProgress object that can be used to query and monitor progress of
|
||||||
the resources being loaded. Also supports cancellation.
|
the resources being loaded. Also supports cancellation.
|
||||||
*/
|
*/
|
||||||
var progress: NSProgress? {
|
var progress: Progress? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let progress = progress else { return }
|
guard let progress = progress else { return }
|
||||||
|
|
||||||
|
@ -47,19 +47,19 @@ class SceneLoaderPreparingResourcesState: GKState {
|
||||||
state machine. Setting the `qualityOfService` as `.Utility` reflects the
|
state machine. Setting the `qualityOfService` as `.Utility` reflects the
|
||||||
fact that this is an important task, but is not blocking the user.
|
fact that this is an important task, but is not blocking the user.
|
||||||
*/
|
*/
|
||||||
operationQueue.qualityOfService = .Utility
|
operationQueue.qualityOfService = .utility
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Begin loading the scene and associated resources in the background.
|
// Begin loading the scene and associated resources in the background.
|
||||||
loadResourcesAsynchronously()
|
loadResourcesAsynchronously()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
// Only valid if the `sceneLoader`'s scene has been loaded.
|
// Only valid if the `sceneLoader`'s scene has been loaded.
|
||||||
case is SceneLoaderResourcesReadyState.Type where sceneLoader.scene != nil:
|
case is SceneLoaderResourcesReadyState.Type where sceneLoader.scene != nil:
|
||||||
|
@ -90,7 +90,7 @@ class SceneLoaderPreparingResourcesState: GKState {
|
||||||
Create an `NSProgress` object with the total unit count equal to the number of entities that
|
Create an `NSProgress` object with the total unit count equal to the number of entities that
|
||||||
need to be loaded plus a unit for loading the scene itself.
|
need to be loaded plus a unit for loading the scene itself.
|
||||||
*/
|
*/
|
||||||
let loadingProgress = NSProgress(totalUnitCount: sceneMetadata.loadableTypes.count + 1)
|
let loadingProgress = Progress(totalUnitCount: sceneMetadata.loadableTypes.count + 1)
|
||||||
|
|
||||||
// Add the `SceneLoaderPreparingResourcesState`'s progress to the overall `sceneLoader`'s progress.
|
// Add the `SceneLoaderPreparingResourcesState`'s progress to the overall `sceneLoader`'s progress.
|
||||||
sceneLoader.progress?.addChild(loadingProgress, withPendingUnitCount: 1)
|
sceneLoader.progress?.addChild(loadingProgress, withPendingUnitCount: 1)
|
||||||
|
@ -105,10 +105,10 @@ class SceneLoaderPreparingResourcesState: GKState {
|
||||||
|
|
||||||
loadSceneOperation.completionBlock = { [unowned self] in
|
loadSceneOperation.completionBlock = { [unowned self] in
|
||||||
// Enter the next state on the main queue.
|
// Enter the next state on the main queue.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.sceneLoader.scene = loadSceneOperation.scene
|
self.sceneLoader.scene = loadSceneOperation.scene
|
||||||
|
|
||||||
let didEnterReadyState = self.stateMachine!.enterState(SceneLoaderResourcesReadyState.self)
|
let didEnterReadyState = self.stateMachine!.enter(SceneLoaderResourcesReadyState.self)
|
||||||
assert(didEnterReadyState, "Failed to transition to `ReadyState` after resources were prepared.")
|
assert(didEnterReadyState, "Failed to transition to `ReadyState` after resources were prepared.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,11 +142,11 @@ class SceneLoaderPreparingResourcesState: GKState {
|
||||||
sceneLoader.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
sceneLoader.error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||||
|
|
||||||
// Enter the next state on the main queue.
|
// Enter the next state on the main queue.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.stateMachine!.enterState(SceneLoaderResourcesAvailableState.self)
|
self.stateMachine!.enter(SceneLoaderResourcesAvailableState.self)
|
||||||
|
|
||||||
// Notify that loading was not completed.
|
// Notify that loading was not completed.
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidFailNotification, object: self.sceneLoader)
|
NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidFailNotification, object: self.sceneLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class SceneLoaderResourcesAvailableState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is SceneLoaderInitialState.Type, is SceneLoaderPreparingResourcesState.Type:
|
case is SceneLoaderInitialState.Type, is SceneLoaderPreparingResourcesState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -31,4 +31,4 @@ class SceneLoaderResourcesAvailableState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,17 @@ class SceneLoaderResourcesReadyState: GKState {
|
||||||
|
|
||||||
// MARK: GKState Life Cycle
|
// MARK: GKState Life Cycle
|
||||||
|
|
||||||
override func didEnterWithPreviousState(previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnterWithPreviousState(previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
// Clear the `sceneLoader`'s progress as loading is complete.
|
// Clear the `sceneLoader`'s progress as loading is complete.
|
||||||
sceneLoader.progress = nil
|
sceneLoader.progress = nil
|
||||||
|
|
||||||
// Notify to any interested objects that the download has completed.
|
// Notify to any interested objects that the download has completed.
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(SceneLoaderDidCompleteNotification, object: sceneLoader)
|
NotificationCenter.default.post(name: NSNotification.Name.SceneLoaderDidCompleteNotification, object: sceneLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func isValidNextState(stateClass: AnyClass) -> Bool {
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
switch stateClass {
|
switch stateClass {
|
||||||
case is SceneLoaderResourcesAvailableState.Type, is SceneLoaderInitialState.Type:
|
case is SceneLoaderResourcesAvailableState.Type, is SceneLoaderInitialState.Type:
|
||||||
return true
|
return true
|
||||||
|
@ -41,8 +41,8 @@ class SceneLoaderResourcesReadyState: GKState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willExitWithNextState(nextState: GKState) {
|
override func willExit(to nextState: GKState) {
|
||||||
super.willExitWithNextState(nextState)
|
super.willExit(to: nextState)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Presenting the scene is a one shot operation. Clear the scene when
|
Presenting the scene is a one shot operation. Clear the scene when
|
||||||
|
@ -50,4 +50,4 @@ class SceneLoaderResourcesReadyState: GKState {
|
||||||
*/
|
*/
|
||||||
sceneLoader.scene = nil
|
sceneLoader.scene = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SpriteKit
|
||||||
|
|
||||||
protocol SceneManagerDelegate: class {
|
protocol SceneManagerDelegate: class {
|
||||||
// Called whenever a scene manager has transitioned to a new scene.
|
// Called whenever a scene manager has transitioned to a new scene.
|
||||||
func sceneManagerDidTransitionToScene(scene: SKScene)
|
func sceneManager(_ sceneManager: SceneManager, didTransitionTo scene: SKScene)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,9 +21,9 @@ final class SceneManager {
|
||||||
// MARK: Types
|
// MARK: Types
|
||||||
|
|
||||||
enum SceneIdentifier {
|
enum SceneIdentifier {
|
||||||
case Home, End
|
case home, end
|
||||||
case CurrentLevel, NextLevel
|
case currentLevel, nextLevel
|
||||||
case Level(Int)
|
case level(Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
@ -49,7 +49,7 @@ final class SceneManager {
|
||||||
|
|
||||||
// If there is no current scene, we can only transition back to the home scene.
|
// If there is no current scene, we can only transition back to the home scene.
|
||||||
guard let currentSceneMetadata = currentSceneMetadata else { return homeScene }
|
guard let currentSceneMetadata = currentSceneMetadata else { return homeScene }
|
||||||
let index = sceneConfigurationInfo.indexOf(currentSceneMetadata)!
|
let index = sceneConfigurationInfo.index(of: currentSceneMetadata)!
|
||||||
|
|
||||||
if index + 1 < sceneConfigurationInfo.count {
|
if index + 1 < sceneConfigurationInfo.count {
|
||||||
// Return the metadata for the next scene in the array.
|
// Return the metadata for the next scene in the array.
|
||||||
|
@ -85,8 +85,8 @@ final class SceneManager {
|
||||||
Load the game's `SceneConfiguration` plist. This provides information
|
Load the game's `SceneConfiguration` plist. This provides information
|
||||||
about every scene in the game, and the order in which they should be displayed.
|
about every scene in the game, and the order in which they should be displayed.
|
||||||
*/
|
*/
|
||||||
let url = NSBundle.mainBundle().URLForResource("SceneConfiguration", withExtension: "plist")!
|
let url = Bundle.main.url(forResource: "SceneConfiguration", withExtension: "plist")!
|
||||||
let scenes = NSArray(contentsOfURL: url) as! [[String: AnyObject]]
|
let scenes = NSArray(contentsOf: url) as! [[String: AnyObject]]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Extract the configuration info dictionary for each possible scene,
|
Extract the configuration info dictionary for each possible scene,
|
||||||
|
@ -116,7 +116,7 @@ final class SceneManager {
|
||||||
deinit {
|
deinit {
|
||||||
// Unregister for `SceneLoader` notifications if the observer is still around.
|
// Unregister for `SceneLoader` notifications if the observer is still around.
|
||||||
if let loadingCompletedObserver = loadingCompletedObserver {
|
if let loadingCompletedObserver = loadingCompletedObserver {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(loadingCompletedObserver, name: SceneLoaderDidCompleteNotification, object: nil)
|
NotificationCenter.default.removeObserver(loadingCompletedObserver, name: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,9 +129,9 @@ final class SceneManager {
|
||||||
This method should be called in preparation for the user needing to transition
|
This method should be called in preparation for the user needing to transition
|
||||||
to the scene in order to minimize the amount of load time.
|
to the scene in order to minimize the amount of load time.
|
||||||
*/
|
*/
|
||||||
func prepareSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) {
|
func prepareScene(identifier sceneIdentifier: SceneIdentifier) {
|
||||||
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier)
|
let loader = sceneLoader(forSceneIdentifier: sceneIdentifier)
|
||||||
sceneLoader.asynchronouslyLoadSceneForPresentation()
|
_ = loader.asynchronouslyLoadSceneForPresentation()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,26 +139,25 @@ final class SceneManager {
|
||||||
currently in memory. Otherwise, presents a progress scene to monitor the progress
|
currently in memory. Otherwise, presents a progress scene to monitor the progress
|
||||||
of the resources being downloaded, or display an error if one has occurred.
|
of the resources being downloaded, or display an error if one has occurred.
|
||||||
*/
|
*/
|
||||||
func transitionToSceneWithSceneIdentifier(sceneIdentifier: SceneIdentifier) {
|
func transitionToScene(identifier sceneIdentifier: SceneIdentifier) {
|
||||||
let sceneLoader = sceneLoaderForSceneIdentifier(sceneIdentifier)
|
let loader = self.sceneLoader(forSceneIdentifier: sceneIdentifier)
|
||||||
|
|
||||||
|
if loader.stateMachine.currentState is SceneLoaderResourcesReadyState {
|
||||||
if sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState {
|
|
||||||
// The scene is ready to be displayed.
|
// The scene is ready to be displayed.
|
||||||
presentSceneForSceneLoader(sceneLoader)
|
presentScene(for: loader)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sceneLoader.asynchronouslyLoadSceneForPresentation()
|
_ = loader.asynchronouslyLoadSceneForPresentation()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mark the `sceneLoader` as `requestedForPresentation` to automatically
|
Mark the `sceneLoader` as `requestedForPresentation` to automatically
|
||||||
present the scene when loading completes.
|
present the scene when loading completes.
|
||||||
*/
|
*/
|
||||||
sceneLoader.requestedForPresentation = true
|
loader.requestedForPresentation = true
|
||||||
|
|
||||||
// The scene requires a progress scene to be displayed while its resources are prepared.
|
// The scene requires a progress scene to be displayed while its resources are prepared.
|
||||||
if sceneLoader.requiresProgressSceneForPreparing {
|
if loader.requiresProgressSceneForPreparing {
|
||||||
presentProgressScene(sceneLoader)
|
presentProgressScene(for: loader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,17 +165,17 @@ final class SceneManager {
|
||||||
// MARK: Scene Presentation
|
// MARK: Scene Presentation
|
||||||
|
|
||||||
/// Configures and presents a scene.
|
/// Configures and presents a scene.
|
||||||
func presentSceneForSceneLoader(sceneLoader: SceneLoader) {
|
func presentScene(for loader: SceneLoader) {
|
||||||
guard let scene = sceneLoader.scene else {
|
guard let scene = loader.scene else {
|
||||||
assertionFailure("Requested presentation for a `sceneLoader` without a valid `scene`.")
|
assertionFailure("Requested presentation for a `sceneLoader` without a valid `scene`.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold on to a reference to the currently requested scene's metadata.
|
// Hold on to a reference to the currently requested scene's metadata.
|
||||||
currentSceneMetadata = sceneLoader.sceneMetadata
|
currentSceneMetadata = loader.sceneMetadata
|
||||||
|
|
||||||
// Ensure we present the scene on the main queue.
|
// Ensure we present the scene on the main queue.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
/*
|
/*
|
||||||
Provide the scene with a reference to the `SceneLoadingManger`
|
Provide the scene with a reference to the `SceneLoadingManger`
|
||||||
so that it can coordinate the next scene that should be loaded.
|
so that it can coordinate the next scene that should be loaded.
|
||||||
|
@ -184,7 +183,7 @@ final class SceneManager {
|
||||||
scene.sceneManager = self
|
scene.sceneManager = self
|
||||||
|
|
||||||
// Present the scene with a transition.
|
// Present the scene with a transition.
|
||||||
let transition = SKTransition.fadeWithDuration(GameplayConfiguration.SceneManager.transitionDuration)
|
let transition = SKTransition.fade(withDuration: GameplayConfiguration.SceneManager.transitionDuration)
|
||||||
self.presentingView.presentScene(scene, transition: transition)
|
self.presentingView.presentScene(scene, transition: transition)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -199,23 +198,23 @@ final class SceneManager {
|
||||||
self.progressScene = nil
|
self.progressScene = nil
|
||||||
|
|
||||||
// Notify the delegate that the manager has presented a scene.
|
// Notify the delegate that the manager has presented a scene.
|
||||||
self.delegate?.sceneManagerDidTransitionToScene(scene)
|
self.delegate?.sceneManager(self, didTransitionTo: scene)
|
||||||
|
|
||||||
// Restart the scene loading process.
|
// Restart the scene loading process.
|
||||||
sceneLoader.stateMachine.enterState(SceneLoaderInitialState.self)
|
loader.stateMachine.enter(SceneLoaderInitialState.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the progress scene to show the progress of the `sceneLoader`.
|
/// Configures the progress scene to show the progress of the `sceneLoader`.
|
||||||
func presentProgressScene(sceneLoader: SceneLoader) {
|
func presentProgressScene(for loader: SceneLoader) {
|
||||||
// If the `progressScene` is already being displayed, there's nothing to do.
|
// If the `progressScene` is already being displayed, there's nothing to do.
|
||||||
guard progressScene == nil else { return }
|
guard progressScene == nil else { return }
|
||||||
|
|
||||||
// Create a `ProgressScene` for the scene loader.
|
// Create a `ProgressScene` for the scene loader.
|
||||||
progressScene = ProgressScene.progressSceneWithSceneLoader(sceneLoader)
|
progressScene = ProgressScene.progressScene(withSceneLoader: loader)
|
||||||
progressScene!.sceneManager = self
|
progressScene!.sceneManager = self
|
||||||
|
|
||||||
let transition = SKTransition.doorsCloseHorizontalWithDuration(GameplayConfiguration.SceneManager.progressSceneTransitionDuration)
|
let transition = SKTransition.doorsCloseHorizontal(withDuration: GameplayConfiguration.SceneManager.progressSceneTransitionDuration)
|
||||||
presentingView.presentScene(progressScene!, transition: transition)
|
presentingView.presentScene(progressScene!, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,9 +232,9 @@ final class SceneManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up scenes that are no longer accessible.
|
// Clean up scenes that are no longer accessible.
|
||||||
let allScenes = Set(sceneLoaderForMetadata.keys)
|
var unreachableScenes = Set(sceneLoaderForMetadata.keys)
|
||||||
let unreachableScenes = allScenes.subtract(possibleScenes)
|
unreachableScenes.subtract(possibleScenes)
|
||||||
|
|
||||||
for sceneMetadata in unreachableScenes {
|
for sceneMetadata in unreachableScenes {
|
||||||
let resourceRequest = sceneLoaderForMetadata[sceneMetadata]!
|
let resourceRequest = sceneLoaderForMetadata[sceneMetadata]!
|
||||||
resourceRequest.purgeResources()
|
resourceRequest.purgeResources()
|
||||||
|
@ -269,11 +268,11 @@ final class SceneManager {
|
||||||
// Avoid reregistering for the notification.
|
// Avoid reregistering for the notification.
|
||||||
guard loadingCompletedObserver == nil else { return }
|
guard loadingCompletedObserver == nil else { return }
|
||||||
|
|
||||||
loadingCompletedObserver = NSNotificationCenter.defaultCenter().addObserverForName(SceneLoaderDidCompleteNotification, object: nil, queue: NSOperationQueue.mainQueue()) { [unowned self] notification in
|
loadingCompletedObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.SceneLoaderDidCompleteNotification, object: nil, queue: OperationQueue.main) { [unowned self] notification in
|
||||||
let sceneLoader = notification.object as! SceneLoader
|
let sceneLoader = notification.object as! SceneLoader
|
||||||
|
|
||||||
// Ensure this is a `sceneLoader` managed by this `SceneManager`.
|
// Ensure this is a `sceneLoader` managed by this `SceneManager`.
|
||||||
guard let managedSceneLoader = self.sceneLoaderForMetadata[sceneLoader.sceneMetadata] where managedSceneLoader === sceneLoader else { return }
|
guard let managedSceneLoader = self.sceneLoaderForMetadata[sceneLoader.sceneMetadata], managedSceneLoader === sceneLoader else { return }
|
||||||
|
|
||||||
guard sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState else {
|
guard sceneLoader.stateMachine.currentState is SceneLoaderResourcesReadyState else {
|
||||||
fatalError("Received complete notification, but the `stateMachine`'s current state is not ready.")
|
fatalError("Received complete notification, but the `stateMachine`'s current state is not ready.")
|
||||||
|
@ -287,7 +286,7 @@ final class SceneManager {
|
||||||
a progress scene.
|
a progress scene.
|
||||||
*/
|
*/
|
||||||
if sceneLoader.requestedForPresentation {
|
if sceneLoader.requestedForPresentation {
|
||||||
self.presentSceneForSceneLoader(sceneLoader)
|
self.presentScene(for: sceneLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the scene loader's presentation preference.
|
// Reset the scene loader's presentation preference.
|
||||||
|
@ -298,25 +297,25 @@ final class SceneManager {
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
|
||||||
/// Returns the scene loader associated with the scene identifier.
|
/// Returns the scene loader associated with the scene identifier.
|
||||||
func sceneLoaderForSceneIdentifier(sceneIdentifier: SceneIdentifier) -> SceneLoader {
|
func sceneLoader(forSceneIdentifier sceneIdentifier: SceneIdentifier) -> SceneLoader {
|
||||||
let sceneMetadata: SceneMetadata
|
let sceneMetadata: SceneMetadata
|
||||||
switch sceneIdentifier {
|
switch sceneIdentifier {
|
||||||
case .Home:
|
case .home:
|
||||||
sceneMetadata = sceneConfigurationInfo.first!
|
sceneMetadata = sceneConfigurationInfo.first!
|
||||||
|
|
||||||
case .CurrentLevel:
|
case .currentLevel:
|
||||||
guard let currentSceneMetadata = currentSceneMetadata else {
|
guard let currentSceneMetadata = currentSceneMetadata else {
|
||||||
fatalError("Current scene doesn't exist.")
|
fatalError("Current scene doesn't exist.")
|
||||||
}
|
}
|
||||||
sceneMetadata = currentSceneMetadata
|
sceneMetadata = currentSceneMetadata
|
||||||
|
|
||||||
case .Level(let number):
|
case .level(let number):
|
||||||
sceneMetadata = sceneConfigurationInfo[number]
|
sceneMetadata = sceneConfigurationInfo[number]
|
||||||
|
|
||||||
case .NextLevel:
|
case .nextLevel:
|
||||||
sceneMetadata = nextSceneMetadata
|
sceneMetadata = nextSceneMetadata
|
||||||
|
|
||||||
case .End:
|
case .end:
|
||||||
sceneMetadata = sceneConfigurationInfo.last!
|
sceneMetadata = sceneConfigurationInfo.last!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ A subclass of `NSOperation` that maps the different states of an `NSOperation`
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Operation: NSOperation {
|
class SceneOperation: Operation {
|
||||||
// MARK: Types
|
// MARK: Types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,35 +18,35 @@ class Operation: NSOperation {
|
||||||
*/
|
*/
|
||||||
@objc enum State: Int {
|
@objc enum State: Int {
|
||||||
/// The `Operation` is ready to begin execution.
|
/// The `Operation` is ready to begin execution.
|
||||||
case Ready
|
case ready
|
||||||
|
|
||||||
/// The `Operation` is executing.
|
/// The `Operation` is executing.
|
||||||
case Executing
|
case executing
|
||||||
|
|
||||||
/// The `Operation` has finished executing.
|
/// The `Operation` has finished executing.
|
||||||
case Finished
|
case finished
|
||||||
|
|
||||||
/// The `Operation` has been cancelled.
|
/// The `Operation` has been cancelled.
|
||||||
case Cancelled
|
case cancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
/// Marking `state` as dynamic allows this property to be key-value observed.
|
/// Marking `state` as dynamic allows this property to be key-value observed.
|
||||||
dynamic var state = State.Ready
|
dynamic var state = State.ready
|
||||||
|
|
||||||
// MARK: NSOperation
|
// MARK: NSOperation
|
||||||
|
|
||||||
override var executing: Bool {
|
override var isExecuting: Bool {
|
||||||
return state == .Executing
|
return state == .executing
|
||||||
}
|
}
|
||||||
|
|
||||||
override var finished: Bool {
|
override var isFinished: Bool {
|
||||||
return state == .Finished
|
return state == .finished
|
||||||
}
|
}
|
||||||
|
|
||||||
override var cancelled: Bool {
|
override var isCancelled: Bool {
|
||||||
return state == .Cancelled
|
return state == .cancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -22,7 +22,7 @@ class SceneOverlay {
|
||||||
init(overlaySceneFileName fileName: String, zPosition: CGFloat) {
|
init(overlaySceneFileName fileName: String, zPosition: CGFloat) {
|
||||||
// Load the scene and get the overlay node from it.
|
// Load the scene and get the overlay node from it.
|
||||||
let overlayScene = SKScene(fileNamed: fileName)!
|
let overlayScene = SKScene(fileNamed: fileName)!
|
||||||
let contentTemplateNode = overlayScene.childNodeWithName("Overlay") as! SKSpriteNode
|
let contentTemplateNode = overlayScene.childNode(withName: "Overlay") as! SKSpriteNode
|
||||||
|
|
||||||
// Create a background node with the same color as the template.
|
// Create a background node with the same color as the template.
|
||||||
backgroundNode = SKSpriteNode(color: contentTemplateNode.color, size: contentTemplateNode.size)
|
backgroundNode = SKSpriteNode(color: contentTemplateNode.color, size: contentTemplateNode.size)
|
||||||
|
@ -34,7 +34,7 @@ class SceneOverlay {
|
||||||
backgroundNode.addChild(contentNode)
|
backgroundNode.addChild(contentNode)
|
||||||
|
|
||||||
// Set the content node to a clear color to allow the background node to be seen through it.
|
// Set the content node to a clear color to allow the background node to be seen through it.
|
||||||
contentNode.color = .clearColor()
|
contentNode.color = .clear
|
||||||
|
|
||||||
// Store the current size of the content to allow it to be scaled correctly.
|
// Store the current size of the content to allow it to be scaled correctly.
|
||||||
nativeContentSize = contentNode.size
|
nativeContentSize = contentNode.size
|
||||||
|
|
|
@ -32,8 +32,8 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
let centerDividerWidth: CGFloat
|
let centerDividerWidth: CGFloat
|
||||||
var hideThumbStickNodes: Bool = false {
|
var hideThumbStickNodes: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
leftThumbStickNode.hidden = hideThumbStickNodes
|
leftThumbStickNode.isHidden = hideThumbStickNodes
|
||||||
rightThumbStickNode.hidden = hideThumbStickNodes
|
rightThumbStickNode.isHidden = hideThumbStickNodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
|
|
||||||
// Setup pause button.
|
// Setup pause button.
|
||||||
let buttonSize = CGSize(width: frame.height / 4, height: frame.height / 4)
|
let buttonSize = CGSize(width: frame.height / 4, height: frame.height / 4)
|
||||||
pauseButton = SKSpriteNode(texture: nil, color: UIColor.clearColor(), size: buttonSize)
|
pauseButton = SKSpriteNode(texture: nil, color: UIColor.clear, size: buttonSize)
|
||||||
pauseButton.position = CGPoint(x: 0, y: frame.height / 2)
|
pauseButton.position = CGPoint(x: 0, y: frame.height / 2)
|
||||||
|
|
||||||
super.init(texture: nil, color: UIColor.clearColor(), size: frame.size)
|
super.init(texture: nil, color: UIColor.clear, size: frame.size)
|
||||||
rightThumbStickNode.delegate = self
|
rightThumbStickNode.delegate = self
|
||||||
leftThumbStickNode.delegate = self
|
leftThumbStickNode.delegate = self
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
A `TouchControlInputNode` is designed to receive all user interaction
|
A `TouchControlInputNode` is designed to receive all user interaction
|
||||||
and forwards it along to the child nodes.
|
and forwards it along to the child nodes.
|
||||||
*/
|
*/
|
||||||
userInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
@ -121,11 +121,11 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
|
|
||||||
// MARK: UIResponder
|
// MARK: UIResponder
|
||||||
|
|
||||||
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesBegan(touches, withEvent: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
let touchPoint = touch.locationInNode(self)
|
let touchPoint = touch.location(in: self)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Ignore touches if the thumb stick controls are hidden, or if
|
Ignore touches if the thumb stick controls are hidden, or if
|
||||||
|
@ -137,20 +137,20 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
}
|
}
|
||||||
|
|
||||||
if touchPoint.x < 0 {
|
if touchPoint.x < 0 {
|
||||||
leftControlTouches.unionInPlace([touch])
|
leftControlTouches.formUnion([touch])
|
||||||
leftThumbStickNode.position = pointByCheckingControlOffset(touchPoint)
|
leftThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
|
||||||
leftThumbStickNode.touchesBegan([touch], withEvent: event)
|
leftThumbStickNode.touchesBegan([touch], with: event)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rightControlTouches.unionInPlace([touch])
|
rightControlTouches.formUnion([touch])
|
||||||
rightThumbStickNode.position = pointByCheckingControlOffset(touchPoint)
|
rightThumbStickNode.position = pointByCheckingControlOffset(suggestedPoint: touchPoint)
|
||||||
rightThumbStickNode.touchesBegan([touch], withEvent: event)
|
rightThumbStickNode.touchesBegan([touch], with: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesMoved(touches, withEvent: event)
|
super.touchesMoved(touches, with: event)
|
||||||
/*
|
/*
|
||||||
If the touch pertains to a `thumbStickNode`, pass the
|
If the touch pertains to a `thumbStickNode`, pass the
|
||||||
touch along to be handled.
|
touch along to be handled.
|
||||||
|
@ -160,44 +160,44 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
over the the `rightThumbStickNode`s zone or vice versa,
|
over the the `rightThumbStickNode`s zone or vice versa,
|
||||||
while ensuring it is handled by the correct thumb stick.
|
while ensuring it is handled by the correct thumb stick.
|
||||||
*/
|
*/
|
||||||
let movedLeftTouches = touches.intersect(leftControlTouches)
|
let movedLeftTouches = touches.intersection(leftControlTouches)
|
||||||
leftThumbStickNode.touchesMoved(movedLeftTouches, withEvent: event)
|
leftThumbStickNode.touchesMoved(movedLeftTouches, with: event)
|
||||||
|
|
||||||
let movedRightTouches = touches.intersect(rightControlTouches)
|
let movedRightTouches = touches.intersection(rightControlTouches)
|
||||||
rightThumbStickNode.touchesMoved(movedRightTouches, withEvent: event)
|
rightThumbStickNode.touchesMoved(movedRightTouches, with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesEnded(touches, withEvent: event)
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
let touchPoint = touch.locationInNode(self)
|
let touchPoint = touch.location(in: self)
|
||||||
|
|
||||||
/// Toggle pause when touching in the pause node.
|
/// Toggle pause when touching in the pause node.
|
||||||
if pauseButton === nodeAtPoint(touchPoint) {
|
if pauseButton === atPoint(touchPoint) {
|
||||||
gameStateDelegate?.controlInputSourceDidTogglePauseState(self)
|
gameStateDelegate?.controlInputSourceDidTogglePauseState(self)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let endedLeftTouches = touches.intersect(leftControlTouches)
|
let endedLeftTouches = touches.intersection(leftControlTouches)
|
||||||
leftThumbStickNode.touchesEnded(endedLeftTouches, withEvent: event)
|
leftThumbStickNode.touchesEnded(endedLeftTouches, with: event)
|
||||||
leftControlTouches.subtractInPlace(endedLeftTouches)
|
leftControlTouches.subtract(endedLeftTouches)
|
||||||
|
|
||||||
let endedRightTouches = touches.intersect(rightControlTouches)
|
let endedRightTouches = touches.intersection(rightControlTouches)
|
||||||
rightThumbStickNode.touchesEnded(endedRightTouches, withEvent: event)
|
rightThumbStickNode.touchesEnded(endedRightTouches, with: event)
|
||||||
rightControlTouches.subtractInPlace(endedRightTouches)
|
rightControlTouches.subtract(endedRightTouches)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
|
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
||||||
super.touchesCancelled(touches, withEvent: event)
|
super.touchesCancelled(touches!, with: event)
|
||||||
|
|
||||||
leftThumbStickNode.resetTouchPad()
|
leftThumbStickNode.resetTouchPad()
|
||||||
rightThumbStickNode.resetTouchPad()
|
rightThumbStickNode.resetTouchPad()
|
||||||
|
|
||||||
// Keep the set's capacity, because roughly the same number of touch events are being received.
|
// Keep the set's capacity, because roughly the same number of touch events are being received.
|
||||||
leftControlTouches.removeAll(keepCapacity: true)
|
leftControlTouches.removeAll(keepingCapacity: true)
|
||||||
rightControlTouches.removeAll(keepCapacity: true)
|
rightControlTouches.removeAll(keepingCapacity: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience Methods
|
// MARK: Convenience Methods
|
||||||
|
@ -228,4 +228,4 @@ class TouchControlInputNode: SKSpriteNode, ThumbStickNodeDelegate, ControlInputS
|
||||||
return CGPoint(x: boundX, y: boundY)
|
return CGPoint(x: boundX, y: boundY)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Sample code project: DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
|
Sample code project: DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
|
||||||
Version: 2.2
|
Version: 2.3
|
||||||
|
|
||||||
IMPORTANT: This Apple software is supplied to you by Apple
|
IMPORTANT: This Apple software is supplied to you by Apple
|
||||||
Inc. ("Apple") in consideration of your agreement to the following
|
Inc. ("Apple") in consideration of your agreement to the following
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
|
# DemoBots: Building a Cross Platform Game with SpriteKit and GameplayKit
|
||||||
|
|
||||||
DemoBots is a fully-featured 2D game built with SpriteKit and GameplayKit, and written in Swift 2.0. It demonstrates how to use agents, goals, and behaviors to drive the movement of characters in your game, and how to use rule systems and state machines to provide those characters with intelligent behavior. You'll see how to integrate on-demand resources into a game to optimize resource usage and reduce the time needed to download additional levels.
|
DemoBots is a fully-featured 2D game built with SpriteKit and GameplayKit, and written in Swift. It demonstrates how to use agents, goals, and behaviors to drive the movement of characters in your game, and how to use rule systems and state machines to provide those characters with intelligent behavior. You'll see how to integrate on-demand resources into a game to optimize resource usage and reduce the time needed to download additional levels.
|
||||||
|
|
||||||
DemoBots takes advantage of the Xcode 7 scene and actions editor to create detailed level designs and animations. The sample also contains assets tailored to ensure the best experience on every supported device.
|
DemoBots takes advantage of the Xcode scene and actions editor to create detailed level designs and animations. The sample also contains assets tailored to ensure the best experience on every supported device.
|
||||||
|
|
||||||
## Release Note
|
## Release Note
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@ DemoBots takes advantage of the Xcode 7 scene and actions editor to create detai
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
Xcode 7.3, OS X 10.11 SDK, iOS 9.0 SDK, tvOS 9.0 SDK
|
Xcode 8.0, OS X 10.12 SDK, iOS 10.0 SDK, tvOS 10.0 SDK
|
||||||
|
|
||||||
### Runtime
|
### Runtime
|
||||||
|
|
||||||
OS X 10.11, iOS 9.0, tvOS 9.0
|
OS X 10.12, iOS 10.0, tvOS 10.0
|
||||||
|
|
||||||
## About DemoBots
|
## About DemoBots
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue