Anuncios

Bienvenidos sean a este post, en el post de hoy nos centraremos en nuestra clase encargada de manejar a nuestra pelota en el juego.

Anuncios
Anuncios

En realidad nuestra pelota sera dibujada por la clase PongJuego a traves del metodo dibujar, aunque todavia no le hemos agregado el metodo encargados de esto, y para poder dibujar nuestra pelota vamos a necesitar manejar cuatro variables:

  • Una para la altura de la pelota
  • Una para el ancho de la pelota
  • Una para la velocidad del eje X
  • Una para la velocidad del eje Y

Para conmemorar al juego original, y por un tema de practicidad no les voy a mentir, vamos a usar una pelota cuadrada, por este motivo tenemos una variable para el ancho y la altura de la pelota y las otras dos variables las usaremos para la velocidad de desplazamiento de la misma, todas estas variables seran de tipo float pero por qué este tipo y no tipo int? En realidad por un lado es por como trabaja la pantalla y por otro lado nos sera mas preciso a la hora de ubicar nuestra pelota para iniciar, tambien nos permitira mantener una velocidad constante entre las distintas resoluciones que nos podemos encontrar entre los distintos dispositivos.

Anuncios

El metodo que usaremos para dibujar nuestra pelota sera el drawRect de la clase Canvas, este metodo tiene la particularidad de poder recibir tanto valores de tipo float como int y el se encarga de manejarlas por nosotros para usarlas en la pantalla.

Anuncios
Anuncios

Para este caso el verdadero responsable de dibujar nuestra pelota y bate y a su vez detectar cuando uno impacta con el otro va a ser nuestro Game Engine, para que esto funcione es necesario saber donde esta cada uno y cual es su tamaño, una solucion es utilizar en la clase PongJuego variables de tipo privada y despues obtenerlas por medio de metodos getter, como necesitamos obtener tres datos (tamaño, tamaño vertical y tamaño horizontal) lo cual implica usar tres variables y como cada funcion solo puede devolver un valor esto implica que debemos usar tres funciones getter y en este caso seria un desperdicio de recursos para cada vez que necesitamos dibujar la pelota, la solucion es empaquetar todos estos datos en un solo objeto de la clase RectF.

Anuncios

La clase RectF es muy versatil y la estaremos usando en nuestros proyectos venideros de una forma u otra, esta compuesta de cuatro variables:

  • bottom (parte inferior)
  • left (izquierda)
  • right (derecha)
  • top (parte superior)
Anuncios

Como todos estos datos son publicos nos permiten averiguar el tamaño de nuestro objeto de mejor manera, para comenzar con nuestra clase primero vamos a agregar nuestras variables:

package org.example.pong;

import android.graphics.RectF;

class Pelota {
    private RectF mRect;
    private float mXVelocidad;
    private float mYVelocidad;
    private float mPelotaAncho;
    private float mPelotaAlto;
}

En este caso les traigo el codigo completo porque todavia es corto pero tambien para ver como tuvimos que agregar un paquete para el primer objeto que creamos, tambien volvimos privada a esta clase (si ven la generada de forma predeterminada tenia la palabra public y se la eliminamos), el primer objeto es mRect y es de tipo RectF, las siguientes dos variables son para la velocidad en los ejes X e Y, despues tendremos dos variables para el ancho y alto de la pelota, en este caso son todos de tipo float, nuestro siguiente paso sera agregar un constructor para la pelota:

    Pelota(int screenX){
        mPelotaAlto = screenX / 100;
        mPelotaAncho = screenX / 100;
        mRect = new RectF();
    }
Anuncios

Este constructor lo utilizaremos cada vez que creamos un nuevo objeto de tipo Pelota, a la cual le pasaremos el ancho de la pantalla, en este caso nos aseguramos que la pelota sea cuadrada y del tamaño del 1% del ancho de la pantalla, esto es gracias a los valores que le asignamos a las variables mPelotaAlto y mPelotaAncho, por ultimo asignaremos el espacio en memoria de nuestro objeto mRect y tambien seteara de manera predeterminada a cero a las cuatro variables de RectF.

Anuncios

Nuestra siguiente modificacion sera agregar el proximo metodo:

    RectF getmRect(){
        return mRect;
    }
Anuncios

