[LibGDX] Control Scene2D Buttons With A Controller

LibGDX provides a great way to setup a UI, but it only works with touch or mouse and keyboard.  I really wanted to control the main menu of my game with my controller, so I made a wrapper to simulate mouse movement with the D-pad and mouse click with the A button.

Note: this only supports buttons in a single row or column, I will probably being adding support for selecting buttons in a table grid when I add my in game menu. Also, I may add support for controlling buttons through the keyboard arrow keys.

Hopefully this helps someone else!

public class MenuControllerListener implements ControllerListener {

private final Group buttonGroup;

private int currentButtonIndex = 0;

public MenuControllerListener(Group buttonGroup) {
this.buttonGroup = buttonGroup;
}

@Override public void connected(Controller controller) { }
@Override public void disconnected(Controller controller) { }

@Override
public boolean buttonDown(Controller controller, int buttonCode) {
if(buttonGroup.getChildren().size == 0) return false;
if(controller.getName().toLowerCase().contains("xbox") &&
controller.getName().contains("360")){
switch(buttonCode) {
case Xbox360Pad.BUTTON_A:
Actor currentButton = buttonGroup.getChildren().get(currentButtonIndex);
return clickButton(currentButton);
}
}
return false;
}

@Override
public boolean buttonUp(Controller controller, int buttonCode) {
if(buttonGroup.getChildren().size == 0) return false;
if(controller.getName().toLowerCase().contains("xbox") &&
controller.getName().contains("360")){
switch(buttonCode) {
case Xbox360Pad.BUTTON_A:
Actor currentButton = buttonGroup.getChildren().get(currentButtonIndex);
return releaseButton(currentButton);
}
}
return false;
}

/**
* Simulate button click down.
* @param button
* @return
*/
private boolean clickButton(Actor button) {
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.touchDown);
event.setButton(Input.Buttons.LEFT);

button.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}

/**
* Simulate button click release.
* @param button
* @return
*/
private boolean releaseButton(Actor button) {
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.touchUp);
event.setButton(Input.Buttons.LEFT);

button.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}

@Override
public boolean axisMoved(Controller controller, int axisCode, float value) { return false; }

@Override
public boolean povMoved(Controller controller, int povCode, PovDirection value) {
if(buttonGroup.getChildren().size == 0) return false;
if(controller.getName().toLowerCase().contains("xbox") &&
controller.getName().contains("360")){

unselectButton(buttonGroup.getChildren().get(currentButtonIndex));

switch(value) {
case north:
case west:
currentButtonIndex--;
break;
case south:
case east:
currentButtonIndex++;
break;
default:
break;
}

currentButtonIndex = currentButtonIndex % buttonGroup.getChildren().size;
if(currentButtonIndex < 0) currentButtonIndex = buttonGroup.getChildren().size - 1;

return selectButton(buttonGroup.getChildren().get(currentButtonIndex));
}

return false;
}

/**
* Simulate mousing over a button.
* @param button
* @return
*/
private boolean selectButton(Actor button) {
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.enter);

button.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}

/**
* Simulate mousing off of a button.
* @param button
* @return
*/
private boolean unselectButton(Actor button) {
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.exit);

button.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}

@Override public boolean xSliderMoved(Controller controller, int sliderCode, boolean value) { return false; }
@Override public boolean ySliderMoved(Controller controller, int sliderCode, boolean value) { return false; }
@Override public boolean accelerometerMoved(Controller controller, int accelerometerCode, Vector3 value) { return false; }
}

Example usage:

TextButton.TextButtonStyle buttonStyle = new TextButton.TextButtonStyle();
buttonStyle.font = new BitmapFont();
buttonStyle.font.scale(2f);
buttonStyle.fontColor = Color.WHITE;
buttonStyle.overFontColor = Color.LIGHT_GRAY;
buttonStyle.downFontColor = Color.GRAY;

newGameButton = new TextButton("New Game", buttonStyle);
continueButton = new TextButton("Continue", buttonStyle);
optionsButton = new TextButton("Options", buttonStyle);
exitButton = new TextButton("Exit", buttonStyle);

Table buttonGroup = new Table();
buttonGroup.setFillParent(true);
buttonGroup.align(Align.center);
buttonGroup.add(newGameButton);
buttonGroup.row();
buttonGroup.add(continueButton);
buttonGroup.row();
buttonGroup.add(optionsButton);
buttonGroup.row();
buttonGroup.add(exitButton);

stage.addActor(buttonGroup);

Controllers.addListener(new MenuControllerListener(buttonWrapper));