pixelboyz logo
Desarrollo de Videojuegos

Tutorial de LibGDX Parte 14: Compatibilidad con gamepad

libgdxController

Índice de contenido


Hoy vamos a ver cómo agregar soporte para gamepad en LibGDX, específicamente el controlador XBox 360. ¿Por qué el controlador Xbox 360? Bueno, es el controlador que yo (y el 90% de los otros jugadores de PC) tengo. Debería poder modificar lo siguiente para que funcione con cualquier gamepad, pero se deja como ejercicio para el espectador.

Hay dos cosas de las que debemos ser conscientes de inmediato. En primer lugar, la compatibilidad con el controlador se realiza a través de una extensión, por lo que cuando crea su proyecto, debe seleccionar Controlador de la siguiente manera:

A continuación, LibGDX en realidad no es compatible con el controlador 360 de fábrica, solo se envía con asignaciones para el controlador Ouya. Afortunadamente, gracias al poder de Google, ¡encontré a otra persona que hizo el trabajo por nosotros! Hay un ejemplo de código a mitad de camino a través de este hilo. Descargue la fuente incluida y guárdela en un archivo llamado XBox360Pad.java. Sí, el caso es importante. Así que debería verse así:

package com.gamefromscratch;

import com.badlogic.gdx.controllers.PovDirection;

// This code was taken from http://www.java-gaming.org/index.php?topic=29223.0
// With thanks that is!

public class XBox360Pad
{
    /*
     * It seems there are different versions of gamepads with different ID 
     Strings.
     * Therefore its IMO a better bet to check for:
     * if (controller.getName().toLowerCase().contains("xbox") &&
                   controller.getName().contains("360"))
     *
     * Controller (Gamepad for Xbox 360)
       Controller (XBOX 360 For Windows)
       Controller (Xbox 360 Wireless Receiver for Windows)
       Controller (Xbox wireless receiver for windows)
       XBOX 360 For Windows (Controller)
       Xbox 360 Wireless Receiver
       Xbox Receiver for Windows (Wireless Controller)
       Xbox wireless receiver for windows (Controller)
     */
    //public static final String ID = "XBOX 360 For Windows (Controller)";
    public static final int BUTTON_X = 2;
    public static final int BUTTON_Y = 3;
    public static final int BUTTON_A = 0;
    public static final int BUTTON_B = 1;
    public static final int BUTTON_BACK = 6;
    public static final int BUTTON_START = 7;
    public static final PovDirection BUTTON_DPAD_UP = PovDirection.north;
    public static final PovDirection BUTTON_DPAD_DOWN = PovDirection.south;
    public static final PovDirection BUTTON_DPAD_RIGHT = PovDirection.east;
    public static final PovDirection BUTTON_DPAD_LEFT = PovDirection.west;
    public static final int BUTTON_LB = 4;
    public static final int BUTTON_L3 = 8;
    public static final int BUTTON_RB = 5;
    public static final int BUTTON_R3 = 9;
    public static final int AXIS_LEFT_X = 1; //-1 is left | +1 is right
    public static final int AXIS_LEFT_Y = 0; //-1 is up | +1 is down
    public static final int AXIS_LEFT_TRIGGER = 4; //value 0 to 1f
    public static final int AXIS_RIGHT_X = 3; //-1 is left | +1 is right
    public static final int AXIS_RIGHT_Y = 2; //-1 is up | +1 is down
    public static final int AXIS_RIGHT_TRIGGER = 4; //value 0 to -1f
}

Ahora entremos directamente con una muestra:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.ControllerListener;
import com.badlogic.gdx.controllers.Controllers;
import com.badlogic.gdx.controllers.PovDirection;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector3;

public class Gamepad extends ApplicationAdapter implements ControllerListener {
   SpriteBatch batch;
   Sprite sprite;
   BitmapFont font;
   boolean hasControllers = true;
   String message = "Please install a controller";

   @Override
   public void create () {
      batch = new SpriteBatch();
      sprite = new Sprite(new Texture("badlogic.jpg"));
        sprite.setPosition(Gdx.graphics.getWidth()/2 -sprite.getWidth()/2,
                           Gdx.graphics.getHeight()/2-sprite.getHeight()/2);

        // Listen to all controllers, not just one
        Controllers.addListener(this);

        font = new BitmapFont();
        font.setColor(Color.WHITE);


        if(Controllers.getControllers().size == 0)
        {
            hasControllers = false;
        }
    }

