Bienvenidos sean a este post, hoy continuaremos con lo iniciado en el post anterior para este caso extenderemos un poco mas a VistaJuego agregando la nave y luego haremos una representacion vectorial de nuestros graficos, pongamos manos a la obra.

Anuncios

Abramos nuestro Android Studio y nuestro proyecto Asteroides, luego pasaremos a la vista VistaJuego y agregaremos las siguientes lineas inmediatamente despues de donde iniciamos nuestra clase sin reemplazar solo agregar:

public class VistaJuego extends View {
    // //// 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;

Estas va a ser la declaracion de nuestras variables para la creacion de la nave, como pueden ver primero creamos el objeto de la clase Grafico llamado nave, luego crearemos una variable llamada giroNave para almacenar el incremento de la direccion, la variable aceleracionNave se encarga del aumento de la velocidad, luego tendremos tres constantes para limitar la velocidad maxima de la nave (MAX_VELOCIDAD_NAVE), el limite de giro de la nave (PASO_GIRO_NAVE) y por ultimo el incremento de la aceleracion (PASO_ACELERACION_NAVE), ahora pasaremos al constructor de la clase y agregaremos la siguiente linea:

drawableNave = context.getResources().getDrawable(
        R.drawable.nave);

Esta linea se encargara de asignar el grafico de la nave a nuestro objeto para la nave, despues agregaremos la siguiente linea:

nave = new Grafico(this,drawableNave);

Esta se encargara de crear el grafico de nuestra nave, nuestro siguiente paso sera ubicar la nave en el centro de la pantalla para ello agregaremos las siguientes lineas en el metodo onSizeChanged():

nave.setCenY(alto/2);
nave.setCenX(ancho/2);

Estas se encargaran de calcular la mitad de nuestro ancho y alto para ubicarlo en los respectivos ejes y por ultimo dibujaremos nuestro grafico para ello agregaremos la siguiente linea en onDraw():

nave.dibujaGrafico(canvas);

Como pueden ver ya tenemos nuestra nave dibujada en la vista, si lo probamos nos quedara de la siguiente forma

android45
Anuncios

Esto ya esta tomando mejor forma pero nos puede ocurrir como se ve en pantalla que los asteroides pueden aparecer muy proximo de nuestra nave y por ende terminar el juego de manera abrupta, para evitar ello deberemos hacer nuestra ultima modificacion, en este caso en onSizeChanged(), reemplazaremos esta seccion del codigo:

for(Grafico asteroide: asteroides){
    asteroide.setCenX((int) (Math.random() * ancho));
    asteroide.setCenY((int) (Math.random() * alto));
}

Con este nuevo codigo:

for(Grafico asteroide: asteroides){
    do {
        asteroide.setCenX((int) (Math.random() * ancho));
        asteroide.setCenY((int) (Math.random() * alto));
    } while(asteroide.distancia(nave) < (ancho+alto)/5);
}

Realizada esta modificacion, si probamos nuestra app ahora obtendremos un resultado similar a este

android46

Ahora nuestros asteroides estan mucho mas alejados de nuestra nave, veamos como quedo el codigo de la clase VistaJuego luego de todas las modificaciones:

package org.example.asteroides;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

import java.util.Vector;

public class VistaJuego extends View {
    // //// 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;


    public VistaJuego(Context context, AttributeSet attrs){
        super(context, attrs);
        Drawable drawableNave, drawableAsteroide, drawableMisil;
        drawableAsteroide = context.getResources().getDrawable(
                R.drawable.asteroide1);
        drawableNave = context.getResources().getDrawable(
                R.drawable.nave);
        asteroides = new Vector<Grafico>();
        nave = new Grafico(this,drawableNave);
        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);
        }

    }

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

    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        for(Grafico asteroide: asteroides){
            asteroide.dibujaGrafico(canvas);
        }
        nave.dibujaGrafico(canvas);
    }
}

Hasta aca hemos logrado agregar nuestra nave y asteroides en la vista pero que sucederia si giramos nuestra pantalla sin querer, esta se reiniciara haciendo a nuestro juego volver a empezar y para evitar esto deberemos hacer una modificacion en AndroidMainfest.xml, para ello debemos modificar la actividad Juego de la siguiente forma:

<activity android:name=".Juego"
    android:label="@string/tituloJuego"
    android:screenOrientation="landscape"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
