Bienvenidos sean a este post, hasta el post anterior hemos visto como crear las balas y como integrar a nuestro heroe en el juego y en el caso de hoy finalizaremos con la deteccion de colisiones y la interfaz o HUD.
Nuestro primer paso sera volver a la clase BalaceraJuego, en la declaracion de las variables vamos a agregar las siguientes, preferentemente despues de que creamos a Pepe:
private boolean mImpacto = false;
private int mNumImpactos;
private int mEscudo = 10;
private long mTiempoInicioJuego;
private long mMejorTiempoJuego;
private long mTotalTiempoJuego;
La primer variable la usaremos para saber cuando nos impactaron, la segunda llevara el conteo de las veces que nos impactaron, la tercera sera el escudo que protege a Pepe, estas tres son particularmente relacionadas al jugador, las siguientes tres son mas relacionadas al juego porque la primera sera para saber cuando empezamos a jugar, la siguiente sera para almacenar siempre nuestro mejor tiempo y la ultima sera para saber cuanto tiempo estuvimos jugando, la siguiente modificacion sera agregar el siguiente codigo en detectarColisiones:
for(int i=0; i < mNumBalas; i++){
if (RectF.intersects(mBalas[i].getRect(), mPepe.getRect())){
mSP.play(mBeepID,1,1,0,0,1);
mImpacto = true;
mBalas[i].invertirVelocidadX();
mBalas[i].invertirVelocidadY();
mNumImpactos++;
if (mNumImpactos==mEscudo){
mPausado = true;
mTotalTiempoJuego = System.currentTimeMillis()
- mTiempoInicioJuego;
iniciaJuego();
}
}
}
En esta ocasion volvemos a repetir un ciclo for para poder acceder a cada uno de los objetos creados que representan a la balas, luego tendremos un condicional donde usa la clase RectF y por medio de intersects verificamos si Pepe y alguna de las balas se interceptan, en caso de ser verdadero procede a reproducir por medio mSP, el objeto de SoundPool, el sonido de impacto, en este caso mBeepId, despues le dice a mImpacto que es verdadero, es decir que nos dieron, las siguientes dos lineas se usan para invertir la bala y luego incrementamos a mNumImpactos, despues viene un condicional donde verifica si mNumImpactos es igual a mEscudo, en caso de ser verdadero procede a pausar el juego, por medio de setear en true a mPausado, luego asignamos a mTotalTiempoJuego la diferencia del tiempo actual del CPU con el valor de cuando iniciamos el juego, mTiempoInicioJuego, para luego volver a llamar iniciaJuego, con esto ya tenemos completo el metodo detectarColisiones porque antes logramos que las balas no se vayan de la pantalla y ahora agregamos los impactos sobre Pepe, nuestra siguiente modificacion sera para mostrar el HUD en pantalla y para ello vamos a agregar el siguiente bloque en el metodo dibujar:
mPincel.setTextSize(mFontTamano);
mCanvas.drawText("Balas: " + mNumBalas
+ " Escudo: " + (mEscudo - mNumImpactos)
+ " Mejor tiempo: " + (mMejorTiempoJuego /
MILES_EN_SEGUNDOS),
mFontMargen,mFontTamano,mPincel);
if (!mPausado){
mCanvas.drawText("Segundos de supevivencia: "
+ ((System.currentTimeMillis() -
mTiempoInicioJuego)
/ MILES_EN_SEGUNDOS)
,mFontMargen,mFontMargen*30, mPincel);
}
En este bloque primero setearemos el tamaño de texto de nuestro pincel, para eso usamos a mFontTamano, luego por medio de drawText escribiremos en el Canvas, para este caso informaremos la cantidad de balas, luego el valor del escudo y el mejor tiempo que hicimos, despues tenemos un condicional donde verifica si mPausado es distinto de true, esto significa que el juego no esta pausado, y este bloque se encarga de mostrar los segundos que transcurren mientras jugamos, nuestra siguiente modificacion sera el efecto de teletransportacion y para ello modificaremos el metodo onTouchEvent de la siguiente manera:
@Override
public boolean onTouchEvent(MotionEvent evento){
switch (evento.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
if (mPausado){
mTiempoInicioJuego = System.currentTimeMillis();
mPausado = false;
}
if (mPepe.teleTransporte(evento.getX(), evento.getY())){
mSP.play(mTeleportID,1,1,0,0,1);
}
break;
case MotionEvent.ACTION_UP:
mPepe.setTeleporteDisponible();
generaBalas();
break;
}
return true;
}
En este caso modificamos completamente al metodo, en este caso agregamos un switch donde por medio de getAction y ACTION_MASK sabremos si presionamos la pantalla, el primer case es para saber si estamos tocando la pantalla, el primer condicional verifica si el juego esta pausado y en este caso vamos a iniciar a mTiempoInicioJuego con el tiempo actual del CPU y seteamos a mPausado como false, el segundo condicional verifica si la funcion esta en estado verdadero y en caso de ser cierto reproduce el sonido de teletransporte, con esto concluimos el case pasemos al otro.
El siguiente case se encarga de verficar si sacamos el dedo de la pantalla, en este caso llama a setTeleporteDisponible para despues llamar a generaBalas y comenzar a generarlas por cada vez que tocamos la pantalla, y para finalizar el return true para seguir en este entorno, volvamos a modificar la funcion imprimriDepuracion de la siguiente manera:
private void imprimirDepuracion(){
int depuraTamano = 35;
int depuraInicio = 150;
mPincel.setTextSize(depuraTamano);
mCanvas.drawText("FPS: " + mFPS,
10,depuraInicio + depuraTamano, mPincel);
mCanvas.drawText("Pepe left: " + mPepe.getRect().left,
10,depuraInicio + (depuraTamano * 2),
mPincel);
mCanvas.drawText("Pepe top: " + mPepe.getRect().top,
10,depuraInicio + (depuraTamano * 3),
mPincel);
mCanvas.drawText("Pepe right: " + mPepe.getRect().right,
10,depuraInicio + (depuraTamano * 4),
mPincel);
mCanvas.drawText("Pepe bottom: " + mPepe.getRect().bottom,
10,depuraInicio + (depuraTamano * 5),
mPincel);
mCanvas.drawText("Pepe centerX: " + mPepe.getRect().centerX(),
10,depuraInicio + (depuraTamano * 6),
mPincel);
mCanvas.drawText("Pepe centerY: " + mPepe.getRect().centerY(),
10,depuraInicio + (depuraTamano * 7),
mPincel);
}
La modificacion mas importante que hicimos fue quitar el texto que mostraba las balas por otros que nos muestran cada una de las propiedades de Pepe, solo nos restan dos modificaciones mas.
La primera va a ser sobre nuestro metodo generaBalas donde reemplazaremos este bloque:
generaX = mRandomX.nextInt(mScreenX);
generaY = mRandomY.nextInt(mScreenY);
velocidadX = 1;
if (mRandomX.nextInt(2) == 0)
velocidadX = -1;
velocidadY = 1;
if (mRandomY.nextInt(2) == 0)
velocidadY = -1;
Por el siguiente:
if (mPepe.getRect().centerX()<(mScreenX/2)){
generaX = mRandomX.
nextInt(mScreenX/2) + mScreenX/2;
velocidadX = 1;
} else {
generaX = mRandomX.nextInt(mScreenX / 2);
velocidadX = -1;
}
if(mPepe.getRect().centerY() < (mScreenY/2)){
generaY = mRandomY.nextInt(mScreenY/2 ) + mScreenY/2;
velocidadY = 1;
} else {
generaY = mRandomY.nextInt(mScreenY/2);
velocidadY = -1;
}
En esta ocasion agregamos dos condicionales para que la bala no se genere cerca de Pepe, una ayuda debemos darle no?, para el primer caso verificamos que el valor de centerX de Pepe es menor que la mitad de mScreenX, si se cumple la condicion significa que Pepe esta a la izquierda y nosotros generamos la bala a la derecha y que se dirija a la derecha, en caso contrario (else) Pepe esta a la derecha y generamos la bala a la izquierda y esta avanza hacia la izquierda, en el segundo condicional hacemos exactamente lo mismo pero para saber si esta abajo o arriba, por eso comparamos a centerY con la mitad de mScreenY si es verdad quiere decir que Pepe esta arriba y generamos la bala abajo haciendo que tambien se desplace hacia abajo, de lo contrario esta abajo y generamos la bala arriba, para nuestra ultima modificacion vamos a ir a iniciaJuego y le agregaremos el siguiente bloque:
public void iniciaJuego(){
mNumImpactos = 0;
mNumBalas = 0;
mImpacto = false;
if (mTotalTiempoJuego > mMejorTiempoJuego)
mMejorTiempoJuego = mTotalTiempoJuego;
}
Basicamente lo que hacemos es resetear todos nuestros contadores, epecialmente el de impactos y el de balas y por ultimo chequeamos si el valor total del juego es mejor al que tiene mMejorTiempoJuego se lo asignamos con lo cual cada vez que rompamos un «record» se almacenara automaticamente, si lo probamos nos sucedera esto

