La Bola y el Pong como IColisionador e IColisionable

Esta entrada pertenece a ActionScript 3 - Guía para Principiantes.


Un pequeño problema...

Antes de nada vamos a realizar algo de trabajo sucio y molesto. En nuestro afán de mantener nuestro código ordenado, no serán pocas las veces que tengamos que reescribir y cambiar cosas que ya teníamos, como es el caso que se nos presenta a continuación.


Queremos una organización como la de la imagen. Así que vamos a crear las carpetas que nos falten y mover los .as que tenemos a su lugar correspondiente. Además, en las carpetas colisionable y colisionador vamos a clickar con el boton derecho, Add... y en esta ocasión, como lo que queremos son dos nuevas interfaces -> New Interface... Lo haremos por dos veces, con los nombres IColisionador e IColisionable en sus respectivas carpetas.




Y, aunque no lo pareciese, con este simple movimiento nos hemos cargado varias cosas que antes funcionaban. Abre ahora Bola.as y la clase Pong.as y verás dos mensajes advirtiéndote que el nombre de paquete no concucerda con la ruta de carpetas. Y no sólo eso, también en el fichero Flash, si nos vamos a las propiedades del Clip de Película Bola o de Pong de la bibloteca, si en el apartado Clase clickamos en el tic que valida la clase, también obtendremos un error. ¿Qué ha pasado? Pues que hemos movido los archivos de carpeta, y ahora Flash nos lo encuentra.


La solución es sencilla: en "Pong.as" cambiamos la actual cabecera del paquete por esto: package mi.pong.objeto.colisionable (la nueva ruta de carpetas en la que se encuentra Pong) y en las propiedades del símbolo Flash cambiamos la clase por su nueva localización mi.pong.objeto.colisionable.Pong. Repetimos proceso (sólo que cambiando "colisionable" por "colisionador") con la clase Bola.


En el futuro tendremos cuidado para evitar estas pérdidas de tiempo.


IColisionable e IColisionador

Definimos en sendos archivos las interfaces IColisionable


package mi.pong.objeto.colisionable 
{
import flash.display.DisplayObject;
import mi.pong.objeto.colisionador.IColisionador;

/**
* ...
* @author ASL
*/
public interface IColisionable
{
function getHitBox():DisplayObject;
function recibeColision(objeto:IColisionador):void;
function getY():Number;
function getHeight():Number;
}

}

e IColisionador


package mi.pong.objeto.colisionador 
{
import flash.display.DisplayObject;
import mi.pong.objeto.colisionable.IColisionable;

/**
* ...
* @author ASL
*/
public interface IColisionador
{
function getHitBox():DisplayObject;
function colisiona(objeto:IColisionable):void;
}

}

Vamos por partes:


- function getHitBox():DisplayObject; Esta función, definida en ambas interfaces, será la que proporciene el área de colisión de cada objeto. El rectángulo transparente al que llamamos "hitBox" y que añadimos en los símbolos Bola y Pong de nuestro Flash en capítulos anteriores. El objeto que se devuelve es de tipo DisplayObject, que es una clase antecesora (el padre del padre del padre...) de MovieClip. ¿Por qué DisplayObject y no MovieClip? Por generalidad. DisplayObject representa cualquier objeto que pueda representarse en el canvas de Flash. MovieClip representa un único tipo de objetos que pueden representarse por pantalla.


- function recibeColision( objeto:IColisionador ) y function colisiona( objeto:IColisionable ) : es la función que se ejecutará cuándo el objeto reciba la colisión / colisione. Además, le pasaremos como parámetro aquello contra lo que colisionó.


- function getY():Number; function getHeight():Number; Sólo las hemos puesto (de momento) en IColisionable. Como sus nombres indican, nos devuelven la posición en el eje "y" y la altura del pong. Las necesitaremos para calcular la trayectoria de la bola.


Muy bien, tenemos nuestras dos interfaces. ¿Y? ¿Qué tienen que ver con Bola y Pong? Bueno, de momento nada.


