miércoles, 15 de mayo de 2013

Implementación de controles de juegos multiplataforma con código centrado.

En el desarrollo de juegos multiplataforma siempre tenemos el inconveniente de que los controles cambian de un entorno a otro y más cuando tenemos tecnologías que usan teclado, mouse y touch.

Pues en la búsqueda de hacer un control unisono para múltiples plataformas y que el código base del juego este centrado y este no tenga problemas a la hora de identificar los controles, pues usé el paradigma orientado a objetos y cree una clase que me abstrae los controles y los coloca en una especie de caja, por lo que solo se tiene que implementar la forma de captura de cada botón en las distintas plataformas y el juego solo leerá la clase con los botones y la lógica solo se ejecutará 1 vez y no múltiples veces por las diferentes plataformas.

.Pero que librerías o APIS tenemos que nos permiten crear juegos en múltiples plataformas?, pues LibGDX es una de estas y al estar escrita en JAVA permite que esta corra en diversas tecnologías sin problemas y  es hay en donde LibGDX demuestra su potencial, pues usando un proyecto base (Este contiene toda la lógica del juego y es el juego en sí) y los proyectos respectivos para lanzar el juego en las diferentes plataformas, es decir, que si queremos que corra en pc tenemos un proyecto que lanza el código del proyecto base y la librería se encarga de convertirlo en un software de pc y así sucede lo mismo con ANDROID.

Ahora que hablamos un poco de la tecnología a usar y la librería que nos dará soporte, pues queda definir el esquema de organización de un juego multiplataforma y mas o menos se ve de la siguiente manera:

Pues como verán los proyectos solo indican que son lanzadores, pero para los curiosos así se ven las clases main de los proyectos:

PC:

package com.alssoftrd.games.pruebared;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "PruebaRed";
cfg.useGL20 = false;
cfg.width = 800;
cfg.height = 480;

new LwjglApplication(new Prueba(1), cfg);
}
}


ANDROID:

package com.alssoftrd.games.pruebared;

import android.os.Bundle;

import com.alssoftrd.games.pruebared.PruebaRed;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     
        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
        cfg.useGL20 = false;
        cfg.useAccelerometer = false;
        cfg.useCompass = false;
     
        initialize(new Prueba(0), cfg);
    }
}


Como ven solo existe un poco de código básico en cada proyecto, claro esta que hay que adicionar las librerías de cada plataforma y anexar como parte del código el proyecto base.

Ahora que vemos el esquema de trabajo de forma más gráfica, pues procedemos a hacer la clase de los controles que permitirán que los controles trabajan de forma normal en múltiples plataformas.

Primero definimos los botones y como es para explicar pues tomaré un control simple como los siguientes; que son un PAD (flechas de movimiento) y 2 botones básicos que son A y B.

Ahora que sabemos que botones usaremos pues procedemos a crear la clase con las siguientes caracteristicas: El PAD serán 4 variables booleanos que darán la dirección en sus respectivas axis y los botones A y B serán booleanos que indicarán cuando se presionan y cuando no.

En el PAD como uso la librería LibGDX para facilitarme la creación de el aspecto visual del mismo uso una clase existente y esta solo se usará en el proyecto de android y la clase se vera más o menos así:


import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad.TouchpadStyle;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;

public class AControl {

private boolean a;
private boolean b;
// private boolean start;
private boolean left;
private boolean right;
private boolean up;
private boolean down;
private Touchpad pad = null;
private TouchpadStyle estiloPad;
private Skin skinPad;

public AControl() {
this.a = false;
this.b = false;
this.left = false;
this.right = false;
this.up = false;
this.down = false;
}

private void cargarPad() {
                // Esto es para darle un aspecto visual, lo cual ignoro.
skinPad = new Skin();
TextureAtlas atlas = Manager.get().get("data/Pad/Pad.pack",
TextureAtlas.class);
estiloPad = new TouchpadStyle();
skinPad.addRegions(atlas);
estiloPad.background = skinPad.get("Pad", Drawable.class);
estiloPad.knob = skinPad.get("ButtonPad", Drawable.class);
pad = new Touchpad(20, estiloPad);
pad.setBounds(15, 15, 150, 150);
}

public boolean isA() {
return a;
}

public boolean getA() {
return a;
}

public void setA(boolean a) {
this.a = a;
}

public boolean isB() {
return b;
}

public boolean getB() {
return b;
}

public void setB(boolean b) {
this.b = b;
}

public boolean isLeft() {
return this.left;
}

public boolean getLeft() {
return this.left;
}

public void setLeft(boolean left) {
this.left = left;
}

public boolean isRight() {
return this.right;
}

public boolean getRight() {
return this.right;
}

public void setRight(boolean right) {
this.right = right;
}

public boolean isUp() {
return this.up;
}

public boolean getUp() {
return this.up;
}

public void setUp(boolean up) {
this.up = up;
}

public boolean isDown() {
return this.down;
}

public boolean getDown() {
return this.down;
}

public void setDown(boolean down) {
this.down = down;
}

public Touchpad getPad() {
if(pad == null) {
cargarPad();
}
return pad;
}
}


Como verán existen métodos que se pueden pensar que están repetidos, pero solo es por comodidad. Como ven todos los botones están abstraídos en una serie de booleanos y así solo tengo que saber en que plataforma estoy corriendo y asignar los botones a sus respectivas variables.

Muchos siguen pensando ¿que hago con esta clase?, es esta es la clase base de donde el juego leerá, pero todavia falta leer los inputs y asignarles y esto se encarga esta clase que usando singleton manejamos los controles en las diversas plataformas.


import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class Control {

private AControl controles;
        // 0 es ANDROID y 1 es PC
private int plataforma;

public Control(int plataforma) {
this.controles = new AControl();
this.plataforma = plataforma;
}

public void captura() {
if (plataforma == 0) {
capturaAndroid();
return;
}
capturaPc();
}

private void capturaAndroid() {

if (get().getPad().getKnobPercentX() > 0) {
controles.setRight(true);
} else {
controles.setRight(false);
}
if (get().getPad().getKnobPercentX() < 0) {
controles.setLeft(true);
} else {
controles.setLeft(false);
}
                // Faltan los botones de el axis Y
                // Faltan los botones A y B
}

private void capturaPc() {
// Movimiento del personaje
controles.setLeft(Gdx.input.isKeyPressed(Keys.A));
controles.setRight(Gdx.input.isKeyPressed(Keys.D));
controles.setUp(Gdx.input.isKeyPressed(Keys.W));
controles.setDown(Gdx.input.isKeyPressed(Keys.S));
// Manejo de los botones de acción
controles.setA(Gdx.input.isButtonPressed(Input.Buttons.LEFT));
controles.setB(Gdx.input.isButtonPressed(Input.Buttons.RIGHT));
}

public AControl get() {
return controles;
}

private int getPlataforma() {
return plataforma;
}

public void setTouchPad(Stage stage) {
if (getPlataforma() == 0) {
stage.addActor(get().getPad());
Gdx.input.setInputProcessor(stage);
}
}
}


Como verán el touchpad que esta en la clase de Acontrol se usa de forma independiente y este solo hay que activarle y gráficar  pues de forma indirecta puedo saber si el usuario esta pulsando el touchpad y saber en que dirección.

Entonces ya tenemos los controles abstraídos y que son manejables en diferentes entornos, pues lo único que hay que hacer es usar el método captura de la clase Control en el ciclo, para que este refresque en cada frame del juego para saber si se pulsan o no los botones y usando la clase get de la clase Control obtenemos los controles abstraídos y podemos usarle para crear la lógica de nuestro juego.

Mas adelante subiré ejemplos de código y proyectos usando LibGDX mostrando como se usan los códigos presentados.