1

The Virtual controllers appear but are then obscured by objects added to the view. I've also tried adding the virtual controllers in the UIViewController but this doesn't work either.

Is it possible to use GCVirtualController directly with SKScene?

class GameScene: SKScene {
    private var _virtualController: Any?
    public var virtualController: GCVirtualController? {
        get { return self._virtualController as? GCVirtualController }
        set { self._virtualController = newValue }
    }
    override func didMove(to view: SKView) {
    let background = SKSpriteNode(imageNamed: ".jpg")
        background.zPosition = -1
        addChild(background)

        let virtualConfig = GCVirtualController.Configuration()
        virtualConfig.elements = [GCInputLeftThumbstick, GCInputRightThumbstick, GCInputButtonA, GCInputButtonB]
        virtualController = GCVirtualController(configuration: virtualConfig)
        virtualController?.connect()
    }
}

It appears the issue only occurs when pushing from one ViewController to the GameViewController.

enter image description here

When launching to the GameViewController the issue does not occur.

originalmyth
  • 129
  • 6
  • 1
    Copy-pasting the code in the question into the default 'Game' template in Xcode 13.1 shows the virtual controller for me (iOS 15, iPod touch 7th gen simulator) . – Brendan Lane Oct 29 '21 at 08:06
  • Hi Brendan, I hadn't realised the issue only occurred when the GameViewController wasn't the launch target. The code does indeed work when the GameViewController is the entry point on the Storyboard but if a different ViewController is and then I show from that ViewController I experience the issue. I've updated the question to show this, thanks for taking a look :) – originalmyth Oct 29 '21 at 16:05
  • 1
    Yep, I see the same behaviour. I was able to determine that the virtual controller does actually appear, but it appears _underneath_ the view being rendered by the second view controller (I set the second view's alpha to 0.5 and could see the controls underneath). No idea why though! – Brendan Lane Nov 08 '21 at 07:46

3 Answers3

2

The Virtual Game Controller can run on any view controller running iOS 15 At least but for the purpose of work it is best viewed in landscape but you need to complete the codes as a physical gamepad.

For the virtual game controller to appear you need to register a physical game controller and apply the functions of notification when connect and disconnect a controller as you do with physical controller exactly.

Here is a code to setup and register a virtual and physical game controller I use and it works for me.

1st you need to import the Game Controller Library

import GameController

Then you define the Virtual Controller Under your Controller Class

class GameViewController: UIViewController {

// Virtual Onscreen Controller
private var _virtualController: Any?
@available(iOS 15.0, *)
public var virtualController: GCVirtualController? {
  get { return self._virtualController as? GCVirtualController }
  set { self._virtualController = newValue }
}

And then you call the setupGameController Function In Your ViewDidLoad()

override func viewDidLoad() {
    super.viewDidLoad() 

    //your code 

    setupGameController()
}

and here is the main function to setup your Virtual and physical game controller

func setupGameController() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.handleControllerDidConnect),
            name: NSNotification.Name.GCControllerDidBecomeCurrent, object: nil)

        NotificationCenter.default.addObserver(
        self, selector: #selector(self.handleControllerDidDisconnect),
        name: NSNotification.Name.GCControllerDidStopBeingCurrent, object: nil)

        if #available(iOS 15.0, *)
        {
            let virtualConfiguration = GCVirtualController.Configuration()
            virtualConfiguration.elements = [GCInputLeftThumbstick,
                                         GCInputRightThumbstick,
                                         GCInputButtonA,
                                         GCInputButtonB]
            virtualController = GCVirtualController(configuration: virtualConfiguration)
        
            // Connect to the virtual controller if no physical controllers are available.
            if GCController.controllers().isEmpty {
                virtualController?.connect()
            }
        }
    
    guard let controller = GCController.controllers().first else {
        return
    }
    registerGameController(controller)
}

Then to act with the virtual or physical gamepad actions you need to assign the connect and register for the game controller as

func handleControllerDidConnect(_ notification: Notification) {
    guard let gameController = notification.object as? GCController else
    {
        return
    }
    unregisterGameController()
    
    if #available(iOS 15.0, *)
    {
        if gameController != virtualController?.controller
        {
            virtualController?.disconnect()
        }
    }
    registerGameController(gameController)
}

