Bienvenidos sean a este post, hoy comenzaremos con los ultimos pasos sobre nuestro juego al completar la ultima clase que nos quedo pendiente.
Para comenzar agregaremos el siguiente bloque donde estan las declaraciones de nuestras variables y objetos, esto lo agregaremos al principio de la clase:
class Snake {
private ArrayList<Point> ubicacionSegmentos;
private int mTamanoSegmento;
private Point rangoMov;
private int pointMitad;
private enum Cabeza{
ARRIBA,
ABAJO,
IZQUIERDA,
DERECHA
};
private Cabeza cabeza = Cabeza.DERECHA;
private Bitmap mCabezaArr;
private Bitmap mCabezaAbj;
private Bitmap mCabezaDer;
private Bitmap mCabezaIzq;
private Bitmap mCuerpo;
}
Nota: Deben borrar la palabra public del inicio de la clase para convertirla en privada, por este motivo lo muestro en el bloque.
La primer linea nos servira para tener un ArrayList donde almacenaremos la ubicacion de todos los segmentos de nuestra Snake o serpiente en la grilla, la siguiente linea nos sevira para almacenar cuan grande sera cada segmento, la tercer linea almacenara cuan grande es la grilla donde nos moveremos, la siguiente linea define donde es exactamente el centro de la pantalla, luego tendremos un enum donde lo usaremos para hacer un seguimiento de hacia donde esta yendo la cabeza, despues crearemos una variable de este enum llamada cabeza a la cual le asignaremos el valor de DERECHA para iniciarla de esta forma, por ultimo tendremos cinco objetos de tipo Bitmap, los cuales los usaremos para esto:
- En la primera, almacenaremos la imagen de la cabeza cuando mira hacia arriba
- En la segunda, cuando mira hacia abajo
- En la tercera, cuando mira a la izquierda
- En la cuarta, cuando mira a la derecha
- En la ultima, sera para almacenar el cuerpo de la serpiente
Con esto ya tenemos lo basico para poder construir a nuestra serpiente, el siguiente paso sera crear el constructor para nuestra clase y para ello agregaremos el siguiente bloque:
Snake(Context contexto, Point rm, int ss){
ubicacionSegmentos = new ArrayList<>();
mTamanoSegmento = ss;
mRangoMov = rm;
mCabezaIzq = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaIzq = Bitmap.createScaledBitmap(mCabezaIzq,
ss,ss,false);
mCabezaDer = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaArr = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaAbj = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
Matrix matrix = new Matrix();
matrix.preScale(-1,1);
mCabezaDer = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(-90);
mCabezaArr = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(180);
mCabezaAbj = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
mCuerpo = BitmapFactory.decodeResource(
contexto.getResources(),R.drawable.cuerpo);
mCuerpo = Bitmap.createScaledBitmap(
mCuerpo,ss,ss,false);
pointMitad = rm.x * ss / 2;
}
Pasemos a describir nuestro constructor, la primer linea sera la encargada de iniciar a nuestro ArrayList, veamos las siguientes dos lineas:
mTamanoSegmento = ss;
mRangoMov = rm;
Estas se encargaran de definir el tamaño del segmento y de la grilla, respectivamente, con los valores recibidos como argumentos, despues vendra el siguiente bloque:
mCabezaIzq = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaIzq = Bitmap.createScaledBitmap(mCabezaIzq,
ss,ss,false);
mCabezaDer = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaArr = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaAbj = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
En este caso definimos a cada uno de los bitmaps que usaremos para mostrar la direccion de la cabeza de la serpiente, observen que en esta ocasion usamos al metodo decodeResource de BitmapFactory para poder asignarle el archivo cabeza.png, la unica excepcion es cuando iniciamos a mCabezaIzq donde inmediatamente usamos a createScaledBitmap en mCabezaIzq para poder crear nuestro bitmap, utilizamos el valor recibido en ss para indicarle el ancho y el alto por ultimo enviamos un false para que no use el filtro, esto debemos hacerlo ahora para que pueda ser usado porque sino hacemos esto cada vez que lo ejecutemos nos devolvera una excepcion de OutOfMemory debido a que la carga de la misma imagen en los distintos objetos hace un desborde de la memoria de la maquina virtual, hasta aqui tenemos la misma imagen para cada uno de los objetos, veamos el siguiente bloque:
Matrix matrix = new Matrix();
matrix.preScale(-1,1);
mCabezaDer = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(90);
mCabezaArr = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(180);
mCabezaAbj = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
Con nuestras imagenes asignadas a los objetos de tipo Bitmap nuestro siguiente paso sera crear un objeto de tipo Matrix, y la siguiente linea sera definir un preScale, en este caso definimos nuevamente a mCabezaDer donde esta vez usaremos a createBitmap, volvemos a usar la imagen mCabezaIzq, le pasamos el left y top con los valores de cero, el ancho y el alto con el valor de ss, despues usamos la matriz que creamos antes y por ultimo aplicamos el filtro, definimos el preRotate para nuestra matrix con un valor de 90°, luego volvemos a definir a mCabezaArr aplicandole un createBitmap con matrix y esto hara que la cara mire para arriba, por ultimo volvemos a modificar a preRotate a 180° para asignarlo a mCabezaAbj lo cual hara que nuestra cara mire para abajo, por ultimo tendremos estas lineas:
mCuerpo = BitmapFactory.decodeResource(
contexto.getResources(),R.drawable.cuerpo);
mCuerpo = Bitmap.createScaledBitmap(
mCuerpo,ss,ss,false);
pointMitad = rm.x * ss / 2;
La primera sera la encargada de asignar la imagen para el cuerpo de la serpiente a mCuerpo, con la siguiente crearemos una imagen escalada de la misma y la ultima sera la encargada de calcular la mitad de la pantalla para saber cual mitad de la pantalla fue presionada, con esto concluimos el constructor pasemos al siguiente metodo:
void reset (int an, int al){
cabeza = cabeza.DERECHA;
ubicacionSegmentos.clear();
ubicacionSegmentos.add(new Point(an/2,al/2));
}
Este metodo es que usaremos para reiniciar un nuevo juego, para ello volveremos a iniciar el valor de cabeza para que mire a la derecha, la siguiente linea se encarga de limpiar nuestro ArrayList es decir que limpia el cuerpo, por ultimo le agrega un nuevo point en base a la mitad de los valores recibidos como argumentos (an corresponde al ancho, al corresponde al alto), con esto ya podremos iniciar un nuevo juego cada vez que fallemos, nuestro siguiente metodo sera el encargado de movernos para ello usaremos el siguiente bloque:
void mover(){
for(int i=ubicacionSegmentos.size()-1; i > 0; i--){
ubicacionSegmentos.get(i).x = ubicacionSegmentos.get(i-1).x;
ubicacionSegmentos.get(i).y = ubicacionSegmentos.get(i-1).y;
}
Point p = ubicacionSegmentos.get(0);
switch (cabeza){
case ARRIBA:
p.y--;
break;
case ABAJO:
p.y++;
break;
case DERECHA:
p.x++;
break;
case IZQUIERDA:
p.x--;
break;
}
ubicacionSegmentos.set(0,p);
}
Nuestro primer bloque es muy particular porque tendra un bucle for pero en cuenta regresiva, esto se encarga de mover el cuerpo desde la ultima posicion hasta la primera en la posicion en frente de ella, lo que hace es reasignar el valor de x e y de ubicacionSegmentos, nuestra siguiente linea se encarga de crear un objeto de tipo Point llamado p la cual recibira a la cabeza de la serpiente, porque es la primera posicion de ubicacionSegmentos y despues tendremos un bloque de switch donde verifica el valor de cabeza y de cada una de las constantes de la misma y en caso de coinicidir el valor asignado con el case procede a disminuir o incrementar el valor x o y asignado a p respectivamente, por ultimo le asignamos el nuevo valor de x o y en p a la primera posicion de ubicacionSegmentos, con esto completamos el movimiento de nuestra serpiente nuestro siguiente paso sera la deteccion de la muerte de la misma y para ello agregaremos el siguiente metodo:
boolean detectarMuerte(){
boolean muerto = false;
if (ubicacionSegmentos.get(0).x==-1 ||
ubicacionSegmentos.get(0).x >= mRangoMov.x ||
ubicacionSegmentos.get(0).y == -1 ||
ubicacionSegmentos.get(0).y >= mRangoMov.y){
muerto = true;
}
for(int i=ubicacionSegmentos.size()-1; i > 0; i--){
if (ubicacionSegmentos.get(0).x ==
ubicacionSegmentos.get(i).x &&
ubicacionSegmentos.get(0).y ==
ubicacionSegmentos.get(i).y){
muerto = true;
}
}
return muerto;
}
Este metodo sera de tipo boolean lo cual solo debe devolver un true o un false, en la primer linea creamos una variable de tipo boolean a la cual llamaremos muerto y le asignaremos un valor false, despues usaremos un condicional donde si los ejes x o y estan por fuera de los rangos de nuestra grilla, es decir que esta por fuera de la pantalla, por lo cual primero verifica si x es igual a -1 o si x es mayor al eje X de mRangoMov y lo mismo con el y, si se cumple cualquiera de estas condiciones (dado que usamos un OR Logico en todas las condiciones) procede a cambiar el valor de muerto a true, el siguiente condicional verifica si se mordio a si misma, es decir que la cabeza toco a alguna parte del cuerpo, en este caso volvemos a usar el bucle for para pasar por los segmentos del cuerpo y si el eje x de la cabeza, la primera posicion, coincide con algun eje x del cuerpo y a su vez ocurre lo mismo con el eje y procede a setear a muerto como true, en esta ocasion al usar un AND Logico se tiene que cumplir las dos coincidencias para que se ejecute el bloque del if, por ultimo devolvemos el valor de muerto, con esto completamos la deteccion de la muerte, nuestro siguiente metodo sera para la deteccion si la manzana fue comida, para ello agregaremos el siguiente metodo:
boolean chekaCena(Point l){
if (ubicacionSegmentos.get(0).x == l.x &&
ubicacionSegmentos.get(0).y == l.y){
ubicacionSegmentos.add(new Point(-10,-10));
return true;
}
return false;
}
Este metodo recibe un argumento de tipo Point llamado l, luego tendremos un condicional donde verifica si el eje x y el eje y del primer elemento de nuestros segmentos (es decir la cabeza) coincide con los ejes x e y de la manzana respectivamente donde en caso de ser verdadero procede a agregar un nuevo segmento a nuestro cuerpo pero aqui lo ubicaremos afuera de la pantalla porque en el proximo movimiento procede a agregarlo de manera correcta por ultimo devuelve un true, en caso de no cumplirse la condicion devuelve un false, pasemos al penultimo metodo que es uno de los principales porque sera el encargado de dibujar a nuestra serpiente:
void dibujar(Canvas canvas, Paint pincel){
if (!ubicacionSegmentos.isEmpty()){
switch (cabeza){
case DERECHA:
canvas.drawBitmap(mCabezaDer,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case IZQUIERDA:
canvas.drawBitmap(mCabezaIzq,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case ABAJO:
canvas.drawBitmap(mCabezaAbj,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case ARRIBA:
canvas.drawBitmap(mCabezaArr,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
}
for(int i=1; i < ubicacionSegmentos.size(); i++){
canvas.drawBitmap(mCuerpo,
ubicacionSegmentos.get(i).x * mTamanoSegmento,
ubicacionSegmentos.get(i).y * mTamanoSegmento,
pincel);
}
}
}
Este metodo se podria haber implementado en la clase SnakeJuego pero como vamos a necesitar algunos datos que son de la clase Snake es preferible que esta clase se encargue de manejarlo, tambien nos evita agregar una excesiva complejidad en el engine del juego, para comenzar a hablar sobre este metodo observemos que esta «encapuslado» (aunque no es una encapsulacion verdadero pero lo uso para indicar que esta encerrado) en un condicional donde verifica que ubicacionSegmentos, es decir el ArrayList, no este vacio porque de lo contrario no ejecuta ninguna accion pero en el caso de que no este vacio procede a ejecutar todo el bloque para «dibujar» a nuestra serpiente, el primer bloque es un switch donde verifica el valor de cabeza, para ello usamos distintos case donde verifica cada uno de los valores que puede tomar, en todos los casos usaremos a drawBitmap en canvas para dibujarlo para explicarlo tomemos uno de ejemplo:
canvas.drawBitmap(mCabezaDer,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
En este caso tomamos el case donde usa el constante de DERECHA, en este caso primero asignamos el bitmap que definimos para mostrar la cabeza mirando a la derecha, luego por medio de cada uno de los ejes del primer elemento multiplicado por el tamaño del segmento estableceremos los ejes x e y de nuestro canvas y por ultimo pasamos a pincel para dibujarlo, esto lo hacemos en todo los case pero variamos la imagen dependiendo de la constante, luego tendremos un bucle for encargado de dibujar el cuerpo de nuestra serpiente donde a diferencia de como veniamos trabajando hasta ahora sumara en lugar de restar, tendra como limite el tamaño de nuestro ArrayList y comenzara desde uno (porque cero es la cabeza) y usara un canvas para dibujarlo pero a diferencia del anterior solo usara a mCuerpo, el eje x y eje y lo hara de forma similar pero en este caso el valor de i para ubicarnos en la posicion correspondiente y por ultimo usaremos a pincel para dibujarlo, por ultimo tendremos este metodo:
void cambiarDireccion(MotionEvent evento){
if (evento.getX() >= pointMitad){
switch (cabeza){
case ARRIBA:
cabeza = cabeza.DERECHA;
break;
case ABAJO:
cabeza = cabeza.IZQUIERDA;
break;
case IZQUIERDA:
cabeza = cabeza.ARRIBA;
break;
case DERECHA:
cabeza = cabeza.ABAJO;
break;
}
} else {
switch (cabeza){
case ARRIBA:
cabeza = cabeza.IZQUIERDA;
break;
case ABAJO:
cabeza = cabeza.DERECHA;
break;
case IZQUIERDA:
cabeza = cabeza.ABAJO;
break;
case DERECHA:
cabeza = cabeza.ARRIBA;
break;
}
}
}
En este metodo recibiremos un objeto de tipo MotionEvent al cual llamaremos evento, este metodo sera el encargado de verificar si al momento de tocar la pantalla se hizo del lado derecho o izquierdo, para ello tendremos un condicional donde verifica que el eje x de evento sea mayor o igual al valor de pointMitad, esto equivale a decir que toco el lado derecho, y por medio de un switch verifica los valores que puede tener cabeza y en dependiendo del valor asignara uno nuevo pero en caso contrario, es decir que tocamos el izquierdo, usaremos el mismo switch pero esta vez invertiremos los valores anteeriores, con esto ya tenemos completa nuestra clase Snake veamos como quedo nuestro codigo final:
Snake.java
package org.example.snake;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import java.util.ArrayList;
class Snake {
private ArrayList<Point> ubicacionSegmentos;
private int mTamanoSegmento;
private Point mRangoMov;
private int pointMitad;
private enum Cabeza{
ARRIBA,
ABAJO,
IZQUIERDA,
DERECHA
};
private Cabeza cabeza = Cabeza.DERECHA;
private Bitmap mCabezaArr;
private Bitmap mCabezaAbj;
private Bitmap mCabezaDer;
private Bitmap mCabezaIzq;
private Bitmap mCuerpo;
Snake(Context contexto, Point rm, int ss){
ubicacionSegmentos = new ArrayList<>();
mTamanoSegmento = ss;
mRangoMov = rm;
mCabezaArr = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaAbj = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaDer = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaIzq = BitmapFactory.decodeResource(
contexto.getResources(),
R.drawable.cabeza);
mCabezaIzq = Bitmap.createScaledBitmap(mCabezaIzq,
ss,ss,false);
Matrix matrix = new Matrix();
matrix.preScale(-1,1);
mCabezaDer = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(-90);
mCabezaArr = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
matrix.preRotate(180);
mCabezaAbj = Bitmap.createBitmap(mCabezaIzq,
0,0,ss,ss,matrix,true);
mCuerpo = BitmapFactory.decodeResource(
contexto.getResources(),R.drawable.cuerpo);
mCuerpo = Bitmap.createScaledBitmap(
mCuerpo,ss,ss,false);
pointMitad = rm.x * ss / 2;
}
void reset (int an, int al){
cabeza = cabeza.DERECHA;
ubicacionSegmentos.clear();
ubicacionSegmentos.add(new Point(an/2,al/2));
}
void mover(){
for(int i=ubicacionSegmentos.size()-1; i > 0; i--){
ubicacionSegmentos.get(i).x = ubicacionSegmentos.get(i-1).x;
ubicacionSegmentos.get(i).y = ubicacionSegmentos.get(i-1).y;
}
Point p = ubicacionSegmentos.get(0);
switch (cabeza){
case ARRIBA:
p.y--;
break;
case ABAJO:
p.y++;
break;
case DERECHA:
p.x++;
break;
case IZQUIERDA:
p.x--;
break;
}
ubicacionSegmentos.set(0,p);
}
boolean detectarMuerte(){
boolean muerto = false;
if (ubicacionSegmentos.get(0).x==-1 ||
ubicacionSegmentos.get(0).x > mRangoMov.x ||
ubicacionSegmentos.get(0).y == -1 ||
ubicacionSegmentos.get(0).y > mRangoMov.y){
muerto = true;
}
for(int i=ubicacionSegmentos.size()-1; i > 0; i--){
if (ubicacionSegmentos.get(0).x ==
ubicacionSegmentos.get(i).x &&
ubicacionSegmentos.get(0).y ==
ubicacionSegmentos.get(i).y){
muerto = true;
}
}
return muerto;
}
boolean chekaCena(Point l){
if (ubicacionSegmentos.get(0).x == l.x &&
ubicacionSegmentos.get(0).y == l.y){
ubicacionSegmentos.add(new Point(-10,-10));
return true;
}
return false;
}
void dibujar(Canvas canvas, Paint pincel){
if (!ubicacionSegmentos.isEmpty()){
switch (cabeza){
case DERECHA:
canvas.drawBitmap(mCabezaDer,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case IZQUIERDA:
canvas.drawBitmap(mCabezaIzq,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case ABAJO:
canvas.drawBitmap(mCabezaAbj,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
case ARRIBA:
canvas.drawBitmap(mCabezaArr,
ubicacionSegmentos.get(0).x * mTamanoSegmento,
ubicacionSegmentos.get(0).y * mTamanoSegmento,
pincel);
break;
}
for(int i=1; i < ubicacionSegmentos.size(); i++){
canvas.drawBitmap(mCuerpo,
ubicacionSegmentos.get(i).x * mTamanoSegmento,
ubicacionSegmentos.get(i).y * mTamanoSegmento,
pincel);
}
}
}
void cambiarDireccion(MotionEvent evento){
if (evento.getX() >= pointMitad){
switch (cabeza){
case ARRIBA:
cabeza = cabeza.DERECHA;
break;
case ABAJO:
cabeza = cabeza.IZQUIERDA;
break;
case IZQUIERDA:
cabeza = cabeza.ARRIBA;
break;
case DERECHA:
cabeza = cabeza.ABAJO;
break;
}
} else {
switch (cabeza){
case ARRIBA:
cabeza = cabeza.IZQUIERDA;
break;
case ABAJO:
cabeza = cabeza.DERECHA;
break;
case IZQUIERDA:
cabeza = cabeza.ABAJO;
break;
case DERECHA:
cabeza = cabeza.ARRIBA;
break;
}
}
}
}
Nota: El ultimo metodo puede resultar confuso pero cuando lo pongamos en practica se entendera porque se usan estos valores.
Con esto ya tenemos todo lo necesario para dibujar a nuestra serpiente a medida que vaya comiendo las manzanas, en el proximo post veremos como se implementa en la clase SnakeJuego y finalizaremos nuestro juego para poder probarlo.
En resumen, hoy hemos creado la clase Snake, hemos visto como implementar a Matrix para rotar y escalar nuestras imagenes, tambien hemos visto tanto la forma de mover muchos elementos relacionados por medio de un ArrayList, hemos visto como incrementarlo, como detectar cuando haya un impacto entre distintos elementos, ya sea para sumar puntos o para detectar una colision entre nuestra cabeza y nuestro cuerpo para perder, en el proximo post se pondra mas interesante, 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