Bienvenidos a la tercera parte de la serie de entradas en el ciclo Creando una aplicación de Android. Si aún no lo has hecho, comienza desde la primera entrada mostrada en el menú inmediatamente superior.
Partiremos del resultado de la entrada anterior en la cual teníamos el menú creado como en la siguiente imagen:
En esta entrada vamos a crear el juego, incluyendo todas las clases necesarias para que funcione y en la siguiente entrada crearemos la lógica de movimientos. ¡Comencemos!
Importante: esta entrada va a basarse en los códigos creados en las anteriores entradas del ciclo al que pertenece. Tienes disponible pinchando aquí el proyecto de Eclipse con todo lo hecho hasta ahora, el cual puedes importar a tu Eclipse y comenzar a trabajar.
Lo primero que vamos a hacer es crear una nueva Activity que será el juego, y que se ejecutará en el momento en que pinchemos en el botón Jugar del menú mostrado arriba.
Vamos a comenzar añadiendo una nueva clase a nuestro proyecto, que será nuestra nueva actividad. Sin embargo esta es la primera vez que vamos a crear una actividad sin que sea el propio asistente de Eclipse el que cree el código por defecto, y es interesante darse cuenta de que no se sobrescribe la función onCreate() automáticamente.
Hacemos click derecho sobre el paquete com.vidasconcurrentes.pongvc y seleccionamos New > Class. En la ventana emergente que aparece vamos a donde pone Superclass y a la derecha pulsamos en Browse. Escribimos android.app.Activity y aceptamos. Escribimos el nombre para nuestra clase, por ejemplo PongJuego, y creamos la clase. Ahora pinchamos en Source > Override/Implement methods, y buscamos onCreate(Bundle). Ahora nuestra clase debería ser parecida a esta:
package com.vidasconcurrentes.pongvc;
import android.app.Activity;
import android.os.Bundle;
public class Test extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
}
Como vemos, no tiene un View que pintar, así que lo que vamos a hacer ahora es crearlo. Para hacerlo, vamos a seguir el siguiente tutorial que hice hace unas semanas. Pincha en el enlace, sigue los pasos y tendrás algo como esto:- PongJuego.java:
package com.vidasconcurrentes.pongvc; import com.vidasconcurrentes.pongvc.pintado.PongGameView; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class PongJuego extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(new PongGameView(this)); } } - PongGameView.java:
package com.vidasconcurrentes.pongvc.pintado; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.view.SurfaceHolder; import android.view.SurfaceView; public class PongGameView extends SurfaceView implements SurfaceHolder.Callback { private PongGameThread paintThread; public PongGameView(Context context) { super(context); getHolder().addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { paintThread = new PongGameThread(getHolder(), this); paintThread.setRunning(true); paintThread.start(); } @Override public void surfaceDestroyed(SurfaceHolder arg0) { boolean retry = true; paintThread.setRunning(false); while (retry) { try { paintThread.join(); retry = false; } catch (InterruptedException e) { } } } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); } } - PongGameThread.java:
package com.vidasconcurrentes.pongvc.pintado; import android.graphics.Canvas; import android.view.SurfaceHolder; public class PongGameThread extends Thread { private SurfaceHolder sh; private PongGameView view; private boolean run; public PongGameThread(SurfaceHolder sh, PongGameView view) { this.sh = sh; this.view = view; run = false; } public void setRunning(boolean run) { this.run = run; } public void run() { Canvas canvas; while(run) { canvas = null; try { canvas = sh.lockCanvas(null); synchronized(sh) { view.onDraw(canvas); } } finally { if(canvas != null) sh.unlockCanvasAndPost(canvas); } } } }
Nota: es importante darse cuenta de que estas dos clases están dentro de un nuevo paquete llamado com.vidasconcurrentes.pongvc.pintado. Es interesante mantener los códigos de nuestros proyectos ordenados, y los paquetes nos ayudan a agrupar aquellas clases que sirven para el mismo fin.
Ya tenemos todo lo necesario para pintar nuestra nueva Activity. Ahora sólo nos falta ejecutarla. Para ello vamos a usar un Intent. El Intent es una abstracción para definir operaciones asíncronas como ejecutar nuevas actividades y pasar información entre éstas, entre otras.
Una Activity creada de esta forma puede considerarse una sub-actividad si está previsto que devuelva algún dato a la actividad que la creó. Contamos con dos funciones para crear actividades:
- startActivity(): esta función crea una nueva Activity que no devolverá información al acabar. Una actividad creada de esta forma se considera una actividad aparte.
- startActivityForResult(): esta función crea una sub-actividad que devolverá ciertos valores al finalizar. Cuando lo haga, la actividad que la creó entrara en su función onActivityResult(), para procesar los datos necesarios.
Después de esta breve teoría, vamos a lo que nosotros necesitamos. Hemos de recordar que las actividades forman una pila, de modo que si ejecutamos el juego desde el menú y luego acabamos esa actividad (por ejemplo con la tecla apropiada en nuestro terminal), vamos a volver al menú. Por ahora no vamos a necesitar guardar el progreso del juego, por lo que no vamos a devolverle al menú información alguna. Ejecutemos entonces nuestra nueva Activity. Recordemos que nuestro menú tenía una parte del código así:
[...]
TextView play = (TextView)findViewById(R.id.play_button);
play.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), R.string.menu_play,
Toast.LENGTH_SHORT).show();
}
});
[...]
Pues ahora la modificamos para que sea así:[...]
TextView play = (TextView)findViewById(R.id.play_button);
play.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), R.string.menu_play,
Toast.LENGTH_SHORT).show();
empiezaJuego();
}
});
[...]
Donde la función empiezaJuego() es:private void empiezaJuego() {
Intent juego = new Intent(this, PongJuego.class);
this.startActivity(juego);
}
El constructor de Intent recibe qué clase crea la Activity y de qué clase es esta Activity nueva. Sólo nos resta añadir código a la función onCreate() de nuestra nueva Activity. Escribimos lo siguiente en ella:@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(new PongGameView(this));
}
Aún nos falta una cosa, y es añadir nuestra nueva actividad al AndroidManifest.xml. Si no lo añadimos no podremos ejecutar la actividad. Añadimos lo siguiente antes de </application>.<activity android:name=".PongJuego" android:label="@string/app_name" android:screenOrientation="landscape"> </activity>Ahora sí, podemos ejecutar nuestro proyecto. Click derecho sobre el proyecto, Run as > Android Application. Si todo está bien hecho, veremos el menú, y al pulsar sobre Jugar nos abrirá una nueva ventana con el fondo blanco. ¡Ya hemos creado nuestra actividad para pintar el juego!
Lo siguiente que vamos a hacer son los elementos que van a hacer falta en el juego. Haciendo un pequeño análisis previo (con poca vista al futuro), vamos a necesitar una bola y dos raquetas. Tanto las raquetas como la pelota tienen que moverse por la pantalla (y sin salirse), pero no necesitan más funcionalidad.
Es ahora el momento en el que diseñamos nuestro juego. Debemos recordar que los ordenadores (y en este caso nuestros dispositivos) son estúpidos, no saben nada. Un computador no sabe qué es una Raqueta, no sabe qué es una Bola, no sabe qué es una pantalla ni siquiera sabe qué es una actividad. Sin embargo sí que sabe qué es un número, qué es un carácter y qué es un booleano (sabe qué es cada tipo de dato primitivo).
Así que podemos pensar que una Raqueta es un rectángulo. ¿Qué es un rectángulo? Un rectángulo no es más que un punto origen, un ancho y un alto. ¿Y qué es un punto? Un punto va a ser un par de coordenadas en un plano (la pantalla).
Por tanto tenemos que una Raqueta es dos números que representan las coordenadas del origen, un número que representa el ancho y un número que representa el alto. ¿Y una Bola? Recordemos que en el Pong original, la bola era un cuadrado. Por tanto, y teniendo en cuenta que un Cuadrado es un Rectángulo con el ancho y alto iguales, podemos llegar a la conclusión de que ambas tienen en común su representación. Esto nos permite pensar en una superclase ElementoPong que tenga la funcionalidad necesaria para representar tanto Raquetas como Bolas.
Creamos un nuevo paquete llamado com.vidasconcurrentes.pongvc.juego. En él, creamos una nueva clase llamada Coordenada, y escribimos el siguiente código:
package com.vidasconcurrentes.pongvc.juego;
public class Coordenada {
private int x;
private int y;
public Coordenada(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
Añadimos los getters y setters para ambos atributos porque queremos poder consultar sus valores y cuando movamos los elementos, querremos modificarlos.Ahora creamos una nueva clase llamada ElementoPong, y escribimos el siguiente código:
package com.vidasconcurrentes.pongvc.juego;
import android.graphics.Rect;
public abstract class ElementoPong {
protected Coordenada origen;
protected int ancho;
protected int alto;
public ElementoPong(Coordenada origen, int ancho, int alto) {
this.origen = origen;
this.ancho = ancho;
this.alto = alto;
}
public int getOrigenX() {
return origen.getX();
}
public int getOrigenY() {
return origen.getY();
}
public int getAncho() {
return ancho;
}
public int getAlto() {
return alto;
}
public Rect getRectElemento() {
return new Rect(getOrigenX(), getOrigenY(),
getOrigenX()+ancho, getOrigenY()+alto);
}
}
Con esto estamos creando un elemento del juego. Con el modificador abstract en la declaración de la clase estamos diciendo que no se pueden instanciar objetos de esta clase, con lo que obligamos a crear clases que hereden de ésta (y por tanto hereden todos los atributos y funcionalidad ya descrita). La función getRectElemento() nos será útil a la hora de pintar los elementos, entre otras.Además querremos que nuestros elementos se muevan, pero cada uno se moverá de forma distinta. En este caso vamos a crear una interfaz que obligue a las clases que la implementen a incluir una función para moverse. El código es el siguiente:
package com.vidasconcurrentes.pongvc.juego;
public interface ElementoPongMovil {
public void move();
}
Ahora sólo resta crear nuestra clase Raqueta y nuestra clase Bola. El código para la clase Raqueta será:package com.vidasconcurrentes.pongvc.juego;
public class Raqueta extends ElementoPong
implements ElementoPongMovil {
public Raqueta(Coordenada origen, int ancho, int alto) {
super(origen, ancho, alto);
}
@Override
public void move() {
}
}
Y el código para la clase Bola será:package com.vidasconcurrentes.pongvc.juego;
public class Bola extends ElementoPong
implements ElementoPongMovil {
public Bola(Coordenada origen, int ancho, int alto) {
super(origen, ancho, alto);
}
@Override
public void move() {
}
}
Son iguales por ahora, ya que no hemos dado funcionalidad al método move() aún.Para acabar con esta primera parte en la creación de la Activity del juego, vamos a mostrar todo en la pantalla. El primer paso va a ser añadir dos raquetas y una bola a nuestra aplicación. Para ello, vamos a la clase PongGameView y le añadimos los tres nuevos atributos, de modo que sean:
private PongGameThread paintThread; private ElementoPong raquetaIzda; private ElementoPong raquetaDcha; private ElementoPong bola;Ahora instanciamos dichos atributos. Dependiendo de si nuestras instanciaciones van a necesitar de las dimensiones de nuestra superficie, crearemos nuestros objetos en el constructor de PongGameView si no lo necesitamos, o dentro del método surfaceCreated() si vamos a necesitarlo. En este caso vamos a necesitarlo, así que nuestro surfaceCreated() será así:
@Override
public void surfaceCreated(SurfaceHolder holder) {
raquetaIzda = new Raqueta(new Coordenada(50,getHeight()/2-50),
20,100);
raquetaDcha = new Raqueta(new Coordenada(getWidth()-70,
getHeight()/2-50),20,100);
bola = new Bola(new Coordenada(getWidth()/2-5,getHeight()/2-5),
10,10);
paintThread = new PongGameThread(getHolder(), this);
paintThread.setRunning(true);
paintThread.start();
}
Esto hace que tengamos las raquetas en los laterales a la misma distancia de su pared, y la bola centrada en pantalla. Las raquetas serán de 20x100 píxeles mientras que la bola será de 10x10 píxeles.Sólo nos resta añadir código al pintado para que muestre dichos elementos. Para ello modificamos el método onDraw() de la siguiente forma:
@Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
canvas.drawColor(Color.BLACK);
canvas.drawRect(raquetaIzda.getRectElemento(), paint);
canvas.drawRect(raquetaDcha.getRectElemento(), paint);
canvas.drawRect(bola.getRectElemento(), paint);
}
Et voilà! Podemos ejecutar nuestra aplicación y probar que lo que hemos hecho está bien. Si has seguido el tutorial paso a paso, tendrás lo siguiente al pulsar Jugar en el menú:En esta entrada hemos continuado el código de la entrada anterior en la que teníamos un menú, y hemos añadido la funcionalidad para que al pulsar en un determinado elemento del menú se cree nuestro juego. Además hemos creado las clases necesarias para representar nuestro juego y hemos pintado estos elementos en pantalla.
Puedes descargar el proyecto de Eclipse con todo lo hecho hasta ahora pulsando aquí. Además puedes importarlo a tu workbench directamente y trabajar con ello.
En la siguiente entrada vamos a crear la lógica del juego: movimientos de las raquetas, movimiento de la pelota, restricciones de dichos movimientos (que no se salga de la pantalla, que rebote la pelota...) y la creación del bucle de juego.
¡Un saludo a todos los lectores, y gracias por dedicarle tiempo!





comentarios