Bienvenidos sean a este post, hoy volveremos a nuestra app Asteroides y en esta ocasion agregaremos los misiles para nuestra nave, abramos nuestro proyecto y vayamos al recurso drawable ubicado en res/drawable deberemos tener tres archivos para los misiles (misil1.png, misil2.png, misil3.png), con nuestros recursos en el proyecto ahora procederemos a modificar el codigo de nuestra clase VistaJuego agregando el siguiente bloque:

// //// MISIL //////
private Grafico misil;
private static int PASO_VELOCIDAD_MISIL = 12;
private boolean misilActivo = false;
private int tiempoMisil;

En este bloque solo declararemos las variables y objetos para nuestros misiles, definiremos PASO_VELOCIDAD_MISIL y el estado de nuestro misil (misilActivo), ahora pasaremos a graficar nuestros misiles tanto de forma vectorial como de forma bitmap, para ello agregaremos el siguiente bloque dentro del constructor de nuestra clase, junto a los otros bloques encargados de dibujar a nuestros componentes:

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);
}

En este bloque la primera parte se encargara de dibujar nuestro misil si el grafico es del tipo vectorial pero no lo ubicara solamente lo dibujara, para saber un poco mas les recomiendo este post, y en la segunda parte se encarga de asignar el grafico llamado misil1.png a nuestro objeto drawableMisil. Nuestra siguiente modificacion sera la encargada de asignar el grafico generado o asignado a nuestro misil para ello agregaremos la siguiente linea en el constructor:

misil = new Grafico(this,drawableMisil);

Nuestro siguiente modificacion sera sobre el metodo actualizarFisica() para ello agregaremos el siguiente bloque:

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;
}
}
}
}

Si misil esta activo incremetara la posicion, si el tiempoMisil es menor a cero hace desaparecer al misil, en el siguiente bloque verifica si nuestro misil colisiono con algunos de los asteroides y en caso de ser verdadero procedera a llamar al metodo destruyeAsteroide(), el cual todavia no existe. En nuestro siguiente paso sera declararlo dentro del metodo onDraw() para dibujar al mismo, para ello agregaremos las siguiente lineas dentro del metodo:

if (misilActivo)
misil.dibujaGrafico(canvas);

En estas lineas utilizaremos a misilActivo, es decir si misilActivo existe (es decir que es true) procedera a dibujar nuestro misil. A continuacion agregaremos dos metodos mas para poder controlar nuestros misiles:

private void destruyeAsteroide(int i){
asteroides.remove(i);
misilActivo = false;
}

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;
}

El primer metodo es el encargado de quitar a nuestro asteroide, el que colisiono con nuestro misil, el segundo sera el encargado de activar nuestro misil, donde calculara el angulo de salida y utilizara el PASO_VELOCIDAD_MISIL para la velocidad del mismo, por ultimo activaremos el mismo con la ultima linea, ahora solo nos resta algunas modificaciones para poder activar la accion para disparar los misiles, para llamar al metodo activaMisil() debes quitar los comentarios de dos bloques:

case KeyEvent.KEYCODE_ENTER:
activaMisil();
break;

if (disparo) {
activaMisil();
}

Para ir finalizando haremos una serie de modificaciones mas para poder probrar nuestra app, tenemos el siguiente metodo:

@Override
synchronized protected void onDraw(Canvas canvas){
....
}

Lo modificaremos de la siguiente forma:

@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);
}

Esto lo habiamos implementado anteriormente para evitar inconvenientes con los threads, nuestra primera modificacion para decirle que en lugar de hacer con el metodo onDraw() debera hacerlo con asteroides unicamente, nuestra siguiente modificacion sera en este metodo:

synchronized protected void actualizarFisica(){
....
}

De la siguiente forma:

protected void actualizarFisica(){
...
}

Aca solamente eliminamos el metodo synchronized, el resto del metodo no se modificara, nuestro siguiente paso sera modificar el  metodo destruyeAsteroide de la siguiente forma:

private void destruyeAsteroide(int i){
synchronized (asteroides){
asteroides.removeElementAt(i);
misilActivo = false;
}
}

Al igual que antes es para evitar tener inconvenientes con los hilos y los asteroides pudiendo acarrearnos algun error con todo esto modificado ya podriamos probar nuestra app, el resultado sera como se ve en el siguiente video:

Como pueden ver cuando tocamos la pantalla nuestro misil sale de la nave y cuando impacta con el asteroide lo desaparece de la actividad.

Fe de Errata: haciendo una verificacion de este programa me di cuenta que cometi un error con el metodo verificaColision(), deben modificarlo de la siguiente forma:

public boolean verificaColision(Grafico g){
return (distancia(g) < (radioColision + g.radioColision));
}

Con esto aclarado prosigamos con el post.

Hasta aca tenemos la capacidad de disparar un misil y destruir los asteroides, en otro post haremos una modificacion para poder disparar mas de un misil a la vez pero por ahora lleguemos hasta aca y no lo compliquemos mas, para ir finalizando veamos como quedo finalmente nuestro codigo:

package com.tinchicus.asteroides;

import android.content.Context;
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.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import java.util.List;
import java.util.Vector;

public class VistaJuego extends View implements SensorEventListener {
// //// ASTEROIDES //////
private Vector<Grafico> asteroides;
private int numAsteroides = 5;
private int numFragmentos = 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;

public VistaJuego(Context context, AttributeSet attrs){
super(context, attrs);
Drawable drawableNave, drawableAsteroide, drawableMisil;

SharedPreferences pref = PreferenceManager
.getDefaultSharedPreferences(getContext());
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);
ShapeDrawable dAsteroide = new ShapeDrawable(
new PathShape(pathAsteroide,1,1));
dAsteroide.getPaint().setColor(Color.WHITE);
dAsteroide.getPaint().setStyle(Paint.Style.STROKE);
dAsteroide.setIntrinsicWidth(50);
dAsteroide.setIntrinsicHeight(50);
drawableAsteroide = dAsteroide;
setBackgroundColor(Color.BLACK);
} else {
drawableAsteroide = context.getResources().getDrawable(
R.drawable.asteroide1);
}
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);
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);
}
}

@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:
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);
}

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;
}
}
}
}

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){
synchronized (asteroides){
asteroides.removeElementAt(i);
misilActivo = false;
}
}

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;
}
}

En resumen, hoy hemos agregado un misil, hemos implementado algunas acciones antes desarrolladas, hemos hecho una pequeña modificacion en synchronized para trabajar de una forma mas acotada con los threads generados por nuestros asteroides, espero les haya sido util sigueme en Twitter, Facebook o Google+ para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios