Вы находитесь на странице: 1из 45

Creacin de un juego (1): Introduccion

Antes de empezar a crear un juego deberemos pensar en todos los aspectos que incluir, por lo tanto
es mas que recomendable que cojas papel y lpiz y empieces a pensar en los siguientes conceptos:
Mecnica bsica del juego, incluyendo un concepto de nivel si corresponde
Una historia de fondo con los personajes principales
Una lista de tems, power-ups u otras cosas que modifiquen a los personajes, la
mecnica o el mundo del juego
Diseo grfico de la historia y los personajes
Bocetos de todas las pantallas del juego y transiciones entre ellas
Transformar el juego en un archivo ejecutable

1. Mecnica bsica del juego


En este apartado nos centraremos en disear la mecnica de nuestro juego. Botones necesarios para
manejar a nuestro personajes o personajes, botones para acceder a otras pantallas, mens de
pausa, ... Que pasa con los tems cuando se usan o que pasa con el juego cuando se termina. Estos
serian algunos aspectos a tener en cuenta a la hora de crear la mecnica de nuestro juego.

2. Historia y diseo
Esta parte depende de nuestra habilidades como dibujante, pero esta orientada al diseo de nuestros
personajes, tems, power-ups y el mundo que lo rodea. A parte tendremos que pensar la historia o
trama principal de nuestro juego. Tamin podremos incluir sonidos o msica que harn mucho mas
llamativa nuestra creacin.

3. Pantallas y transiciones
Una vez tenemos diseada la mecnica del juego, la historia y los personajes, llega el momento de
plantearse como sern nuestras pantallas y transiciones entre ellas. Pero deberemos tener en cuenta
varias cosas: una pantalla es una unidad llena de elementos y es responsable de una parte del juego,
cada pantalla puede estar compuesta de mltiples componentes y una pantalla permite al usuario
interactuar con los elementos que la componen.

Sabiendo esto ya podremos disear las pantallas que tendr nuestro juego, como por ejemplo puede
ser un men principal donde habr dos botones (uno iniciara el juego y otro mostrara las
puntuaciones) a parte mostrara el titulo del juego y otro botn de opciones. Una pantalla donde el
usuario podr interactuar y jugar con nuestra creacin, compuesta de varios botones (pausa,
movimiento de nuestro personaje, ...). Podramos disear pantallas de ayuda, de pausa, de seleccin
de nivel, de juego terminado, .... Todo depender de la idea que tengamos de nuestro juego.

4. Programar el cdigo del juego


Llega la hora de transformar todos nuestros diseos del juego en un archivo ejecutable y lo haremos
a travs de herramientas Android que son relevantes para la programacin de juegos. Para ello
usaremos interfaces por dos razones: nos permiten concentrar la semntica del cdigo sin necesidad
de conocer los detalles de implementacin y por otro lado nos permitir intercambiar la aplicacin
mas tarde (es decir, en lugar de utilizar la representacin 2D CPU, mas adelante podramos explotar
OpenGL ES para visualizar nuestro juego).
Podramos definir a una interface como un conjunto de mtodos abstractos, es decir, en una
interface declaramos los nombres de los mtodos, la lista de argumentos y el tipo de retorno para
cada metodo, pero no escribimos el bloque de cdigo de cada mtodo. De esto ultimo se encarga la
clase que implemente la interface que deber sobreescribir esos mtodos con bloques de cdigo o
bien declarar nuevamente los mtodos abstractos convirtindose en una clase abstracta.
Siguiendo nuestro articulo podemos dividir las partes del cdigo en los siguientes mdulos (cada
modulo constara de una interface como minimo), que sern los encargados de llevar a cabo todo el
diseo de nuestro juego:
Gestor de pantallas: sera el responsable de crear, cerrar, pausar o reanudar nuestras
pantallas del juego.
Input: este modulo esta relacionado con el gestor de ventanas y sera en encargado de
manejar las entradas del usuario (eventos touch, key, acelerometro, ...)
FileIO: encargado de leer, escribir en archivos. Tanto internal como external storage o
incluso shared preferences.
Graficos: este es probablemente el mas complejo, a parte del juego en si. Se encargara
de cargar los diseos y dibujarlos en la pantalla.
Audio: controlara todo efecto de sonido o msica de nuestro juego.
Framework del juego: esta vinculado a todos los anteriores y proporcionara una base
fcil de usar para escribir nuestros juegos.

Creacin de un juego (2): Modulo FileIO


Empezamos con uno de los mdulos mas fciles, que sera el encargado de leer o escribir en
archivos. Para ello crearemos InputStream y OutputStream, que se podran definir como
mecanismos estndar de Java para leer y escribir en archivos. A parte aadiremos un nuevo mtodo
relacionado con SharedPrefernces.
La lectura de archivos sera muy usada para los archivos de nivel, imagenes y archivos de audio. En
cambio la escritura la usaremos con menos frecuencia para mantener las puntuaciones,
configuraciones del juego o guardar un estado del juego para que el usuario pueda volver donde lo
haba dejado.

1. Interface FileIO
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface FileIO {
public InputStream leerAsset(String fileName) throws IOException;
public InputStream leerFile(String fileName) throws IOException;
public OutputStream escribirFile(String fileName) throws IOException;
}

Como se puede observar hemos declarado 3 mtodos:


leerAsset: lo usaremos para leer los fichero de la carpeta assets.
leerFile: nos servir para leer los archivos que hemos creado.
escribirFile: y el ultimo lo usaremos para guardar en un archivo las puntuaciones,
configuraciones, ...
Los dos primeros mtodos nos devolvern un InputStream (una fuente donde leer bytes) y el ultimo
un OutputStream (una fuente donde escribir bytes). En los tres mtodos le pasamos como parmetro
"fileName" que sera el nombre del archivo a leer o escribir. Tambin manejamos la IOException
que se encargara de manejar los errores que pueda haber en la lectura o escritura.

2. Implementar interface FileIO


import java.io.File;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.os.Environment;
import android.preference.PreferenceManager;
import com.example.slange.interfaces.FileIO;
public class AndroidFileIO implements FileIO {
Context mcontext;
AssetManager assets;
String externalStoragePath;
public AndroidFileIO(Context context) {
this.context = context;
this.assets = context.getAssets();
this.externalStoragePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + File.separator;
}
public InputStream leerAsset(String fileName) throws IOException {
return assets.open(fileName);
}
public InputStream leerFile(String fileName) throws IOException {
return new FileInputStream(externalStoragePath + fileName);
}
public OutputStream escribirFile(String fileName) throws IOException {
return new FileOutputStream(externalStoragePath + fileName);
}
public SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(context);
}
}

Primero creamos tres objetos: un Context, un AssetManager y un String. Despus en el constructor


de nuestra clase indicamos como parmetro un context que sera almacenado en nuestro objeto
mcontext, seguimos almacenando una instancia AssetManager en el objeto assets, y para terminar
almacenamos la raz de nuestro almacenamiento externo en el objeto externalStoragePath.
Con el mtodo leerAsset podremos abrir cualquier archivo ubicado en la carpeta assets de nuestro
proyecto, para ello hacemos uso del mtodo open. Como parmetro indicaremos el archivo a abrir.
En el mtodo leerFile creamos un nuevo FileInputStream indicando como parmetro el nombre del
archivo. Esto nos devolver un archivo para su lectura.
El mtodo escribirFile hace lo contrario, nos devolver un archivo para su lectura.
Y por ultimo hemos aadido un nuevo mtodo, getPreferences, que nos devuelve una instancia
SharedPreferences que podremos usar tanto para leer como para escribir en un archivo de
preferencias.
Para terminar, comentar que podramos aadir un mtodo para comprobar si el almacenamiento
externo esta montado, es solo de lectura o simplemente no se dispone de l.

Creacin de un juego (3): Modulo Audio


Para la creacin de nuestro juego no vamos a disear un proceso de audio avanzado, simplemente
nos centraremos en reproducir archivos de msica y efectos de sonido. Para ello crearemos tres
interfaces: Sonido, Msica y Audio.
Sonido: nos permitir reproducir efectos de sonido que almacenaremos en la memoria
RAM.
Musica: reproducir archivos de gran tamao directamente desde el disco a la tarjeta de
sonido.
Audio: sera la responsable de crear sonidos y msica a partir de archivos ubicados en la
carpeta assets.

1.1 Interface de Sonido


public interface Sound {
public void play(float volume);
public void dispose();
}

Simplemente necesitaremos dos mtodos para los efectos de sonido, el mtodo play para
reproducirlo y el mtodo dispose que liberara el efecto de sonido de la memoria RAM.

1.2 Interface de Msica


public interface Music {
public void play();
public void stop();
public void pause();
public void setLooping(boolean looping);
public void setVolume(float volume);
public boolean isPlaying();
public boolean isStopped();
public boolean isLooping();
public void dispose();
}