func handleControllerDidDisconnect(_ notification: Notification) {
    unregisterGameController()
    
    if #available(iOS 15.0, *) {
        if GCController.controllers().isEmpty
        {
            virtualController?.connect()
        }
    }
}

func registerGameController(_ gameController: GCController) {

    var buttonA: GCControllerButtonInput?
    var buttonB: GCControllerButtonInput?
    
    if let gamepad = gameController.extendedGamepad
    {
        buttonA = gamepad.buttonA
        buttonB = gamepad.buttonB
    }
    
    buttonA?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
        // Put here the codes to run when button A clicked
        print("Button A Pressed")
    }

    buttonB?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
        // Put here the codes to run when button B clicked
        print("Button B Pressed")
    }
}

func unregisterGameController()
{
    
}

And Here Is A Result of the code on a very basic ship sample of Xcode

Virtual Game Controller On iOS Device

Ahmed El-Bermawy
  • 2,173
  • 1
  • 14
  • 16
1

Add this code to Ahmed El-Bermawy's solution:

@objc private func handleControllerDidConnect(_ notification: Notification) {
    
    let controller : GCController = notification.object as! GCController
    
    let status = "MFi Controller: \(String(describing: controller.vendorName)) is connected"
    
    print(status)
    
    registerGameController(controller)

    // make GCControllerView transparent
    
    for TransitionView in UIApplication.shared.keyWindow?.subviews ?? [] {
                    
        for DropShadowView in TransitionView.subviews {
                            
            for LayoutContainerView in DropShadowView.subviews {
                                    
                for ContainerView in LayoutContainerView.subviews {
                                        
                    if "\(ContainerView.classForCoder)" == "GCControllerView" {
                        
                        ContainerView.alpha = 0.6
                        
                    }
                    
                }
                
            }
            
        }
        
    }

}

@objc private func handleControllerDidDisconnect(_ notification: Notification) {
    
    let controller : GCController = notification.object as! GCController
    
    let status = "MFi Controller: \(String(describing: controller.vendorName)) is disconnected"
    
    print(status)
            
}

Additionally, this is how you can control the buttons:

func registerGameController(_ gameController: GCController) {

    print("registerGameController")
    
    var leftThumbstick : GCControllerDirectionPad?
    var buttonA : GCControllerButtonInput?
    var buttonB : GCControllerButtonInput?

    if let gamepad = gameController.extendedGamepad {
        
        leftThumbstick = gamepad.leftThumbstick
        buttonA = gamepad.buttonA
        buttonB = gamepad.buttonB

    }
    
    leftThumbstick?.valueChangedHandler = { [unowned self] _, xValue, yValue in
        
        // Code to handle movement here ...
        
        print("xValue: \(xValue)")
        print("yValue: \(yValue)")
        
        if xValue > 0.5 {
            
            // pressing right 

        } else if xValue < -0.5 {
            
            // pressing left

        } else {
            
            // center

        }

        if yValue > 0.5 {
            
            // pressing up 

        } else if yValue < -0.5 {
            
            // pressing down

        } else {
            
            // center

        }

    }
    
    buttonA?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
        
        // Put here the codes to run when button A clicked

        print("Button A Pressed")
        
        if value == 0 {
            
            // releasing A
            
        } else {
            
            // holding A
            
        }

    }

    buttonB?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in

        // Put here the codes to run when button B clicked

        print("Button B Pressed")
        
        if value == 0 {
            
            // releasing B
            
        } else {
            
            // holding B
            
        }
        
    }

}
Michael N
  • 436
  • 5
  • 6
0

I've encountered the same problem, presenting UIViewController using UINavigationController's present method(no storyboards used).

navigationController.present(
  GameViewController(),
  animated: false
)

I fixed it by setting UINavigationController's viewControllers property to needed view controllers, instead of pushing.

navigationController.viewControllers = [
  UIViewController(),
  GameViewController()
]
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
DjUlt
  • 9
  • 3