Create virtual game controller in Swift

WWDC 2021 brought new API for creating and customising ( to an extent ) a virtual game controller for your application.


To display a virtual game controller on the screen, import the GameController Module

import GameController


Create a function where you start observing when a new game controller connects and disconnects to and from your device. We do this in order to make some input registration on connecting a new virtual game controller and some housekeeping after the controller has disconnected.

    private func addObservers() {
  //1
        NotificationCenter.default.addObserver(forName: NSNotification.Name.GCControllerDidConnect,
                                               object: nil,
                                               queue: nil) { [weak self] notification in
            print("Controller connected ...")
  //2
            guard let gameController = notification.object as? GCController,
                  gameController.vendorName != nil else {
                      return
                  }
  //3
            self?.registerControllerInputs()
        }

  //4
        NotificationCenter.default.addObserver(forName: NSNotification.Name.GCControllerDidDisconnect,
                                               object: nil,
                                               queue: nil) { [weak self] notification in
            print("Controller disconnected ... ")
  //5
            self?.virtualController = nil
        }
    }


 Let's break this down:

 1 - Observe for when a controller connects to your device
 2 - Unwrap the GCController object and if you like, also check if it has a vendorName
 3 - Register all the buttons, pads and other input controls from your controller ( we will get to that later )
 4 - Also register for when your game controller disconnects from your device
 5 - Make sure you set your local variable that holds the virtual controller reference to nil ( read on )


 Now that we know when a new game controller connects, let's create one and make it connect. First of all, create a new variable that will hold a reference to our virtual game controller somewhere in our code. 

     private var virtualController: GCVirtualController?


Next, let's write the piece of code that connects a new virtual game controller.

     private func setupVirtualController() {
        let virtualConfiguration = GCVirtualControllerConfiguration()
        virtualConfiguration.elements = [GCInputLeftThumbstick,
                                         GCInputRightThumbstick,
                                         GCInputButtonA,
                                         GCInputButtonB]
        virtualController = GCVirtualController(configuration: virtualConfiguration)
        virtualController?.connect()
    }


 As you can see above, you create a game controller by first creating a GCVirtualControllerConfiguration and set its elements value to be an array of input options like a thumbstick or a button. You can customise your game controller elements to your needs. You pass that configuration as a parameter for the GCVirtualController initialiser and set it to be your virtualController.

We still haven't covered the registration of the buttons and pads of your game controller. Let's do that now.


     private func registerControllerInputs() {
  #1
        guard let gameController = virtualController?.controller else { return }

  #2
        (gameController.physicalInputProfile[GCInputButtonA] as? GCControllerButtonInput)?.valueChangedHandler = { [weak self] button, value, pressed in
            if value == 1 {
                print("button: \(button) pressed")
            }
        }

        (gameController.physicalInputProfile[GCInputButtonB] as? GCControllerButtonInput)?.valueChangedHandler = { button, value, pressed in
            if value == 1 {
                print("button: \(button) pressed")
            }
        }
        (gameController.physicalInputProfile[GCInputLeftThumbstick] as? GCControllerDirectionPad)?.valueChangedHandler = { button, value, pressed in
            print(value)
        }
        (gameController.physicalInputProfile[GCInputRightThumbstick] as? GCControllerDirectionPad)?.valueChangedHandler = { button, value, pressed in
            print(value)
        }
    }


 So what's happening here:
 1 - First you need to make sure that the instance of your virtual game controller exists.
 2 - If it is, good, we go over each input element of our virtual game controller, and set a valueChangedHandler to specify what that input control will trigger. I am simply printing out that  the button was pressed or the value of the thumbstick for the pads.

 Don't forget to register all the input elements that you specified in the virtual game controller configuration.

 When you are done, call these methods whenever you want the virtual controller to be initialised, e.g. in a viewDidLoad method

     override func viewDidLoad() {
        super.viewDidLoad()

        addObservers()
        setupVirtualController()
    }