Programación de juegos para móviles con J2ME

Durante los cap�tulos siguientes se profundiza en los diferentes aspectos concernientes a la programaci�n de videojuegos. Ya dispones de las herramientas necesarias para emprender la aventura, as� que si�ntate c�modamente, flexiona tus dedos y prep�rate para la diversi�n. Para ilustrar las t�cnicas que se describir�n en los pr�ximos cap�tulos desarrollaremos un peque�o videojuego. Va a ser un juego sin grandes pretensiones, pero que nos va a ayudar a entender los diferentes aspectos que encierra este fascinante mundo. Nuestro juego va a consistir en lo que se ha dado en llamar shooter en el argot de los videojuegos. Quiz�s te resulte m�s familiar �matamarcianos�. En este tipo de juegos manejamos una nave que tiene que ir destruyendo a todos los enemigos que se pongan en su camino. En nuestro caso, va a estar ambientado en la segunda guerra mundial, y pilotaremos un avi�n que tendr� que destruir una orda de aviones enemigos. El juego es un homenaje al m�tico 1942.

Este cap�tulo lo vamos a dedicar a los sprites. Seguro que alguna vez has jugado a Space Invaders. En este juego, una peque�a nave situada en la parte inferior de la pantalla dispara a una gran cantidad de naves enemigas que van bajando por la pantalla hacia el jugador. Pues bien, nuestra nave es un sprite, al igual que los enemigos, las balas y los escudos. Podemos decir que un sprite es un elemento gr�fico determinado (una nave, un coche, etc...) que tiene entidad propia y sobre la que podemos definir y modificar ciertos atributos, como la posici�n en la pantalla, si es o no visible, etc... Un sprite, pues, tiene capacidad de movimiento. Distinguimos dos tipos de movimiento en los sprites: el movimiento externo, es decir, el movimiento del sprite por la pantalla, y el movimiento interno o animaci�n.

Para posicionar un sprite en la pantalla hay que especificar sus coordenadas. Es como el juego de los barquitos, en el que para identificar un cuadrante hay que indicar una letra para el eje vertical (lo llamaremos eje Y) y un n�mero para el eje horizontal (al que llamaremos eje X). En un ordenador, un punto en la pantalla se representa de forma parecida. La esquina superior izquierda representa el centro de coordenadas. La figura siguiente muestra el eje de coordenadas en una pantalla con una resoluci�n de 320 por 200 p�xeles.

Un punto se identifica dando la distancia en el eje X al lateral izquierdo de la pantalla y la distancia en el eje Y a la parte superior de la pantalla. Las distancias se miden en p�xeles. Si queremos indicar que un sprite est� a 100 p�xeles de distancia del eje vertical y 150 del eje horizontal, decimos que est� en la coordenada (100,150).

Imagina ahora que jugamos a un videjuego en el que manejamos a un hombrecillo. Podremos observar c�mo mueve las piernas y los brazos seg�n avanza por la pantalla. �ste es el movimiento interno o animaci�n. La siguiente figura muestra la animaci�n del sprite de un gato.

Otra caracter�stica muy interesante de los sprites es que nos permiten detectar colisiones entre ellos. Esta capacidad es realmente interesante si queremos conocer cuando nuestro avi�n ha chocado con un enemigo o con uno de sus misiles.

.�Control de sprites

Vamos a realizar una peque�a librer�a (y cuando digo peque�a, quiero decir realmente peque�a) para el manejo de los sprites. Luego utilizaremos esta librer�a en nuestro juego, por supuesto, tambi�n puedes utilizarla en tus propios juegos, as� como ampliarla, ya que cubrir� s�lo los aspectos b�sicos en lo referente a sprites.

Dotaremos a nuestra librer�a con capacidad para movimiento de sprites, animaci�n (un soporte b�sico) y detecci�n de colisiones.

Para almacenar el estado de los Sprites utilizaremos las siguientes variables.

	private int posx,posy;
	private boolean active;
	private int frame,nframes;
	private Image[] sprites;

