Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 DesignEssentialsGraphics & Games

WWDC21 · 15 min · Design / Essentials / Graphics & Games

Tap into virtual and physical game controllers

It’s time to up your input game: Learn about the latest improvements to virtual and physical game controllers for iPhone, iPad, Mac, and Apple TV. Meet the virtual on-screen controller, which turns touch input into game controller input, and find out how to add controller sharing features to your app. We’ll also show you how to support adaptive trigger technology found in DualSense controllers, provide best practices for controller support, and take you through some common pre-flight checks around accessible and customizable input before submitting to the App Store. For more information on saving highlight clips from a game controller, check out “Discover rolling clips in ReplayKit” from WWDC21.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 4 snippets

GameController Basics swift · at 2:06 ↗
func setupGameController() {
    // Add handler for when controller connects.
    NotificationCenter.default.addObserver(
        forName: NSNotification.Name.GCControllerDidConnect,
        object: nil,
        queue: nil)
        { (note) in
            guard let _controller = note.object? as GCController else {
              return
            }
            // Add controller input change handlers.
            _controller.physicalInputProfile[GCInputButtonA]?.valueChangedHandler = { ... }
            _controller.physicalInputProfile[GCInputButtonB]?.valueChangedHandler = { ... }
        }

    // Add handler for when controller disconnects.
    NotificationCenter.default.addObserver(
        forName: NSNotification.Name.GCControllerDidDisconnect,
        object: nil,
        queue: nil)
        { (note) in ... }
}

// Polling for controller input.
func checkInput() {
    if controller.physicalInputProfile[GCInputButtonA]?.pressed { ... }
    if controller.physicalInputProfile[GCInputButtonB]?.pressed { ... }
}
Virtual Controller Initial swift · at 8:42 ↗
// Creating an on-screen controller

let virtualConfiguration = GCVirtualControllerConfiguration()

virtualConfiguration.elements = [GCInputLeftThumbstick,
                                 GCInputRightThumbstick,
                                 GCInputButtonA,
                                 GCInputButtonB]

let virtualController = GCVirtualController(configuration: virtualConfiguration)

virtualController.connect()
Customizing Buttons swift · at 9:17 ↗
// Creating customized buttons

vc.changeElement(GCInputButtonA) { 
  config in
    let spinningPath = UIBezierPath()
    // load or draw the spinning attack path
    config.path = spinningPath
    return config
}

vc.changeElement(GCInputButtonB) {
  config in
    let jumpPath = UIBezierPath()
    // load or draw the jump path
    config.path = jumpPath
    return config
}
DualSense Adaptive Triggers swift · at 12:06 ↗
func updateControllerAdaptiveTriggers() {
  guard let dualSense = GCController.current?.physicalInputProfile as? GCDualSenseGamepad
    else {
        return
    }
  let adaptiveTrigger = dualSense.rightTrigger
  if playerIsPullingSlingshot {
    let resistiveStrength = min(1, 0.4 + adaptiveTrigger.value)
    if adaptiveTrigger.value < 0.9 {
      adaptiveTrigger.setModeFeedbackWithStartPosition(
        0,
        resistiveStrength: resistiveStrength)
    } else {
      adaptiveTrigger.setModeVibrationWithStartPosition(
        0,
        amplitude: resistiveStrength,
        frequency: 0.03)
    }
  } else if adaptiveTrigger.mode != .off {
    adaptiveTrigger.setModeOff()
  }
}

Resources