Question

I'm attempting to make my menu navigable with the arrow keys or via the d-pad on a controller. So Far I've had no luck.

The question is: Can someone walk me through how to make my current menu or any libgdx menu keyboard accessible?

I'm a bit noobish with some stuff and I come from a Javascript background.

Here's an example of what I'm trying to do:

http://dl.dropboxusercontent.com/u/39448/webgl/qb/qb.html

For a simple menu that you can just add a few buttons to and it run out of the box use this:

http://www.sadafnoor.com/blog/how-to-create-simple-menu-in-libgdx/

Or you can use my code but I use a lot of custom styles.

And here's an example of my code:

import aurelienribon.tweenengine.Timeline;
import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenManager;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.project.game.tween.ActorAccessor;

public class MainMenu implements Screen {

 private SpriteBatch batch;
 private Sprite menuBG;
 private Stage stage;
 private TextureAtlas atlas;
 private Skin skin;
 private Table table;
 private TweenManager tweenManager;

 @Override
 public void render(float delta) {
  Gdx.gl.glClearColor(0, 0, 0, 1);
  Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);


  batch.begin();
     menuBG.draw(batch);
  batch.end();

  //table.debug();


  stage.act(delta);
  stage.draw();
  //Table.drawDebug(stage);

  tweenManager.update(delta);
}

@Override
public void resize(int width, int height) {
  menuBG.setSize(width,  height);
  stage.setViewport(width, height, false);
  table.invalidateHierarchy();
}

@Override
public void resume() {

}

@Override
public void show() {

  stage = new Stage();

  Gdx.input.setInputProcessor(stage);

  batch = new SpriteBatch();

  atlas = new TextureAtlas("ui/atlas.pack");
  skin = new Skin(Gdx.files.internal("ui/menuSkin.json"), atlas);

  table = new Table(skin);
  table.setBounds(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());


  // Set Background 
  Texture menuBackgroundTexture = new Texture("images/mainMenuBackground.png");
  menuBG = new Sprite(menuBackgroundTexture);
  menuBG.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

  // Create Main Menu Buttons

  // Button Play
  TextButton buttonPlay = new TextButton("START", skin, "inactive");
  buttonPlay.addListener(new ClickListener() {
     @Override
     public void clicked(InputEvent event, float x, float y) {
        ((Game) Gdx.app.getApplicationListener()).setScreen(new LevelMenu());
     }
  });

  buttonPlay.addListener(new InputListener() {
       public boolean keyDown (InputEvent event, int keycode) {
               System.out.println("down");
               return true;
       }
  });

  buttonPlay.padBottom(12);
  buttonPlay.padLeft(20);
  buttonPlay.getLabel().setAlignment(Align.left);

  // Button EXTRAS
     TextButton buttonExtras = new TextButton("EXTRAS", skin, "inactive");
    buttonExtras.addListener(new ClickListener() {
         @Override
         public void clicked(InputEvent event, float x, float y) {
             ((Game) Gdx.app.getApplicationListener()).setScreen(new ExtrasMenu());
         }

    });
    buttonExtras.padBottom(12);
    buttonExtras.padLeft(20);
    buttonExtras.getLabel().setAlignment(Align.left);

    // Button Credits
     TextButton buttonCredits = new TextButton("CREDITS", skin, "inactive");
    buttonCredits.addListener(new ClickListener() {
         @Override
         public void clicked(InputEvent event, float x, float y) {
           ((Game) Gdx.app.getApplicationListener()).setScreen(new Credits());
         }

    });
    buttonCredits.padBottom(12);
    buttonCredits.padLeft(20);
    buttonCredits.getLabel().setAlignment(Align.left);

    // Button Settings
    TextButton buttonSettings = new TextButton("SETTINGS", skin, "inactive");
    buttonSettings.addListener(new ClickListener() {
         @Override
         public void clicked(InputEvent event, float x, float y) {
           ((Game) Gdx.app.getApplicationListener()).setScreen(new Settings());
         }

    });
    buttonSettings.padBottom(12);
    buttonSettings.padLeft(20);
    buttonSettings.getLabel().setAlignment(Align.left);

  // Button Exit
    TextButton buttonExit = new TextButton("EXIT", skin, "inactive");
  buttonExit.addListener(new ClickListener() {
     @Override
     public void clicked(InputEvent event, float x, float y) {
        Gdx.app.exit();
     }
  });
  buttonExit.padBottom(12);
  buttonExit.padLeft(20);
  buttonExit.getLabel().setAlignment(Align.left);


  // Adding Heading-Buttons to the cue
  table.add().width(190);
  table.add().width((table.getWidth() / 10) * 3);
  table.add().width((table.getWidth() / 10) * 5).height(140).spaceBottom(50);
  table.add().width(190).row();

  table.add().width(190);
  table.add(buttonPlay).spaceBottom(20).width(460).height(110);
  table.add().row();

  table.add().width(190);
  table.add(buttonExtras).spaceBottom(20).width(460).height(110);
  table.add().row();

  table.add().width(190);
  table.add(buttonCredits).spaceBottom(20).width(460).height(110);
  table.add().row();

  table.add().width(190);
  table.add(buttonSettings).spaceBottom(20).width(460).height(110);
  table.add().row();

  table.add().width(190);
  table.add(buttonExit).width(460).height(110);
  table.add().row();
  stage.addActor(table);

  // Animation Settings
  tweenManager = new TweenManager();
  Tween.registerAccessor(Actor.class, new ActorAccessor());


  // Heading and Buttons Fade In
  Timeline.createSequence().beginSequence()
     .push(Tween.set(buttonPlay, ActorAccessor.ALPHA).target(0))
     .push(Tween.set(buttonExtras, ActorAccessor.ALPHA).target(0))
     .push(Tween.set(buttonCredits, ActorAccessor.ALPHA).target(0))
     .push(Tween.set(buttonSettings, ActorAccessor.ALPHA).target(0))
     .push(Tween.set(buttonExit, ActorAccessor.ALPHA).target(0))

     .push(Tween.to(buttonPlay, ActorAccessor.ALPHA, .5f).target(1))
     .push(Tween.to(buttonExtras, ActorAccessor.ALPHA, .5f).target(1))
     .push(Tween.to(buttonCredits, ActorAccessor.ALPHA, .5f).target(1))
     .push(Tween.to(buttonSettings, ActorAccessor.ALPHA, .5f).target(1))
     .push(Tween.to(buttonExit, ActorAccessor.ALPHA, .5f).target(1))
     .end().start(tweenManager);

  tweenManager.update(Gdx.graphics.getDeltaTime());

}

public static Vector2 getStageLocation(Actor actor) {
   return actor.localToStageCoordinates(new Vector2(0, 0));
}

@Override
public void dispose() {
  stage.dispose();
  atlas.dispose();
  skin.dispose();
  menuBG.getTexture().dispose();
}

@Override
public void hide() {
  dispose();
}

@Override
public void pause() {

}

}
Was it helpful?

Solution

I just figured this out myself!

I made a ControllerListener that simulates the Mouse Input events that Scene2D buttons expect. As for controlling them with the arrow keys, you could easily apply the same methods to a new InputProccessor that handled their respective key events.

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

This requires you to add your buttons to a Table, example use:

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));

OTHER TIPS

To handle events use the InputProcessor interface to develop a simple InputProcessor which you add to take the Gdx input events.

MyInputProcessor inputProcessor = new MyInputProcessor();
Gdx.input.setInputProcessor(inputProcessor);

Just handle the different keys inside of the

   @Override
   public boolean keyDown (int keycode) {
      return false;
   }

You do use the Keyinteger values for it. Just use a simple switch case to check which key get pressed.

import com.badlogic.gdx.Input.Keys;

and use the statics for example Keys.LEFT. A simple switchcase like this:

@Override
public boolean keyDown (int keycode) {
        switch (keycode) {
        case Keys.LEFT:
            // handle left push
            break;
        case Keys.RIGHT:
            // handle right push
            break;
        case Keys.DOWN:
            // handle down push
            break;
        // handle more keys here if you need

        default:
            // unused key pushed
            break;
        }
    return false;
}

Dont forget to return false or true depending on what you need. As you can see, it is a simple integer value, so if there is a key from a controler do check which intergervalue it is to use it inside of your switch.

For more information about the interface and how everything should work take a look at the Wiki of libgdx and get more detailed explenation. InputHandling

Regards

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top