Aqu necesitaremos los controles tpicos de un reproductor de msica: play, stop y pause. Despus
el mtodo setLooping lo usaremos para poner la msica en modo bucle/loop. Con el mtodo
setVolume indicaremos el volumen de salida de nuestra msica. Continuamos creando tres mtodos
(isPlaying, isStopped, isLooping) que usaremos para comprobar si la msica se esta reproduciendo,
esta parada o esta en modo bucle/loop. Y para terminar usaremos el mtodo dispose para liberar los
recursos de nuestro reproductor una vez que ya no lo necesitemos.

1.3 Interface de Audio


public interface Audio {
public Music newMusic(String filename);
public Sound newSound(String filename);
}

Con esta interface conseguiremos reducir en numero de instancias de nuestro cdigo y simplemente
la usaremos para crear nuevos objetos de Sonido o Audio, pasndole como parmetro el nombre del

archivo.

2.1 Implementar interface de Sonido


import android.media.SoundPool;
import com.example.slange.interfaces.Sound;
public class AndroidSound implements Sound {
int soundId;
SoundPool soundPool;
public AndroidSound(SoundPool soundPool, int soundId) {
this.soundId = soundId;
this.soundPool = soundPool;
}
public void play(float volume) {
soundPool.play(soundId, volume, volume, 0, 0, 1);
}
public void dispose() {
soundPool.unload(soundId);
}
}

Empezamos creando una variable int, que sera donde almacenaremos la id de nuestro efecto de
sonido y creamos un objeto soundPool que nos ayudara a gestionar y reproducir los efectos de
sonido.
En el constructor de la clase simplemente almacenamos los parmetros del constructor en las dos
variables que hemos creado para esta clase.
Para reproducir los efectos de sonido usaremos el mtodo play de la clase SoundPool:
play(soundID, leftVolume, rightVolume, priority, loop, rate)
Nos pide como parmetro la id del sonido que la conseguiremos mas adelante con el mtodo load
de esta misma clase, el volumen del canal izquierdo y derecho (usaremos los parmetros de nuestro
mtodo en este caso, el rango va desde 0.0 a 1.0), la prioridad de reproduccin (0 es la mas baja),
modo loop (0 desactivado y -1 activado) y para finalizar la tasa de reproduccin (el valor normal es
1, pero su rango va desde 0.5 a 2.0).
Para liberar el recurso de la memoria en nuestro SoundPool usaremos el mtodo unload que nos
pide como parmetro la id del sonido a liberar.

2.2 Implementar interface de Msica


import java.io.IOException;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import com.example.slange.interfaces.Music;
public class AndroidMusic implements Music, OnCompletionListener {
MediaPlayer mediaPlayer;
boolean isPrepared = false;

Comentar que a parte de implementar nuestra interface Msica, tambin implementamos la


interface OnCompletionListener que nos pide sobreescribir el mtodo onCompletion. Esta interface
se encarga de hacer una llamada a su mtodo una vez que la reproduccin a terminado, es decir,
cuando termina de sonar la msica porque a llegado a su fin.
Y el bloque sinchronized se encargara automticamente de gestionar los estados del reproductor
(reproduciendo, parado, pausado, preparado). Con ello conseguiremos no tener dos estados
diferentes a la vez y por lo tanto no tener excepciones IllegalStateException.
Empezamos la interface creando un objeto MediaPlayer que sera el encargado de preparar,
reproducir, pausar y parar nuestra msica. Tambin creamos un booleano para almacenar el estado
preparado del reproductor de msica.
public AndroidMusic(AssetFileDescriptor assetDescriptor) {
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(),
assetDescriptor.getStartOffset(),
assetDescriptor.getLength());
mediaPlayer.prepare();
isPrepared = true;
mediaPlayer.setOnCompletionListener(this);
} catch (Exception e) {
throw new RuntimeException("Error al cargar la musica");
}
}

Ya en el constructor primero iniciamos nuestro mediaplayer y encapsulamos todo en un bloque trycatch por si acaso hay problemas al cargar el archivo de msica.

Establecemos nuestra fuente de msica con el metodo setDataSource, que nos pide como parmetro
un FileDescriptor (usamos el parmetro AssetFileDescriptor con su mtodo getFileDescriptor que
nos devuelve un archivo de la carpeta assets para poder leer sus datos, as como el desplazamiento y
su longitud), el punto inicial de nuestro archivo de msica (con el
mtodo getStartOffset establecemos el inicio) y el final del archivo de msica (con el
mtodo getLength establecemos el final).
Preparamos el archivo para su reproduccin con el mtodo prepare y guardamos el estado true en
nuestra variable booleana.
Para terminar establecemos la llamada al mtodo onCompletion y creamos una nueva excepcin
con un mensaje personalizado.
public void dispose() {
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
}

Comprobamos si se esta reproduciendo y en este caso pararemos la reproduccin con el


mtodo stop y liberaremos el recurso de nuestro mediaplayer con el mtodo release.
public boolean isLooping() {
return mediaPlayer.isLooping();
}

Devuelve true en caso de que el reproductor este en modo bucle/loop.


public boolean isPlaying() {
return mediaPlayer.isPlaying();
}

Devuelve true en caso de que el reproductor este reproduciendo la msica.


public boolean isStopped() {
return !isPrepared;
}

Comprobaremos nuestra variable booleana y nos devolver false en caso de que se este
reproduciendo.
public void pause() {
if (mediaPlayer.isPlaying())
mediaPlayer.pause();
}

Comprobaremos si se esta reproduciendo y en este caso pausamos la reproduccin.


