API de bajo nivel API para juegos (javax.microedition.lcdui.game) 

F. Javier García Castellano
Web: http://decsai.ugr.es/~fjgc, Mail: fjgc@decsai.ugr.es
IndiceInicioPerl

(C) Decsai
Web: http://decsai.ugr.es

5.5.- API de bajo nivel para juegos (javax.microedition.lcdui.game)

Una de las mejoras más importantes que presenta MIDP 2.0 es el desarrollo de juegos con gráficos a través del paquete game del interfaz gráfico de bajo nivel (javax.microedition.lcdui.game). A través de estas nuevas clases podemos desarrollar juegos, de forma más rápida y más eficiente. De todos modos, hay que tener en cuenta que todas estas nuevas clases pertenecen a MIDP 2.0 y en todos los móviles que sólo soporten MIDP 1.0 estas aplicaciones no podrán funcionar.

La idea básica es que la pantalla del juego se compone de capas (Layers), de forma que todas estas capas puedan ser manejadas por separado y sea java quien se encargue de pintar las distintas capas. El área de trabajo puede ser mayor que el tamaño de la pantalla del dispositivo y el movimiento o scroll de esta área puede resultar costoso. El API de juegos proporciona una vista de una parte del área de trabajo que compone una pantalla del juego (no una pantalla en el dispositivo).

Hay cinco clases nuevas en el paquete game:

5.5.1.- La clase CanvasGame

Esta clase trae dos novedades respecto a la clase Canvas, el tratamiento de teclas y la recuperación del estado del teclado.

En CanvasGame se puede recuperar el estado de las teclas a través del método getKeyStates de la clase GameCanvas.

public int getKeyStates()

Tenemos las siguientes constates enteras que representa las teclas:

DOWN_PRESSED Abajo
UP_PRESSED Arriba
LEFT_PRESSED Izquierda
RIGHT_PRESSED Derecha
FIRE_PRESSED Disparo
GAME_A_PRESSED La tecla de juego A (puede no existir)
GAME_B_PRESSED La tecla de juego B (puede no existir)
GAME_C_PRESSED La tecla de juego C (puede no existir)
GAME_D_PRESSED La tecla de juego D (puede no existir)

Un ejemplo de cómo sería la captura de teclas:


//Miramos que tecla se ha pulsado
int keyState = getKeyStates();

/si se ha pulsado izquierda o derecha, movemos mi sprite
if ((keyState & LEFT_PRESSED) != 0) {
	miSprite.move(-1,0)
}
else if ((keyState & RIGHT_PRESSED) != 0) {
	miSprite.move(1,0)
}

//pintamos mi sprite
miSprite.paint(g);

//pintamos la pantalla
flushGraphics();

El buffer asíncrono que nos proporciona CanvasGame nos va a permitir no tener que usar doble buffer, cuando queramos que se dibuje en la pantalla nuestro CanvasGame, llamaremos a los siguientes métodos flushGraphics() para pintar toda la pantalla o tan sólo una parte.

public void flushGraphics()
public void flushGraphics(int x, int y, int width,int height)

5.5.2.- La clase Layer

Las capas proporcionan una forma de manejar objetos gráficos en la pantalla. Esta es una clase abstracta de la cual heredan los elementos Sprite y TiledLayer son capas.

La clase Layer nos propociona métodos para conocer la posición de una capa y su ancho y alto:

int getWidth()
int getHeight()
int getX()
int getY()

Y nos permite cambiar la visibilidad de una capa y preguntar si es visible

boolean isVisible()
setVisible(boolean visible)

También proporciona métodos para cambiar de posición la capa

void move(int dx, int dy)
void setPosition(int x,int y)

Además tiene un método paint() que redibuja el Layer en pantalla

void paint(Graphics g)

5.5.3.- La clase LayerManager

La clase LayerManager trabaja con una serie de objetos Layer simplificando el proceso de dibujar dichas capas en la pantalla.

De esta forma, la clase LayerManager tiene una lista ordenada donde los capas se puede insertar, añadir o borrar. El índice de cada capa en dicha lista corresponde con un orden en la coordenada z, por tanto, la capa con índice 0 es la más cercana al usuario y cuanto más grande el índice, más lejana. Los métodos que proporciona para maneja dicha lista son:

void insert(Layer l,int index)
void append(Layer l)
void remove(Layer l)
Layer getLayerAt(int index)
int getSize()

En la clase LayerManager la view window controla la visión que es visible respecto al origen de coordenadas. Cambiando la posición de la view window provoca un efecto de scroll de las vista del usuario. PAra especificar la view window se tiene el siguiente método:

public void setViewWindow(int x, int y, int width,int height)

El método paint de LayerManager se encarga de pintar a cada una de las capas invocando a cada uno de sus métodos paint. Las coordenadas que recibe como indica donde se pinta la view window.

void paint(Graphics g, int x, int y)

Ejemplo de setViewWindow(52,11,85,85) y paint(g,17,17)


Mediante la view window podemos tener escenarios más grandes que la pantalla del móvil por los cuales puede discurrir nuestro juego.

5.5.4.- La clase Sprite

Un Sprite es una capa gráfica, que puede representar cualquier cosa, normalmente los Sprites son imágenes animadas. Por lo que un Sprite se compone de varios Frames (Fotogramas). La clase Sprite contiene varias imágenes del mismo tamaño que se guardan en un sólo objeto Image, estas imágenes son los distintos fotogramas que nos permiten configurar una animación. Si se utiliza más de un fotograma, lo cual será lo más normal, la objeto Image es dividido en una serie de fotogramas iguales de un determinado ancho y alto. Tal y como se muestra en la siguiente figura, un conjunto de fotogramas se puede almacenar de diferentes formas en una sola imagen .png (las que puede leer la clase Image).

Ejemplo de una imagen con un Sprite


Para crear un Sprite se utiliza el siguiente constructor:

public Sprite(Image image, int frameWidth, int frameHeight)
Image spriteImage;

try{ 
     spriteImage = Image.createImage("/res/sprite.png"); 
} catch (Exception ex){}

Sprite miSprite = new Sprite(spriteImage, 7, 7);

A cada fotograma se le asigna un índice identificativo. El primero (el cero) es el fotograma que está situado en la esquina superior izquierda y lo siguientes índices los va asignado de izquierda a derecha y de arriba a abajo. El método getRawFrameCount() devuelve el número de fotogramas.

 int getRawFrameCount()

Para configurar la animación a partir de la imagen existe lo que se denomina secuencia de fotogramas (frame sequence) que nos dirá el orden en el cual se van a dibujar los fotogramas. Por defecto recorre todos los fotogramas de la imagen, pero hay casos en los que esto no nos interesa, en el ejemplo anterior sin ir más lejos. Para definir una secuencia de fotogramas se utiliza un array con los índices de fotogramas válidos en el orden que queremos que se muestren. Se utiliza el método setFrameSequence para fijar la secuencia de fotogramas y el método getFrameSequenceLength para obtener el número de fotogramas de la secuencia.

int getFrameSequenceLength()
void setFrameSequence(int[] sequence)

De esta forma la siguiente secuencia:


Genera el siguiente sprite


La clase Sprite tiene varios métodos para movernos dentro de la secuencia de frames, que nos van a permitir, respectivamente, irnos al frame anterior de la secuencia, al siguiente, obtener el frame actual de y activar el frame actual de la secuencia:

void prevFrame() 
void nextFrame() 
int getFrame() 
void setFrame(int sequenceIndex) 

La clase Sprite nos proporciona varios métodos para hacer transformaciones del sprite mediante el método setTransform. Hay diferentes transformaciones que son rotaciones en ángulos de 90 grados e imágenes simétricas respecto al eje y (efecto espejo/mirroring).

void setTransform(int transform)

El punto mediante el cual se realiza las transformaciones es el punto de referencia que estará en el centro de la imagen

Otro elemento importante dentro de la programación de juegos es la detección de colisiones. La clase Sprite contiene cuatro métodos relacionados con la detección de colisiones:

collidesWith(Image image, int x, int y, boolean pixelLevel)
collidesWith(Sprite s, boolean pixelLevel)
collidesWith(TiledLayer t, boolean pixelLevel)
defineCollisionRectangle(int x, int y, int width, int height)

Los tres primeros nos permiten detectar colisiones entre un Sprite y un objeto Image, dos Sprites y entre un Sprite y un TiledLayer. El cuarto método nos sirve para definir los pixels del sprite que se van a usar para detectar colisiones.

5.5.5.- La clase TiledLayer

La clase TiledLayer es un componente gráfico que está compuesto de varias celdas (baldosas). Nos va a permitir crear escenarios bastante grandes sin tener que usar por ello imágenes grandes. Es una técnica muy usada en juegos bidimensionales.

Para crear las celdas de un TiledLayer utilizamos un objeto Image de forma similar a como lo hacemos con los Sprite y como se puede ver en el siguiente ejemplo:


En el constructor tenemos que indicar además del objeto Image a usar, el número de filas y columnas de la rejilla, la imagen y el ancho y alto de cada baldosa.

public TiledLayer(int columns, int rows, Image image,int tileWidth, int tileHeight)

A cada baldosa se le asigna un índice identificativo. El primero (el uno) es la baldosa que está situado en la esquina superior izquierda y lo siguientes índices los va asignado de izquierda a derecha y de arriba a abajo. El índice cero se utiliza para no usar baldosas.

El número de filas y columnas de la celda nos sirven para componer el paisaje y el ancho y alto de la rejilla. El tamaño de dicha rejilla vendrá dado por su número de filas y columnas y por el tamaño de cada baldosa. La rejilla se puede rellenar baldosa a baldosa con setCell o con varias iguales mediante fillCells. Un ejemplo de una celda rellena con el anterior ejemplo de TiledLayer sería:


public void setCell(int col, int row, int tileIndex)
public void fillCells(int col, int row, int numCols, int numRows, int tileIndex)

La clase TiledLayer usa las imágenes colocándolas dentro de una rejilla.

El siguiente código muestra un ejemplo de cómo rellenar una rejilla:

Ejemplo de un TiledLayer

private TiledLayer baldosas;
private LayerManager capas;
private Image imagenBaldosas;
public final int TILE_NADA = 0;
public final int TILE_BOSQUE_IZQ = 1;
public final int TILE_BOSQUE_MED = 2;
public final int TILE_BOSQUE_DCH = 3;
public final int TILE_BOSQUE = 4;
public final int TILE_AGUA1 = 5;
public final int TILE_AGUA2 = 6;
public final int TILE_AGUA3 = 7;
public final int TILE_SOL = 8;

capas = new LayerManager();
try{
imagenBaldosas = Image.createImage("/baldosas.png");
}catch (IOException ex){}
baldosas = new TiledLayer(40, 16, imagenBaldosas, 7, 7);
baldosas.fillCells(0, 0, 40, 16, TILE_ANADA);
baldosas.fillCells(14, 12, 12, 4, TILE_AGUA1); 
baldosas.fillCells(0, 10, 14, 6, TILE_BOSQUE);

capas.append(baldosas);