Antes de continuar, recordemos que la idea era que el Escenario tratara los objetos que contuviere de manera genérica, sin preocuparse de si eran bolas, pongs, ítems... Sólo necesitaba saber si eran IColisionable o IColisionador. Pues el siguiente paso es convertir las clases que tenemos de momento (Bola y Pong) en ICoisionable o IColisonador. Y eso lo conseguimos implementando las respectivas interfaces.


Bola como IColsionador

La bola es un colisionador. Para decírselo a ActionScript 3, debemos especificarlo en Bola.as.


En la zona de importación, importamos la interfaz deseada. En verdad, no es realmente necesaria esta importación, porque, al encontrarse en la misma carpeta, Bola "ve" a IColisionador. La que sí es necesaria es IColisionable, debido a colisiona(objeto:IColisionable):


import mi.pong.objeto.colisionador.IColisionador;
import mi.pong.objeto.colisionable.IColisionable;

Ahora, en la definición de clase, especificamos que Bola implementa IColisionable:


public class Bola extends MovieClip implements IColisionador 
{
...

Y por último, implementamos los métodos de la interfaz en la clase Bola. Hay que implementarlos todos, si no obtendríamos un error. Añadimos las definiciones al final de la clase:


  // Implementación de la interfaz IColisionable
public function getHitBox():DisplayObject
{
return hitBox;
}

public function colisiona(objeto:IColisionable):void
{

}

- En getHitBox() devolvemos hitBox que es el área de colisión definida en forma de rectángulo en Flash. Vamos, que estamos devolviendo la cajita transparente que tiene la Bola. La necesitaremos para calcular colisiones.


- De momento, dejamos sin definir colsiona(objeto:IColisionable). Lo haremos cuándo tengamos establecido el flujo de control del escenario.


Pong como IColisionable

Los pasos a seguir en este caso son análogos a los seguidos con Bola:



import mi.pong.objeto.colisionador.IColisionador;
import mi.pong.objeto.colisionable.IColisionable;

public class Pong extends MovieClip implements IColisionable
{
...

  // Interfaz IColisionable
public function getHitBox():DisplayObject
{
return hitBox;
}

public function recibeColision(objeto:IColisionador):void
{

}

public function getY():Number
{
return y;
}

public function getHeight():Number
{
return height;
}

- En getHitBox() devolvemos igualmente hitBox y en getY() y getHeight() la posición "y" y la altura del objeto respectivamente.


Para cerrar el ciclo y e intentar aclarar un poco las ideas respecto a las interfaces, vayámonos a la clase Escenario, y añadamos el siguiente código:


package mi.pong.escenario 
{
import flash.display.MovieClip;
import mi.pong.objeto.colisionable.IColisionable;
import mi.pong.objeto.colisionador.IColisionador;

/**
* @author ASL
*/
public class Escenario extends MovieClip
{
// Objetos estáticos sobre los que se puede colisionar, incluidos los pongs
protected var colisionables:Vector.<IColisionable>;

// Objetos móviles que pueden colisionar con objetos estáticos o con otros
// objetos móviles
protected var colisionadores:Vector.<IColisionador>;

public function Escenario()
{

}
}
}

Lo más importante es protected var colisionables:Vector.<IColisionable> y protected var colisionadores:Vector.<IColisionador>.


- Primero, y volviendo a la POO, protected indica que el atributo es solamente visible por las clases herederas y por la propia clase..


- Y segundo, aquí vemos por fin el uso de las interfaces. Especificamos dos listas (Vector), una que contiene objetos de tipo IColisionable y la otra de tipo IColisionador. Y el escenario no sabe si son bolas, pongs, ítems... Sólo sabe que unas colisionan y otras son colisionables. Y las trata a todas por igual.


Un último detalle, para que Flash reconozca la clase Vector, debéis comprobar dos cosas:


1) En FlashDevelop, en Project -> Properties -> Output -> Target debe estar seleccionado Flash Player 10.


2) En Flash CS4, en Archivo -> Configuración de la Publicación -> en la pestaña Flash -> Reproductor y debe poner igualmente Flash Player 10.


Si utilizas versiones de Flash anteriores y no dispones de la clase Vector puedes utilizar perfectamente en su lugar en la clase Array.








Anterior Índice Siguiente