DemoBots/KeyboardControlInputSource.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
An implementation of the `ControlInputSourceType` protocol that enables support for keyboard input on OS X. |
*/ |
import simd |
class KeyboardControlInputSource: ControlInputSourceType { |
// MARK: Properties |
/// The vector used to keep track of movement. |
var currentDisplacement = float2() |
/// Bookkeeping to ignore repeating keys. |
var downKeys = Set<Character>() |
/// `ControlInputSourceType` delegates. |
weak var gameStateDelegate: ControlInputSourceGameStateDelegate? { |
didSet { |
// When the delegate is assigned, reset the control state. |
resetControlState() |
} |
} |
weak var delegate: ControlInputSourceDelegate? { |
didSet { |
// When the delegate is assigned, reset the control state. |
resetControlState() |
} |
} |
let allowsStrafing = false |
/// Values representing different relative motions the keyboard is capable of supplying. |
static let forwardVector = float2(x: 1, y: 0) |
static let backwardVector = float2(x: -1, y: 0) |
static let clockwiseVector = float2(x: 0, y: -1) |
static let counterClockwiseVector = float2(x: 0, y: 1) |
// MARK: Control Handling |
func handleMouseDownEvent() { |
delegate?.controlInputSourceDidBeginAttacking(self) |
} |
func handleMouseUpEvent() { |
delegate?.controlInputSourceDidFinishAttacking(self) |
} |
/// The logic matching a key press to `ControlInputSourceDelegate` calls. |
func handleKeyDown(forCharacter character: Character) { |
// Ignore repeat input. |
if downKeys.contains(character) { |
return |
} |
downKeys.insert(character) |
// Retrieve the `relativeDisplacement` vector mapped for each displacement character ("wasd" and arrow keys). |
if let relativeDisplacement = relativeDisplacementForCharacter(character) { |
// Add to the `currentDisplacement` to track the overall displacement. |
currentDisplacement += relativeDisplacement |
if isDirectionalDisplacementVector(relativeDisplacement) { |
// Forward or backward displacement. |
delegate?.controlInputSource(self, didUpdateWithRelativeDisplacement: currentDisplacement) |
} |
else { |
// Rotational displacement. |
delegate?.controlInputSource(self, didUpdateWithRelativeAngularDisplacement: currentDisplacement) |
} |
/* |
Game focus navigation relies on strict 2D coordinates. |
Translate the relative input into directional coordinates. |
*/ |
let directionalVector = float2(x: -relativeDisplacement.y, y: relativeDisplacement.x) |
if let direction = ControlInputDirection(vector: directionalVector) { |
gameStateDelegate?.controlInputSource(self, didSpecifyDirection: direction) |
} |
} |
else if isAttackCharacter(character) { |
// An attack command was requested. |
delegate?.controlInputSourceDidBeginAttacking(self) |
// Ignore the spacebar for game selection behavior. All other attack commands are valid. |
if character != " " { |
gameStateDelegate?.controlInputSourceDidSelect(self) |
} |
} |
else { |
// Account for the other possible kinds of actions. |
#if DEBUG |
switch character { |
case "/": |
gameStateDelegate?.controlInputSourceDidToggleDebugInfo(self) |
case "[": |
gameStateDelegate?.controlInputSourceDidTriggerLevelSuccess(self) |
case "]": |
gameStateDelegate?.controlInputSourceDidTriggerLevelFailure(self) |
default: break |
} |
#endif |
} |
} |
// Handle the logic matching when a key is released to `ControlInputSource` delegate calls. |
func handleKeyUp(forCharacter character: Character) { |
// Ensure the character was accounted for by `handleKeyDown(forCharacter:)`. |
guard downKeys.remove(character) != nil else { return } |
if let relativeDisplacement = relativeDisplacementForCharacter(character) { |
// Subtract from the `currentDisplacement` if a displacement key has been released. |
currentDisplacement -= relativeDisplacement |
if downKeys.isEmpty { |
// Ensure that the `currentDisplacement` is zero if there are no keys pressed. |
currentDisplacement = float2() |
} |
if isDirectionalDisplacementVector(relativeDisplacement) { |
delegate?.controlInputSource(self, didUpdateWithRelativeDisplacement: currentDisplacement) |
} |
else { |
delegate?.controlInputSource(self, didUpdateWithRelativeAngularDisplacement: currentDisplacement) |
} |
} |
else if isAttackCharacter(character) { |
// An attack command finished. |
delegate?.controlInputSourceDidFinishAttacking(self) |
} |
else { |
// Account for the other possible kinds of actions. |
switch character { |
case "p": |
gameStateDelegate?.controlInputSourceDidTogglePauseState(self) |
default: break |
} |
} |
} |
// MARK: ControlInputSourceType |
func resetControlState() { |
// Reset the `currentDisplacement` and clear the currently tracked keys. |
currentDisplacement = float2() |
downKeys.removeAll() |
delegate?.controlInputSource(self, didUpdateWithRelativeDisplacement: currentDisplacement) |
delegate?.controlInputSource(self, didUpdateWithRelativeAngularDisplacement: currentDisplacement) |
} |
// MARK: Convenience |
private func isDirectionalDisplacementVector(_ displacement: float2) -> Bool { |
return displacement == KeyboardControlInputSource.forwardVector |
|| displacement == KeyboardControlInputSource.backwardVector |
} |
private func relativeDisplacementForCharacter(_ character: Character) -> float2? { |
let mapping: [Character: float2] = [ |
// Up arrow. |
Character(UnicodeScalar(0xF700)!): KeyboardControlInputSource.forwardVector, |
"w": KeyboardControlInputSource.forwardVector, |
// Down arrow. |
Character(UnicodeScalar(0xF701)!): KeyboardControlInputSource.backwardVector, |
"s": KeyboardControlInputSource.backwardVector, |
// Left arrow. |
Character(UnicodeScalar(0xF702)!): KeyboardControlInputSource.counterClockwiseVector, |
"a": KeyboardControlInputSource.counterClockwiseVector, |
// Right arrow. |
Character(UnicodeScalar(0xF703)!): KeyboardControlInputSource.clockwiseVector, |
"d": KeyboardControlInputSource.clockwiseVector |
] |
return mapping[character] |
} |
/// Indicates if the provided character should trigger an attack. |
private func isAttackCharacter(_ character: Character) -> Bool { |
return ["f", " ", "\r"].contains(character) |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13