Bienvenidos sean a este post, hoy veremos como agregar un cartel de victoria y de derrota a nuestro juego Asteroides, para ello debemos abrir Android Studio y nuestro proyecto Asteroides, si no tienes este juego puedes hacerlo siguiendo estos posts:
- Listado de posts para crear Asteroides
- Asteroides, creando la actividad principal
- Continuamos con la actividad principal de Asteroides
- Tuneando a Asteroides
- Introduciendo movimiento en Asteroides
- Manejo de la nave con la pantalla tactil
- Añadiendo gestos a Asteroides
- Misil en Asteroides
- Efecto de audio en Asteroides
- Fragmentando los asteroides
- Modificando las preferencias de Asteroides
- Mas sobre preferencias en Asteroides
- Bases de datos y Asteroides
- servicio web y Asteroides
Una vez creado el juego vamos a la clase VistaJuego y agregaremos las siguientes variables:
// //// ESTADOS DE JUEGO //////
public static final int ESTADO_JUGANDO = 0;
public static final int ESTADO_VICTORIA = 1;
public static final int ESTADO_DERROTA = 2;
private int estado = ESTADO_JUGANDO;
private View vistaVictoria;
private View vistaDerrota;
Las tres primeras son constantes y seran para identificar cada estado del juego (Jugando, ganar, perder), despues crearemos una variable llamada estado donde le asignaremos el valor de ESTADO_JUGANDO, para luego crear dos objetos de tipo View que despues usaremos para vincular el codigo Java con el Layout, en la misma clase añadiremos los siguientes dos metodos:
public void setVistaVictoria(View vista){
vistaVictoria=vista;
}
public void setVistaDerrota(View vista){
vistaDerrota=vista;
}
Estos dos metodos se encargaran de asignar a los objetos vistaVictoria o vistaDerrota la vista informada como argumento, por ahora solamente hace esto, nuestra siguiente modificacion sera en onDraw() donde agregaremos el siguiente bloque:
if (estado == ESTADO_VICTORIA){
vistaVictoria.setVisibility(VISIBLE);
} else if (estado == ESTADO_DERROTA){
vistaDerrota.setVisibility(VISIBLE);
}
En este caso verificara el valor de estado, si es igual a ESTADO_VICTORIA, procedera a hacer vistaVictoria visible por medio de setVisibility(), de lo contrario si es igual a ESTADO_DERROTA procedera a hacer visible a vistaDerrota de la misma forma que la anterior, nuestra siguiente modificacion sera en destruyeAsteroide() donde modificaremos este condicional:
if(asteroides.isEmpty()){
salir();
}
De esta forma:
if(asteroides.isEmpty()){
estado = ESTADO_VICTORIA;
salir();
}
Este condicional chequea que no existan mas asteroides o trozos de los mismos y procede a setear a estado con ESTADO_VICTORIA, esto ocasionara que nos muestre el proximo cartel de victoria, ya haremos uno, y luego la posibilidad de agregar nuestro nombre en la lista de putuaciones, nuestra siguiente modificacion sera en actualizarFisica() donde tenemos este bucle for:
for(Grafico asteroide : asteroides){
if(asteroide.verificaColision(nave))
salir();
}
Y lo modificaremos de la siguiente forma:
for(Grafico asteroide : asteroides){
if(asteroide.verificaColision(nave))
estado = ESTADO_DERROTA;
salir();
}
En este caso cambiaremos el valor de estado a ESTADO_DERROTA para notificar que perdimos y mostrar el cartel pertinente, ya lo haremos, y por medio de salir() veremos la posibilidad de agregar nuestro nombre a la lista de puntuaciones, nuestra siguiente modificacion sera en el metodo salir() donde podemos borrar (o comentar) la linea: padre.finish(), tal como se ve a continuacion:
private void salir(){
Bundle bundle = new Bundle();
bundle.putInt("puntuacion", puntuacion);
Intent intento = new Intent();
intento.putExtras(bundle);
padre.setResult(Activity.RESULT_OK,intento);
// padre.finish();
}
Esto solamente sera para mostrar la notificacion de ingreso de puntuaciones pero no matara a la clase Juego, nuestra siguiente modificacion sera para el layout juego.xml donde cambiaremos el codigo actual por el siguiente:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.tinchicus.asteroides.VistaJuego
android:id="@+id/VistaJuego"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:background="@drawable/fondo" />
<TextView
android:id="@+id/Victoria"
android:text="Has Ganado!!"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="35sp"
android:textColor="#FF0000"
android:background="#7F000000"
android:visibility="invisible"/>
<TextView
android:id="@+id/Derrota"
android:text="Game Over, Man!!"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="35sp"
android:textColor="#FF0000"
android:background="#7F000000"
android:visibility="invisible"/>
</FrameLayout>
En este caso no solamente cambiamos de LinearLayout por FrameLayout sino que a su vez agregamos dos TextView para mostrar la victoria y la derrota llamados Victoria y Derrota respectivamente, seguimos manteniendo el fondo pero la unica diferencia es que ambos TextView ocuparan toda la pantalla y no se veran porque en visibility los hicimos invisible, si recuerdan en onDraw nosotros le dijimos con el condicional que agregamos que dependiendo de estado se vuelven visible vistaVictoria o vistaDerrota, nuestra siguiente modificacion sera en la clase Juego donde primero agregaremos el siguiente objeto:
VistaJuego vistaJuego;
Y luego modificaremos el metodo onCreate() de la clase Juego para agregar las siguientes lineas:
vistaJuego = (VistaJuego) findViewById(R.id.VistaJuego);
vistaJuego.setVistaVictoria(findViewById(R.id.Victoria));
vistaJuego.setVistaDerrota(findViewById(R.id.Derrota));
En este caso asociaremos a vistaJuego con el layout VistaJuego y luego usaremos los metodos setVistaVictoria() y setVistaDerrota() que agregamos anteriormente para asociarlos a sus respectivos elementos del layout, veamos a continuacion como quedo el codigo de la clase VistaJuego:
package org.example.asteroides;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.graphics.drawable.shapes.RectShape;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import java.util.List;
import java.util.Vector;
public class VistaJuego extends View implements SensorEventListener {
// //// CONFIG //////
SharedPreferences pref = PreferenceManager
.getDefaultSharedPreferences(getContext());
private int puntuacion = 0;
private static Activity padre;
// //// ASTEROIDES //////
private Drawable drawableAsteroide[] = new Drawable[3];
private Vector<Grafico> asteroides;
private int numAsteroides = 5;
private int numFragmentos = Integer.parseInt(pref
.getString("fragmentos","3"));
// //// NAVE //////
private Grafico nave;
private int giroNave;
private double aceleracionNave;
private static final int MAX_VELOCIDAD_NAVE = 20;
private static final int PASO_GIRO_NAVE = 5;
private static final float PASO_ACELERACION_NAVE = 0.5f;
// //// THREAD y TIEMPO //////
private ThreadJuego hilo = new ThreadJuego();
private static int PERIODO_PROCESO = 50;
private long ultimoProceso = 0;
// //// MISIL //////
private Grafico misil;
private static int PASO_VELOCIDAD_MISIL = 12;
private boolean misilActivo = false;
private int tiempoMisil;
// //// MULTIMEDIA //////
SoundPool soundPool;
int idDisparo, idExplosion;
// //// ESTADOS DE JUEGO //////
public static final int ESTADO_JUGANDO = 0;
public static final int ESTADO_VICTORIA = 1;
public static final int ESTADO_DERROTA = 2;
private int estado = ESTADO_JUGANDO;
private View vistaVictoria;
private View vistaDerrota;
public VistaJuego(Context context, AttributeSet attrs){
super(context, attrs);
Drawable drawableNave, drawableMisil;
if (pref.getString("graficos","1").equals("0")){
Path pathAsteroide = new Path();
pathAsteroide.moveTo((float) 0.3, (float) 0.0);
pathAsteroide.lineTo((float) 0.6, (float) 0.0);
pathAsteroide.lineTo((float) 0.6, (float) 0.3);
pathAsteroide.lineTo((float) 0.8, (float) 0.2);
pathAsteroide.lineTo((float) 1.0, (float) 0.4);
pathAsteroide.lineTo((float) 0.8, (float) 0.6);
pathAsteroide.lineTo((float) 0.9, (float) 0.9);
pathAsteroide.lineTo((float) 0.8, (float) 1.0);
pathAsteroide.lineTo((float) 0.4, (float) 1.0);
pathAsteroide.lineTo((float) 0.0, (float) 0.6);
pathAsteroide.lineTo((float) 0.0, (float) 0.2);
pathAsteroide.lineTo((float) 0.3, (float) 0.0);
for(int i=0; i < 3; i++){
ShapeDrawable dAsteroide = new ShapeDrawable(
new PathShape(pathAsteroide,1,1));
dAsteroide.getPaint().setColor(Color.WHITE);
dAsteroide.getPaint().setStyle(Paint.Style.STROKE);
dAsteroide.setIntrinsicHeight(50 - i * 14);
dAsteroide.setIntrinsicWidth(50 - i * 14);
drawableAsteroide[i] = dAsteroide;
}
setBackgroundColor(Color.BLACK);
} else {
drawableAsteroide[0] = context.getResources()
.getDrawable(R.drawable.asteroide1);
drawableAsteroide[1] = context.getResources()
.getDrawable(R.drawable.asteroide2);
drawableAsteroide[2] = context.getResources()
.getDrawable(R.drawable.asteroide3);
}
if (pref.getString("graficos","1").equals("0")){
Path pathNave = new Path();
pathNave.moveTo((float)0.0,(float)0.0);
pathNave.lineTo((float)1.0,(float)0.5);
pathNave.lineTo((float)0.0,(float)1.0);
pathNave.lineTo((float)0.0,(float)0.0);
ShapeDrawable dNave = new ShapeDrawable(
new PathShape(pathNave,1,1));
dNave.getPaint().setColor(Color.WHITE);
dNave.getPaint().setStyle(Paint.Style.STROKE);
dNave.setIntrinsicHeight(15);
dNave.setIntrinsicWidth(20);
drawableNave = dNave;
} else {
drawableNave = context.getResources().getDrawable(
R.drawable.nave);
}
if (pref.getString("graficos","1").equals("0")){
ShapeDrawable dMisil = new ShapeDrawable(new RectShape());
dMisil.getPaint().setColor(Color.WHITE);
dMisil.getPaint().setStyle(Paint.Style.STROKE);
dMisil.setIntrinsicWidth(15);
dMisil.setIntrinsicHeight(3);
drawableMisil = dMisil;
} else {
drawableMisil = context.getResources().getDrawable(
R.drawable.misil1);
}
asteroides = new Vector<Grafico>();
nave = new Grafico(this,drawableNave);
misil = new Grafico(this,drawableMisil);
for(int i = 0; i < numAsteroides; i++) {
Grafico asteroide = new Grafico(this, drawableAsteroide[0]);
asteroide.setIncY(Math.random() * 4 - 2);
asteroide.setIncX(Math.random() * 4 - 2);
asteroide.setAngulo((int) (Math.random() * 360));
asteroide.setRotacion((int) (Math.random() * 8 - 4));
asteroides.add(asteroide);
}
SensorManager mSensorManager = (SensorManager)
context.getSystemService(Context.SENSOR_SERVICE);
List<Sensor> listaSensores = mSensorManager.getSensorList(
Sensor.TYPE_ORIENTATION);
if (!listaSensores.isEmpty()){
Sensor orientacionSensor = listaSensores.get(0);
mSensorManager.registerListener(this,orientacionSensor,
SensorManager.SENSOR_DELAY_GAME);
}
soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
idDisparo = soundPool.load(context,R.raw.disparo,0);
idExplosion = soundPool.load(context,R.raw.explosion,0);
}
@Override
public boolean onKeyDown(int codigoTecla, KeyEvent evento){
super.onKeyDown(codigoTecla, evento);
boolean procesada = true;
switch(codigoTecla) {
case KeyEvent.KEYCODE_DPAD_UP:
aceleracionNave = +PASO_ACELERACION_NAVE;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
//Toast.makeText(this, "Izquierda", Toast.LENGTH_SHORT).show();
giroNave = -PASO_GIRO_NAVE;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
giroNave = +PASO_GIRO_NAVE;
break;
case KeyEvent.KEYCODE_ENTER:
activaMisil();
break;
default:
procesada = false;
break;
}
return procesada;
}
@Override
public boolean onKeyUp(int codigoTecla, KeyEvent evento){
super.onKeyUp(codigoTecla, evento);
boolean procesada = true;
switch(codigoTecla) {
case KeyEvent.KEYCODE_DPAD_UP:
aceleracionNave = 0;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
giroNave = 0;
break;
default:
procesada = false;
break;
}
return procesada;
}
@Override
protected void onSizeChanged(int ancho, int alto, int ancho_anter,
int alto_anter){
super.onSizeChanged(ancho,alto,ancho_anter,alto_anter);
nave.setCenY(alto/2);
nave.setCenX(ancho/2);
for(Grafico asteroide: asteroides){
do {
asteroide.setCenX((int) (Math.random() * ancho));
asteroide.setCenY((int) (Math.random() * alto));
} while(asteroide.distancia(nave) < (ancho+alto)/5);
}
ultimoProceso = System.currentTimeMillis();
hilo.start();
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
synchronized (asteroides){
for(Grafico asteroide: asteroides){
asteroide.dibujaGrafico(canvas);
}
}
nave.dibujaGrafico(canvas);
if (misilActivo)
misil.dibujaGrafico(canvas);
if (estado == ESTADO_VICTORIA){
vistaVictoria.setVisibility(VISIBLE);
} else if (estado == ESTADO_DERROTA){
vistaDerrota.setVisibility(VISIBLE);
}
}
protected void actualizarFisica(){
long ahora = System.currentTimeMillis();
if (ultimoProceso + PERIODO_PROCESO > ahora){
return;
}
double retardo = (ahora - ultimoProceso) / PERIODO_PROCESO;
ultimoProceso = ahora;
nave.setAngulo((int) (nave.getAngulo() + giroNave * retardo));
double nIncX = nave.getIncX() + aceleracionNave *
Math.cos(Math.toRadians(nave.getAngulo())) * retardo;
double nIncY = nave.getIncY() + aceleracionNave *
Math.sin(Math.toRadians(nave.getAngulo())) * retardo;
if (Math.hypot(nIncX,nIncY) <= MAX_VELOCIDAD_NAVE){
nave.setIncX(nIncX);
nave.setIncY(nIncY);
}
nave.incrementaPos(retardo);
for(Grafico asteroide: asteroides){
asteroide.incrementaPos(retardo);
}
if(misilActivo){
misil.incrementaPos(retardo);
tiempoMisil-=retardo;
if(tiempoMisil<0){
misilActivo=false;
} else {
for(int i=0; i < asteroides.size(); i++)
if(misil.verificaColision(asteroides.elementAt(i))){
destruyeAsteroide(i);
break;
}
}
}
for(Grafico asteroide : asteroides){
if(asteroide.verificaColision(nave))
estado = ESTADO_DERROTA;
salir();
}
}
class ThreadJuego extends Thread {
@Override
public void run(){
while(true){
actualizarFisica();
}
}
}
private float mX=0, mY=0;
private boolean disparo=false;
@Override
public boolean onTouchEvent(MotionEvent event){
super.onTouchEvent(event);
float x = event.getX();
float y = event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
disparo=true;
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dy<6 && dx>6) {
giroNave = Math.round((x - mX) / 2);
disparo = false;
} else if(dx<6 && dy>6){
aceleracionNave = Math.round((mY - y)/2);
disparo = false;
}
break;
case MotionEvent.ACTION_UP:
giroNave = 0;
aceleracionNave = 0;
if (disparo) {
activaMisil();
}
break;
}
mX = x; mY = y;
return true;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy){}
private boolean hayValorinicial = false;
private float valorInicial;
@Override
public void onSensorChanged(SensorEvent evento){
float valor = evento.values[2];
if (!hayValorinicial){
valorInicial=valor;
hayValorinicial=true;
}
giroNave = (int) (valor - valorInicial)/3;
}
private void destruyeAsteroide(int i){
int tam;
if (asteroides.get(i).getDrawable()!=drawableAsteroide[2]){
if(asteroides.get(i).getDrawable()==drawableAsteroide[1]){
tam=2;
} else {
tam=1;
}
for(int n = 0; n < numFragmentos; n++){
Grafico asteroide = new Grafico(this,drawableAsteroide[tam]);
asteroide.setCenY(asteroides.get(i).getCenY());
asteroide.setCenX(asteroides.get(i).getCenX());
asteroide.setIncX(Math.random() * 7-2-tam);
asteroide.setIncY(Math.random() * 7-2-tam);
asteroide.setAngulo((int) (Math.random() * 360));
asteroide.setRotacion((int) (Math.random() * 8-4));
asteroides.add(asteroide);
}
}
synchronized (asteroides){
asteroides.removeElementAt(i);
misilActivo = false;
soundPool.play(idExplosion,1,1,0,0,1);
}
puntuacion += 1000;
if(asteroides.isEmpty()){
estado = ESTADO_VICTORIA;
salir();
}
}
private void activaMisil(){
misil.setCenX(nave.getCenX());
misil.setCenY(nave.getCenY());
misil.setAngulo(nave.getAngulo());
misil.setIncX(Math.cos(Math.toRadians(misil.getAngulo())) *
PASO_VELOCIDAD_MISIL);
misil.setIncY(Math.sin(Math.toRadians(misil.getAngulo())) *
PASO_VELOCIDAD_MISIL);
tiempoMisil = (int) Math.min(this.getWidth() / Math.abs(misil.
getIncX()), this.getHeight() / Math.abs(misil.getIncY())) - 2;
misilActivo = true;
soundPool.play(idDisparo,1,1,1,0,1);
}
public static void setPadre(Activity padre){
VistaJuego.padre = padre;
}
private void salir(){
Bundle bundle = new Bundle();
bundle.putInt("puntuacion", puntuacion);
Intent intento = new Intent();
intento.putExtras(bundle);
padre.setResult(Activity.RESULT_OK,intento);
// padre.finish();
}
public void setVistaVictoria(View vista){
vistaVictoria=vista;
}
public void setVistaDerrota(View vista){
vistaDerrota=vista;
}
}
Y a continuacion veremos como quedo finalmente el codigo de nuestra clase Juego:
package org.example.asteroides;
import android.app.Activity;
import android.os.Bundle;
public class Juego extends Activity {
VistaJuego vistaJuego;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.juego);
VistaJuego.setPadre(this);
vistaJuego = (VistaJuego) findViewById(R.id.VistaJuego);
vistaJuego.setVistaVictoria(findViewById(R.id.Victoria));
vistaJuego.setVistaDerrota(findViewById(R.id.Derrota));
}
}
Con todas estas modificaciones realizadas podemos proceder a probar nuestro juego como se ve en el siguiente video:
No esperen que les muestre el cartel de Victoria, porque en este emulador es imposible jugarlo, cuando perdemos podemos ver el mensaje de derrota y cuando al apretar el boton para volver y salir nos da la posibilidad de agregar nuestro nombre para la lista de puntuacione, si lograron lo visto en pantalla Felicidades!!! Ya tienen un juego casi apto para consumo humano 😄
En resumen, hoy hemos visto como poder agregar un cartel que notifique la victoria o la derrota en nuestro juego, como implementarlo y las modificaciones necesarias, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

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