En esta imagen podemos ver varias detalles, el primero es la letra un poco grande para esta pantalla y por otro lado aparecen los segundos de supervivencia en la base, esto es debido a que para mostrarlos utilizamos el condicional que mPausado sea distinto de true, es decir false, pero como no le asignamos ningun valor adopta a null y con esto cumple la condicion y por ende nos sucede lo que se ve en la imagen, para cambiar esto vamos a poner manos a la obra.
Para nuestro primer cambio que no mencione vamos a modificar la cantidad de balas a mostrar en pantalla, para ello cambiaremos la constante con el siguiente valor:
private final int BALASMAXIMA = 10000;
Con esto podremos generar un verdadero infierno de balas, la segunda modificacion que haremos sera para que no aparezca la notificacion de los segundos de supervivencia y para ello a la variable mPausado la definiremos de esta manera:
private boolean mPausado = true;
Con esto ya podemos corregir mencionado al principio y por ultimo modificaremos estas dos lineas en el metodo imprimirDepuracion:
int depuraTamano = 20;
int depuraInicio = 150;
Con estas simples variaciones podemos pasar a probar nuestro juego pero antes veamos como quedo el codigo final de BalaceraJuego:
BalaceraJuego.java
package org.example.balacera;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.Random;
class BalaceraJuego extends SurfaceView implements Runnable {
boolean mDepurando = true;
private Thread mJuegoTread=null;
private volatile boolean mJugando;
private boolean mPausado = true;
private SurfaceHolder mNuestroHolder;
private Canvas mCanvas;
private Paint mPincel;
private long mFPS;
private final int MILES_EN_SEGUNDOS = 1000;
private int mScreenX;
private int mScreenY;
private int mFontTamano;
private int mFontMargen;
private SoundPool mSP;
private int mBeepID = -1;
private int mTeleportID = -1;
private final int BALASMAXIMA = 10000;
private Bala [] mBalas = new Bala[BALASMAXIMA];
private int mNumBalas = 0;
private int mGeneracionRatio = 1;
private Random mRandomX = new Random();
private Random mRandomY = new Random();
private Pepe mPepe;
private boolean mImpacto = false;
private int mNumImpactos;
private int mEscudo = 10;
private long mTiempoInicioJuego;
private long mMejorTiempoJuego;
private long mTotalTiempoJuego;
public BalaceraJuego(Context contexto, int x, int y){
super(contexto);
mScreenX = x;
mScreenY = y;
mFontTamano = mScreenX / 20;
mFontMargen = mScreenX / 50;
mNuestroHolder = getHolder();
mPincel = new Paint();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
AudioAttributes atributos = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
mSP = new SoundPool.Builder().setMaxStreams(5)
.setAudioAttributes(atributos)
.build();
} else {
mSP = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
}
try{
mBeepID = mSP.load(contexto,R.raw.beep,0);
mTeleportID = mSP.load(contexto,R.raw.teleport,0);
} catch (Exception e){
Log.e("Error","Ha fallado la carga de alguno de los archivos");
}
for(int a=0; a < mBalas.length; a++){
mBalas[a]=new Bala(mScreenX);
}
mPepe = new Pepe(contexto, mScreenX, mScreenY);
iniciaJuego();
}
public void iniciaJuego(){
mNumImpactos = 0;
mNumBalas = 0;
mImpacto = false;
if (mTotalTiempoJuego > mMejorTiempoJuego)
mMejorTiempoJuego = mTotalTiempoJuego;
}
private void generaBalas(){
if (mNumBalas<BALASMAXIMA)
{
int generaX;
int generaY;
int velocidadX;
int velocidadY;
if (mPepe.getRect().centerX()<(mScreenX/2)){
generaX = mRandomX.
nextInt(mScreenX/2) + mScreenX/2;
velocidadX = 1;
} else {
generaX = mRandomX.nextInt(mScreenX / 2);
velocidadX = -1;
}
if(mPepe.getRect().centerY() < (mScreenY/2)){
generaY = mRandomY.nextInt(mScreenY/2 ) + mScreenY/2;
velocidadY = 1;
} else {
generaY = mRandomY.nextInt(mScreenY/2);
velocidadY = -1;
}
mBalas[mNumBalas].generar(
generaX,
generaY,
velocidadX,
velocidadY);
mNumBalas++;
}
}
@Override
public void run(){
while(mJugando){
long frameInicioTiempo=System.currentTimeMillis();
if (!mPausado){
actualizar();
detectarColisiones();
}
dibujar();
long frameEsteTiempo=System.currentTimeMillis()
- frameInicioTiempo;
if (frameEsteTiempo>=1)
mFPS = MILES_EN_SEGUNDOS/frameEsteTiempo;
}
}
private void actualizar(){
for(int i = 0; i < mNumBalas; i++){
mBalas[i].actualizar(mFPS);
}
}
private void detectarColisiones(){
for(int i=0; i < mNumBalas; i++){
if (mBalas[i].getRect().bottom > mScreenY){
mBalas[i].invertirVelocidadY();
} else if (mBalas[i].getRect().top<0){
mBalas[i].invertirVelocidadY();
} else if (mBalas[i].getRect().left<0){
mBalas[i].invertirVelocidadX();
} else if (mBalas[i].getRect().right > mScreenX){
mBalas[i].invertirVelocidadX();
}
}
for(int i=0; i < mNumBalas; i++){
if (RectF.intersects(mBalas[i].getRect(), mPepe.getRect())){
mSP.play(mBeepID,1,1,0,0,1);
mImpacto = true;
mBalas[i].invertirVelocidadX();
mBalas[i].invertirVelocidadY();
mNumImpactos++;
if (mNumImpactos==mEscudo){
mPausado = true;
mTotalTiempoJuego = System.currentTimeMillis()
- mTiempoInicioJuego;
iniciaJuego();
}
}
}
}
private void dibujar(){
if (mNuestroHolder.getSurface().isValid()){
mCanvas = mNuestroHolder.lockCanvas();
mCanvas.drawColor(Color.argb(255,243,111,36));
mPincel.setColor(Color.argb(255,255,255,255));
for(int i=0; i<mNumBalas; i++){
mCanvas.drawRect(mBalas[i].getRect(),mPincel);
}
mCanvas.drawBitmap(mPepe.getBitmap(),
mPepe.getRect().left,mPepe.getRect().top,mPincel);
mPincel.setTextSize(mFontTamano);
mCanvas.drawText("Balas: " + mNumBalas
+ " Escudo: " + (mEscudo - mNumImpactos)
+ " Mejor tiempo: " + (mMejorTiempoJuego / MILES_EN_SEGUNDOS),
mFontMargen,mFontTamano,mPincel);
if (!mPausado){
mCanvas.drawText("Segundos de supevivencia: "
+ ((System.currentTimeMillis() - mTiempoInicioJuego) / MILES_EN_SEGUNDOS)
,mFontMargen,mFontMargen*30, mPincel);
}
if (mDepurando)
imprimirDepuracion();
mNuestroHolder.unlockCanvasAndPost(mCanvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent evento){
switch (evento.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
if (mPausado){
mTiempoInicioJuego = System.currentTimeMillis();
mPausado = false;
}
if (mPepe.teleTransporte(evento.getX(), evento.getY())){
mSP.play(mTeleportID,1,1,0,0,1);
}
break;
case MotionEvent.ACTION_UP:
mPepe.setTeleporteDisponible();
generaBalas();
break;
}
return true;
}
public void pausa(){
mJugando = false;
try{
mJuegoTread.join();
} catch (InterruptedException e){
Log.e("Error","Joining el thread");
}
}
public void retomar(){
mJugando = true;
mJuegoTread = new Thread(this);
mJuegoTread.start();
}
private void imprimirDepuracion(){
int depuraTamano = 20;
int depuraInicio = 150;
mPincel.setTextSize(depuraTamano);
mCanvas.drawText("FPS: " + mFPS,
10,depuraInicio + depuraTamano, mPincel);
mCanvas.drawText("Pepe left: " + mPepe.getRect().left,
10,depuraInicio + (depuraTamano * 2),
mPincel);
mCanvas.drawText("Pepe top: " + mPepe.getRect().top,
10,depuraInicio + (depuraTamano * 3),
mPincel);
mCanvas.drawText("Pepe right: " + mPepe.getRect().right,
10,depuraInicio + (depuraTamano * 4),
mPincel);
mCanvas.drawText("Pepe bottom: " + mPepe.getRect().bottom,
10,depuraInicio + (depuraTamano * 5),
mPincel);
mCanvas.drawText("Pepe centerX: " + mPepe.getRect().centerX(),
10,depuraInicio + (depuraTamano * 6),
mPincel);
mCanvas.drawText("Pepe centerY: " + mPepe.getRect().centerY(),
10,depuraInicio + (depuraTamano * 7),
mPincel);
}
}
Con todo esto ya modificado podemos pasar a probar nuestro juego mediante el siguiente video
Si lograron lo mismo que en el video Felicitaciones!!! ya hemos dado un paso muy grande a la programacion de juegos en Android, pudimos generar un hibrido entre los dos proyectos y a su vez tambien agregamos una complejidad mucho mas interesante, si desean eliminar los datos de depuracion recuerden modificar a mDepurando como false y desaparecera.
En resumen, hoy hemos finalizado nuestro tercer juego, hemos visto como lograr que nuestro personaje se teletransporte, hemos visto como trabaja la deteccion de colisiones con imagenes (Bitmap), hemos visto como almacenar informacion y despues utilizar para generar nuestros puntajes y puntajes mas altos, 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.

Tambien podes donar
Es para mantenimiento del sitio, gracias!
$1.00