public void play() {
if (mediaPlayer.isPlaying())
return;
try {

synchronized (this) {
if (!isPrepared)
mediaPlayer.prepare();
mediaPlayer.start();
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

Lo primero que hacemos es comprobar si se esta reproduciendo y en este caso devolvemos la


funcin y ya no hace nada mas. En el caso contrario encapsulamos todo en un bloque try-catch
manejando dos excepciones. Una vez dentro del bloque, sincronizamos el estado del reproductor y
comprobamos si esta preparado a travs de nuestra variable booleana, en caso de que no lo este lo
preparamos con el mtodo prepare y para terminar empezamos la reproduccin con el mtodo start.
public void setLooping(boolean isLooping) {
mediaPlayer.setLooping(isLooping);
}

Ponemos el reproductor en modo bucle/loop a travs del mtodo mediaplayer setLooping,


indicndolo con el parmetro booleano de nuestro mtodo isLooping.
public void setVolume(float volume) {
mediaPlayer.setVolume(volume, volume);
}

Indicamos el volumen derecho e izquierdo a travs del mtodo mediaplayer setVolume y usando
nuestro parmetro float volume.
public void stop() {
mediaPlayer.stop();
synchronized (this) {
isPrepared = false;
}
}

Paramos la reproduccin con el mtodo mediaplayer stop y seguidamente sincronizamos el estado


del reproductor y ponemos nuestra variable a false indicando que no esta preparada la reproduccin.
public void onCompletion(MediaPlayer player) {
synchronized (this) {
isPrepared = false;
}
}

Para terminar sobreescribimos el mtodo onCompletion y simplemente sincronizamos el estado y


ponemos nuestra variable a false.

2.3 Implementar interface de Audio


import java.io.IOException;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioManager;
import android.media.SoundPool;
import com.example.slange.interfaces.Audio;
import com.example.slange.interfaces.Music;
import com.example.slange.interfaces.Sound;
public class AndroidAudio implements Audio {
AssetManager assets;
SoundPool soundPool;

Comentar que a parte de implementar nuestra interface Audio, estamos importando las interfaces de
Sonido y Msica.
Empezamos creando un objeto AssetManager y SoundPool, ya conocidos en estos ltimos artculos.
public AndroidAudio(Activity activity) {
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
this.assets = activity.getAssets();
this.soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
}

Ya en el constructor le aadimos como parmetro una Activity y usamos el


mtodo setVolumeControlStream que nos permitir cambiar el volumen con los controles de
hardware del dispositivo y nos pide como parmetro una fuente de msica que le indicamos que va
a ser un stream de msica a travs de la clase AudioManager.
En nuestra variable assets almacenamos una instancia de la carpeta assets.
Y en la variable soundPool almacenamos un nuevo sonido SoundPool indicando como parmetro el
numero mximo de sonidos simultneos (20), el tipo de sonido
(AudiosManager.STREAM_MUSIC) y la calidad de nuestro sonido (actualmente no tiene ningn
efecto, por lo que su valor por defecto es 0).
public Music newMusic(String filename) {

try {
AssetFileDescriptor assetDescriptor = assets.openFd(filename);
return new AndroidMusic(assetDescriptor);
} catch (IOException e) {
throw new RuntimeException("Error al cargar: '" + filename + "'");
}
}

Nos devolver un nuevo objeto AndroidMusic que cargaremos desde la carpeta assets.
public Sound newSound(String filename) {
try {
AssetFileDescriptor assetDescriptor = assets.openFd(filename);
int soundId = soundPool.load(assetDescriptor, 0);
return new AndroidSound(soundPool, soundId);
} catch (IOException e) {
throw new RuntimeException("Error al cargar: '" + filename + "'");
}
}
}

Y para terminar el mtodo newSound nos devolver un nuevo objeto AndroidSound que cargaremos
desde la carpeta assets, almacenando en memoria su id con el mtodo load.
En ambos casos manejamos la excepcin IOException en caso de que algo vaya mal a la hora de
cargar los archivos.

Creacin de un juego (4): Modulo Input


Modulo encargado de recoger y procesar los eventos de entrada del usuario. En Android tenemos
tres mtodos de entrada principales: pantalla tctil, teclado y acelermetro. Podremos recoger esos
eventos de entrada de dos maneras:
Polling: solo comprobara el estado actual del evento, cualquier estado entre el evento
actual y la ultima comprobacin se perder. Muy til para los eventos tctiles pero
inadecuado para recoger texto.
Handling: este mtodo nos dar una historia cronolgica completa de todos los eventos
que se han producido. Mecanismo muy adecuado para recoger texto o cualquier otra
tarea que se base en el orden de los acontecimientos.
Por lo tanto en este modulo vamos a crear tres manejadores (handlers) para recoger los eventos de
teclado, eventos de un toque tctil y eventos multi toque.

1.1 Interface Input

Esta interface sera la encargada de recoger todos los eventos de entrada del usuario. Para ello
creamos dos clases: una se encargara de los eventos del teclado (KeyEvent) y la otra de los eventos
tctiles (TouchEvent). Tambin declararemos varios mtodos que se explicaran a continuacin.
public static class KeyEvent {
public static final int KEY_DOWN = 0;
public static final int KEY_UP = 1;
public int type;
public int keyCode;
public char keyChar;
public String toString() {
StringBuilder builder = new StringBuilder();
if (type == KEY_DOWN)
builder.append("key down, ");
else
builder.append("key up, ");
builder.append(keyCode);
builder.append(",");
builder.append(keyChar);
return builder.toString();
}
}

Empezamos creando un par de variables que nos servirn para identificar el tipo de evento del
teclado:
KEY_DOWN: sucede cuando el usuario mantiene el dedo en una tecla del teclado.
KEY_UP: este evento sucede cuando el usuario levanta ese dedo de la tecla.
Por lo tanto de este modo conseguimos registrar los caracteres que va pulsando el usuario en su
teclado, estos caracteres los almacenaremos en la variable keyChar. La variable type se usara para
determinar el tipo de evento (0=KEY_DOWN y 1=KEY_UP). Y en la variable keyCode
almacenaremos el valor Unicode de cada carcter.
Creamos el mtodo toString para almacenar todos esos datos de un evento de teclado en un
StringBuilder que sera convertido a String y devuelto por el mtodo.
public static class TouchEvent {
public static final int TOUCH_DOWN = 0;
public static final int TOUCH_UP = 1;
public static final int TOUCH_DRAGGED = 2;
public int type;
public int x, y;

public int pointer;


public String toString() {
StringBuilder builder = new StringBuilder();
if (type == TOUCH_DOWN)
builder.append("touch down, ");
else if (type == TOUCH_DRAGGED)
builder.append("touch dragged, ");
else
builder.append("touch up, ");
builder.append(pointer);
builder.append(",");
builder.append(x);
builder.append(",");
builder.append(y);
return builder.toString();
}
}

Clase muy similar a la anterior, pero aqu tenemos tres tipos de evento tctil:
TOUCH_DOWN: sucede cuando el usuario mantiene un dedo en la pantalla.
TOUCH_DRAGGED: sucede cuando el usuario mueve un dedo por la pantalla.
TOUCH_UP: sucede cuando el usuario levanta el dedo de la pantalla.
En las variables x e y, almacenaremos las coordenadas de los eventos touch y en la variable pointer
la id de ese evento touch. Comentar que podemos tener varios eventos touch simultneamente y
para diferenciarlos lo haremos por su id.
Terminamos con el mtodo to String, que nos devolver un String con todos los datos de ese evento
touch.
public boolean isKeyPressed(int keyCode)
public boolean isTouchDown(int pointer);
public int getTouchX(int pointer);
public int getTouchY(int pointer);
public float getAccelX();
public float getAccelY();

public float getAccelZ();


public List<KeyEvent> getKeyEvents();
public List<TouchEvent> getTouchEvents();

Podramos decir que los siete primeros mtodos son mtodos polling y los dos ltimos handling.
Con el metodo isKeyPressed comprobaremos si el parmetro keyCode esta siendo pulsado o no.
Usando el metodo isTouchDown comprobaremos si hay un evento touch en un puntero
determinado.
Podremos conseguir las coordenadas X e Y de un evento touch con los mtodos: getTouchX y
getTouchY.
Tambin podremos consultar las coordenadas X, Y, y Z del acelermetro con los mtodos:
getAccelX, getAccelY y getAccelZ.
Los dos ltimos mtodos: getKeyEvents y getTouchEvents, nos devolvern una lista de sus
respectivos eventos.

1.2 Interface TouchHandler


import java.util.List;
import com.example.slange.interfaces.Input.TouchEvent;
import android.view.View.OnTouchListener;
public interface TouchHandler extends OnTouchListener {
public boolean isTouchDown(int pointer);
public int getTouchX(int pointer);
public int getTouchY(int pointer);
public List<TouchEvent> getTouchEvents();
}

Interface que usa los mismos mtodos que la anterior pero con la excepcin de que esta interface
extiende a OnTouchListener que nos ayudara a controlar los eventos touch en una view. A parte
importamos la clase TouchEvent de la interface Input.

2 Clase Pool
Se podra decir que su funcin principal es reutilizar eventos de usuario, esta clase tambin es
conocida como recolector de basura. Continuamente los eventos touch o key generan instancias que
se van acumulando en una lista y perjudicando a nuestro sistema Android. Para solucionar esto
vamos a implementar un concepto conocido como Pooling. En lugar de ir acumulando nuevas
instancias en una lista, esta clase se encargara de reutilizar las instancias mas antiguas y as
conseguir un equilibrio para nuestro sistema. A continuacin vamos a ver un ejemplo de este
concepto explicando detenidamente su metodologa:
import java.util.ArrayList;
import java.util.List;
public class Pool<T> {
public interface PoolObjectFactory<T> {
public T createObject();
}

Esta clase se denomina Genrica en Java y para implementar clases u objetos genricos usamos:
<>.
Es algo complejo as que recomiendo buscar informacin en internet.
Pero para hacernos una idea, la T indica la clase de objeto que usaremos en esta clase. Como
nosotros vamos a usar esta clase para reciclar los eventos touch y key, podemos sustituir esa T por
KeyEvent o TouchEvent y hacernos una idea de su funcionamiento. (En los siguientes puntos
veremos el uso de esta clase)
Lo primero que hacemos en esta clase es crear una interface genrica PoolObjectFactory con un
nico mtodo createObject que nos devolver una nueva instancia del evento de usuario.
private final List<T> freeObjects;
private final PoolObjectFactory<T> factory;
private final int maxSize;

Primero creamos un ArrayList freeObjects que sera donde se almacenen los eventos de usuario.
El segundo objeto factory, sera donde almacenemos las nuevas instancias de la interface
PoolObjectFactory.
Y el ultimo miembro maxSize, sera el numero mximo de eventos que almacenara nuestra clase
Pool.
public Pool(PoolObjectFactory<T> factory, int maxSize) {
this.factory = factory;
this.maxSize = maxSize;
this.freeObjects = new ArrayList<T>(maxSize);
}

El constructor toma como parmetros las instancias PoolObjectFactory y el numero mximo de


eventos a almacenar (maxSize).
Dentro del constructor almacenamos estos parmetros es sus respectivas variables (factory,
maxSize) y creamos una nueva lista (freeObjects) con el tamao mximo de eventos a recoger.
public T newObject() {
T object = null;
if (freeObjects.isEmpty())
object = factory.createObject();
else
object = freeObjects.remove(freeObjects.size() - 1);
return object;
}

Este mtodo sera el encargado de entregarnos un nuevo objeto del evento de usuario.
Se comprueba si la lista freeObjects esta vaca y en ese caso se crea una nueva instancia con ese
evento. En caso contrario se borra el ultimo evento de la lista.
Para finalizar el mtodo nos devolver el nuevo objeto.
public void free(T object) {
if (freeObjects.size() < maxSize)
freeObjects.add(object);
}
}

Para finalizar la clase, el mtodo free sera el encargado de almacenar los eventos de usuario que ya
no necesitemos. El mtodo simplemente inserta la nueva instancia del objeto en la lista freeObjects.
A continuacin veremos el uso de esta clase en nuestros manejadores (Handlers).

3.1 Clase KeyBoardHandler


En los siguientes tres puntos vamos a crear tres clases que nos servirn para manejar los eventos de
usuario. A estas clases se les denomina manejadores o mas bien "Handlers" en ingles. Primero
empezaremos con los eventos del teclado, vamos a ver esta clase en detalle.
import java.util.ArrayList;
import java.util.List;
import android.view.View;
import android.view.View.OnKeyListener;
import com.example.slange.interfaces.Input.KeyEvent;

import com.example.slange.interfaces.Pool;
import com.example.slange.interfaces.Pool.PoolObjectFactory;
public class KeyboardHandler implements OnKeyListener {
boolean[] pressedKeys = new boolean[128];
Pool<KeyEvent> keyEventPool;
List<KeyEvent> keyEventsBuffer = new ArrayList<KeyEvent>();
List<keyEvent> keyEvents = new ArrayList<KeyEvent>();

Empezamos implementando la interface OnKeyListener que nos pide sobreescribir el mtodo


onKey. Tambin importamos la clase KeyEvent de nuestra interface Input, la clase Pool y la
interface PoolObjectFactory.
Creamos una variable booleana con 128 posibilidades, que nos servir para conocer el estado actual
(presionado o no) de cada tecla. Cada tecla del teclado tiene asignada una constante
(KEYCODE_XXX) que va desde 0 hasta 127.
La siguiente se encargara de recoger los eventos del teclado y llevarlos a nuestra clase Pool. Para
ello indicamos entre <> los eventos que queremos mandar a la clase Pool, en este caso KeyEvent.
El primer ArrayList (keyEventsBuffer) es genrico y especificamos que va a ser una lista con
eventos KeyEvent. En esta lista se almacenaran los eventos que aun no han sido consumidos por
nuestro juego, es decir, los nuevos eventos.
Y el segundo ArrayList (keyEvents) es un segundo buffer para recoger los eventos del teclado, mas
adelante veremos su uso.
public KeyboardHandler(View view) {
PoolObjectFactory<KeyEvent> factory = new PoolObjectFactory<KeyEvent>() {
public KeyEvent createObject() {
return new KeyEvent();
}
};
keyEventPool = new Pool<KeyEvent>(factory, 100);
view.setOnKeyListener(this);
view.setFocusableInTouchMode(true);
view.requestFocus();
}

En el constructor usaremos como parmetro la view de la que queremos recibir los eventos del
teclado.
Creamos un objeto PoolObjectFactory que sera el encargado de recoger los eventos KeyEvent y
devolvernos una nueva instancia KeyEvent.
Continuamos creando nuestro recolector de eventos de teclado (keyEventPool), indicando como
parmetro la instancia KeyEvent que debe almacenar nuestro Pool y como segundo parmetro el

numero mximo de instancias a almacenar.


Establecemos que la view registre los eventos de teclado con el mtodo setOnKeyListener que har
una llamada al mtodo oKey para conocer el evento de teclado.
Nos aseguramos que la view puede recibe el foco aunque este en modo tctil
(setFocusableInTouchMode) y as pueda recibir los eventos de teclado.
Y terminamos estableciendo que la view recibe el foco (requestFocus).
public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
if (event.getAction() == android.view.KeyEvent.ACTION_MULTIPLE)
return false;
synchronized (this) {
KeyEvent keyEvent = keyEventPool.newObject();
keyEvent.keyCode = keyCode;
keyEvent.keyChar = (char) event.getUnicodeChar();
if (event.getAction() == android.view.KeyEvent.ACTION_DOWN) {
keyEvent.type = KeyEvent.KEY_DOWN;
if(keyCode > 0 && keyCode < 127)
pressedKeys[keyCode] = true;
}
if (event.getAction() == android.view.KeyEvent.ACTION_UP) {
keyEvent.type = KeyEvent.KEY_UP;
if(keyCode > 0 && keyCode < 127)
pressedKeys[keyCode] = false;
}
keyEventsBuffer.add(keyEvent);
}
return false;
}

Este mtodo sera llamado cada vez que la view registre un evento de teclado.
Lo primero que hacemos es comprobar si el evento es de accin mltiple (ACTION_MULTIPLE) y
directamente lo descartamos ya que estos eventos no nos sirven.
Debemos utilizar un bloque synchronized ya que los eventos se reciben en la interface de usuario y
se leen en el hilo principal. Tenemos que asegurarnos de que ninguno de los dos accede en paralelo.
Dentro del bloque lo primero que hacemos es mandar el evento de teclado a nuestro recolector de
basura (este se encargara de crear una nueva instancia o de reciclar una antigua).
Despus de registrar el evento, almacenamos el carcter del teclado (keyCode) y su valor Unicode
(keyChar) ayudandonos de los parmetros del mtodo.
Lo siguiente es comprobar que tipo de evento a tenido lugar (ACTION_DOWN, ACTION_UP) en

los dos casos registramos en nuestro keyEvent.type el tipo de evento. Comprobamos si la tecla
pulsada esta dentro del rango de constantes (0 hasta 127) y dependiendo del tipo de evento
devolveremos true o false a nuestra variable booleana pressedKeys.
Para terminar almacenamos nuestro evento de teclado en la lista keyEventBuffer.
public boolean isKeyPressed(int keyCode) {
if (keyCode < 0 || keyCode > 127)
return false;
return pressedKeys[keyCode];
}

Con este mtodo sabremos si una tecla es pulsada o no.


Para ello comprobamos si el parmetro keyCode esta dentro del rango (0 a 127) y en caso de que no
lo este devolvemos el mtodo con un false. En caso de que se encuentre dentro de ese rango, el
mtodo devolver true en la posicin keyCode de nuestra variable pressedKeys.
public List<KeyEvent> getKeyEvents() {
synchronized (this) {
int len = keyEvents.size();
for (int i = 0; i < len; i++) {
keyEventPool.free(keyEvents.get(i));
}
keyEvents.clear();
keyEvents.addAll(keyEventsBuffer);
keyEventsBuffer.clear();
return keyEvents;
}
}
}

Mtodo que nos devolver la lista de eventos de teclado mas nuevos. Se encargara de pasar los
eventos de nuestra lista a nuestro recolector de basura Pool. A parte de limpiar nuestra lista. Y aqu
es donde entra en juego el segundo buffer de almacenamiento de eventos. Comentar que deberemos
llamar frecuentemente a este metodo para limpiar los eventos de teclado.
Como en el mtodo onKey, aqui tambin debemos usar un bloque synchronized.
Con el bucle for mandamos todos los eventos de la lista keyEvents a nuestro recolector de basura
keyEventPool, haciendo uso del mtodo free que se encargara de almacenarlos.
Despus limpiamos la lista keyEvents con el mtodo clear, aadimos a la lista keyEvents los nuevos
eventos keyEventsBuffer y limpiamos nuestro buffer principal keyEventsBuffer.
Finalmente el mtodo devuelve la lista keyEvents con los ltimos eventos producidos.

3.2 Clase SingleTouchHandler


En los dos siguientes puntos vamos a crear dos clases que se encargaran de manejar los eventos
touch (single y multi). Antiguamente los eventos singletouch se usaban para un nivel de API 4 o
inferior. Y lo eventos multitouch se implementaron a partir de la API 5.
Actualmente hay un porcentaje muy pequeo de terminales en el mercado que usa una versin de
Android inferior a la 2.0 (0.2%) y con el tiempo terminara por desaparecer del mercado. Por lo tanto
no tenemos que preocuparnos por esto. Pero vamos a conocer las dos clases (multi y single) que nos
sern de gran ayuda para crear nuestros juegos. Vamos a ver la clase SingleTouch en detalle:
import java.util.ArrayList;
import java.util.List;
import android.view.MotionEvent;
import android.view.View;
import com.example.slange.interfaces.Pool;
import com.example.slange.interfaces.TouchHandler;
import com.example.slange.interfaces.Input.TouchEvent;
import com.example.slange.interfaces.Pool.PoolObjectFactory;
public class SingleTouchHandler implements TouchHandler {
boolean isTouched;
int touchX;
int touchY;
Pool<TouchEvent> touchEventPool;
List<TouchEvent> touchEvents = new ArrayList<TouchEvent>();
List<TouchEvent> touchEventsBuffer = new ArrayList<TouchEvent>();
float scaleX;
float scaleY;

Empezamos implementando nuestra interface TouchHandler que nos har sobreescribir sus cuatro
mtodos mas el mtodo onTouch de la interface OnTouchListener. Tambin importamos la clase
TouchEvent de nuestra interface Input, la clase Pool y la interface PoolObjectFactory.
En el primer miembro isTouched almacenaremos si un puntero (un dedo) esta tocando la pantalla. Y
en las dos siguientes variables (touchX, touchY) almacenaremos las coordenadas de ese puntero.
Creamos los tres miembros ya vistos en el punto anterior, para manejar las instancias de los eventos
touch.
Y los dos ltimos miembros los usaremos para tratar con las diferentes resoluciones de pantalla,
mas adelante veremos su uso.
public SingleTouchHandler(View view, float scaleX, float scaleY) {
PoolObjectFactory<TouchEvent> factory = new

PoolObjectFactory<TouchEvent>() {
public TouchEvent createObject() {
return new TouchEvent();
}
};
touchEventPool = new Pool<TouchEvent>(factory, 100);
view.setOnTouchListener(this);
this.scaleX = scaleX;
this.scaleY = scaleY;
}

Constructor muy parecido al de KeyBoardHandler, a diferencia que en este registramos los eventos
touch en nuestra view a travs del mtodo setOnTouchListener que har una llamada al mtodo
onTouch. Y aqu almacenamos los parmetros (scaleX, scaleY) en sus respectivas variables.
public boolean onTouch(View v, MotionEvent event) {
synchronized(this) {
TouchEvent touchEvent = touchEventPool.newObject();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchEvent.type = TouchEvent.TOUCH_DOWN;
isTouched = true;
break;
case MotionEvent.ACTION_MOVE:
touchEvent.type = TouchEvent.TOUCH_DRAGGED;
isTouched = true;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
touchEvent.type = TouchEvent.TOUCH_UP;
isTouched = false;
break;
}
touchEvent.x = touchX = (int)(event.getX() * scaleX);
touchEvent.y = touchY = (int)(event.getY() * scaleY);
touchEventsBuffer.add(touchEvent);
return true;
}
}

Mtodo que consigue el mismo resultado que el mtodo onKey de nuestra clase KeyboardHandler,

pero en este caso manejamos eventos TouchEvent.


Creamos un nuevo objeto TouchEvent que almacenamos en nuestro Pool touchEventPool.
Con el bloque switch comprobamos que tipo de evento a tenido lugar y lo almacenamos en la
variable de nuestro objeto touchEvent.type. Segn el tipo de evento almacenamos en nuestra
variable isTouched true o false.
Almacenamos las coordenadas de ese evento en las variables de nuestro objeto (touchEvent.x,
touchEvent.y) y tambin en las variables de nuestra clase (touchX, touchY). Para conocer las
coordenadas usamos el parmetro del mtodo (event) con sus mtodos (getX, getY) y esto lo
multiplicamos por la escala de la pantalla (mas adelante veremos como calcular la escala para los
diferentes tamaos de pantalla).
Para terminar aadimos nuestro objeto touchEvent a nuestra lista touchEventsBuffer y devolvemos
true al mtodo.
public boolean isTouchDown(int pointer) {
synchronized(this) {
if(pointer == 0)
return isTouched;
else
return false;
}
}

Con este mtodo comprobaremos si el puntero esta tocando la pantalla


public int getTouchX(int pointer) {
synchronized(this) {
return touchX;
}
}
public int getTouchY(int pointer) {
synchronized(this) {
return touchY;
}
}

Usando estos dos mtodos podremos conocer las coordenadas del puntero. Nos devolver el valor
que haya almacenado en touchX y touchY.
public List<TouchEvent> getTouchEvents() {
synchronized(this) {
int len = touchEvents.size();
for( int i = 0; i < len; i++ )
touchEventPool.free(touchEvents.get(i));

touchEvents.clear();
touchEvents.addAll(touchEventsBuffer);
touchEventsBuffer.clear();
return touchEvents;
}
}

Mismo mtodo que la clase KeyboardHandler. Recordar que deberemos llamar a este mtodo
frecuentemente.

3.3 Clase MultiTouchHandler


Clase que va a ser muy similar a la anterior pero con la diferencia que en esta clase vamos a usar
mas de un puntero.
import java.util.ArrayList;
import java.util.List;
import android.annotation.TargetApi;
import android.view.MotionEvent;
import android.view.View;
import com.example.slange.interfaces.Input.TouchEvent;
import com.example.slange.interfaces.Pool;
import com.example.slange.interfaces.TouchHandler;
import com.example.slange.interfaces.Pool.PoolObjectFactory;
public class MultiTouchHandler implements TouchHandler {
private static final int MAX_TOUCHPOINTS = 10;
boolean[] isTouched = new boolean[MAX_TOUCHPOINTS];
int[] touchX = new int[MAX_TOUCHPOINTS];
int[] touchY = new int[MAX_TOUCHPOINTS];
int[] id = new int[MAX_TOUCHPOINTS];
Pool<TouchEvent> touchEventPool;
List<TouchEvent> touchEvents = new ArrayList<TouchEvent>();
List<TouchEvent> touchEventsBuffer = new ArrayList<TouchEvent>();
float scaleX;
float scaleY;

Tenemos la misma implementacin e importacin que la clase anterior.


Empezamos declarando el numero mximo de punteros que vamos a controlar, en este caso sern
10.

Luego creamos cuatro variables para almacenar los datos de cada puntero. Si esta tocando la
pantalla (isTouched), sus coordenadas x e y (touchX, touchY) y su identidad (id).
Lo siguiente es lo mismo que en la clase anterior.
public MultiTouchHandler(View view, float scaleX, float scaleY) {
PoolObjectFactory<TouchEvent> factory = new
PoolObjectFactory<TouchEvent>() {
public TouchEvent createObject() {
return new TouchEvent();
}
};
touchEventPool = new Pool<TouchEvent>(factory, 100);
view.setOnTouchListener(this);
this.scaleX = scaleX;
this.scaleY = scaleY;
}

El constructor es el mismo que la clase anterior.


public boolean onTouch(View v, MotionEvent event) {
synchronized (this) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK)
>> MotionEvent.ACTION_POINTER_ID_SHIFT;
int pointerCount = event.getPointerCount();
TouchEvent touchEvent;
for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
if (i >= pointerCount) {
isTouched[i] = false;
id[i] = -1;
continue;
}
int pointerId = event.getPointerId(i);
if (event.getAction() != MotionEvent.ACTION_MOVE && i !=
pointerIndex) {
continue;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
touchEvent = touchEventPool.newObject();

touchEvent.type = TouchEvent.TOUCH_DOWN;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = true;
id[i] = pointerId;
touchEventsBuffer.add(touchEvent);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_UP;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = false;
id[i] = -1;
touchEventsBuffer.add(touchEvent);
break;
case MotionEvent.ACTION_MOVE:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_DRAGGED;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = true;
id[i] = pointerId;
touchEventsBuffer.add(touchEvent);
break;
}
}
return true;
}
}

Mtodo que a priori puede parecer complicado pero es muy similar al anterior, lo que en este caso
almacenamos todos los punteros que pueda haber en la pantalla.
En la variable action almacenaremos el tipo de evento que tiene lugar. En pointerIndex
almacenaremos el ndice del puntero, este ndice puede cambiar si un puntero suelta la pantalla, por

lo que usamos la variable pointerId para almacenar la identidad real de cada puntero.
public boolean isTouchDown(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return false;
else
return isTouched[index];
}
}
public int getTouchX(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return 0;
else
return touchX[index];
}
}
public int getTouchY(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return 0;
else
return touchY[index];
}
}

Con estos mtodos podremos saber si un puntero en concreto (lo indicaremos con el parmetro
pointer) esta tocando la pantalla y tambin podremos conocer sus coordenadas x e y.
public List<TouchEvent> getTouchEvents() {
synchronized (this) {
int len = touchEvents.size();
for (int i = 0; i < len; i++)
touchEventPool.free(touchEvents.get(i));
touchEvents.clear();
touchEvents.addAll(touchEventsBuffer);
touchEventsBuffer.clear();
return touchEvents;

}
}

Mismo mtodo que en clases anteriores.


private int getIndex(int pointerId) {
for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
if (id[i] == pointerId) {
return i;
}
}
return -1;
}

