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.
Soy un Ingeniero de Sistemas y Computación egresado de la Pontificia Universidad Católica Madre y Maestra (PUCMM) y este es un sitio web donde muestro algunos algoritmos, técnicas y análisis de casos que afronto como ingeniero y resuelvo con mis destrezas y conocimientos. Amante de los gatos, Project Manager con más de 4 años de experiencia, SCRUM Manager y desarrollador entusiasta. Por cierto odio los warning
miércoles, 15 de mayo de 2013
sábado, 6 de abril de 2013
Renderizado con LIBGDX y servidor con WEBSOCKET
Con la tecnología cada vez variando más, empieza a crecer la gama de dispositivos que como ingenieros tenemos que dominar y programar en ellos, pero con tecnologías que corren en múltiples plataformas como JAVA es posible desarrollar de una forma centralizada para muchas tecnologías.
Una tendencia actual es separar cada dispositivo aunque se tenga la misma aplicación. Un ejemplo claro esta en el desarrollo de juegos, es decir, un juego MMO que se desarrollo para Android por su mayoría tiene una versión en Iphone, Web o inclusive PC, pero estos no comparten un solo servicio, lo cual separa a los usuarios en grupos. Solo los usuarios de PC pueden jugar entre sí en el mismo servidor y lo mismo pasa con las diferentes plataformas, pero es posible que se puedan todos conectar a un solo servidor y jugar sin importar la plataforma?, pues la respuesta es si.
Es posible conectar estas plataformas usando WebSocket como un estándar para la conexión, pero requiere de ingenio para que las ventajas y desventajas de usar una plataforma u otra no se sobrepongan en la aplicación o juego.
Aquí les dejo un video mostrando esta posibilidad de desarrollar juegos y aplicaciones que funcionen como una sola.
Los recursos utilizados fueron LIBGDX, WebSocket tanto para pc y web (sí web, ya que son 2 librerías diferentes, pero que cumplen la misma función.).
Desarrollar bajo este paradigma es más complicado, ya que afronta diferentes interfaces y dispositivos, por lo que mantener una GUI estandar para cada uno de estos tiene sus retos.
domingo, 10 de marzo de 2013
Implementación y sobrecarga de un TableModel personalizado en JAVA
Cuando vamos a presentar datos que son muy parecidos a EXCEL usamos un tabla y en JAVA usamos el jXTable, pero por lo general usamos un TablaModel básico, es decir, no se ajusta a las caracteristicas que deseamos y en vez de facilitarnos la vida, nos la complica.
Aquí un ejemplo de un TableModel:
Ahora ya tenemos la clase base de nuestro TableModel, pero quiero un TableModel más personalizado y que se ajuste a mi necesidad, pues lo que haremos es lo siguiente:
Crearemos una clase especial que utilizará mi clase anterior como base.
Como verán creo un objeto con mis propios constructores y asigno los datos que deseo, pero si se fijan hago uso de un modelo de datos de representación de filas de una base de datos y lo uso en mi TableModel, para facilitarme la vida a la hora de manipular los datos basados en un objeto POJO de JAVA. Si no saben lo que es un objeto POJO de JAVA, pues en un intento de definirlo vulgarmente es una clase que sigue ciertas reglas establecidas y no extiende de nada. Aqui un ejemplo del modelo que use:
y por ultimo un ejemplo de como instanciar y asignar el TableModel a una tabla:
Como verán es muy sencillo, pero y como consigo los datos de los cuales fueron seleccionados?, pues conseguir los datos seria algo así
Esto supone ser un método dentro de la clase anterior.
Excelente, pero y como obtengo la fila cuando se ordenan los datos?
Ya con esto tenemos un modelo de un TableModel funcional.
Aquí un ejemplo de un TableModel:
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public class MiTableModel extends AbstractTableModel {
public boolean cellEditable = false;
public List dataArray;
public int[] columneditable=null;
public ArrayList columnIdentifiers;
@Override
public int getRowCount() {
return dataArray.size();
}
@Override
public int getColumnCount() {
return columnIdentifiers.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return dataArray.get(rowIndex);
}
@Override
public String getColumnName(int column) {
return columnIdentifiers.get(column);
}
public ArrayList getColumnIdentifiers() {
return columnIdentifiers;
}
public void removeRow(int row) {
dataArray.remove(row);
fireTableRowsDeleted(row, row);
}
public void addRow(Object data){
this.dataArray.add(data);
fireTableDataChanged();
}
public Object getRow(int index){
return dataArray.get(index);
}
public List getData() {
return dataArray;
}
public void setData(List dataArray) {
this.dataArray = dataArray;
}
}
Ahora ya tenemos la clase base de nuestro TableModel, pero quiero un TableModel más personalizado y que se ajuste a mi necesidad, pues lo que haremos es lo siguiente:
Crearemos una clase especial que utilizará mi clase anterior como base.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TMPersonalizado extends MiTableModel {
private String[] columnasNombres;
public TMPersonalizado() {
headerDefault();
initTable();
}
public TMPersonalizado(String[] cn) {
columnasNombres = cn;
initTable();
}
private void headerDefault() {
columnasNombres = new String[2];
columnasNombres[0] = "Una columna. 1";
columnasNombres[1] = "Una columna. 2";
}
public void updateTable(){
//dataArray = ManejoBanco.getInstancia().getLista();
fireTableDataChanged();
}
private void initTable(){
ArrayList header = new ArrayList();
header.addAll(Arrays.asList(columnasNombres));
columnIdentifiers = header;
dataArray = new ArrayList();
fireTableDataChanged();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
MisDatos misDatos = (MisDatos) dataArray.get(rowIndex);
switch(columnIndex){
case 0:
return misDatos.getMiDato1();
case 1:
return misDatos.getMiDato2();
default:
return null;
}
}
}
Como verán creo un objeto con mis propios constructores y asigno los datos que deseo, pero si se fijan hago uso de un modelo de datos de representación de filas de una base de datos y lo uso en mi TableModel, para facilitarme la vida a la hora de manipular los datos basados en un objeto POJO de JAVA. Si no saben lo que es un objeto POJO de JAVA, pues en un intento de definirlo vulgarmente es una clase que sigue ciertas reglas establecidas y no extiende de nada. Aqui un ejemplo del modelo que use:
public class MisDatos {
private int miDato1;
private String miDato2;
public int getMiDato1() {
return miDato1;
}
public void setMiDato1(int miDato1) {
this.miDato1 = miDato1;
}
public int getMiDato2() {
return miDato2;
}
public void setMiDato2(int miDato2) {
this.miDato2 = miDato2;
}
}
y por ultimo un ejemplo de como instanciar y asignar el TableModel a una tabla:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jdesktop.swingx.JXTable;
public class Prueba {
private List dats = new ArrayList();
private TMPersonalizado tm = new TMPersonalizado();
private JXTable mitabla;
public Prueba() {
mitabla = new JXTable();
tm.setData(dats);
mitabla.setModel(tm);
}
}
Como verán es muy sencillo, pero y como consigo los datos de los cuales fueron seleccionados?, pues conseguir los datos seria algo así
Esto supone ser un método dentro de la clase anterior.
public MisDatos captura() {
MisDatos fila = (MisDatos) tm.getRow(mitabla.getSelectedRow());
return fila;
}
Excelente, pero y como obtengo la fila cuando se ordenan los datos?
public MisDatos capturaOrdenada() {
MisDatos fila = (MisDatos) tm.getRow(mitabla.convertRowIndexToModel(mitabla.getSelectedRow()));
return fila;
}
Ya con esto tenemos un modelo de un TableModel funcional.
Que haríamos sin @Override?
Cuando programamos tendemos a escribir código con funcionalidades específicas y en combinación con piezas, libs o apis aveces estas no traen lo que necesitamos y por ignorancia o otra razón tendemos a reinventar la rueda.
Es por eso que se creo @Override en los lenguajes de programación POO, para convertir algo que no hace lo que queremos en lo que buscamos.
Un ejemplo claro es cuando tenemos un carro, si el carro no tiene el motor que queremos solo reemplazamos el motor y no el carro completo(reinventar la rueda) y esto es más o menos un ejemplo de @Override en la vida real.
Muchas veces es mejor sobrecargar un método que escribir la clase completa, ya que podríamos añadir métodos específicos de nuestra necesidad si es que no contempla esas caracteristicas el objeto que estamos usando.
Es por eso que se creo @Override en los lenguajes de programación POO, para convertir algo que no hace lo que queremos en lo que buscamos.
Un ejemplo claro es cuando tenemos un carro, si el carro no tiene el motor que queremos solo reemplazamos el motor y no el carro completo(reinventar la rueda) y esto es más o menos un ejemplo de @Override en la vida real.
Muchas veces es mejor sobrecargar un método que escribir la clase completa, ya que podríamos añadir métodos específicos de nuestra necesidad si es que no contempla esas caracteristicas el objeto que estamos usando.
domingo, 30 de diciembre de 2012
Punteros y arreglos en C que se unen.
Cuando trabajamos en C/C++ usamos punteros debido a que son una herramienta muy buena para facilitarnos la vida, pero un detalle interesante de estos que no siempre funcionan como uno quiere.
Como sabemos los punteros son direcciones de memoria y utilizar estos puede ocasionar que la aplicación colapse por acceso de memoria no autorizado, pero los utilizamos porque reducen el código y nos ayudan muchísimo.
Aquí un ejemplo de punteros que nos dan problemas cuando iniciamos y no sabemos por que. Para sorpresa del profesor se encuentra raro cuando nos revelamos a utilizar C y optamos por un lenguaje de alto nivel.
#include <stdio.h>
int *a, *b, *c;
void f(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
a = i;
}
void g(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
b = i;
}
void h(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
c = i;
}
int main() {
f(1);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
g(2);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
h(3);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
}
Noten que imprimo siempre a[0], a[1] y a[2] siempre, pero después de llamar a una de mis funciones para asignar valores a otro puntero.
Como sabemos los punteros son direcciones de memoria y utilizar estos puede ocasionar que la aplicación colapse por acceso de memoria no autorizado, pero los utilizamos porque reducen el código y nos ayudan muchísimo.
Aquí un ejemplo de punteros que nos dan problemas cuando iniciamos y no sabemos por que. Para sorpresa del profesor se encuentra raro cuando nos revelamos a utilizar C y optamos por un lenguaje de alto nivel.
#include <stdio.h>
int *a, *b, *c;
void f(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
a = i;
}
void g(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
b = i;
}
void h(int x) {
int i[3];
i[0] = x;
i[1] = x + 1;
i[2] = x + 2;
c = i;
}
int main() {
f(1);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
g(2);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
h(3);
printf("a = {%d,%d,%d}\n", a[0], a[1], a[2]);
}
Noten que imprimo siempre a[0], a[1] y a[2] siempre, pero después de llamar a una de mis funciones para asignar valores a otro puntero.
Suscribirse a:
Entradas (Atom)
