Android Colision En este tutorial aprenderemos a manejar

Transcripción

Android Colision En este tutorial aprenderemos a manejar
Android Colision En este tutorial aprenderemos a manejar colisiones entre objetos, implementar listas encadenadas para manejar colecciones de objetos y también implementaremos la clase MediaPlayer la cual nos será de utilidad para reproducir vía streaming archivos de audio. La aplicación mostrará en pantalla un elefante que se desplaza a través de ella y cuya dirección de desplazamiento podemos modificar al tocar la pantalla del dispositivo. Se muestran también tres ratones que persiguen al elefante y al colisionar con el lo envían de vuelta a su posición inicial. Para este tutorial tomamos como base el tutorial anterior por lo cual puedes crear un proyecto nuevo o seguir trabajando sobre el proyecto creado en el tutorial anterior. Si vas a crear un proyecto nuevo crea uno para la plataforma 2.2 y nombra a la Actividad principal TutorialElefanteColision. La única clase que permanece exactamente igual que en los tutoriales anteriores es la clase Animacion por lo cual puedes copiarla tal cual para agregarla al nuevo proyecto. Manifiesto Esta aplicación no ocupará algún requerimiento adicional a los que hemos utilizado en los tutoriales anteriores, por lo cual el manifiesto debe quedar exactamente igual que los anteriores con la única excepción del nombre de la Actividad en caso de que hayas creado una nueva. <?xml version="1.0" encoding="utf-­‐8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jcj.testElefante" android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal"> <application android:label="@string/app_name" android:icon="@drawable/icon" android:debuggable="true"> <activity android:name="TutorialElefanteColision" android:label="@string/app_name" android:configChanges="keyboard| keyboardHidden|orientation" android:screenOrientation="portrait"> <intent-­‐filter> <action android:name= "android.intent.action.MAIN" /> <category android:name= "android.intent.category.LAUNCHER" /> </intent-­‐filter> </activity> </application> <uses-­‐permission android:name="android.permission.WAKE_LOCK"/> <uses-­‐permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-­‐sdk android:minSdkVersion="3" android:targetSdkVersion="8"/> </manifest> En la descripción de este tutorial se menciona que la aplicación desplegará el elefante con el cual hemos estado trabajando, sin embargo se menciona también que se mostrarán tres ratones que perseguirán al elefante. Esto quiere decir que adicionalmente a nuestra clase Elefante crearemos una clase adicional para los ratones, la clase Raton. Ambas clases tendrán características similares: dirección, posición en ‘x’ y ‘y’, una animación y un rectángulo el cual nos ayudará a determinar si los objetos colisionan entre sí. Dado a que ambas clases compartirán las mismas características, crearemos una clase adicional, la clase Animal, de la cual las clases Elefante y Raton heredarán sus atributos. Dentro del paquete donde se encuentra la Actividad agregamos tres nuevas clases de Java, una para la clase Animal , otra para la clase Elefante y una última para la clase Raton. Clase Animal La clase Animal incluye cinco miembros para el manejo de la posición en los ejes ‘x’ y ‘y’, la dirección del movimiento, la animación del animal y un rectángulo para detectar colisiones. Adicionalmente la clase cuenta con cuatro constantes que nos ayudan a definir la dirección de movimiento de los objetos. Iniciamos importando las clases requeridas por la clase Animal y definiendo a la misma y a los miembros y constantes que la componen. import android.graphics.Rect; public class Animal { //Dirección del Animal, Izquierda public static final int LEFT = 1; //Dirección del Animal, Derecha public static final int RIGHT = 2; //Dirección del Animal, Arriba public static final int UP = 3; //Dirección del Animal, Abajo public static final int DOWN = 4; //Posición del Animal public int x, y; //Dirección actual del Animal public int dir; //Rectángulo que contiene al Animal public Rect rectangulo; //Animación del Animal public Animacion animacion; Las constantes LEFT, RIGHT, UP y DOWN ayudan a determinar la dirección de movimiento del objeto Animal. Los miembros x y y guardan la posición del objeto Animal mientras que dir guarda la dirección en la cual se mueve el objeto Animal. El miembro rectangulo de la clase Rect define un rectángulo que contiene al objeto. La clase Rect contiene el método intersect() el cual nos ayuda con el manejo de colisiones entre dos objetos ya que detecta cuando dos objetos de la clase se intersectan. El miembro animacion de la clase Animacion guarda la animación del objeto. La clase Animal esta compuesta por dos métodos, el método constructor de la clase y el método getRectangulo(). En el método constructor de la clase se reciben como parámetros los valores para la posición en ‘x’ y ‘y’ y la animación del objeto y se asignan a los miembros correspondientes. El método getRectangulo() regresa un objeto de la clase Rect el cual define el rectángulo que contiene al objeto en la posición actual. /** * Método constructor de la clase Animal * * @param x es la posición en X del Animal * @param y es la posición en Y del Animal * @param animacion es la animación del Animal */ public Animal(int x, int y, Animacion animacion) { this.x = x; this.y = y; this.animacion = animacion; } /** * Método getRectangulo * * Retorna el rectángulo que contiene al objeto Animal * en la posición actual * * @return Regresa un objeto Rect con el rectángulo del Animal */ public Rect getRectangulo() { rectangulo = new Rect(x, y, x + animacion.getCuadro().getWidth(), y + animacion.getCuadro().getHeight()); return rectangulo; } } Clase Elefante y Clase Raton Las clases Elefante y Raton heredarán sus atributos de la clase Animal así que ambas únicamente se componen por un método constructor que manda a llamar al constructor de la clase Animal. Adicionalmente, dentro del método constructor, la clase Elefante define un valor por defecto para la dirección del objeto. Clase Elefante public class Elefante extends Animal { /** * Método constructor de la clase Elefante * * Sobrescribe el método constructor de la clase Animal y asigna * el valor RIGHT por defecto a la dirección del objeto * * @param x es la posición en X del Elefante * @param y es la posición en Y del Elefante * @param animacion es la animación del Elefante */ public Elefante(int x, int y, Animacion animacion) { super(x, y, animacion); dir = RIGHT; } } Clase Raton public class Raton extends Animal { /** * Método constructor de la clase Raton * * Sobrescribe el método constructor de la clase Animal * * @param x es la posición en X del Raton * @param y es la posición en Y del Raton * @param animacion es la animación del Raton */ public Raton(int x, int y, Animacion animacion) { super(x, y, animacion); } } Actividad TutorialElefanteColision Uno de los principales puntos a destacar para este tutorial es la implementación de la clase MediaPlayer que utilizaremos para la reproducción del sonido de fondo de la aplicación vía streaming. Otros puntos son el uso de listas encadenadas, que en este caso en particular utilizaremos para manejar los objetos de la clase Raton, y la implementación de la clase Rect para el manejo de colisiones entre objetos. Volviendo a la clase MediaPlayer, anteriormente en el tutorial Animación en Android empezamos a trabajar con la reproducción de archivos de sonido cortos que podemos cargar sin problema a la memoria mediante la implementación de clase SoundPool, sin embargo, debido a que buscamos optimizar el uso de la memoria necesitamos implementar otros recursos para reproducir archivos de sonido más largos sin saturar la memoria de provocando que la aplicación se vuelva lenta en ejecución. Aquí es donde entra la clase MediaPlayer que es una herramienta que nos permite reproducir los archivos de audio mediante un stream. Iniciamos nuestra Actividad importando las clases que estaremos implementando así como también por la definición de la misma y de sus miembros. import android.app.Activity; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.SoundPool; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnTouchListener; import android.view.Window; import android.view.WindowManager; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; public class TutorialElefanteColision extends Activity implements OnTouchListener { //Vista de la Actividad RenderView renderView; //Candado para evitar que el dispositivo duerma WakeLock wakeLock; //Objeto Elefante Elefante elefante; //Lista encadenada de objetos Raton LinkedList<Raton> ratones; //Animación del elefante Animacion elefanteAnim; //Animación de los ratones Animacion ratonAnim; //Colección de sonidos SoundPool pool; //Reproductor de multimedia MediaPlayer mediaPlayer; //Identificador de sonido del elefante int elefanteId = -­‐1; //Identificador de sonido de los ratones int ratonId = -­‐1; //Identificador de sonido de explosión int explisionId = -­‐1; //Posición ‘x’ y ‘y’ de los ratones int xR, yR; //Objetos Bitmap para el manejo de imágenes Bitmap cuadro, frameBuffer; //Escala en ‘x’ y ‘y’ float scaleX, scaleY; El miembro renderView de la clase RenderView es la vista con la cual estaremos trabajando en la Actividad. El miembro wakeLock de la clase WakeLock nos ayudará a configurar el dispositivo para evitar que este entre en estado de reposo después de cierto tiempo. Los miembros elefante y ratones se integran con los miembros elefanteAnim y ratonAnim respectivamente para crear el elefante y los ratones que se mostrarán en la pantalla. El miembro elefante es un objeto de la clase Elefante mientras que ratones es una lista encadenada de objetos Raton. El miembro pool de la clase SoundPool es una colección de sonidos donde incluiremos los efectos de sonido que incluiremos en la aplicación. Utilizamos el miembro mediaPlayer de la clase MediaPlayer para reproducir el sonido de fondo que se escuchará mientras la aplicación esta en ejecución. Los miembros elefanteId, ratonId y explosionId nos ayudan a identificar cada uno de los efectos de sonido para reproducirlos. Los miembros enteros de xR y yR serán utilizados para determinar la posición de los ratones en la pantalla. Los dos miembros de la clase Bitmap, cuadro y frameBuffer nos ayudarán con el manejo de los gráficos en la aplicación; cuadro nos servirá de apoyo para cargar las imágenes de los cuadros de la animación del elefante mientras que frameBuffer es un buffer virtual donde dibujamos los elementos de la aplicación para posteriormente proyectarlos a la pantalla. Finalmente Los miembros scaleX y scaleY nos ayudarán a calcular la escala que se aplique a cada uno de los ejes. /** * Método onCreate sobrescrito de la clase Activity * * Llamado cuando la Actividad se crea por primera vez, en * este se realiza la configuración de la actividad * * @param savedInstanceState es el estado de la última ejecución de la * Actividad */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Configuración para pantalla en fullscreen requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //Ancho del buffer int frameBufferWidth = 320; //Alto del buffer int frameBufferHeight = 480; //Crea el buffer frameBuffer = Bitmap.createBitmap(frameBufferWidth, frameBufferHeight, Config.RGB_565); //Escala en x basada en el ancho del buffer y el ancho de la //pantalla del dispositivo scaleX = (float) frameBufferWidth / getWindowManager().getDefaultDisplay().getWidth(); //Escala en y basada en el alto del buffer y el alto de la pantalla del //dispositivo. scaleY = (float) frameBufferHeight / getWindowManager(). getDefaultDisplay().getHeight(); //Evita que el dispositivo se duerma PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock( PowerManager.FULL_WAKE_LOCK, “Test”); //Establece el control del volumen del sonido setVolumeControlStream(AudioManager.STREAM_MUSIC); //Crea una colección de sonidos para reproducir pool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0); //Crea un nuevo reproductor de multimedia para reproducir //audio vía streaming mediaPlayer = new MediaPlayer(); //Crea un nuevo objeto Animacion para la animación del elefante elefanteAnim = new Animacion(); //Crea un nuevo objeto Animacion para la animación de los //ratones ratonAnim = new Animacion(); try { //Crea un administrador de contenidos para cargar los //contenidos AssetManager assetManager = getAssets(); //Descriptor para cargar los archivos de sonido AssetFileDescriptor descriptor; //Carga el archivo de sonido del elefante descriptor = assetManager.openFd( “Sounds/Fx/elefante.ogg”); //Agrega el efecto de sonido del elefante a la colección de //sonidos elefanteId = pool.load(descriptor, 1); //Carga el archivo de sonido del ratón descriptor = assetManager.openFd( “Sounds/Fx/raton.ogg”); //Agrega el efecto de sonido del ratón a la colección de //sonidos ratonId = pool.load(descriptor, 1); //Carga el archivo de sonido de la explosión descriptor = assetManager.openFd( “Sounds/Fx/explosion.ogg”); //Agrega el efecto de sonido de la explosión a la colección //de sonidos explosionId = pool.load(descriptor, 1); //Carga el audio para la música de fondo descriptor = assetManager.openFd( “Sounds/Tracks/happy.ogg”); //Asigna el archivo de audio al reproductor de multimedia mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength()); //Verifica que el archivo de sonido pueda ser reproducido mediaPlayer.prepare(); //Se cicla la reproducción del archivo de sonido mediaPlayer.setLooping(true); InputStream is; //Carga las imágenes de la animación del elefante for (int i = 0; i < 6; i++) { //Carga la imagen is = assetManager.open(“Images/Elefante/” + i + “.png”); cuadro = BitmapFactory.decodeStream(is); is.close(); //Agrega la imagen a la animación } //Carga las imágenes de la animación de los ratones for (int i = 0; i < 12; i++) { //Carga la imagen is = assetManager.open(“Images/Raton/” + i + “.png”); cuadro = BitmapFactory.decodeStream(is); is.close(); elefanteAnim.sumaCuadro(cuadro, 0.1f); //Agrega la imagen a la animación ratonAnim.sumaCuadro(cuadro, 0.1f); } } catch (IOException e) { // } //Crea un nuevo objeto Elefante elefante = new Elefante(frameBufferWidth / 2 – elefanteAnim.getCuadro().getWidth() / 2, frameBufferHeight / 2 – elefanteAnim.getCuadro().getHeight() / 2, elefanteAnim); //Crea la lista de objetos Raton ratones = new LinkedList<Raton>(); //Crea tres objetos Raton for (int i = 0; i < 3; i++) { //Asigna una posición al azar para el objeto Raton xR = (int) (Math.random() * frameBufferWidth) – ratonAnim.getCuadro().getWidth(); yR = (int) (Math.random() * frameBufferHeight) – ratonAnim.getCuadro().getHeight(); //Añade el objeto Raton a la lista ratones.add(new Raton(xR, yR, ratonAnim)); } //Crea y establece la vista de la Actividad y establece un //OnTouchListener para esta renderView = new RenderView(this); renderView.setOnTouchListener(this); setContentView(renderView); } El método onCreate inicia realizando la configuración de la pantalla para visualizarla en fullscreen, determina también las dimensiones del buffer que utilizaremos para desplegar los elementos de la aplicación y calcula la escala en x y en y en base a las dimensiones de la pantalla del dispositivo y las dimensiones del buffer. Así mismo, configuramos la Actividad para que el dispositivo no entre en reposo y controle el volumen del sonido. Se crean una colección de sonidos, una reproductor de multimedia (pool y mediaPlayer) para cargar los efectos de sonido y las música de fondo respectivamente. Se crean también dos objetos de la clase Animacion para las animaciones del elefante y el ratón. Cargamos todos los recursos (audio y gráficos) y posteriormente se crean el elefante y los ratones de la aplicación, Por último, se crea la vista de la Actividad y se le asigna un OnTouchListener para manejar los eventos de toque y se establece como vista de la Actividad. Retomando el tema del MediaPlayer, cargamos los archivos de audio a través del método setDataSource() al cual le enviamos tres valores como parámetros; el primero de ellos es el descriptor del archivo el cual obtenemos de la misma manera que obtenemos el descriptor para los sonidos, el segundo valor indica el punto donde inicia la pista y este lo podemos obtener con el método getStartOffset() de la clase AssetFileDescriptor, el tercero y último valor indica la longitud de la pista la cual podemos obtener con la ayuda del método getLength() de la misma clase. Ahora bien, una vez que hemos cargado el archivo de audio es necesario llamar al método prepare() de la clase MediaPlayer el cual se encarga de verificar si la el archivo esta listo para ser reproducido. Ya que hemos llamado al método prepare() entonces el archivo puede ser reproducido, esto lo logramos con el método play(). Para controlar la reproducción del archivo se cuenta también con los métodos pause() y stop() los cuales, como su nombre lo indica, ayudan respectivamente a pausar y detener el audio. Si se desea reproducir de nuevo el audio tras haber llamado al método pause() se puede tranquilamente llamar de nueva cuenta al método play() para continuar con la reproducción, sin embargo si se desea hacer esto mismo tras haber llamado al método stop() es necesario llamar en primera instancia al método prepare() para posteriormente poder llamar al método play() y reproducir el audio sin problema alguno. Otros dos métodos que nos serán de utilidad de esta misma clase son los métodos release() y setLooping(). El método release() se encarga de liberar de memoria todos los recursos que eran ocupados por el audio mientras que con el método setLooping() podemos ciclar la reproducción del archivo de tal manera que se reproduzca continuamente sin parar hasta que se lo indiquemos. Como podemos observar, la colección de objetos Raton, ratones, que creamos esta definida como una lista encadenada. Para crear una nueva lista encadenada utilizamos la siguiente instrucción: lista = new LinkedList<Objeto>(); , donde Objeto es la clase de los objetos que contendrá la lista. Para añadir elementos a una lista encadenada tenemos el método add() al cual le enviamos como parámetro el elemento que se desea agregar. Por otro lado, para accesar a algún elemento de la lista contamos con el método get() al cual le enviamos como parámetro el índice de la posición del elemento que deseamos accesar, de manera similar a como lo haríamos con un arreglo. Continuando con nuestra Actividad, ahora definimos los métodos onPause() y onResume() los cuales nos ayudarán a sincronizar el thread de la Actividad con el thread de la vista que estamos manejando de tal manera que ambos trabajen a la par para evitar algún problema. /** * Método onPause sobrescrito de la clase Activity * * Llamado cuando la Actividad pasa a segundo plano pero aun no es * visible, libera recursos usados por la Actividad */ @Override protected void onPause() { super.onPause(); //Pausa la vista de la Actividad renderView.pause(); //Libera el candado que protege la pantalla wakeLock.release(); //Se liberan los efectos de sonido que actualmente se encuentran //en la colección pool.release(); //Pausa el reproductor de multimedia if (mediaPlayer != null) { mediaPlayer.pause(); //Si la aplicación será destruida detiene el reproductor y //libera la memoria if (isFinishing()) { mediaPlayer.stop(); mediaPlayer.release(); } } } /** * Método onResume sobrescrito de la clase Activity * * Llamado cuando la Actividad vuelve a primer plano */ @Override protected void onResume() { super.onResume(); //Reanuda la vista de la Actividad renderView.resume(); //Retoma el candado que protege a la pantalla wakeLock.acquire(); //Inicia la reproducción del reproductor multimedia if (mediaPlayer != null) { mediaPlayer.start(); } } En el método onPause() pausamos el thread de la vista llamando a su método pause(). Liberamos el wake lock para que el sistema o la actividad que se encuentre actualmente en primer plano pueda encargarse de manejarlo. También liberamos los recursos de la colección de sonidos para liberar memoria a través del método release() de la clase SoundPool. Verificamos si el reproductor de multimedia contiene algo para pausarlo y si es necesario detener la reproducción. En el método onResume() simplemente reanudamos el thread de la vista llamando a su método resume() y retomamos el wake lock para evitar que el dispositivo entre en estado de reposo. Reiniciamos también la reproducción del reproductor de multimedia. En seguida sobrescribimos el método onTouch() de la interfase OnTouchListener. Este método nos ayudará en el manejo de los eventos de toque de tal manera que podamos actualizar el la aplicación en base a las acciones del usuario. El método onTouch() recibe como parámetros la vista a la cual esta asignado el OnTouchListener y un evento de la clase MotionEvent el cual contiene la información del toque. Para un evento de este tipo existen cuatro diferentes tipos básicos de acciones a las cuales puede corresponder: ACTION_DOWN, ACTION_MOVE, ACTION_CANCEL y ACTION_UP. Un evento del tipo ACTION_DOWN corresponde a la acción del usuario de presionar un punto en la pantalla con el dedo. Un evento del tipo ACTION_MOVE corresponde a la acción del usuario de arrastrar el dedo sobre la pantalla. Un evento del tipo ACTION_CANCEL corresponde a la acción del usuario de interrumpir un gesto que este realizando sobre la pantalla del dispositivo. Finalmente, un evento del tipo ACTION_UP corresponde a la acción del usuario de despegar el dedo de la pantalla del dispositivo. /** * Método onTouch sobrescrito de la interfase OnTouchListener * * Maneja los eventos de contacto en la Actividad * * @param v es la vista que contiene el OnTouchListener * @param event son los eventos de contacto atrapados por el * OnTouchListener * @return regresa verdadero cuando es llamado */ @Override public boolean onTouch(View v, MotionEvent event) { //Maneja los eventos de contacto switch (event.getAction()) { //Cuando se hace contacto con el dedo case MotionEvent.ACTION_DOWN: // break; //Cuando se mueble dedo haciendo contacto case MotionEvent.ACTION_MOVE: // break; case MotionEvent.ACTION_CANCEL: // break; //Cuando se deja de hacer contacto case MotionEvent.ACTION_UP: //Cambia la dirección de movimiento del objeto //Elefante dependiendo del punto de contacto if ((event.getX() * scaleX) < elefante.x) elefante.dir = Elefante.LEFT; else if ((event.getX() * scaleX) > elefante..x + elefante.animacion.getCuadro().getWidth()) elefante.dir = Elefante.RIGHT; else if ((event.getY() * scaleY) < elefante.y) elefante.dir = Elefante.UP; else if ((event.getY() * scaleY) > elefante.y + elefante.animacion.getCuadro().getHeight()) elefante.dir = Elefante.DOWN; break; } return true; } Cuando el usuario hace un toque sobre la pantalla cambia la dirección de movimiento del elefante dependiendo de la región donde el usuario hizo el toque con respecto a la posición del elefante. Ahora definimos la clase interna RenderView en la Actividad. La clase RenderView extiende a la clase SurfaceView e implementa a la interfase Runnable lo cual permite que esta clase corra en su propio thread. Empezamos esta clase por definir sus miembros. /** * Clase RenderView * * Clase interna de la clase TutorialElefante * Maneja una vista y su ciclo de vida en un thread independiente */ public class RenderView extends SurfaceView implements Runnable { //Thread de la vista Thread renderThread = null; //Contenedor de la vista SurfaceHolder holder; //Canvas para dibujar Canvas canvas; //Bandera para conocer el estado de la Actividad volatile boolean running = false; //Controladores de tiempo float tiempoTick = 0, tick = 0.1f; El renderThread es el thread mismo de la vista el cual tendremos que sincronizar con el thread de la Actividad. El miembro holder de la clase SurfaceHolder es una superficie que contiene a la vista con la cual estamos trabjando. Con la ayuda del miembro canvas podremos dibujar sobre nuestro buffer virtual para posteriormente proyectarlo en la pantalla. El miembro running nos indicará si el thread esta actualmente activo lo cual nos ayuda para la sincronización entre el thread de la vista y el thread de la Actividad. Los miembros tiempoTick y tick nos indican el tiempo transcurrido y el tiempo que debe durar cada ciclo de actualización respectivamente. /** * Método constructor de la clase RenderView * * Llama al método constructor de la clase base al cual * le pasa el contexto como parámetro y crea los objetos * requeridos por la clase * * @param context es el contexto desde el cual fue llamado el * método */ public RenderView(Context context) { super(context); //Obtiene el contenedor holder = getHolder(); //Crea un nuevo objeto canvas con el buffer creado en la //Actividad canvas = new Canvas(frameBuffer); } Los métodos resume() y pause() de esta clase se encargan respectivamente de reanudar y pausar el thread de la vista así como también de informar a esta del estado del thread. /** * Método resume * * Llamado cuando la Actividad vuelve a primer plano, * inicia el thread de la vista */ public void resume() { //La bandera indica que la Actividad esta en ejecución running = true; //Crea un nuevo thread para la vista renderThread = new Thread(this); //Inicializa el thread de la vista renderThread.start(); } /** * Método pause * * Llamado cuando la Actividad pasa a segundo plano, * detiene el thread de la vista */ public void pause() { //La bandera indica que la Actividad no esta en ejecución running = false; //Espera a que el thread de la vista se detenga while(true) { try { renderThread.join(); break; } catch (InterruptedException e) { // } } } En el método run() actualizamos los objetos Elefante y Raton en base al tiempo transcurrido desde la última actualización. Para esto llamamos al método actualiza() el cual recibe como parámetro el tiempo. También dibujamos al elefante y los ratones en el buffer virtual para posteriormente proyectar al mismo en la pantalla del dispositivo. /** * Método run sobrescrito de la clase Thread * * Llamado cuando el thread de la vista esta en ejecución, * se encarga de actualizar la vista de la Actividad */ public void run() { //Objeto Rect crea un rectángulo Rect dstRect = new Rect(); //Obtiene el tiempo actual long tiempoI = System.nanoTime(); //El ciclo se ejecuta cuando la Actividad esta en ejecución while (running) { //Verifica que exista una vista válida if(!holder.getSurface().isValid()) continue; //Calcula el tiempo transcurrido float tiempo = (System.nanoTime() – tiempoI) / } 1000000000.0f; //Obtiene el tiempo actual tiempoI = System.nanoTime(); //Pinta un fondo amarillo canvas.drawRGB(255, 255, 0); //Actualiza los objetos en base al tiempo //transcurrido actualiza(tiempo); //Dibuja el objeto Elefante en el buffer canvas.drawBitmap(elefante.animacion.getCuadro(), elefante.x, elefante.y, null); //Dibuja los ratones en el buffer for (int i = 0; i < ratones.size(); i++) canvas.drawBitmap( ratones.get(i).animacion.getCuadro(), ratones.get(i).x, ratones.get(i).y, null); //Crea un nuevo canvas con la vista actual Canvas pantalla = holder.lockCanvas(); //Determina la resolución de la pantalla pantalla.getClipBounds(dstRect); //Dibuja el buffer en la pantalla con el tamaño de la //pantalla pantalla.drawBitmap(frameBuffer, null, dstRect, null); holder.unlockCanvasAndPost(pantalla); } Ahora definimos el método actualiza() el cual se encarga de actualizar la posición y la animación tanto del objeto Elefante como de los objetos Raton en base al tiempo transcurrido. En este mismo método mandamos a checar las colisiones entre objetos y las colisiones con las orillas del frame. Para actualizar los objetos llamamos a los métodos elefanteAvanza() y ratonAvanza() que mas adelante definiremos. Por otro lado, llamamos a los métodos colisionFrame() y colisionAnimales() para checar las colisiones de los objetos. /** * Método actualiza * * Llamado para actualizar el estado de los objetos Elefante y * Raton en base al tiempo que ha transcurrido * * @param tiempo es el tiempo transcurrido desde la última * actualización */ private void actualiza(float tiempo) { //Guarda el tiempo transcurrido tiempoTick += tiempo; } //Actualiza mientras el tiempo transcurrido sea mayor al //tiempo de actualización while(tiempoTick > tick) { tiempoTick -­‐= tick; //Actualiza la posición del objeto Elefante elefanteAvanza(); //Actualiza la posición de los objetos Raton ratonAvanza(); //Checa la colisión de los objetos con las orillas del //frame colisionFrame(); //Checa la colisión entre objetos colisionAnimales(); } //Actualiza la animación del elefante en base al tiempo //transcurrido elefante.animacion.anima(tiempo); //Actualiza la animación de los ratones en base al tiempo //transcurrido for (int i = 0; i < ratones.size(); i++) { ratones.get(i).animacion.anima(tiempo); } Los métodos elefanteAvanza() y ratonAvanza() se encargan de actualizar la posición del elefante y los ratones respectivamente. El método elefanteAvanza() actualiza la posición del objeto Elefante en base a la dirección del movimiento del mismo. El método ratonAvanza(), por su parte, actualiza la posición de los objetos Raton en base a la posición del elefante de tal manera que estos persigan al mismo. /** * Método elefanteAvanza * * Actualiza la posición del objeto Elefante dependiendo de su * dirección actual */ private void elefanteAvanza() { //Avanza a la derecha if (elefante.dir == Elefante.RIGHT) elefante.x += 5; //Avanza a la izquierda if (elefante.dir == Elefante.LEFT) elefante.x -­‐= 5; //Avanza arriba if (elefante.dir == Elefante.UP) elefante.y -­‐= 5; //Avanza abajo if (elefante.dir == Elefante.DOWN) elefante.y += 5; } /** * Método ratonAvanza * * Actualiza la posición de los objetos Raton dependiendo de la * posición actual del objeto Elefante */ private void ratonAvanza() { for (int i = 0; i < ratones.size(); i++) { //Avanza a la derecha if (elefante.x > ratones.get(i).x) ratones.get(i).x += 3; //Avanza a la izquierda else ratones.get(i).x -­‐= 3; //Avanza abajo if (elefante.y > ratones.get(i).y) ratones.get(i).y += 3; //Avanza arriba else ratones.get(i).y -­‐= 3; } } Ahora, como lo mencionamos anteriormente, los métodos colisionFrame() y colisionAnimales() se encargan de manejar las colisiones de los objetos de la Activdad. El método colisionFrame() se asegura que todos los objetos se mantengan dentro del marco del frame de la Actividad de tal manera que siempre estén a la vista del usuario. Para esto, después de actualizar la posición de los objetos, se toma como referencia la misma para verificar que los objetos no hayan rebasado bordes de la pantalla, de ser así el método cambia la posición de los objetos y reproduce un sonido para indicar que hubo una colisión. Para reproducir este sonido implementamos el método playSound() que definiremos más adelante. El método colisionAnimales() saca ventaja del método intersect() de la clase Rect para detectar la colisión entre dos objetos. El método intersect() nos permite saber cuando dos objetos de esta clase se intersectan, es decir, se encuentran colisionando de tal manera que para detectar la colisión entre el elefante y los ratones llamamos al rectángulo de ambos para checar la colisión de estos con el método intersect(). En caso de que los objetos colisionen son movidos a una nueva posición y se reproduce un sonido en señal de que hubo una colisión /** * Método colisionFrame * * Checa la colisión de los objetos Elefante y Raton con las orillas * de la pantalla */ private void colisionFrame() { //Checa colisión del objeto Elefante con las orillas de la //pantalla if(elefante.x + elefante.animacion.getCuadro().getWidth() > frameBuffer.getWidth()) { //Cambia la dirección del movimiento elefante.dir = Elefante.LEFT; //Reproduce el sonido del elefante playSound(elefanteId); } else if(elefante.x < 0) { //Cambia la dirección del movimiento elefante.dir = Elefante.RIGHT; //Reproduce el sonido del elefante playSound(elefanteId); } if(elefante.y + elefante.animacion.getCuadro().getHeight() > frameBuffer.getHeight()) { //Cambia la dirección del movimiento elefante.dir = Elefante.UP; //Reproduce el sonido del elefante playSound(elefanteId); } else if(elefante.y < 0) { //Cambia la dirección del movimiento elefante.dir = Elefante.DOWN; //Reproduce el sonido del elefante playSound(elefanteId); } //Checa colisión de los objetos Raton con las orillas de la //pantalla for (int i = 0; i < ratones.size(); i++) { if(ratones.get(i).x + ratones.get(i).animacion.getCuadro().getWidth() > frameBuffer.getWidth()) { //Aleja al objeto Raton de la orilla ratones.get(i).x -­‐= 3; //Reproduce el sonido del ratón playSound(ratonId); } else if(ratones.get(i).x < 0) { //Aleja al objeto Raton de la orilla ratones.get(i).x += 3; //Reproduce el sonido del ratón playSound(ratonId); } if(ratones.get(i).y + ratones.get(i).animacion.getCuadro().getHeight > frameBuffer.getHeight()){ //Aleja al objeto Raton de la orilla ratones.get(i).y -­‐= 3; //Reproduce el sonido del ratón playSound(ratonId); } else if (ratones.get(i).y < 0) { //Aleja al objeto Raton de la orilla ratones.get(i).y += 3; //Reproduce el sonido del ratón playSound(ratonId); } } } /** * Método colisionAnimales * * Checa la colisión entre objetos Ratón y Elefante */ private void colisionAnimales() { //Para cada Raton checa si hubo colisión con el objeto //Elefante for (int i = 0; i < ratones.size(); i++) { //Si el rectángulo del elefante intersecta con el del //ratón hubo colisión if (elefante.getRectangulo().intersect(ratones.get(i). getRectangulo())) { //Se reposiciona el elefante en el centro de la //pantalla elefante.x = frameBuffer.getWidth() / 2 – elefanteAnim.getCuadro().getWidth() / 2; elefante.y = frameBuffer.getHeight() / 2 – elefanteAnim.getCuadro().getHeight() / 2; //Se reposiciona el ratón que colisionó en //una nueva posición al azar ratones.get(i).x = (int) (Math.random() * frameBuffer.getWidth()) – ratonAnim.getCuadro().getWidth(); ratones.get(i).y = (int) (Math.random() * frameBuffer.getHeight()) – ratonAnim.getCuadro().getHeight(); //Reproduce el sonido de explosión playSound(explosionId); } } } Finalmente definimos el método playSound() el cual sencillamente se ocupa de reproducir el sonido indicado. Para esto primero se asegura de que el mismo no este ya en reproducción y lo detiene para posteriormente reproducirlo de nueva cuenta. El método playSound() recibe como parámetro el id del sonido que se desea reproducir. /** * Método playSound * * Reproduce efectos de sonido en la colección de sonidos * * @param id es el identificador del efecto de sonido a * reproducir */ private void playSound(int id) { //Si el sonido ya estaba en reproducción lo detiene pool.stop(id); //Reproduce el efecto de sonido pool.play(id, 1, 1, 0, 0, 1); } } } 

Documentos relacionados