GCVirtualController not displaying with SKScene

1k Views Asked by At

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.

3

There are 3 best solutions below

0
On

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

0
On

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()
]
0
On

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
            
        }
        
    }

}