Este metodo nos permitira devolver el objeto mRect a la clase PongJuego o la que sea que lo haga, para nuestro siguiente paso implementaremos el metodo encargado de actualizar a nuestra pelota cada vez que se mueve, este metodo sera el invocado por la clase PongJuego una vez por cuadro, esto implica que la clase Pelota es en realidad la encargada de actualizar la posicion de la pelota cada vez que lo necesita la clase PongJuego, veamos el bloque que debemos agregar:

    void actualizar(long fps){
        mRect.left = mRect.left + (mXVelocidad / fps);
        mRect.top = mRect.top + (mYVelocidad / fps);
        mRect.right = mRect.left + mPelotaAncho;
        mRect.bottom = mRect.top + mPelotaAlto;
    }
Anuncios

Como pueden ver esta funcion recibira el valor fps de los frames per second de nuestro equipo, dentro de esta funcion actualizaremos las cuatro variables de nuestro objeto mRect, el valor de left se incrementara con el resultado de la division de mXVelocidad y fps, lo mismo con top pero en lugar de usar mXVelocidad usara a mYVelocidad, despues tendremos el valor de right al cual le asignaremos con la incrementacion de left y el ancho de la pelota, lo mismo para bottom pero en este caso usamos a top y el alto de la misma.

Anuncios

Nuestro siguiente paso sera agregar una serie de metodos que nos ayudaran a la hora de cambiar la conducta de nuestra pelota, para la primera agregaremos los siguientes dos metodos:

    void invertirYVelocidad(){
        mYVelocidad = -mYVelocidad;
    }

    void invertirXVelocidad(){
        mXVelocidad = -mXVelocidad;
    }
Anuncios

En este caso seran dos funciones para invertir el valor del signo de mYVelocidad y mXVelocidad cuando sea necesario, cuando este sea positivo se convertira en negativo y cuando sea negativo lo convertira en positivo, estos metodos entraran en vigencia cuando el objeto sea devuelto, pasemos a agregar la siguiente funcion:

    void reset(int x, int y){
        mRect.left = x / 2;
        mRect.top = 0;
        mRect.right = (x / 2) + mPelotaAncho;
        mRect.bottom = mPelotaAlto;

        mYVelocidad = -(y/3);
        mXVelocidad = y / 3;
    }
Anuncios
Anuncios

Esta funcion se encargara de resetear los valores para nuestra pelota, ya sea porque iniciamos un nuevo juego o porque ocurrio un evento que amerite el reseteo, en este caso estableceremos a left en la mitad del ancho de la pantalla, a top arriba de todo por ser cero, a right el mismo valor que asignamos a left pero le sumamos el ancho de la pelota y por ultimo a bottom le asignaremos el alto de la pelota, nuestros siguiente valores seran para mYVelocidad y mXVelocidad donde usaremos la division de y por tres y a la primera variable le asignaremos el operador negativo para invertirlo, por ultimo agregaremos el siguiente metodo:

    void incremetarVelocidad(){
        mXVelocidad = mXVelocidad * 1.1f;
        mYVelocidad = mYVelocidad * 1.1f;
    }
Anuncios

Este metodo se encarga unicamente de incrementar los valores de velocidad de nuestra pelota, nuestra siguiente modificacion sera para detectar correctamente el impacto de la pelota con el bate y para ello vamos a usar la siguiente funcion:

    void reboteBate(RectF posicionBate){

        float bateCentro = posicionBate.left + (posicionBate.width()/2);
        float pelotaCentro = mRect.left + (mPelotaAncho / 2);
        float interseccRelativa = (bateCentro - pelotaCentro);
        if (interseccRelativa < 0){
            mXVelocidad = Math.abs(mXVelocidad);
        } else {
            mXVelocidad = -Math.abs(mXVelocidad);
        }
        invertirYVelocidad();
    }
Anuncios
Anuncios

En este metodo primero tendremos primero una variable para almacenar el valor del medio del bate y para ello usaremos el valor de left del bate, esto ya lo estableceremos cuando veamos la clase Bate, mas el ancho del bate dividido por dos, despues tendremos una variable para establecer el centro de la pelota, para ello haremos lo mismo que antes pero ahora en lugar de usar el valor de left del bate usaremos el de la pelota y le sumaremos el valor del ancho de la pelota dividido por dos, con estas dos variables ya tenemos los centros de nuestros elementos, despues tendremos otra variable que va a ser el punto de interseccion de ambos elementos y se obtendra por medio de la diferencia entre las dos variables anteriores, luego tendremos un condicional donde verifica si interseccRelativa es menor a cero, en este caso asigna el valor absoluto de mxVelocidad a mxVelocidad, por medio de Math.abs, lo cual hara que la pelota se desplace a la derecha, en caso contrario (ejecucion del else) hace lo mismo pero con el signo negativo y haciendo que vaya a la izquierda, despues llamaremos al metodo invertirYVelocidad para invertir a mYVelocidad, con este metodo ya tenemos un metodo aceptable para detectar la colision y proceder a devolver la pelota, veamos como quedo nuestro codigo final hasta ahora:

Pelota.java

package org.example.pong;

import android.graphics.RectF;

class Pelota {
    private RectF mRect;
    private float mXVelocidad;
    private float mYVelocidad;
    private float mPelotaAncho;
    private float mPelotaAlto;

    Pelota(int screenX){
        mPelotaAlto = screenX / 100;
        mPelotaAncho = screenX / 100;
        mRect = new RectF();
    }

    RectF getmRect(){
        return mRect;
    }

    void actualizar(long fps){
        mRect.left = mRect.left + (mXVelocidad / fps);
        mRect.top = mRect.top + (mYVelocidad / fps);
        mRect.right = mRect.left + mPelotaAncho;
        mRect.bottom = mRect.top + mPelotaAlto;
    }

    void invertirYVelocidad(){
        mYVelocidad = -mYVelocidad;
    }

    void invertirXVelocidad(){
        mXVelocidad = -mXVelocidad;
    }

    void reset(int x, int y){
        mRect.left = x / 2;
        mRect.top = 0;
        mRect.right = (x / 2) + mPelotaAncho;
        mRect.bottom = mPelotaAlto;

        mYVelocidad = -(y/3);
        mXVelocidad = y / 3;
    }

    void incremetarVelocidad(){
        mXVelocidad = mXVelocidad * 1.1f;
        mYVelocidad = mYVelocidad * 1.1f;
    }

    void reboteBate(RectF posicionBate){

        float bateCentro = posicionBate.left + (posicionBate.width()/2);
        float pelotaCentro = mRect.left + (mPelotaAncho / 2);
        float interseccRelativa = (bateCentro - pelotaCentro);
        if (interseccRelativa < 0){
            mXVelocidad = Math.abs(mXVelocidad);
        } else {
            mXVelocidad = -Math.abs(mXVelocidad);
        }
        invertirYVelocidad();
    }
}
Anuncios

Para nuestro siguiente paso implementaremos a Pelota en la clase PongJuego y para ello haremos una serie de modificaciones, la primera sera modificar nuestro constructor agregando la siguiente linea antes del llamado al metodo iniciaNuevoJuego:

        mPelota = new Pelota(mScreenX);
Anuncios

Basicamente lo que hacemos es utilizar el constructor que definimos anteriormente para recibir el valor del tamaño de la pantalla, nuestro siguiente paso sera modificar el metodo actualizar de la siguiente manera:

    private void actualizar(){
        mPelota.actualizar(mFPS);
    }
Anuncios

En este llamaremos al metodo actualizar de la clase Pelota para actualizar por cada frame del gameplay, luego lo modificaremos para agregar la siguiente linea dentro del metodo dibujar:

mCanvas.drawRect(mPelota.getmRect(),mPincel);
Anuncios

En general esta linea la pondremos despues de que definimos el color de mPincel, en este csao dibujara un rectangulo en base a los datos obtenidos por medio de getmRect de nuestro objeto mRect de la clase Pelota, nuestra siguiente modificacion sera en el metodo iniciaNuevoJuego agregando la siguiente linea:

mPelota.reset(mScreenX,mScreenY);
Anuncios

En este caso haremos el reseteo de nuestra pelota por ser el inicio del juego, tal como lo mencionamos antes, con todo esto podriamos probar nuestro juego ahora pero antes veamos el codigo final de esta clase:

PongJuego.java

package org.example.pong;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

class PongJuego extends SurfaceView implements Runnable {

    private final boolean DEPURANDO = true;

    private SurfaceHolder mNuestroCont;
    private Canvas mCanvas;
    private Paint mPincel;

    private long mFPS;

    private final int MILES_EN_SEGUNDO = 1000;

    private int mScreenX;
    private int mScreenY;

    private int mFontTam;
    private int mFontMargen;

    private Bate mBate;
    private Pelota mPelota;

    private int mPuntaje;
    private int mVidas;

    private Thread mJuegoThread = null;
    private volatile boolean mJugando;
    private boolean mPausado = true;

    public PongJuego(Context contexto, int x, int y){
        super(contexto);

        mScreenX = x;
        mScreenY = y;

        mFontTam = mScreenX / 20;
        mFontMargen = mScreenX /75;

        mNuestroCont = getHolder();
        mPincel = new Paint();

        mPelota = new Pelota(mScreenX);

        iniciaNuevoJuego();
    }

    private void iniciaNuevoJuego(){

        mPuntaje = 0;
        mVidas = 3;
        mPelota.reset(mScreenX,mScreenY);
    }

    private void dibujar(){
        if (mNuestroCont.getSurface().isValid()){
            mCanvas = mNuestroCont.lockCanvas();
            mCanvas.drawColor(Color.argb(255,26,128,182));
            mPincel.setColor(Color.argb(255,255,255,255));
            mCanvas.drawRect(mPelota.getmRect(),mPincel);
            mPincel.setTextSize(mFontTam);
            mCanvas.drawText("Puntaje: " + mPuntaje
                            + "  Vidas: " + mVidas,
                    mFontMargen,mFontTam,mPincel);
            if (DEPURANDO){
                imprimirDepuracion();
            }
            mNuestroCont.unlockCanvasAndPost(mCanvas);
        }
    }

    private void imprimirDepuracion(){
        int debugTam = mFontTam / 2;
        int debugComienzo = 150;
        mPincel.setTextSize(debugTam);
        mCanvas.drawText("FPS: " + mFPS,
                10,
                debugComienzo + debugTam,
                mPincel);
    }

    @Override
    public void run(){
        while(mJugando){
            long frameInicio = System.currentTimeMillis();
            if (!mPausado){
                actualizar();
                detectarColisiones();
            }
            dibujar();
            long esteFrameTiempo = System.currentTimeMillis() - frameInicio;
            if (esteFrameTiempo > 0){
                mFPS = MILES_EN_SEGUNDO / esteFrameTiempo;
            }
        }
    }

    private void actualizar(){
        mPelota.actualizar(mFPS);
    }

    private void detectarColisiones(){

    }

    public void pause(){
        mJugando = false;
        try{
            mJuegoThread.join();
        } catch (InterruptedException e){
            Log.e("Error","uniendo a thread");
        }
    }

    public void resume(){
        mJugando = true;
        mJuegoThread = new Thread(this);
        mJuegoThread.start();
    }
}
Anuncios

Con esto si lo probamos y compilamos veremos una salida semejante a esta

Anuncios

Si observan arriba de todo veran un puntito blanco que no es otra cosa que nuestra pelota, como pueden ver esta ubicado en el medio de la pantalla y en la parte superior de la pantalla si se preguntan porque no se mueve esto es debido a que mPausado esta en estado true y por ende el juego esta pausado porque para funcionar debe estar en false, vamos a probar que sucede si lo cambiamos de este estado, es decir debemos cambiar la asignacion de mPausado de la siguiente manera:

private boolean mPausado = false;
Anuncios

Si lo volvemos a compilar y probar nos sucedera lo siguiente

Anuncios

Como pueden ver nos desaparecio la pelota, esto es debido a que todavia no hicimos todo lo necesario para controlar el rebote de nuestra pelota en la pantalla pero no se preocupen porque proximamente ya iremos mejorando estas conductas, si hicieron la modificacion por el momento vuelvan a setear a mPausado como false.

Anuncios

En resumen, hoy hemos visto como confeccionar la clase Pelota, hemos agregado todo lo necesario para crear primero nuestra pelota, hemos visto con cual nos resulto mas practico, hemos creado todo lo necesario para que el game engine deposite toda la confianza en esta clase, hemos creado los metodos necesarios para iniciarlo y para cuando impacte con el bate, por ultimo hemos visto como estamos hasta ahora, espero les haya sido util 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.

Anuncios

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