Con este ultimo mtodo podremos conocer el ndice de un puntero en concreto, para ello usamos el
parmetro pointerId.
Con esto terminamos el articulo, pero comentar que podramos hacer mas clases Handler para
controlar por ejemplo los sensores de un terminal. En el cdigo de ejemplo tenemos dos clases mas
para manejar estos sensores.

Creacin de un juego (5): Modulo Graficos


Este modulo es algo mas complejo de los que llevamos creados hasta ahora. Y sera el responsable
de cargar imgenes (bitmaps) en nuestras pantallas. En principio si queremos grficos de alto
rendimiento primero deberemos saber por lo menos los fundamentos de la programacin grfica y
para ello vamos a conocer los conceptos bsicos en grficos 2D. Crearemos dos interfaces y tres
clases para manejar los grficos de nuestro juego.
Para llevar a cabo esta tarea deberemos de ser capaces de realizar las siguientes operaciones:
Almacenar imgenes en la RAM para su posterior uso.
Limpiar nuestra pantalla de todos los elementos que la componen.
Pintar un pixel de la pantalla con un color especifico.
Dibujar lineas y rectngulos en nuestra pantalla.
Dibujar imgenes en la pantalla cargadas previamente. Ya sea una imagen completa o
una porcin de la misma. Tambin dibujar imgenes con y sin mezcla.
Obtener el tamao de nuestra pantalla de dibujo.

1.1 Interface Pixmap


import com.example.slange.interfaces.Graphics.PixmapFormat;

public interface Pixmap {


public int getWidth();
public int getHeight();
public PixmapFormat getFormat();
public void dispose();
}

Primero importamos la interface Grficos para tener acceso a su miembro PixmapFormat, se


explicara en el siguiente punto del articulo.
Esta interface nos permitir conocer el tamao en pxeles de una imagen, a travs de los mtodos
getWidth y getHeight. Tambin podremos conoces su formato con el mtodo getFormat. Y para
terminar, con el mtodo dispose liberaremos las imgenes cargadas en la memoria.

1.2 Interface Grficos


public interface Graphics {
public static enum PixmapFormat {
ARGB8888, ARGB4444, RGB565
}
public Pixmap newPixmap(String fileName, PixmapFormat format);
public void clear(int color);
public void drawPixel(int x, int y, int color);
public void drawLine(int x, int y, int x2, int y2, int color);
public void drawRect(int x, int y, int width, int height, int color);
public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY,
int srcWidth, int srcHeight);
public void drawPixmap(Pixmap pixmap, int x, int y);
public int getWidth();
public int getHeight();