   @Override
   public void render () {
      Gdx.gl.glClearColor(0, 0, 0, 0);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      batch.begin();
        if(!hasControllers)
            font.draw(batch,message,
                    Gdx.graphics.getWidth()/2 - font.getBounds(message).width/2,
                    Gdx.graphics.getHeight()/2 - font.getBounds(message).height/2);
        else
          batch.draw(sprite, sprite.getX(), sprite.getY(),sprite.getOriginX(),sprite.getOriginY(),
                    sprite.getWidth(),sprite.getHeight(),
                    sprite.getScaleX(),sprite.getScaleY(),sprite.getRotation());
      batch.end();
   }

    // connected and disconnect dont actually appear to work for XBox 360 controllers.
    @Override
    public void connected(Controller controller) {
        hasControllers = true;
    }

    @Override
    public void disconnected(Controller controller) {
        hasControllers = false;
    }

    @Override
    public boolean buttonDown(Controller controller, int buttonCode) {
        if(buttonCode == XBox360Pad.BUTTON_Y)
            sprite.setY(sprite.getY() + 1);
        if(buttonCode == XBox360Pad.BUTTON_A)
            sprite.setY(sprite.getY()-1);
        if(buttonCode == XBox360Pad.BUTTON_X)
            sprite.setX(sprite.getX() - 1);
        if(buttonCode == XBox360Pad.BUTTON_B)
            sprite.setX(sprite.getX() + 1);

        if(buttonCode == XBox360Pad.BUTTON_LB)
            sprite.scale(-0.1f);
        if(buttonCode == XBox360Pad.BUTTON_RB)
            sprite.scale(0.1f);
        return false;
    }

    @Override
    public boolean buttonUp(Controller controller, int buttonCode) {
        return false;
    }

    @Override
    public boolean axisMoved(Controller controller, int axisCode, float value) {
        // This is your analog stick
        // Value will be from -1 to 1 depending how far left/right, up/down the stick is
        // For the Y translation, I use a negative because I like inverted analog stick
        // Like all normal people do! 😉

        // Left Stick
        if(axisCode == XBox360Pad.AXIS_LEFT_X)
            sprite.translateX(10f * value);
        if(axisCode == XBox360Pad.AXIS_LEFT_Y)
            sprite.translateY(-10f * value);

        // Right stick
        if(axisCode == XBox360Pad.AXIS_RIGHT_X)
            sprite.rotate(10f * value);
        return false;
    }

