Академический Документы
Профессиональный Документы
Культура Документы
mejoras
Bienvenidos a la quinta parte de la serie de entradas en el ciclo Creando una aplicacin de
Android. Si an no lo has hecho, comienza desde la primera entrada mostrada en el men
inmediatamente superior.
En esta entrada vamos a continuar con el resultado de la entrada anterior, en la que
habamos acabado la lgica de movimientos y las colisiones. Aadiremos funcionalidades
durante esta entrada y la siguiente para acabar la aplicacin.
Para esta entrada aadiremos vibracin del dispositivo, reproduccin de sonidos y uso del
acelermetro para los movimientos de las raquetas.
Comenzamos!
Importante: esta entrada va a basarse en los cdigos creados en las
anteriores entradas del ciclo al que pertenece. Tienes disponible pinchando aqu (MD5:
31b264db2de4125fa01c52d4d169bc99) el proyecto de Eclipse con todo lo hecho hasta
ahora, el cual puedes importar a tu Eclipse y comenzar a trabajar.
El Android SDK incluye una gran cantidad de clases que hace muy sencillo el uso de los
sensores del dispositivo, la vibracin, la reproduccin de audio Para mostrar cmo de
sencillo es, comenzamos aadindole vibracin a nuestra aplicacin. Comenzamos
abriendo nuestro Eclipse.
Vibracin
Para que una aplicacin pueda vibrar, ya que necesita acceder a un elemento del sistema,
necesita que el usuario acepte el uso de este elemento. Para ello tenemos que aadir al
AndroidManifest.xml que queremos usar vibracin. Tenemos dos formas, la primera es
aadiendo <uses-sdk android:minSdkVersion=7 /> justo detrs de la etiqueta <manifest
>.
La segunda forma consiste en ir a la pestaa Permissions del manifest, pulsar en Add, elegir
Uses Permission y aceptar, y luego escribir android.permission.VIBRATE en el cuadro de
la derecha. Lo siguiente es programar el uso del vibrador.
El uso de la vibracin est definido por un Context, por lo que vamos a necesitar usar el
contexto de nuestra aplicacin para poder obtener el vibrador y posteriormente hacerlo
vibrar. La vibracin la vamos a realizar solamente cuando la Bola rebote en una de las dos
Raquetas. Si recordamos las entradas anteriores, nuestra Bola y nuestra Raqueta tenan una
funcin llamada puedoMover(), heredada de su superclase ElementoPong, que deca si se
poda mover este elemento sin salirse de la pantalla. Sabemos que cuando una bola rebota
es porque se ha chocado con algo. Si se ha chocado con algo pero an se puede mover por
la pantalla significa que, por eliminacin, se ha chocado con una raqueta. Ya tenemos
planteado lo que vamos a hacer, as que hagmoslo.
Primero vamos a aadir un nuevo atributo a la clase BolaMoveThread de la siguiente
forma:
1 private Vibrator v = null;
Este ser nuestro vibrador. Ahora necesitamos inicializarle, puesto que est a null y si lo
usramos ahora la aplicacin se morira debido a un NullPointerException (de ah que se
inicialice a null, para debuggearlo ms fcil). Para inicializarle necesitamos el Context de la
aplicacin, de modo que al constructor de BolaMoveThread le vamos a pasar el Context,
quedando as:
1 public BolaMoveThread(Bola bola, Raqueta izda, Raqueta dcha,
2
Rect screen, Context context) {
3
4
this.bola = bola;
this.raquetaIzda = izda;
5
6
this.raquetaDcha = dcha;
this.screen = screen;
7
8
this.run = false;
this.speed = 1;
this.v =
(Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
10 }
9
Faltara cambiar la forma en que se crea este Thread de la siguiente forma (en el mtodo
surfaceCreated() del PongGameView:
1 bolaThread = new BolaMoveThread((Bola)bola, (Raqueta)raquetaIzda,
2
(Raqueta)raquetaDcha, new Rect(0,0,getWidth(),getHeight()),
3
this.getContext());
Por ltimo slo queda hacer que vibre, para ello vamos a modificar el cdigo del run() del
BolaMoveThread de la siguiente forma:
1 @Override
2 public void run() {
3
4
5
6
7
8
while(run) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
11
12
13
14
}
bola.move(speed, speed);
15
16 }
Con esto estamos haciendo una vibracin breve (50 milisegundos) cada vez que rebotamos
contra una raqueta. Fcil, verdad?
Como informacin extra tenemos que saber que un objeto de la clase Vibrator tambin
puede vibrar dado un patrn, utilizando la funcin vibrate(pattern, repeat), siendo pattern
un long[] y repeat un int que dice cuntas veces se repite (-1 si no queremos repetir).
Sonido
El siguiente paso consistir en aadir sonido a los rebotes, el tpico sonido metlico.
Existen diversas formas de usar sonidos en Android, pero vamos a usar la ms simple de
todas: el MediaPlayer. Lo primero que tenemos que hacer es buscar un sonido que nos
valga. Por lo general se recomienda usar ficheros codificados en formato .ogg (Ogg Vorbis)
por ser el ms compatible y tener una gran compresin. Yo he elegido el siguiente sonido.
Si quieres usar el mismo, haz click derecho sobre el enlace y selecciona Guardar enlace
como (y gurdalo con un nombre distinto, en minsculas, sin espacios y sin tildes).
Ahora crearemos una nueva carpeta dentro de la carpeta res de nuestro proyecto, y la
llamaremos raw. Dentro de ella meteremos nuestro fichero de audio (en mi caso pong.ogg).
A partir de ahora podremos acceder a ello referenciando a R.raw.pong. Aadimos un nuevo
atributo a BolaMoveThread:
1 private MediaPlayer mp = null;
this.bola = bola;
this.raquetaIzda = izda;
5
6
this.raquetaDcha = dcha;
this.screen = screen;
7
8
this.run = false;
this.speed = 1;
this.v =
(Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
10
this.mp = MediaPlayer.create(context, R.raw.pong);
9
11 }
while(run) {
try {
5
6
Thread.sleep(10);
} catch (InterruptedException e) {
7
8
e.printStackTrace();
13
14
15
16
v.vibrate(50);
}
bola.move(speed, speed);
}
17 }
Activar y desactivar
Ahora mismo tenemos que la aplicacin hace que el dispositivo vibre al rebotar con una
pala, y hace que reproduzca un sonido de choque metlico cada vez que rebote con algo
(sea pared o raqueta). Recordemos que tenemos un men en el cual tenamos una parte de
Opciones, pero que no tena nada dentro. Es ahora el momento de crear las opciones del
juego, permitiendo activar y desactivar estas dos funcionalidades.
Comenzamos creando un nuevo layout que tendr el siguiente cdigo:
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow android:id="@+id/tableRow1"
android:layout_width="wrap_content"
6
android:layout_height="wrap_content">
5
<TextView
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
8
android:layout_height="wrap_content"
7
android:width="250dip" android:paddingLeft="30dip"
android:id="@+id/labelSonido"
10
android:text="@string/sonidoMenu"></TextView>
9
<CheckBox android:layout_width="wrap_content"
android:id="@+id/checkBoxSonido"
android:layout_height="wrap_content"
12
android:checked="true"></CheckBox>
11
13
</TableRow>
<TableRow android:id="@+id/tableRow2"
14
android:layout_width="wrap_content"
15
android:layout_height="wrap_content">
<TextView
16
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
18
android:paddingLeft="30dip" android:id="@+id/labelVibracion"
17
19
android:text="@string/vibracionMenu"></TextView>
<CheckBox android:layout_width="wrap_content"
20
android:id="@+id/checkBoxVibracion"
android:layout_height="wrap_content"
android:checked="true"></CheckBox>
22
</TableRow>
21
23 </TableLayout>
<string name="app_name">Pong</string>
<string name="menu_play">Jugar</string>
5
6
<string name="menu_options">Opciones</string>
<string name="menu_exit">Salir</string>
7
8
<string name="sonidoMenu">Sonido</string>
<string name="vibracionMenu">Vibracin</string>
9 </resources>
Tenemos que aadir la Activity al AndroidManifest.xml igual que hicimos en otra de las
entradas. Para ello abrimos el AndroidManifest.xml y aadimos lo siguiente antes de
</application>:
1
<activity android:name=".PongOpcionesActivity"
android:screenOrientation="portrait"></activity>
Lo siguiente que necesitamos hacer es aadir el cdigo necesario para crear una actividad
nueva que muestre este nuevo layout. Creamos una nueva clase llamada
PongOpcionesActivity, cuyo cdigo es el siguiente:
1 package com.vidasconcurrentes.pongvc;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.view.Window;
6 import android.view.WindowManager;
7
8 public class PongOpcionesActivity extends Activity {
9
10
@Override
11
12
1
3
1
4 N,
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREE
1
5 );
1
6
17
18 }
WindowManager.LayoutParams.FLAG_FULLSCREEN
setContentView(R.layout.options);
}
Ahora slo falta hacer el cdigo en la actividad principal para que se ejecute esta. Para ello
vamos a la clase PongvCActivity (la principal), y aadimos la funcin:
1 private void muestraOpciones() {
2
Intent opciones = new Intent(this, PongOpcionesActivity.class);
3
4}
this.startActivity(opciones);
Ahora en el onCreate() de esta clase, vamos al lugar donde registramos el listener para el
botn de Opciones y cambiamos el contenido por lo siguiente:
1 TextView options = (TextView)findViewById(R.id.options_button);
2 options.setOnClickListener(new OnClickListener() {
3
4
5
6
@Override
public void onClick(View v) {
muestraOpciones();
}
7 });
Ahora s, si hacemos una ejecucin, obtendremos esto tras pulsar en el botn Opciones del
men principal:
8
9
10
private PongOpciones() {
sonido = true;
11
12
vibracion = true;
13
14
15
16
if(opciones == null)
opciones = new PongOpciones();
17
18
return opciones;
}
19
20
21
22
sonido = !sonido;
23
24
25
26
vibracion = !vibracion;
}
27
28
29
30
return sonido;
}
31
32
33
34
return vibracion;
35 }
Las ponemos inicializadas a true porque, por defecto, nuestra aplicacin va a tener ambas
activadas. Ahora aadimos el comportamiento al pulsar sobre los checkboxes. Para ello
vamos al onCreate() de la clase PongOpcionesActivity y aadimos lo siguiente al final:
1 CheckBox sonido = (CheckBox) findViewById(R.id.checkBoxSonido);
2 sonido.setOnClickListener(new OnClickListener() {
3
4
@Override
public void onClick(View v) {
5
6
7 });
8
PongOpciones.getInstance().toggleSound();
@Override
public void onClick(View v) {
13
14
PongOpciones.getInstance().toggleVibration();
}
15 });
16
17 sonido.setChecked(PongOpciones.getInstance().soundEnabled());
18 vibracion.setChecked(PongOpciones.getInstance().vibrationEnabled());
Como vemos, lo ltimo que hacemos es poner el estado de cada CheckBox acorde con el
estado de la instancia del patrn Singleton. Cuando pulsamos en un CheckBox, cambiamos
el valor de la variable a la que se refiere de true a false y viceversa.
Efectivamente, si ejecutsemos ahora, comprobaramos que podemos tener ambas activas,
desactivadas o una activa y otra no.
Acelermetro
El acelermetro del dispositivo va a permitirnos registrar movimientos de ste para saber si
se ha cambiado la inclinacin con respecto de la posicin inicial con la que se comenz el
juego. Existen varias formas de usar el acelermetro, y por supuesto existen muchsimas e
infinitas formas de programar dnde debe ir cada cosa en nuestro proyecto.
Mis decisiones han sido las siguientes:
Al igual que hicimos al crear los hilos anteriores (pintado y movimiento de la bola), vamos
a crear un hilo nuevo para las raquetas:
1 package com.vidasconcurrentes.pongvc.juego;
2
3 import android.graphics.Rect;
4
5 public class RaquetaMoveThread extends Thread {
6
7
8
9
10
11
12
13
14
15
raqueta = r;
screen = s;
}
16
17
18
19
20
21
22
23
24
try {
Thread.sleep(10);
25
26
} catch (InterruptedException e) {
e.printStackTrace();
27
28
}
// nuestro codigo va aqui
29
30
}
}
31 }
paintThread.setRunning(false);
bolaThread.setRunning(false);
13
14
raquetaThread.setRunning(false);
while (retry) {
15
16
17
try {
paintThread.join();
bolaThread.join();
18
raquetaThread.join();
19
20
21
22 }
retry = false;
} catch (InterruptedException e) { }
}
Ahora es el momento de crear nuestro acelermetro. Para ello vamos a crear una clase
envolvente para la funcionalidad que queremos que nos ofrezca. Un dispositivo emulado no
puede usar acelermetro, as que ser necesario el uso de un dispositivo fsico.
Nosotros vamos a querer que se mueva la raqueta. Esta raqueta pertenece a una actividad
que est en modo landscape (apaisado) continuamente. Un dispositivo fsico tiene su eje X
a lo alto, es decir, es una lnea imaginaria que va desde los botones del dispositivo hasta el
auricular. Si lo prefers: de abajo a arriba. Su eje Y es el perpendicular a ste que cruza de
lado a lado. Como hasta ahora hemos visto a la hora de pintar, con el dispositivo en modo
apaisado, el eje X ser el ancho y el eje Y ser el alto. De modo que si nosotros rotamos el
dispositivo hacia delante en modo apaisado, queremos que nuestra pala suba. Si rotamos el
dispositivo hacia nosotros en modo apaisado, queremos que nuestra pala baje.
Dicho en otras palabras: si la rotacin del eje X es negativa, queremos que vaya arriba; si
es positiva, queremos que vaya abajo. Para ms informacin sobre los ejes de coordenadas
tridimensionales pulsa aqu.
Por tanto slo nos interesa la rotacin del eje X para nuestra pala, pero podemos acceder a
los valores de Y y Z consultando event.values[1] y event.values[2] respectivamente.
Creamos una nueva clase AcelerometroPong en el paquete
com.vidasconcurrentes.pongvc.juego:
1 package com.vidasconcurrentes.pongvc.juego;
2
3 import android.content.Context;
4 import android.hardware.Sensor;
5 import android.hardware.SensorEvent;
6 import android.hardware.SensorEventListener;
7 import android.hardware.SensorManager;
8
9 public class AcelerometroPong implements SensorEventListener {
10
11
12
13
14
sm = (SensorManager)
context.getSystemService(Context.SENSOR_SERVICE);
16
}
15
17
18
sm.registerListener(this,
sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
20
SensorManager.SENSOR_DELAY_GAME);
19
21
22
23
24
25
26
27
28
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { }
29
30
@Override
31
32
33
34
35
x = Math.round(event.values[0] * 100);
}
}
36
37
38
39
40 }
Desde la Activity PongJuego vamos a usar esta clase como atributo, y aadiremos dos
nuevos mtodos de modo que quedar as:
1 package com.vidasconcurrentes.pongvc;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.view.Window;
6 import android.view.WindowManager;
7
8 import com.vidasconcurrentes.pongvc.juego.AcelerometroPong;
9 import com.vidasconcurrentes.pongvc.pintado.PongGameView;
10
11 public class PongJuego extends Activity {
12
13
14
15
16
@Override
protected void onCreate(Bundle savedInstanceState) {
17
18
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
1
9 N,
2
0 );
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREE
WindowManager.LayoutParams.FLAG_FULLSCREEN
acelerometro = new
AcelerometroPong(this.getApplicationContext());
22
setContentView(new PongGameView(this, acelerometro));
21
23
24
25
26
@Override
protected void onResume() {
27
28
29
super.onResume();
acelerometro.register();
}
30
31
32
@Override
protected void onStop() {
33
34
35
36 }
super.onStop();
acelerometro.unregister();
}
super(context);
getHolder().addCallback(this);
7
8
this.acelerometro = acelerometro;
9}
Ahora, queremos que sea el Thread que controla el movimiento de la raqueta el encargado
de consultar el acelermetro, as que igualmente cambiamos el constructor de ste y lo
aadimos como atributo a RaquetaMoveThread:
screen = s;
this.acelerometro = a;
7}
Adems, aadimos esta nueva variable xInit de tipo Integer (para poder inicializarla a null).
Y por qu queremos inicializarla a null? Es tan slo una pequea treta que se me ha
ocurrido para poder realizar una calibracin en cada ejecucin. Si es null es que an no
hemos calibrado la posicin en la que est el dispositivo al arrancar el Thread, si no es null
es que ya est calibrado.
Ahora modificaremos el run() de este Thread, de la siguiente forma:
1 public void run() {
2
if(xInit == null)
3
xInit = acelerometro.getXInclination();
4
5
6
while(run) {
try {
7
8
9
10
else
Thread.sleep(2);
11
12
} catch (InterruptedException e) {
e.printStackTrace();
13
14
}
if(xInit < acelerometro.getXInclination() - UMBRAL ||
15
16
17
18
raqueta.move(0, 1);
if(xInit > acelerometro.getXInclination() - UMBRAL ||
19
20
21
22
raqueta.move(0, -1);
}
23 }
absoluta (positiva o negativa) de menos de 200 unidades, entonces nos vamos a mover a
una velocidad normal. Si esto es mayor significa que hemos inclinado el dispositivo mucho
ms, por lo que deseamos que se mueva ms rpido. La variable UMBRAL no es ms que
una variable final de la clase que en este caso tiene el valor de 20. Este UMBRAL nos sirve
para no mover la raqueta si el movimiento es demasiado pequeo (quiz por errores de
medida o de redondeo). Adems aadimos la comprobacin de si puedoMover() para evitar
salirse de la pantalla.
Con esto ya tenemos la raqueta izquierda funcionando para moverse con el acelermetro,
pero a la vez funciona con el dedo y esto no es lo deseable.
sonido = true;
vibracion = true;
acelerometro = false;
Aadimos tambin los siguientes dos mtodos, para cambiar el valor y consultarlo:
1 public void toggleAcelerometro() {
2
acelerometro = !acelerometro;
3}
4
5 public boolean accelerometerEnabled() {
6
return acelerometro;
7}
Ahora tenemos que llamar a estos mtodos desde la actividad de las opciones, la cual
necesita ser cambiada para aadir una nueva fila. Por tanto, antes del </TableLayout> de
options.xml, aadimos:
<TableRow android:id="@+id/tableRow3"
1 android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
2 android:layout_height="wrap_content" android:id="@+id/labelAccel"
android:text="@string/accel"
android:layout_marginLeft="30dip"></TextView>
<CheckBox android:layout_width="wrap_content"
3 android:layout_height="wrap_content"
android:id="@+id/checkBoxAccel"></CheckBox>
4 </TableRow>
Hay que aadir tambin el String al fichero strings.xml. De esta forma veramos la siguiente
imagen al ejecutar:
@Override
public void onClick(View v) {
5
6
PongOpciones.getInstance().toggleAcelerometro();
7 });
8
9
acelerometro.setChecked(PongOpciones.getInstance().accelerometerEnabled
());
Es el momento ahora de hacer que la aplicacin use una u otra. Todo el cdigo se va a
poner por tanto en PongGameView. El Thread de la raqueta slo se ejecuta cuando est
activado el acelermetro, por tanto modificamos el cdigo de surfaceCreated() y
cambiamos:
1 if(PongOpciones.getInstance().accelerometerEnabled()) {
2
raquetaThread = new RaquetaMoveThread((Raqueta)raquetaIzda,
3
4
5
6}
raquetaThread.start();
5
6
bolaThread.setRunning(false);
if(PongOpciones.getInstance().accelerometerEnabled())
7
8
raquetaThread.setRunning(false);
while (retry) {
9
10
try {
paintThread.join();
11
12
bolaThread.join();
if(PongOpciones.getInstance().accelerometerEnabled())
13
14
raquetaThread.join();
retry = false;
15
16
} catch (InterruptedException e) { }
}
17 }
Hecho esto podemos ejecutar la aplicacin y trastear con ella activando y desactivando
cosas, reiniciando el juego La idea es iniciar el juego, pulsar la tecla de actividad
anterior que nos lleva al men, elegir Opciones y cambiarlas, dar a actividad anterior y
luego a Jugar.
Aqu llega el final de la entrada de hoy. En ella hemos visto cmo usar distintos sensores
del sistema como la vibracin o el acelermetro, adems de las herramientas que nos ofrece
para reproducir sonidos locales. En la siguiente entrada acabaremos la aplicacin aadiendo
las ltimas mejoras como un pequeo umbral para el juego tctil, el marcador de juego y la
posibilidad de que se cuele la bola y por ltimo una pequea Inteligencia Artificial para la
raqueta derecha.