Necesitamos la coordenada en pantalla del sprite (que almacenamos en posx y posy. La variable active nos servir� para saber si el sprite est� activo. La variable frame almacena el frame actual del sprite, y nframes el n�mero total de frames de los que est� compuesto. Por �ltimo, tenemos un array de objetos Image que contendr� cada uno de los frames del juego.

Como puedes observar no indicamos el tama�o del array, ya que a�n no sabemos cuantos frames tendr� el sprite. Indicaremos este valor en el constructor del sprite.

	// constructor. 'nframes' es el n�mero de frames del Sprite.
	public Sprite(int nframes) {
		// El Sprite no est� activo por defecto.
		active=false;
		frame=1;
		this.nframes=nframes;
		sprites=new Image[nframes+1];
	} 

El constructor se encarga de crear tantos elementos de tipo Image como frames tenga el sprite. Tambi�n asignamos el estado inicial del sprite.

La operaci�n m�s importante de un sprite es el movimiento por la pantalla. Veamos los m�todos que nos permitir�n moverlo.

	public void setX(int x) {
		posx=x;
	}

	public void setY(int y) {
		posy=y;
	}

	int getX() {
		return posx;
	}

	int getY() {
		return posy;
	}

Como puedes observar, el c�digo para posicionar el sprite en la pantalla no puede ser m�s simple. Los m�todos setX() y setY() actualizan las variables de estado del sprite (posx,posy). Los m�todos getX() y getY() realizan la operaci�n contraria, es decir, nos devuelve la posici�n del sprite. Adem�s de la posici�n del sprite, nos va a interesar en determinadas condiciones conocer el tama�o del mismo.

	int getW() {
		return sprites[nframes].getWidth();
	}

	int getH() {
		return sprites[nframes].getHeight();
	}

Los m�todos getW() y getH() nos devuelven el ancho y el alto del sprite en p�xeles. Para ello recurrimos a los m�todos getWidth() y getHeigth() de la clase Image.

Otro dato importante del sprite es si est� activo en un momento determinado.

	public void on() {
		active=true;
	}

	public void off() {
		active=false;
	}

	public boolean isActive() {
		return active;
	}

Necesitaremos un m�todo que active el sprite, al que llamaremos on(), y otro para desactivarlo, que como podr�s imaginar, llamaremos off(). Nos resta un m�todo para conocer el estado del sprite. Hemos llamado al m�todo isActive().

En lo referente al estado necesitamos alg�n m�todo para el control de frames, o lo que es lo mismo, de la animaci�n interna del sprite.

	public void selFrame(int frameno) {
		frame=frameno;
	}

	public int frames() {
		return nframes;
	}

	public void addFrame(int frameno, String path) {
		try {
			sprites[frameno]=Image.createImage(path);
		} catch (IOException e) {
			System.err.println("Can`t load the image " + path + ": " + e.toString());
		}
	}

El m�todo selFrame() fija el frame actual del sprite, mientras que el m�todo frame() nos devolver� el n�mero de frames del sprite.

El m�todo addFrame() nos permite a�adir frames al sprite. Necesita dos par�metros. El par�metro frameno, indica el n�mero de frame, mientras que el par�metro path indica el camino y el nombre del gr�fico que conformar� dicho frame.

Para dibujar el sprite, vamos a crear el m�todo draw(). Lo �nico que hace este m�todo es dibujar el frame actual del sprite en la pantalla.

public void draw(Graphics g) {
	g.drawImage (sprites[frame], posx, posy, Graphics.HCENTER|Graphics.VCENTER);
}

Nos resta dotar a nuestra librer�a con la capacidad de detectar colisiones entre sprites. La detecci�n de colisiones entre sprites puede enfocarse desde varios puntos de vista. Imaginemos dos sprites, nuestro avi�n y un disparo enemigo. En cada vuelta del game loop tendremos que comprobar si el disparo ha colisionado con nuestro avi�n. Podr�amos considerar que dos sprites colisionan cuando alguno de sus p�xeles visibles (es decir, no transparentes) toca con un p�xel cualquiera del otro sprite. Esto es cierto al 100%, sin embargo, la �nica forma de hacerlo es comprobando uno por uno los p�xeles de ambos sprites. Evidentemente esto requiere un gran tiempo de computaci�n, y es inviable en la pr�ctica. En nuestra librer�a hemos asumido que la parte visible de nuestro sprite coincide m�s o menos con las dimensiones de la superficie que lo contiene. Si aceptamos esto, y teniendo en cuenta que una superficie tiene forma cuadrangular, la detecci�n de una colisi�n entre dos sprites se simplifica bastante. S�lo hemos de detectar el caso en el que dos cuadrados se solapen.

En la primera figura no existe colisi�n, ya que no se solapan las superficies (las superficies est�n representadas por el cuadrado que rodea al gr�fico). La segunda figura muestra el principal problema de este m�todo, ya que nuestra librer�a considerar� que ha habido colisi�n cuando realmente no ha sido as�. A pesar de este peque�o inconveniente, este m�todo de detecci�n de colisiones es el m�s r�pido. Es importante que la superficie tenga el tama�o justo para albergar el gr�fico. Este es el aspecto que tiene nuestro m�todo de detecci�n de colisiones.


	boolean collide(Sprite sp) {
		int w1,h1,w2,h2,x1,y1,x2,y2;

		w1=getW();		// ancho del sprite1
		h1=getH();		// altura del sprite1
		w2=sp.getW();	// ancho del sprite2
		h2=sp.getH();	// alto del sprite2
		x1=getX();		// pos. X del sprite1
		y1=getY();		// pos. Y del sprite1
		x2=sp.getX();	// pos. X del sprite2
		y2=sp.getY();	// pos. Y del sprite2

		if (((x1+w1)>x2)&&((y1+h1)>y2)&&((x2+w2)>x1)&&((y2+h2)>y1)) {
			return true;
		} else {
			return false;
		}
	}

Se trata de comprobar si el cuadrado (superficie) que contiene el primer sprite, se solapa con el cuadrado que contiene al segundo.

Hay otros m�todos m�s precisos que nos permiten detectar colisiones. Consiste en dividir el sprite en peque�as superficies rectangulares tal y como muestra la pr�xima figura.

Se puede observar la mayor precisi�n de este m�todo. El proceso de detecci�n consiste en comprobar si hay colisi�n de alguno de los cuadros del primer sprite con alguno de los cuadrados del segundo utilizando la misma comprobaci�n que hemos utilizado en el primer m�todo para detectar si se solapan dos rectangulos. Se deja como ejercicio al lector la implementaci�n de este m�todo de detecci�n de colisiones. A continuaci�n se muestra el listado completo de nuestra librer�a.


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import java.io.*;

class Sprite {

	private int posx,posy;
	private boolean active;
	private int frame,nframes;
	private Image[] sprites;

	// constructor. 'nframes' es el n�mero de frames del Sprite.
	public Sprite(int nframes) {
		// El Sprite no est� activo por defecto.
		active=false;
		frame=1;
		this.nframes=nframes;
		sprites=new Image[nframes+1];
	}


	public void setX(int x) {
		posx=x;
	}

	public void setY(int y) {
		posy=y;
	}

	int getX() {
		return posx;
	}

	int getY() {
		return posy;
	}

	int getW() {
		return sprites[nframes].getWidth();
	}

	int getH() {
		return sprites[nframes].getHeight();
	}

	public void on() {
		active=true;
	}

	public void off() {
		active=false;
	}

	public boolean isActive() {
		return active;
	}

	public void selFrame(int frameno) {
		frame=frameno;
	}

	public int frames() {
		return nframes;
	}

	// Carga un archivo tipo .PNG y lo a�ade al sprite en
	// el frame indicado por 'frameno'
	public void addFrame(int frameno, String path) {
		try {
			sprites[frameno]=Image.createImage(path);
		} catch (IOException e) {
			System.err.println("Can`t load the image " + path + ": " + e.toString());
		}
	}


	boolean collide(Sprite sp) {
		int w1,h1,w2,h2,x1,y1,x2,y2;

		w1=getW();		// ancho del sprite1
		h1=getH();		// altura del sprite1
		w2=sp.getW();	// ancho del sprite2
		h2=sp.getH();	// alto del sprite2
		x1=getX();		// pos. X del sprite1
		y1=getY();		// pos. Y del sprite1
		x2=sp.getX();	// pos. X del sprite2
		y2=sp.getY();	// pos. Y del sprite2

		if (((x1+w1)>x2)&&((y1+h1)>y2)&&((x2+w2)>x1)&&((y2+h2)>y1)) {
			return true;
		} else {
			return false;
		}
	}


	// Dibujamos el Sprite
	public void draw(Graphics g) {
		g.drawImage(sprites[frame],posx,posy,Graphics.HCENTER|Graphics.VCENTER);
	}

Veamos un ejemplo pr�ctico de uso de nuestra librer�a. Crea un nuevo proyecto en KToolBar, y a�ade el programa siguiente en el directorio �src�, junto con la librer�a Sprite.java. Por supuesto necesitar�s incluir el gr�fico hero.png en el directorio �res�.

En los siguientes cap�tulos vamos a basarnos en esta librer�a para el control de los Sprites del juego que vamos a crear.


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class SpriteTest extends MIDlet implements CommandListener {

 private Command exitCommand, playCommand, endCommand;
 private Display display;
 private SSCanvas screen;

 public SpriteTest() {
	display=Display.getDisplay(this);
	exitCommand = new Command("Salir",Command.SCREEN,2);

	screen=new SSCanvas();

	screen.addCommand(exitCommand);
	screen.setCommandListener(this);
 }

 public void startApp() throws MIDletStateChangeException {
	display.setCurrent(screen);
 }

 public void pauseApp() {}

 public void destroyApp(boolean unconditional) {}

 public void commandAction(Command c, Displayable s) {

	if (c == exitCommand) {
		destroyApp(false);
		notifyDestroyed();
	}
 }
}


class SSCanvas extends Canvas {

 private Sprite miSprite=new Sprite(1);

 public SSCanvas() {
	// Cargamos los sprites
	miSprite.addFrame(1,"/hero.png");

	// Iniciamos los Sprites
	miSprite.on();
 }


 public void paint(Graphics g) {

	//  Borrar pantalla 
	g.setColor(255,255,255);
	g.fillRect(0,0,getWidth(),getHeight());

	// situar y dibujar sprite
	miSprite.setX(50);
	miSprite.setY(50);
	miSprite.draw(g);
 }
}

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
SIGUIENTE ARTÍCULO