    @Override
    public boolean povMoved(Controller controller, int povCode, PovDirection value) {
        // This is the dpad
        if(value == XBox360Pad.BUTTON_DPAD_LEFT)
            sprite.translateX(-10f);
        if(value == XBox360Pad.BUTTON_DPAD_RIGHT)
            sprite.translateX(10f);
        if(value == XBox360Pad.BUTTON_DPAD_UP)
            sprite.translateY(10f);
        if(value == XBox360Pad.BUTTON_DPAD_DOWN)
            sprite.translateY(-10f);
        return false;
    }

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

Sorprendentemente, LibGDX en realidad logra admitir controladores en su objetivo HTML, por lo que a continuación se ejecuta el siguiente código. Tal vez.

Sin compatibilidad con iFrame

(Abrir en Nueva ventana)

Por supuesto, estamos hablando aquí de la compatibilidad con gamepad en HTML5, por lo que solo funcionará en un subconjunto muy pequeño de navegadores y es posible que no funcione como se esperaba. Sin embargo, el hecho de que funcione es bastante sorprendente. Es posible que deba abrir los ejemplos en una ventana separada usando el enlace de arriba para que funcione correctamente. Sin embargo, no se preocupe, en un objetivo de escritorio, el código anterior funciona perfectamente.

En este ejemplo estamos adoptando un enfoque basado en eventos. Es decir, a medida que se actualiza el controlador, envía una variedad de eventos a nuestra clase. Esto se hace a través de la interfaz ControllerListener. Como puede ver en las anulaciones, hay una gran cantidad de funciones (movimiento, controles deslizantes, etc.) que son específicas de OUYA. De interés para nosotros son buttonDown, axisMoved y povMoved. buttonDown se llama de manera bastante predecible cuando se presiona uno de los botones del controlador, esto incluye los botones frontales, seleccionar, iniciar y los parachoques, pero no los disparadores. axisMoved se llama para cualquiera de los sticks analógicos, y quizás de manera confusa al principio, los gatillos se mueven. La razón por la que los disparadores son compatibles de esta manera es porque hay un rango de valores en lugar de solo una opción binaria como cuando se trata de botones. La cantidad que se presiona el gatillo es el rango a lo largo del eje del gatillo. Finalmente está povMoved, este es tu DPad, que en realidad es solo un conjunto de 4 botones. Una última cosa a tener en cuenta aquí… los eventos de desconexión y conexión simplemente nunca se activaron para mí, la lógica puede ser específica de OUYA.

Sin embargo, puede notar que el movimiento con el joystick analógico es entrecortado. Esto se debe a que el enfoque basado en eventos no es realmente ideal para controles analógicos. Tienes dos opciones aquí. En lugar de actualizar los controles cada vez que se activa un evento, actualiza un indicador y aplica una lógica de suavizado para mantener el movimiento entre eventos fluido. O, mucho más fácil, usa el sondeo en su lugar. Echemos un vistazo a cómo puede sondear los controles en LibGDX.

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.Controllers;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class Gamepad2 extends ApplicationAdapter {
    SpriteBatch batch;
    Sprite sprite;
    Controller controller;
    BitmapFont font;
    boolean hasControllers = true;
    String message = "Please install a controller";

    @Override
    public void create () {
        batch = new SpriteBatch();
        sprite = new Sprite(new Texture("badlogic.jpg"));
        sprite.setPosition(Gdx.graphics.getWidth()/2 -sprite.getWidth()/2,
                Gdx.graphics.getHeight()/2-sprite.getHeight()/2);

        font = new BitmapFont();
        font.setColor(Color.WHITE);

        if(Controllers.getControllers().size == 0)
        {
            hasControllers = false;
        }
        else
            controller = Controllers.getControllers().first();
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(0, 0, 0, 0);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        if(!hasControllers)
            font.draw(batch,message,
                    Gdx.graphics.getWidth()/2 - font.getBounds(message).width/2,
                    Gdx.graphics.getHeight()/2 - font.getBounds(message).height/2);
        else {
            // Update movement based on movement of left stick
            // Give a "deadzone" of 0.2 - -0.2, meaning the first 20% in either direction will be ignored.
            // This keeps controls from being too twitchy
            // Move by up to 10 pixels per frame if full left or right.
            // Once again I flipped the sign on the Y axis because I prefer inverted Y axis controls.
            if(controller.getAxis(XBox360Pad.AXIS_LEFT_X) > 0.2f  || 
                    controller.getAxis(XBox360Pad.AXIS_LEFT_X) < -0.2f)
                sprite.translateX(controller.getAxis(XBox360Pad.AXIS_LEFT_X) * 10f);
            if(controller.getAxis(XBox360Pad.AXIS_LEFT_Y) > 0.2f  || 
                    controller.getAxis(XBox360Pad.AXIS_LEFT_Y) < -0.2f)
                sprite.translateY(controller.getAxis(XBox360Pad.AXIS_LEFT_Y) * -10f);

            // Poll if user hits start button, if they do, reset position of sprite
            if(controller.getButton(XBox360Pad.BUTTON_START))
                sprite.setPosition(Gdx.graphics.getWidth() / 2 - sprite.getWidth() / 2,
                        Gdx.graphics.getHeight() / 2 - sprite.getHeight() / 2);
            batch.draw(sprite, sprite.getX(), sprite.getY(), sprite.getOriginX(), sprite.getOriginY(), 
                    sprite.getWidth(),sprite.getHeight(), 
                    sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation());
        }
        batch.end();
    }
}

Ahora aquí está este código ejecutándose (más o menos, tal vez)

Sin compatibilidad con iFrame

(Abrir en Nueva ventana)

Aquí puede ver que simplemente sondeamos el controlador para conocer su estado en cada cuadro. getAxis devuelve la cantidad que se presiona el controlador en cualquier dirección a lo largo de ese eje como un valor de -1 a 1. getButton, por otro lado, devuelve un valor booleano que representa si el botón está actualmente presionado o no. Una cosa importante a tener en cuenta aquí es que este código es increíblemente frágil. El controlador LibGDX admite múltiples controladores, pero en este caso simplemente verifico si existe alguno y uso el primero que puedo encontrar. Esto significa que si tiene varios controladores conectados a su PC, se ignorarán todos menos el primero. En segundo lugar, este código simplemente asume que el controlador es un controlador XBox 360, no tengo idea de lo que sucederá si se usa otro controlador en su lugar. Lo más probable es que, en el peor de los casos, los botones no estén bien asignados o no existan.

La única otra cosa a tener en cuenta (y mencionada en los comentarios) es que apliqué un valor de zona muerta de -0.2 a 0.2 para cada dispositivo analógico. Esto evita que el controlador se mueva demasiado y se mueva cuando el usuario pensaría que el control debería estar quieto. En general, este valor de zona muerta es lo que configuraría a través de una configuración de sensibilidad en la configuración de su juego. También invertí el valor del eje Y porque, bueno, ¡así es como debería ser! 🙂

Parte anterior Tabla de contenido siguiente parte



Source link

Tags :
compatibilidad,con,GamePad,LibGDX,parte,Tutorial

Comparte :

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *