Bienvenidos sean a este post, en este post veremos a la clase que vinculara a todas las clases que realizamos hasta ahora.
Para comenzar vamos a crear una nueva clase con las siguientes caracteristicas:
- Nombre: GameEngine
- Tipo: Class
Modifiquemos el codigo creado con el siguiente:
package org.example.pepeaventura;
import android.content.Context;
import android.graphics.Point;
import android.view.SurfaceView;
import java.util.concurrent.CopyOnWriteArrayList;
class GameEngine extends SurfaceView implements
Runnable,
GameEngineBroadcaster,
ControladorEngine {
private Thread mThread = null;
private long mFPS;
private GameState mGameState;
UIcontroladora mUIcontroladora;
private CopyOnWriteArrayList<ObservadorEntrada>
observadorEntradas = new CopyOnWriteArrayList<>();
HUD mHUD;
ManagerNivel mManagerNivel;
EngineFisicas mEngineFisicas;
Renderer mRenderer;
public GameEngine(Context contexto, Point tamano){
super(contexto);
BitmapAlmacen ba = BitmapAlmacen.getInstancia(contexto);
SoundEngine se = SoundEngine.getInstancia(contexto);
mHUD = new HUD(contexto, tamano);
mGameState = new GameState(this, contexto);
mUIcontroladora = new UIcontroladora(this,tamano);
mEngineFisicas = new EngineFisicas();
mRenderer = new Renderer(this, tamano);
mManagerNivel = new ManagerNivel(
contexto,
this, mRenderer.
getPixelsPorMetro());
}
}
Esta clase extendera a SurfaceView para que pueda ser usada como una vista, esto soluciona lo visto en el GameActivity, y luego implementa una serie de interfaces las cuales estan relacionadas con el resto de nuestras clases, nuestro siguiente paso sera crear una serie de variables:
- mThread, sera para el thread de nuestro juego
- mFPS, se almacena los fps (frames-per-seconds)
- mGameState, es para manejar el GameState del juego
- mUIcontroladora, una para manejar los controladores
- observadorEntradas, un ArrayList especial para las entradas
- mHUD, para la interfaz del juego
- mManagerNivel, el encargado de manejar los niveles
- mEngineFisicas, este para manejar las fisicas del juego
- mRenderer, este para hacer el renderizado de los objetos
Con esto podemos manejar todos los objetos de nuestro juego, ya sea desde los niveles hasta los objetos dentro del mismo, de todos el unico particular es observadorEntradas porque en lugar de usar ArrayList usa el metodo CopyOnWriteArrayList, si bien trabajan de la misma forma la diferencia esta en que es un poco mas lento con respecto a ArrayList, y tambien nos permite trabajar en multiples threads sinultaneamente sin romper el juego pero no se preocupen porque no tendremos problemas de performance, luego tendremos un constructor donde iniciaremos cada uno de los elementos que definimos anteriormente, la mayoria los iniciamos con sus respectivos constructores, salvo por el de BitmapAlmacen y SoundEngine donde al trabajar como Singleton debemos usar el metodo encargado de obtener las instancias de los mismos, con esto ya tenemos a nuestras variables miembros y el constructor vamos a agregar los siguientes metodos:
@Override
public void run(){
while(mGameState.getThreadCorriendo()){
long tiempoInicialFrame = System.currentTimeMillis();
if (!mGameState.getPausado()){
mEngineFisicas.actualizar(
mFPS,
mManagerNivel.getGameObjetos(),
mGameState);
}
mRenderer.dibujar(
mManagerNivel.getGameObjetos(),
mGameState,
mHUD);
long tiempoEsteFrame = System.currentTimeMillis()
- tiempoInicialFrame;
if (tiempoEsteFrame >= 1){
final int MILES_EN_SEGUNDOS = 1000;
mFPS = MILES_EN_SEGUNDOS / tiempoEsteFrame;
}
}
}
public void addObservador(ObservadorEntrada o){
observadorEntradas.add(o);
}
public void iniciaNuevoNivel(){
BitmapAlmacen.limpiaAlmacen();
observadorEntradas.clear();
mUIcontroladora.addObservador(this);
mManagerNivel.setNivelActual(mGameState.getNivelActual());
mManagerNivel.construirObjetosJuego(mGameState);
}
El primer metodo sera para implementar a la interfaz Runnable, en esta ocasion tenemos un bucle while donde monitorea si el thread esta corriendo, en caso de ser verdadero iniciaremos una variable con la hora actual de nuestro equipo, luego chequeamos si el juego no esta, tiene el signo de negacion, pausado y en caso de ser verdadero procede a llamar al actualizar de la clase encargada de las fisicas, despues llamaremos al dibujar del Renderer para mostrar los cambios, en caso de haberlos, despues estableceremos el momento actual de nuestro frame donde haremos la diferencia entre el tiempo actual y el que almacenamos al principio, el siguiente condicional verifica si es mayor o igual a uno, en caso de ser cierto crea una constante y despues las usamos junto con el valor establecido anteriormente para establecer el valor de mFPS.
El siguiente metodo se encarga de agregar un nuevo observador para las entradas de nuestro juego pero tambien lo usamos para implementar a GameEngineBroadcaster, el ultimo metodo es el encargado de implementar a ControladorEngine pero tambien se encarga de varias actividades, la primera que realiza el limpiar el Almacen de bitmaps, la segunda limpia todos los observadores de entradas del jugador, despues agrega a este objeto como observador, por ultimo setea el nivel actual y construye los objetos en el mismo, con estos tres metodos ya tenemos un 90% del juego completo y ya logramos implementar correctamente las interfaces de nuestra clase, vamos a agregar los metodos faltantes:
@Override
public boolean onTouchEvent(MotionEvent evento){
for(ObservadorEntrada o : observadorEntradas){
o.handleInput(evento,
mGameState,
mHUD.getControles());
}
return true;
}
public void detenerThread(){
mGameState.detenTodo();
mGameState.detenThread();
try {
mThread.join();
} catch (InterruptedException ie){
Log.e("Excepcion: ",
"detenerThread() " + ie.getMessage());
}
}
public void iniciarThread(){
mGameState.iniciaThread();
mThread = new Thread(this);
mThread.start();
}
El primer metodo sera el encargado de manejar nuestros toques en pantalla, trabaja de forma similar a como trabajamos en el proyecto anterior, donde tenemos un bucle for mejorado para pasar por todos los objetos observadores de entradas y lo enviaremos al handleInput, por ultimo devovlemos un true para mantenerlo activo, los siguientes dos metodos son para detener el thread e iniciarlo respectivamente, en el primer caso llamaremos a los dos metodos para detener todo y el thread de GameState para despues usar el join y detener o eliminar el thread en caso de ser necesario, el metodo try/catch lo usamos para que nos devuelva un mensaje en caso de existir alguna excepcion, para el metodo de inicio llamamos al iniciarThread de GameState y generamos un nuevo thread y lo iniciamos con start, con esto tenemos concluido la primera parte de nuestro juego, veamos como quedo por ahora nuestro codigo para esta clase:
GameEngine.java
package org.example.pepeaventura;
import android.content.Context;
import android.graphics.Point;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceView;
import java.util.concurrent.CopyOnWriteArrayList;
class GameEngine extends SurfaceView implements
Runnable,
GameEngineBroadcaster,
ControladorEngine {
private Thread mThread = null;
private long mFPS;
private GameState mGameState;
UIcontroladora mUIcontroladora;
private CopyOnWriteArrayList<ObservadorEntrada>
observadorEntradas = new CopyOnWriteArrayList<>();
HUD mHUD;
ManagerNivel mManagerNivel;
EngineFisicas mEngineFisicas;
Renderer mRenderer;
public GameEngine(Context contexto, Point tamano){
super(contexto);
BitmapAlmacen ba = BitmapAlmacen.getInstancia(contexto);
SoundEngine se = SoundEngine.getInstancia(contexto);
mHUD = new HUD(contexto, tamano);
mGameState = new GameState(this, contexto);
mUIcontroladora = new UIcontroladora(this,tamano);
mEngineFisicas = new EngineFisicas();
mRenderer = new Renderer(this, tamano);
mManagerNivel = new ManagerNivel(
contexto,
this,
mRenderer.getPixelsPorMetro());
}
@Override
public void run(){
while(mGameState.getThreadCorriendo()){
long tiempoInicialFrame = System.currentTimeMillis();
if (!mGameState.getPausado()){
mEngineFisicas.actualizar(
mFPS,
mManagerNivel.getGameObjetos(),
mGameState);
}
mRenderer.dibujar(
mManagerNivel.getGameObjetos(),
mGameState,
mHUD);
long tiempoEsteFrame = System.currentTimeMillis()
- tiempoInicialFrame;
if (tiempoEsteFrame >= 1){
final int MILES_EN_SEGUNDOS = 1000;
mFPS = MILES_EN_SEGUNDOS / tiempoEsteFrame;
}
}
}
public void addObservador(ObservadorEntrada o){
observadorEntradas.add(o);
}
public void iniciaNuevoNivel(){
BitmapAlmacen.limpiaAlmacen();
observadorEntradas.clear();
mUIcontroladora.addObservador(this);
mManagerNivel.setNivelActual(mGameState.getNivelActual());
mManagerNivel.construirObjetosJuego(mGameState);
}
@Override
public boolean onTouchEvent(MotionEvent evento){
for(ObservadorEntrada o : observadorEntradas){
o.handleInput(evento,
mGameState,
mHUD.getControles());
}
return true;
}
public void detenerThread(){
mGameState.detenTodo();
mGameState.detenThread();
try {
mThread.join();
} catch (InterruptedException ie){
Log.e("Excepcion: ",
"detenerThread() " + ie.getMessage());
}
}
public void iniciarThread(){
mGameState.iniciaThread();
mThread = new Thread(this);
mThread.start();
}
}
Con esto ya podemos hacer la primera prueba sobre nuestro juego, compilemos y veamos como funciona mediante el siguiente video
Si lograron lo mismo que se ve en el video, Felicitaciones!!! Ya tenemos una gran porcentaje del juego resuelto, como pueden el simplemente cargar estos tres niveles ha cambiado a una gran complejidad con respecto a los proyectos anteriores pero esta resultando mucho mas satisfactorio, y todavia falta mucho asi que les pido que no bajen los brazos.
En resumen, hoy hemos completado el GameEngine que se encarga de unir todas las clases que fuimos creando, tambien hemos visto como ahora funciona tanto el toque en la pantalla, como los metodos para poder iniciar y detener nuestro thread cuando sea necesario, espero les haya gustado sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.
Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.


Donación
Es para mantenimento del sitio, gracias!
$1.00