Interface que nos servir para dibujar en pantalla tanto imgenes como pxeles a parte de poder
recuperar el tamao de nuestra pantalla de dibujo (framebuffer).
Primero creamos un enum PixmapFormat donde almacenamos 3 tipos de formato de imagen. Los
enum sirven para restringir el contenido de una variable (en nuestro caso PixmapFormat) a una serie
de valores predefinidos (ARGB8888, ARGB4444, RGB565), es decir, nuestra variable
PixmapFormat solo tienes las 3 posibilidades, esto suele ayudar a reducir los errores de nuestro
cdigo. Esta variable la usaremos para indicar el tipo de formato de imagen que necesitemos,
consiguiendo almacenar la imagen en un tamao menor o mayor:
ARGB444: cada pixel de la imagen se almacena en 2 bytes y los tres canales de color
RGB mas el canal alpha (A) se almacenan con una precisin de 4 bits (16
posibilidades). til cuando queremos usar el menor almacenamiento para nuestras
imgenes, pero se recomienda usar ARGB888.
ARGB888: cada pixel se almacena en 4 bytes. Cada canal ARGB se almacena con una
precisin de 8 bits (256 posibilidades). Esta configuracin es muy flexible y ofrece la
mejor calidad pero mayor tamao de almacenamiento.
RGB565: cada pixel se almacena en 2 bytes y solo disponemos de los canales RGB. el
rojo se almacena con 5 bits de precisin (32 posibilidades), el verde con 6 bits (64
posibilidades) y el azul con 5 bits. Esta configuracin es til cuando se usan imgenes
opacas que no requieren alta definicin de color.
Continuamos creando un mtodo newPixmap que nos devolver un objeto Pixmap y como
parmetros indicaremos el nombre del archivo de imagen y el formato que necesitemos darle.
Los cuatro siguientes metodos (clear, drawPixel, drawLine, drawRect) los usaremos para colorear
pxeles con un color especifico y de varias formas posibles (toda la pantalla, un solo pixel, una linea
o un rectngulo).
Despus tenemos dos mtodos drawPixmap que nos ayudaran a dibujar en pantalla una imagen o
una porcin de imagen en el sitio que le indiquemos de nuestra pantalla de dibujo (framebuffer).
Y por ultimo dos mtodos que nos devolvern el ancho y el alto de la pantalla de dibujo
(framebuffer).

2.1 Implementar interface Pixmap


import android.graphics.Bitmap;
import com.example.slange.interfaces.Graphics.PixmapFormat;
import com.example.slange.interfaces.Pixmap;
public class AndroidPixmap implements Pixmap {
Bitmap bitmap;
PixmapFormat format;

public AndroidPixmap(Bitmap bitmap, PixmapFormat format) {


this.bitmap = bitmap;
this.format = format;
}

A parte de implementar la interface Pixmap, importamos a esta clase el enum PixmapFormat de la


interface Grficos.
Empezamos creando dos objetos: un Bitmap (que nos ayudara a trabajar con archivos de mapa de
bits, es decir, imgenes) y un PixmapFormat (que usaremos para determinar que tipo de formato le
queremos dar a la imagen).
En el constructor simplemente almacenamos los dos parmetros que le pasamos al constructor, en
los dos objetos que hemos creado para esta clase.
public int getWidth() {
return bitmap.getWidth();
}
public int getHeight() {
return bitmap.getHeight();
}

Aplicando los mtodos getWidth y getHeight nos devolver el ancho y alto de nuestra imagen
bitmap.
public PixmapFormat getFormat() {
return format;
}
public void dispose() {
bitmap.recycle();
}
}

El mtodo getFormat nos devolver un PixmapFormat. El formato de nuestra imagen bitmap.


Y para terminar con el mtodo dispose liberaremos de la memoria la imagen asociada al bitmap
gracias al mtodo recycle de la clase Bitmap.

2.2 Implementar interface Grficos


import java.io.IOException;
import java.io.InputStream;
import android.content.res.AssetManager;
import android.graphics.Bitmap;

import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import com.example.slange.interfaces.Graphics;
import com.example.slange.interfaces.Pixmap;
public class AndroidGraphics implements Graphics {
AssetManager assets;
Bitmap frameBuffer;
Canvas canvas;
Paint paint;
Rect srcRect = new Rect();
Rect dstRect = new Rect();

A parte de implementar la interface Graficos, importamos tambin la interface Pixmap ya que


usaremos algn atributo suyo en esta clase.
Empezamos creando un objeto AssetManager que a estas alturas ya sabremos lo que es.
Creamos un objeto Bitmap que como iba indicando pasos atrs sera nuestra pantalla de dibujo
(framebuffer) donde colocaremos los distintos elementos de nuestras pantallas del juego.
Otro objeto Canvas que atender a las llamadas de dibujo, es decir, dibujara pxeles o imgenes en
nuestro framebuffer.
Un objeto Paint que sera el encargado de darle estilo y color a lo que dibujemos en nuestro
framebuffer.
Y por ultimo creamos dos objetos Rect: srcRect se encargara de almacenar las cuatro coordenadas
de un rectngulo (que sern las 4 esquinas de nuestras imgenes) y dstRect se encargara de dibujar
esas imgenes en las coordenadas que le indiquemos.
public AndroidGraphics(AssetManager assets, Bitmap frameBuffer) {
this.assets = assets;
this.frameBuffer = frameBuffer;
this.canvas = new Canvas(frameBuffer);
this.paint = new Paint();
}

Almacenamos los parmetros del constructor de la clase en sus respectivas variables de la clase.
Iniciamos el objeto canvas indicando como parmetro la pantalla donde podr dibujar.
Y terminamos iniciando el objeto Paint.
public Pixmap newPixmap(String fileName, PixmapFormat format) {
Config config = null;
if (format == PixmapFormat.RGB565)

config = Config.RGB_565;
else if (format == PixmapFormat.ARGB4444)
config = Config.ARGB_4444;
else
config = Config.ARGB_8888;
Options options = new Options();
options.inPreferredConfig = config;
InputStream in = null;
Bitmap bitmap = null;
try {
in = assets.open(fileName);
bitmap = BitmapFactory.decodeStream(in);
if (bitmap == null)
throw new RuntimeException("Error al cargar bitmap desde assets
'"
+ fileName + "'");
} catch (IOException e) {
throw new RuntimeException("Error al cargar bitmap desde assets '"
+ fileName + "'");
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
if (bitmap.getConfig() == Config.RGB_565)
format = PixmapFormat.RGB565;
else if (bitmap.getConfig() == Config.ARGB_4444)
format = PixmapFormat.ARGB4444;
else
format = PixmapFormat.ARGB8888;
return new AndroidPixmap(bitmap, format);
}

El mtodo newPixmap intentara cargar una imagen desde la carpeta assets. Parece muy complicado
pero es mas fcil de lo que aparenta.

Empezamos creando un objeto Config que tiene los mismos tipos de formato que nuestro objeto
PixmapFormat. Comprobaremos en que formato (ARGB444, ARGB888, RGB565) viene nuestra
imagen y lo almacenaremos en nuestro objeto config.
Seguidamente creamos un objeto Options para almacenar el tipo de formato config preferido. Este
objeto options se encargara automticamente de establecer este formato si es posible.
Intentamos cargar una imagen desde la carpeta assets al objeto bitmap a travs del mtodo
decodeStream de la clase BitmapFactory que nos pide como parmetro una fuente de datos a leer.
Manejaremos la IOException en caso de que ocurra algo y comprobaremos que nuestro objeto
bitmap es nulo por si acaso. Finalmente si nuestro InputStream no es nulo, lo cerramos.
Tras cargar la imagen en nuestro bitmap, el BitmapFactory podra hacer caso omiso del tipo de
formato de nuestra imagen por lo que tenemos que volver a comprobarlo y almacenarlo en nuestro
parmetro format.
Para terminar devolvemos un nuevo objeto AndroidPixmap indicando los parmetros que hemos
recogido.
public void clear(int color) {
canvas.drawRGB((color & 0xff0000) >> 16, (color & 0xff00) >> 8,
(color & 0xff));
}

Con este mtodo podremos pintar toda la pantalla de nuestro framebuffer del color que se le indique
como parmetro, para ello haremos uso del mtodo drawRGB que nos pide como parmetro un
rango de 0 a 255 para cada color primario(rojo, verde, azul).
public void drawPixel(int x, int y, int color) {
paint.setColor(color);
canvas.drawPoint(x, y, paint);
}

Podremos pintar un pixel con el siguiente mtodo, indicando como parmetro las coordenadas x e y
de nuestro framebuffer. Como parmetro color indicaremos el estilo de color a nuestro objeto paint
y realizaremos el dibujo con el mtodo drawPoint.
public void drawLine(int x, int y, int x2, int y2, int color) {
paint.setColor(color);
canvas.drawLine(x, y, x2, y2, paint);
}

Pintaremos una linea gracias a este mtodo, indicando las coordenadas de inicio (x, y) y las
coordenadas de destino (x2, y2). Para ello hacemos uso del mtodo drawLine de la clase Canvas.
public void drawRect(int x, int y, int width, int height, int color) {
paint.setColor(color);
paint.setStyle(Style.FILL);
canvas.drawRect(x, y, x + width - 1, y + width - 1, paint);
}

Comentar aqu que las coordenadas 0, 0 de x e y de nuestro terminal, se encuentran en la esquina


superior izquierda de la pantalla (por lo tanto empezaremos a sumar hacia abajo y hacia la derecha).
Sabiendo esto, con el siguiente mtodo podremos pintar un rectngulo con el color que se le
indique. El inicio del rectngulo y por lo tanto su esquina superior izquierda la declararemos con los
parmetros x e y. La altura y anchura lo haremos con height y width.
Si nos fijamos le estamos restando -1 a la altura y anchura porque si declarramos un rectngulo de
5x5, el mtodo drawRect coje esos dos pxeles de altura o anchura y le suma el punto de partida con
lo cual se con quedara un rectngulo de 6x6.
public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY,
int srcWidth, int srcHeight) {
srcRect.left = srcX;
srcRect.top = srcY;
srcRect.right = srcX + srcWidth - 1;
srcRect.bottom = srcY + srcHeight - 1;
dstRect.left = x;
dstRect.top = y;
dstRect.right = x + srcWidth - 1;
dstRect.bottom = y + srcHeight - 1;
canvas.drawBitmap(((AndroidPixmap) pixmap).bitmap, srcRect, dstRect,
null);
}

Con este mtodo conseguiremos pintar en pantalla una porcin de imagen y ocurre los mismo que
en la pantalla, las coordenadas 0,0 de x e y de una imagen se encuentran en la esquina superior
izquierda.
Primero deberemos seleccionar la porcin de imagen indicando su esquina superior izquierda con
los parmetros (srcX y srcY), para el ancho usaremos (srcWidth) y para el alto (srcHeight).
Una vez seleccionada el mtodo drawPixmap almacena estos datos en el objeto srcRect.
Y para indicar donde la queremos pintar en nuestro framebuffer usaremos los parmetro (x, y), el
propio mtodo sabiendo la esquina superior izquierda, calculara las cuatro coordenadas y las
almacenara en el objeto dstRect.
Recordar que le restamos -1 ya que sino la imagen exceder en un pixel.
Finalmente podemos pintar esa porcin de imagen con el mtodo drawBitmap de la clase Canvas.
public void drawPixmap(Pixmap pixmap, int x, int y) {
canvas.drawBitmap(((AndroidPixmap)pixmap).bitmap, x, y, null);
}

Este mtodo es similar al anterior pero mucho mas sencillo, con el simplemente dibujaremos en
pantalla una imagen completa en las coordenada (x, y) que se le indique.

public int getWidth() {


return frameBuffer.getWidth();
}
public int getHeight() {
return frameBuffer.getHeight();
}
}