Anuncios

En este caso agregamos una modificacion para que la orientacion de nuestra vista siempre sea landscape (apaisada) para evitar justamente el cambio de la misma, luego agregamos una linea mas para asegurarnos que no aparezca ninguna barra y el juego esta a pantalla completa. En el siguiente video veremos como queda nuestra nueva modificacion

Hasta aca hemos terminado nuestra seccion dedicada a extender la clase VistaJuego para agregar nuestra nave y los asteroides de forma mas eficiente, ahora pasemos a la siguiente seccion: representacion de graficos vectoriales.
Para ello en nuestra clase VistaJuego modificaremos la siguiente linea:

drawableAsteroide = context.getResources().getDrawable(
        R.drawable.asteroide1);

Por el siguiente codigo:

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

En este caso crearemos un objeto para obtener las preferencias de nuestra app, les recomiendo este post para saber mas, donde podremos acceder al archivo donde se almacenan nuestras preferencias por defecto, ahi utilizaremos un condicional donde le diremos que extraiga el valor de graficos, en caso de ser cero procedera a ejecutar el bloque donde crearemos un objeto de la clase Path, despues lo moveremos a un punto inicial, luego por medio del metodo lineTo() iremos haciendo una linea primero de la coordenada donde ejecutamos el moveTo() hacia la nueva coordenada, nuestro siguiente lineTo() lo hara de la ultima coordinada a la siguiente informada y asi sucesivamente en cada una de las lineas hasta volver al principio, nuestro siguiente paso sera crear un objeto de la clase ShapeDrawable y por medio del PathShape() a traves de nuestro path creado anteriormente crearemos nuestro asteroide, luego le setearemos un color por medio de setColor(), despues un estilo por medio de setStyle() y por ultimo le estableceremos un alto (intrinsicHeight) y un ancho (intrinsicWidth), y por ultimo a drawableAsteroide le setearemos todo el valor guardado en dAsteroide, el objeto creado por nosotros para almacenar todos estos datos y el bloque else se ejecutara cuando encuentre que el valor de graficos es distinto de cero y seteara a drawableAsteroide con el recurso de la imagen como lo haciamos antes, ahora si nosotros probamos nuestra app pueden ocurrir dos cosas, que se vea bien desde un principio o lo siguiente

android47

Esto se puede evitar de dos formas, una es agregando la siguiente linea en el bloque if de la ultima modificacion realizada:

setLayerType(View.LAYER_TYPE_SOFTWARE,null);

Y la siguiente linea en el bloque del else:

setLayerType(View.LAYER_TYPE_HARDWARE,null);

O agregando la siguiente linea a nuestra actividad Juego en AndroidManifest.xml:

android:hardwareAccelerated="false"

En cualquiera de los dos casos se obtendra un resultado como se ve a continuacion

Anuncios
android48

Para mi, como en general para Android, es mejor practica utilizarlo por medio del archivo XML ya que nos evitaria tener que utilizar las lineas antes mencionadas pero como sucede habitualmente en programacion siempre para algunos casos va a ser mas practico usarlo en codigo y otros por medio de XML, pasemos a la ultima transformacion y en este caso va a ser nuestra nave, para ello a la siguiente linea de la clase VistaJuego:

drawableNave = context.getResources().getDrawable(
        R.drawable.nave);

Le haremos la siguiente modificacion:

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

Es similar a la anterior, por ahi pueden ver como son menos lineas porque solamente tenemos que generar un triangulo para representar a nuestra nave pero el resto es similar, incluso nuestro bloque else, ahora si lo ejecutamos obtendremos el siguiente resultado

android49

Pasemos a ver el resultado final por medio de este video

Como se puede ver en el video, ahora podremos alternar tranquilamente entre nuestro juego normal y una version retro, si lograron lo mismo que en el video Felicitaciones!!! porque vamos por el buen camino.

Anuncios

En resumen, hoy hemos visto como poder aprovechar nuestras clases Drawable para poder alternar entre graficos en imagenes (bitmap) y graficos dibujados por nosotros por medio de codigo para transformarlos en vectoriales, tambien hemos repasado como obtener los datos de nuestras preferencias para poder cambiar entre los distintos tipos de datos y por ultimo como solucionar algun inconveniente ocasionado por nuestro acelerador de hardware del equipo y/o del software, 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.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00