GameController: How to detect Player Index?

Hi, I'm developing a 2 player TVOS Game using Spritekit.


I have managed to detect a Game Controller by using the below method. However, it does not allow me to distinguish whether a press is coming from player 1 or 2?

I tried using self.gameController.playerIndex to detect player Index. It returns -1 for both Apple TV Remote and 3rd Party SteelSeries Game Controller.


Am I doing it the right way ? is there any other way of doing this ?


-(void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event{
       NSLog(@"press come from game controller player Index: %ld with Vendor Name %@",(long)self.gameController.playerIndex, self.gameController.vendorName);
}
-(void)controllerStateChanged
{
    if ([[GCController controllers] count] > 0)
    {
        NSLog(@"controller connected");
            self.controllerConnected = YES;
          
            self.gameController = [GCController controllers][0];
            if (self.gameController.extendedGamepad == nil)
            {
                self.controllerType = 1;
                        NSLog(@"controller type 1 connected");
            } else
            {
                self.controllerType = 2;
                                        NSLog(@"controller type 2 connected");
            }
          
    } else
    {
            self.controllerConnected = NO;
                                NSLog(@"no controller connected");
    }
    NSLog(@"number of controller connected is %ld",[[GCController controllers] count]);
}
-(void)setupGameController
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerStateChanged) name:GCControllerDidConnectNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerStateChanged) name:GCControllerDidDisconnectNotification object:nil];
  
    [GCController startWirelessControllerDiscoveryWithCompletionHandler:^{
        [self controllerStateChanged];
    }];
}

playerIndex is something you are responsible for setting. -1 means it hasn't been set (by you). When you do set it, it will light up the LED player indicator (on the stratus it has 4 leds towards the top of the controller, under the brand label.


It looks like in the code above you are only using the first controller in the -controllers array:


self.gameController = [GCController controllers][0];


If you have two controllers connected, you'll have two GCController objects. The second controller would be [GCController controllers][1]. Any change to the -controllers array you should be able to catch and handle by observing the GCControllerDidConnectNotification and GCControllerDidDisconnectNotification notifications - a GCController object maps to a physical controller.

Thank you for your reply.


I have made changes to set the playerIndex... and resgister Gamepad to receive notification when buttons is pressed.

I still have issue on how to detect which player is pressing ? because when my gamepad is pressed, PressedBegan is still called.


I'm trying to implement for remote and game controllers.

-(void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event{ 
       NSLog(@"press come from game controller player Index: %ld with Vendor Name %@",(long)self.gameController1.playerIndex, self.gameController1.vendorName);
       NSLog(@"press come from game controller player Index: %ld with Vendor Name %@",(long)self.gameController2.playerIndex, self.gameController2.vendorName); 
}


-(void)controllerStateChanged
{
    if ([[GCController controllers] count] < 1) {
        self.controllerConnected = NO;
        NSLog(@"no controller connected");
    }
    else if([[GCController controllers] count] < 2){
        self.gameController1 = [GCController controllers][0];
        [self.gameController1 setPlayerIndex:0];
    }
    else if([[GCController controllers] count] < 3){
        self.gameController1 = [GCController controllers][0];
        self.gameController2 = [GCController controllers][1];
     
        [self.gameController1 setPlayerIndex:0];
        [self.gameController2 setPlayerIndex:1];
     
        [self registerGamepad];
    }
}


- (void) registerGamepad
{
   GCGamepad *profile = self.gameController2.gamepad;
    profile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
    {
        if (pressed)
            NSLog(@"button A is pressed");
    };
    profile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
    {
        if (pressed)
            NSLog(@"button B is pressed");
    };
}

Some conceptual things about your code:


1.You are assuming that controllers[0] is always Remote controller, but it is possible that due power savings/battery/bt connection problems, the remote could be in a disconnected status, and in a future connection, it can take the 2nd position in the array. It is not guaranteed that it will be always the first element of controllers array. I'd suggest you to check the GCMicroGamepad profile of each controller before making any assignation.

2. Think about this possible scenery: I'm a single player that have bought a gamepad to play my favorite title, because I don't like the Remote for playing. With that code, you are always tied in single player mode to play with the remote. You should revise your concept about the relationship between player and controller.

3. IMHO, assigning callback blocks for each button should be done in a few, very specific cases (only with single player games), because you are losing the reference of the controller. You should setup some ivars in order to accomplish what you are looking for. Instead, the second approach suggested by Apple doc about setting a callback block for each gamecontroller (Listing 3-2 - https://developer.apple.com/library/prerelease/tvos/documentation/ServicesDiscovery/Conceptual/GameControllerPG/ReadingControllerInputs/ReadingControllerInputs.html#//apple_ref/doc/uid/TP40013276-CH3-SW1)is much better for multiplayer environments than assigning a block for each button. You can make an accessory function that processes the event,called from the callback block, better than the pressBegan. With this approach, you have the GCGamepad that originates the event.

hi Macoa,


thank you very much for your reply.


1. I agree with you that my code is too rigid. Are you saying that checking GCMicroGamepad profile allows to identify the type of controller it is ? Remote or GamePad? how do I do this ?

2. I'm assuming if I implement #1 correctly, it will handle this issue.

3. I dont understand on this one. I was using Listing 3-3 as an example in my code. Isn't listing 3-2 also using Block ?

Sorry i'm new to the whole Game controller & Multiplayer programming. Your help is much appreciated.

Hi kewl,


Exactly. Apple recommends to explore from extended profile to micro profile. Source code:

for (GCController *element in [GCController controllers]) {
            if(element.extendedGamepad)
            {
                     // you can assign
                GCExtendedGamepad *myExtendedGamepad=element.extendedGamepad;
                     // also, you can set up your eventhandler for the extended gamepad profile
                    //callback defined in an accessory function
                myExtendedGamepad.valueChangedHandler=^(GCExtendedGamepad *gamepad, GCControllerElement *element)
               {
                    [self manageExtendedGamepadAction:gamepad forElement:element];
               };

            }
            else if (element.gamepad)
            {
                // Same here, but with
                GCGamepad *myGamepad=element.gamepad;
                myGamepad.valueChangedHandler=^(GCGamepad *gamepad, GCControllerElement *element)
               {
                    //callback defined in an accessory function
                    [self manageGamepadAction:gamepad forElement:element];
               };
            }
            else if (element.microGamepad)
            {
                // Here is your remote.
                GCMicroGamepad *myRemote=element.microGamepad;
                myRemote.valueChangedHandler=^(GCMicroGamepad *gamepad, GCControllerElement *element)
               {
                    [self manageMicroGamepadAction:gamepad forElement:element];
               }

            }
        }


And you should need to declare the accessory function (you may declare 3, one per profile, if you want to give full support to any controller, and if every controller has a different 'extra' behavior (for example, thumbstick for zooming or something like that). You should declare the other two for full profile coverage, or make an intermediary if you only use the dPad.


Covering extendedGamepad

-(void)manageExtendedGamepadAction:(GCExtendedGamepad *)gamepad forElement:(GCControllerElement *)element
{

          //Check playerIndex here.
          //if(gamepad.playerIndex==yourindex).....

            if((gamepad.dpad==element) && gamepad.dpad.up.isPressed==1)
                    //Dpad.up of extendedgamepad has been pressed

            if((gamepad.dpad==element) && gamepad.dpad.down.isPressed==1)
                    //Same for down
            // .. continue declaring events

    }
}


Covering Gamepad

-(void)manageGamepadAction:(GCGamepad *)gamepad forElement:(GCControllerElement *)element
{
          //Check playerIndex here.
          //if(gamepad.playerIndex==yourindex).....
            if((gamepad.dpad==element) && gamepad.dpad.up.isPressed==1)
                    //Dpad.up of extendedgamepad has been pressed
            if((gamepad.dpad==element) && gamepad.dpad.down.isPressed==1)
                    //Same for down
            // .. continue declaring events
    }
}


Covering Remote

-(void)manageMicroGamepadAction:(GCMicroGamepad *)gamepad forElement:(GCControllerElement *)element
{
          //Check playerIndex here.
          //You only receive events from Remote here.
          //if(gamepad.playerIndex==yourindex).....

            if((gamepad.dpad==element) && gamepad.dpad.up.isPressed==1)
                    //Dpad.up of extendedgamepad has been pressed
            if((gamepad.dpad==element) && gamepad.dpad.down.isPressed==1)
                    //Same for down
            // .. continue declaring events
    }
}


(Source code syntax not checked, may content typos)


With three accessory, you can exactly define the behavior of your app with any type of controller. However, you can minimize the code by eliminating the extendedGamepad if your game does not needs extra sticks or buttons. The extendedGamepad also return simple 'gamepad' profile, as they are a minimized version.


With this approach, you can load any kind of combination of controllers, and you can check per event the playerIndex that generates the action in the callback function, and you decide the 'element' your app is capable to handle in the same place.

Hi Macoa,


thank you very much, it's very helpful. One last question:


with the remote being detected as MicroGamePad, how do I detech touch, swipe, and press on Menu, Play/Pause button ?

MicroGamePad only has buttonA, X and dPad.


thank you again.

You can set up the usual gestureRecognizers at same time and gather that data (when you set up a projet using game template for tvos, the source code generated shows how to do). But think about that, to have gestures & dpad-style control at same time in a game sounds very complex in terms of playability. About the other buttons, I haven't look for that, but it has to be easy...

Thank you for your reply.


with regards to gamepad or extended gamepad, there's an issue because, when I press buttonA down, it keeps calling the method continously until i lift up buttonA. This does not happen with the remote.

How do I only call once in Gamepad ?


Also, how do we handle iPhone as a Game Controller ?

Many 3rd party MFi gamepads have "analog" A/B/X/Y/etc buttons, where the amount you press the button gives a different value. So, as you are holding down the button the exact amount of pressure is changing, which the controller reports to the ATV, so the GameController framework reports this to your game in case your game cares about how far down the player is pressing the button.

GameController: How to detect Player Index?
 
 
Q