Para terminar la clase sobreescribimos los mtodos detWidth y getHeight que nos devolvern el
tamao de pantalla de dibujo.

2.3 Clase FastRenderView


Podramos decir que esta clase sera la encargada de pintar nuestro framebuffer en la pantalla de
nuestro terminal, colocando el framebuffer en su lugar correspondiente y escalndolo a los
diferentes tamaos de pantalla si es necesario. Tambin nos permitir conocer la pantalla activa del
juego y podremos controlar el DeltaTime en todo momento.
El DeltaTime es un concepto que se utiliza en programacin que relaciona el hardware y su
capacidad de respuesta. Y cuando hablamos de mover grficos el deltatime se calcula llamando a un
temporizador que controlara los fotogramas por segundo del hardware y coger el tiempo que pasa
entre que se ve un fotograma y el siguiente, esto nos servir para mostrar la misma cantidad de
cuadros por segundo.
A continuacin vamos a ir viendo la clase por partes, explicando su metodologa:
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class AndroidFastRenderView extends SurfaceView implements Runnable {
AndroidGame game;
Bitmap framebuffer;
Thread renderThread = null;
SurfaceHolder holder;
volatile boolean running = false;

Extendemos la clase SurfaceView que nos proporciona una superficie de dibujo dentro de una view,
a parte implementamos la interface Runnable que nos servir para manejar hilos secundarios, es
decir, podremos ejecutar cdigo en un hilo diferente al principal.Esta implementacin nos pide
sobreescribir el mtodo run.

Creamos una instancia de la clase AndroidGame (en el articulo 6 veremos el modulo juego y de que
trata esta clase). Y un objeto Bitmap que sera nuestra SurfaceView.
Continuamos creando un objeto Thread que usaremos para crear hilos nuevos o eliminar los
existentes. Un thread se puede definir como una unidad de cdigo en ejecucin.
El objeto SurfaceHolder nos permitir controlar el tamao y formato de la superficie de dibujo,
editar los pxeles y vigilar los cambios en dicha superficie. Esta interface esta disponible a travs de
la clase SurfaceView.
Y por ultimo creamos una variable booleana donde almacenaremos si un hilo debe ser detenido o
reanudado. Con el modificador volatile indicamos que dicha variable puede ser modificada por
varios hilos (threads) de forma simultanea y asncrona, asegurando as su valor en todo momento a
costa de un pequeo impacto en el rendimiento.
public AndroidFastRenderView(AndroidGame game, Bitmap framebuffer) {
super(game);
this.game = game;
this.framebuffer = framebuffer;
this.holder = getHolder();
}

Con el modificador super estamos llamando al constructor de la clase AndroidGame a travs del
parmetro game (AndroidGame se trata de una Activity que veremos en el capitulo siguiente).
Almacenamos tambin nuestro parmetro en la variable game de la clase.
Tambin almacenamos el segundo parmetro en su variable framebuffer. Y en la variable holder
usamos el mtodo getHolder que nos devuelve una instancia SurfaceHolder que almacenamos
tambin.
public void resume() {
running = true;
renderThread = new Thread(this);
renderThread.start();
}

Mtodo que usaremos para crear nuevos hilos. En cada hilo pondremos su variable running a true
indicando que ese hilo esta en ejecucin.
Iniciamos un nuevo hilo y lo ejecutamos con el mtodo start (que este a su vez har una llamada al
mtodo run para el hilo creado).
Con este mtodo resume nos aseguramos de que nuestro nuevo hilo acta bien con el ciclo de vida
de la Activity.
public void run() {
Rect dstRect = new Rect();
long startTime = System.nanoTime();
while(running) {
if(!holder.getSurface().isValid())
continue;

float deltaTime = (System.nanoTime()-startTime) / 1000000000.0f;


startTime = System.nanoTime();
game.getCurrentScreen().update(deltaTime);
game.getCurrentScreen().present(deltaTime);
Canvas canvas = holder.lockCanvas();
canvas.getClipBounds(dstRect);
canvas.drawBitmap(framebuffer, null, dstRect, null);
holder.unlockCanvasAndPost(canvas);
}
}

Este mtodo sera llamado cada vez que se cree un nuevo hilo o pantalla de juego. Y sera el
encargado de actualizar/renderizar los objetos en la pantalla en cada momento. Se ira repitiendo
continuamente en la pantalla que lo llame.
Empezamos creando un objeto Rect que nos servir para almacenar los limites de la pantalla. Y una
variable startTime donde almacenaremos la hora actual en nanosegundos. Un nanosegundo es una
mil millonsima de segundo.
Seguimos creando un bucle while que se repetir mientras nuestro hilo este en ejecucin. Lo
primero que hacemos dentro del hilo es comprobar si existe una superficie valida gracias al bloque
if, si no existe una superficie valida entra en juego continue que hace terminar el bucle.
Si existe una superficie valida calculamos el DeltaTime, lo convertimos a segundos y seguidamente
almacenamos de nuevo la hora actual.
Una vez calculado el DeltaTime usamos los mtodos update y present para ir actualizando la
pantalla actual con el intervalo de tiempo DeltaTime. (en el articulo 6 veremos estos mtodos en
detalle)
Por ultimo creamos un objeto canvas al que le indicamos con el mtodo lockCanvas que puede
comenzar la edicin de pxeles en la superficie de dibujo. Con el mtodo getClipBounds
recuperamos los limites de la superficie de dibujo. Y ya con el mtodo drawBitmap haremos que
pinte una pantalla del juego. Para finalizar llamamos al mtodo unlockCanvasAndPost para
terminar la edicin de pxeles y conseguir mostrar una pantalla de nuestro juego.
public void pause() {
running = false;
while(true) {
try {
renderThread.join();
return;
} catch (InterruptedException e) {
// retry
}
}

Con el mtodo pause bloquearemos el hilo en ejecucin a la espera de que el usuario lo finalice y
termine por desaparecer de la memoria.

Creacin de un juego (6): Modulo Juego


Con este modulo terminamos la interface bsica para empezar a desarrollar juegos. Es el modulo
mas importante ya que se encarga de gestionar todas las clases que hemos visto con anterioridad.
Todo lo que tenemos que hacer es encajar todas las piezas teniendo en cuenta varias cosas:
Deberemos realizar la gestin de ventanas (pantallas de juego) creando una Activity y
una instancia AndroidFastRenderView a parte de manipular el ciclo de vida en la
actividad de forma limpia
Crear y gestionar un WakeLock para que la pantalla no se apague mientras jugamos
Crear instancias para manejar todos los mdulos anteriores (Grficos, Audio, FileIO, ...)
Administrar las pantallas e integrarlas con el ciclo de vida de la actividad

1.1 Interface Game


Esta interface sera la encargada de darnos acceso a todos los mdulos que hemos desarrollado
anteriormente y sera capaz de una gestin absoluta de las pantallas del juego. Veamos a
continuacin los componentes de esta interface:
public interface Game {
public Input getInput();
public FileIO getFileIO();
public Graphics getGraphics();
public Audio getAudio();
public void setScreen(Screen screen);
public Screen getCurrentScreen();
public Screen getStartScreen();
}

Los cuatro primeros mtodos nos devolvern una instancia de cada una de las interfaces para poder

trabajar con sus mtodos.


El mtodo setScreen nos permite configurar la pantalla actual del juego, para ello lo indicaremos en
su parmetro.
Con el mtodo getCurrentScreen podremos saber que pantalla esta activa en ese momento.
Y con el ultimo mtodo getStartScreen iniciaremos nuestro juego.

1.2 Interface Screen


La ultima pieza de nuestro rompecabezas es la clase abstracta Screen. Esta vez creamos una clase
abstracta en vez de una interface porque habr veces que no necesitemos usar todos sus mtodos.
Esta clase nos ayudara a presentar los objetos en pantalla, actualizar la pantalla segn el delta time,
pausar o reanudar el juego. Cada pantalla que creemos en nuestro juego deber implementar esta
interface excepto la primera pantalla o pantalla de inicio de nuestro juego que deber extender la
clase AndroidGame sobreescribiendo el mtodo getStartScreen. Vamos a ver la interface:
public abstract class Screen {
protected final Game game;
public Screen(Game game) {
this.game = game;
}
public abstract void update(float deltaTime);
public abstract void present(float deltaTime);
public abstract void pause();
public abstract void resume();
public abstract void dispose();
}

Lo primero que hacemos es crear una instancia de nuestra interface Juego y ya en el constructor
almacenamos su parmetro en ella. Con esto conseguimos dos cosas: tener acceso a todos los
mdulos de la interface juego (Input, FileIO, Graficos, Audio) y a parte poder crear una nueva
ventada desde la pantalla actual, llamando al mtodo Game.setScreen.
Por lo tanto cuando implementamos esta interface necesitamos tener acceso a todos los mdulos
para crear y gestionar esa pantalla de nuestro juego.
Con los mtodos update y present, podremos actualizar y presentar los componentes en la pantalla.
Se llamara a la instancia juego en cada iteracin del bucle principal.

Los mtodos pause y resume actuaran cuando el juego se ponga en pausa o se reanude. Esto se
realiza de nuevo con la instancia juego y se aplicara a la pantalla actual.
Para finalizar usaremos el mtodo dispose para liberar todos los recursos de la memoria. Se deber
llamar a este mtodo justo despus de llamar a Game.setScreen. Y recordar que sera el ultimo
momento donde prodremos guardar los datos de esa pantalla.

2.1 Implementar interface Game


Como se puede observar en lo que llevamos de articulo, todo el trabajo que hemos realizado esta
dando sus frutos y con pocas lineas de cdigo tenemos acceso a todos los mtodos de nuestras
interfaces. Para terminar vamos a crear una clase que practicamente terminara de encajar todas las
piezas de nuestro puzzle. Se encarga de toda la gestin de nuestras interfaces, vamos a verla:
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Window;
import android.view.WindowManager;
import com.example.slange.interfaces.Audio;
import com.example.slange.interfaces.FileIO;
import com.example.slange.interfaces.Game;
import com.example.slange.interfaces.Graphics;
import com.example.slange.interfaces.Input;
import com.example.slange.interfaces.Screen;
public abstract class AndroidGame extends Activity implements Game {
AndroidFastRenderView renderView;
Graphics graphics;
Audio audio;
Input input;
FileIO fileIO;
Screen screen;
WakeLock wakeLock;

Empezamos la clase extendiendo de Activity e implementando la interface Game.

Creamos la instancia renderView que sera nuestra superficie de dibujo y otra instancia graphics que
nos ayudara a dibujar en esa superficie.
Continuamos creando las instancias audio, input, fileIO y screen para poder tener acceso a sus
mtodos.
Y el ultimo miembro wakeLock lo usaremos para mantener la pantalla encendia.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
boolean isLandscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
int frameBufferWidth = isLandscape ? 480 : 320;
int frameBufferHeight = isLandscape ? 320 : 480;
Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth,
frameBufferHeight, Config.RGB_565);
float scaleX = (float) frameBufferWidth
/ getWindowManager().getDefaultDisplay().getWidth();
float scaleY = (float) frameBufferHeight
/ getWindowManager().getDefaultDisplay().getHeight();
renderView = new AndroidFastRenderView(this, frameBuffer);
graphics = new AndroidGraphics(getAssets(), frameBuffer);
fileIO = new AndroidFileIO(this);
audio = new AndroidAudio(this);
input = new AndroidInput(this, renderView, scaleX, scaleY);
screen = getStartScreen();
setContentView(renderView);
PowerManager powerManager =
(PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK,
"GLGame");
}

Lo primero que hacemos en este mtodo es quitar la barra de titulo de la aplicacin y ponerla a
pantalla completa.
Lo siguiente que hacemos es comprobar que orientacin tiene la pantalla, para ello consultamos su

orientacin con el mtodo getResources().getConfiguration().orientation y si es igual a la


orientacin landscape, almacenamos true en nuestra variable isLandscape. En caso contrario se
almacenara false.
Despus tenemos dos miembros (framebufferWidth y frameBufferHeight) que nos servirn para
delimitar el ancho y el alto de nuestra superficie de dibujo. Dependiendo del valor isLandScape se
almacenara un valor u otro en cada variable.
Sabiendo esos datos creamos nuestra superficie de dibujo frameBuffer. Con una configuracin
RGB565, que har que nuestro dibujo se complete un poco mas rpido.
En las dos siguientes variables (scaleX, scaleY) calculamos la escala para los diferentes tamaos de
pantalla. En nuestro caso estamos usando una resolucin de 480x320 o 320x480. Con lo cual
debemos calcular la escala para tamaos de pantalla mas grandes o mas pequeos.
Continuamos iniciando todos nuestros objetos y aplicando sus parmetros si es necesario. Y
terminamos estableciendo nuestra superficie de dibujo renderView como la View principal.
Por ultimo almacenamos en nuestro wakeLock una instancia de servicio que se encarga de mantener
la pantalla encendida.
public void onResume() {
super.onResume();
wakeLock.acquire();
screen.resume();
renderView.resume();
}

Con el mtodo wakeLock.acquire creamos un bloqueo para que la pantalla se quede encendida.
El mtodo screen.resume reanudara la Activity.
Y con renderView.resume reanudara nuestra superficie de dibujo.
public void onPause() {
super.onPause();
wakeLock.release();
renderView.pause();
screen.pause();
if (isFinishing())
screen.dispose();
}

Con el mtodo wakeLock.release liberamos el bloqueo de iluminacin de pantalla, ahora se apagara


segn la configuracin de tiempo que tenga el terminal del usuario.
Primero deberemos pausar la superficie de dibujo (renderView.pause) y despus la pantalla de juego
(screen.pause) sino podramos tener problemas de concurrencia ya que trabajan en hilos diferentes,
por un lado tenemos el hilo de la interface y por el otro el hilo principal.
public Input getInput() {
return input;

}
public FileIO getFileIO() {
return fileIO;
}
public Graphics getGraphics() {
return graphics;
}
public Audio getAudio() {
return audio;
}

No necesita explicacin, simplemente se devuelve la instancia correspondiente a cada uno. Mas


tarde se podr llamar a estos mtodos desde una clase que implemente la interface Screen.
public void setScreen(Screen screen) {
if (screen == null)
throw new IllegalArgumentException("Pantalla nula");
this.screen.pause();
this.screen.dispose();
screen.resume();
screen.update(0);
this.screen = screen;
}
public Screen getCurrentScreen() {
return screen;
}

Con el primer mtodo podremos establecer una pantalla del juego, para ello lo indicaremos en su
parmetro screen. Lo primero que hacemos en el mtodo es comprobar si esa pantalla es nula, en
caso de que sea as creamos una nueva excepcin.
A continuacin le decimos a la pantalla actual que entre en pausa y libere sus recursos para que
pueda dar cabida a la nueva pantalla.
Seguidamente le pedimos que se reanude y actualice con un delta time de 0 segundos. Y para
terminar almacenamos la nueva pantalla en nuestra variable screen.
Normalmente llamaremos a este mtodo dentro del mtodo Screen.update.
Para finalizar usaremos el mtodo getCurrentScreen para conocer la pantalla actual del juego.

Con esto concluimos nuestra interface y ya disponemos de una base para crear juegos.

Вам также может понравиться