Produciendo y detectando los goles

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


¡Gol!

Si nuestro propósito es crear un marcador que contabilice el número de goles anotado por cada jugador, lo primero que necesitamos es ser capaces de detectar los goles.


Para detectar los goles vamos a utilizar Eventos.


¿Qué es un evento?

En Programación Orientada a Objetos, un evento es un suceso que ocurre en un determinado momento y que podemos detectar.


Simple, ¿no? Se produce un gol => Lo detecto => Lo reflejo en el marcador.


Definición de GolEvent

Lo primero que debemos hacer es definir el Evento. El Evento, como cualquier otro concepto u objeto en POO, es una clase. Pero, para especificar que es un evento, ésta debe heredar de la clase de ActionScript Event.


Para mantener el pulcro orden que hemos intentado llevar hasta ahora, vamos a crear una nueva subcarpeta en pong llamada evento, y en ella vamos a crear la clase GolEvent. Pero antes, un pequeño concepto para entender el código de GolEvent:


¿Qué es un atributo estático (static) o de clase?

Es un atributo global compartido por todas las instancias (objetos) de una clase. Su definición es la siguiente:


visibilidad static atributo_de_clase:Tipo = valor;

Es decir, con static especificamos que se trata de un atributo de clase. ¿Y para qué sirve esto? Pongamos un ejemplo.


public class MiClase
{
public static var numero:Number = 0;
}

Ahora, sólo con importar la clase MiClase tendríamos acceso a "MiClase.numero" sin necesidad de crear ningún objeto de esta clase. Por ejemplo, el código:


trace(MiClase.numero);

tendría como salida:


0

Hay algunos detalles más detrás de los miembros static (cómo que las funciones también pueden serlo), pero de momento esto nos sirve para continuar.


Ahora sí, la definición de GolEvent:


package mi.pong.evento
{
import flash.events.Event;

/**
* ...
* @author ASL
*/
public class GolEvent extends Event
{
public static const GOL_EVENT:String = "GolEvent";
public static const IZQUIERDA:uint = 0;
public static const DERECHA:uint = 1;

public var lado:uint;

public function GolEvent(lado_gol:uint)
{
super(GOL_EVENT);
lado = lado_gol;
}

}

}

- GOL_EVENT:String: Este es el "nombre del evento". La clase Event "nos obliga" a definir un string identificativo para los eventos. Este string se envía al constructor padre a través de super(GOL_EVENT); Posteriormente, este string nos servirá para detectar el evento. El modificador const (en contraste con var) especifica que el valor de este atributo no puede ser cambiado.


- IZQUIERDA, DERECHA: Estas dos constantes nos servirán para determinar en cuál de los dos lados se produjo el gol, para poder sumarlo al marcador correspondiente.


- lado: Esta atributo contendrá el lado en el que se produjo el gol (IZQUIERDA o DERECHA).


- En el constructor, lo primero que hacemos es llamar al constructor padre (el de la clase Event) con en el nombre del evento. Esto debemos hacerlo obligatoriamente siempre. Lo hacemos con la palabra especial super, que explicaré a continuación. Después asignamos al atributo lado el lado desde el que se produjo el gol, que viene dado en forma de parámetro en el constructor.


- Notad que, por convenio, las constantes van escritas con mayúsculas.


¿Para qué sirve la palabra clave "super"?

La palabra super nos da acceso desde una clase hija a los miembros de su clase padre. Es decir, si yo tengo una clase "Transporte" con una función "viajar" y quiero acceder a ella desde su clase hija "Avión" puedo hacerlo con super.viajar().


En verdad, super no se utiliza con este fin, puesto que se puede acceder a viajar (y a cualquier otro miembro de Transporte) sin necesidad de especificarlo.


El único caso de uso real es en el del constructor. Aunque no se especifique textualmente, internamente, todos los constructores de clases heredades definen como primera instrucción super(), que llama al constructor de la clase padre. Cuándo lo definimos explícitamente, como en el caso de GolEvent es porque necesitamos especificar parámetros para el constructor de la clase padre.


¿Quién produce el GolEvent?

El siguiente paso es producir el evento para posteriormente detectarlo.


Aunque se podría hacer de diferentes maneras, de todos los elementos que de momento tenemos en nuestro juego, el que más sentido tiene que produzca los eventos de Gol es la Bola. Al fin y al cabo, es la que mejor sabe cuándo ha llegado a los confines del escenario.


Añadimos el código necesario en la función loop de Bola, en dónde ya detectamos cuándo llega a los límites del campo:


  public function loop(e:Event)
{
x += vx;
y += vy;

if (y < 0)
{
y = 0;
vy = -vy;
}
else if (y > stageRef.stageHeight)
{
y = stageRef.stageHeight;
vy = -vy;
}

if (x < 0)
{
iniciaPosicion();
dispatchEvent(new GolEvent(GolEvent.DERECHA));
vx = 5;
vy = 0;
}
else if (x > stageRef.stageWidth)
{
iniciaPosicion();
dispatchEvent(new GolEvent(GolEvent.IZQUIERDA));
vx = -5;
vy = 0;
}
}

- dispatchEvent(new GolEvent(GolEvent.DERECHA)): esta función (heredada de MovieClip) permite a la bola lanzar un evento que alguien puede escuchar. Básicamente esto se traduce en: "Oye, mira, soy la bola. Que ha habido gol por la derecha." Por la izquierda es análogo.


La clave ahora está en detectar ese evento y procesarlo.


¿Quién detecta el GolEvent?

Cómo la idea era y es que el Escenario se encargue de la mayor lógica del juego posible, será él quién detecte el evento. La manera de detectar los eventos es algo que ya hemos hecho en varias ocasiones, con la función addEventListener(...). Vamos a realizar una modificación en la función creaBolas del Escenario:


  public function creaBolas(num_bolas:uint)
{
for (var i:uint = 0; i < num_bolas; i++)
{
var bola:Bola = new Bola(stageRef);
bola.addEventListener(GolEvent.GOL_EVENT, golAnotado, false, 0, true);
stageRef.addChild(bola);
colisionadores.push(bola);
}
}

- bola.addEventListener(GolEvent.GOL_EVENT, golAnotado, ...): Añadimos un eventListener ("oyente de eventos") a la bola, que queda a la expectativa de que ésta haga un dispatchEvent(...) que contenga un GolEvent para ejecutar la función golAnotado.


Cobra aquí importancia el atributo estático GOL_EVENT, y cómo lo especificamos en la clase padre Event (a través de super(GOL_EVENT)), convirtiéndolo así en el "nombre del evento". Ahora en addEventListener indicamos que el evento que nos interesa detectar es el que lleva ese nombre.


Ahora sólo nos queda definir la función golAnotado:


  public function golAnotado(e:GolEvent)
{
trace("¡Gol!");
}

- golAnotado(e:GolEvent): Al utilizarla en addEvenlistener, la función golAnotado recibe como parámetro un evento (en este caso, de tipo GolEvent).


De momento, esto nos sirve para comprobar si todo el flujo de eventos funciona. En el siguiente capítulo crearemos los marcadores.








Anterior Índice Siguiente

Bug (I): La Bola loca

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


Bug (I): La bola loca

Los errores imprevistos (o bugs) son algo bastante común en la programación, y que suelen aparecer en la ejecución continuada de nuestros programas. Por mucho que se planee una implementación de código, siempre acaba apareciendo una situación inesperada que rompe nuestro preciado código. No pasa nada. Si aparece un bug se soluciona, y a otra cosa.


En este caso, nosotros encontramos un error si intentamos golpear la pelota con la partes puntiagudas (los extremos) del pong. No siempre sucede, pero la mayoría de las veces la bola empieza a rebotar dentro del pong. Queda atrapada.


En código, el problema viene dado porque la bola rebota continuadas veces en poco tiempo, cambiando su dirección de izquierda a derecha (y viceversa) una y otra vez sin llegar a salir del área de colisión del pong.


La solución que propongo es añadir un Timer a la bola. Un reloj que, tras la colisión, espere un tiempo para volver a comprobar si hay colisiones, y así dar tiempo a la bola para abandonar la zona de colisiones del pong.


En código, añadimos estos atributos a la clase Bola:


  // Para evitar múltiples colisiones
private var colisionado:Boolean = false;
private var timer:Timer = new Timer(100);

Añadimos la inicialización del timer al constructor:


  public function Bola(stage:Stage) 
{
stageRef = stage;
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
iniciaPosicion();
timer.addEventListener(TimerEvent.TIMER, colisionTime, false, 0, true);
timer.stop();
}

Lo que decimos con este código, es que cada vez que se cumpla el tiempo especificado en el constructor del Timer se ejecute la función colisionTime. Además, lo detenemos con timer.stop(), porque de momento no queremos que corra. El código de colisionTime es el siguiente:


  public function colisionTime(e:TimerEvent)
{
timer.stop();
colisionado = false;
}

Es decir, pasado el tiempo, indicamos que la bola "ha dejado de colisionar". Pero nos falta, entonces, ver cuándo colisionado cambia a true. Este cambio sucede en la función colisiona:


  public function colisiona(objeto:IColisionable):void
{
if (!colisionado)
{
var vel_x:Number = Math.abs(vx);
if (vel_x < max_velocidad)
{
vel_x += acc;
}

if (vx > 0)
{
vx = -vel_x;
}
else
{
vx = vel_x;
}
vy = ((y - objeto.getY()) / objeto.getHeight() * 2) * vel_x;

colisionado = true;
timer.start();
}
}

Si no acaba de colisionar, dejamos que se produzcan las modificaciones propias de la colisión. Después ponemos colisionado a "true" e iniciamos el reloj, que, pasado el periodo marcado, volverá a dejar la bola activa para colisiones.


Sin embargo, si la bola acabara de colisionar, la función no se ejecutaría.


Si soy sincero, la solución que he encontrado no me acaba de convencer. Funciona, sí, pero quizá algo así debería tener una solución más sencilla. Si alguien tiene una solución mejor, que no dude en proponerla en los comentarios.








Anterior Índice Siguiente

El código del Escenario

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


La nueva clase Main

Como dijimos anteriormente, la clase Escenario será la encargada de controlar la lógica del juego. La clase Main ahora únicamente se encargará de crear el control de teclado, el Escenario y de añadirlo a Flash. La clase Main presupone (y presupone bien) que la clase Escenario se encargará de todo. Así pues, esta es nuestra nueva clase Main, que no tiene mayor misterio:


package
{
import flash.display.MovieClip;
import mi.pong.escenario.Escenario;
import com.senocular.utils.KeyObject;

/**
* ...
* @author ASL
*/
public class Main extends MovieClip
{
private var key:KeyObject;

public function Main()
{
key = new KeyObject(stage);
var escenario:Escenario = new Escenario(stage, key);
stage.addChildAt(escenario, 0);
}

}

}

El código del Escenario

Para empezar, necesitamos añadir nuevos atributos en la clase Escenario para soportar la nueva lógica:


  // 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>;

// Variables generales
protected var key:KeyObject;
protected var stageRef:Stage;

Y modificar el constructor del escenario:


(Por comodidad, no añado los imports del código, pero recuerda que todas las clases que utilices deben estar importadas, si no, el código no compilará)


  public function Escenario(stage:Stage, keyObject:KeyObject) 
{
stageRef = stage;
key = keyObject;
colisionables = new Vector.<IColisionable>;
colisionadores = new Vector.<IColisionador>;
creaPongs(2);
creaEscenario();
creaBolas(1);
addEventListener(Event.ENTER_FRAME, bucle, false, 0, true);
}

Vemos que en el constructor recibimos el stage y el control de teclado keyObject, que asignamos a sus atributos correspondientes. Igualmente, creamos las listas de colisionables y colisionadores y después vienen una serie de funciones (aún no definidas) en las que inicializamos todo el escenario: creaPongs, creaEscenario, creaBola.


Finalmente, añadimos al escenario un EventListener que al inicio de cada FRAME ejecutará la función bucle (también sin definir). Esta función será el bucle del juego.


Puede parecer que estamos haciendo muchas cosas y todas ellas muy complejas, pero no es así. Repasemos: queremos transferir la lógica del Main al Escenario para lograr un mayor encapsulamiento. Así que hemos añadido los atributos necesarios para esta tarea al Escenario y los hemos inicializado. Hemos creado, también, los pongs, hemos dibujado el escenario y creado una bola (aunque aún no hayamos visto el código concreto de nada de esto). Y además, iniciado el bucle del juego, que se encarga de toda la lógica.


Veamos ahora el código concreto de cuada una de estas funciones no definidas:


creaPongs

  /**
* Añade un número de pongs al escenario.
* @param num_pongs Número de pongs a crear.
*/
public function creaPongs(num_pongs:uint)
{
for (var i:uint = 0; i < num_pongs; i++)
{
var miPong:Pong = new Pong(stageRef, i, key);
stageRef.addChild(miPong);
colisionables.push(miPong);
}
}

Un bucle en el que creamos un pong por vuelta. Y además, lo añadimos al escenario y a la lista de colisionables. Clave este último detalle.


El código entre en /** ... */ es documentación del código. En @param estamos especificando que la función recibe como parámetro "num_pongs". Existen herramientas que pueden traducirnos toda esta información en una web, al estilo de la documentación de ActionScript 3. Pero este es un tema que, de momento, no se va a cubrir en esta guía.


creaBolas

  /**
* Añade un número de bolas al escenario.
* @param num_bolas Número de bolas a crear.
*/
public function creaBolas(num_bolas:uint)
{
for (var i:uint = 0; i < num_bolas; i++)
{
var bola:Bola = new Bola(stageRef);
stageRef.addChild(bola);
colisionadores.push(bola);
}
}

Análogo (para variar) a la creación de pongs. La única diferencia, que la bola, como corresponde, se añade a la lista de colisionadores.


creaEscenario

  /**
* Crea el escenario. Un cuadrado verde que ocupa todo el stage.
*/
public function creaEscenario()
{
graphics.beginFill(0x66FF99);
graphics.drawRect(0, 0, stageRef.stageWidth, stageRef.stageHeight);
graphics.endFill();
}

La apariencia del Escenario por defecto es sencilla: un cuadrado verde que ocupa todo la pantalla Flash.


En esta función, hacemos uso de las funciones que ofrece ActionScript 3 para dibujar directamente sobre la pantalla Flash. Aunque parece complejo, en verdad es bastante sencillo. El único pero es que hay que seguir una metodología bastante marcada para que todo funcione correctamente. Algunas claves (que ampliaremos en los siguientes capítulos):


1) Las funciones de dibujo se invocan siempre desde el atributo graphics (cuya clase es Graphics) que contiene la clase Sprite y sus herederas. MovieClip entre ellas.


2) beginFill(color): indicamos que vamos a comenzar a dibujar formas del color que especificamos en el parámetro color.


3) drawRect(x, y, width, height): Dibujamos un rectángulo que parte en el punto x e y cuyo ancho mide width y cuyo alto mide height.


4) endFill(): indicamos que hemos terminado de dibujar.


Importante: Todas las instrucciones de dibujado de formas deben estar obligatoriamente entre beginFill y endFill, si no, obtendremos resultados inesperados (A aquéllos que hayan trabajado con OpenGL esto les sonará).


bucle

  /**
* Bucle principal del escenario.
* @param e Evento que produjo el disparo de la función.
*/
private function bucle(e:Event)
{
for (var i = 0; i < colisionadores.length; i++)
{
for (var j = 0; j < colisionables.length; j++)
{
if (colisionadores[i].getHitBox().hitTestObject(colisionables[j].getHitBox()))
{
colisionadores[i].colisiona(colisionables[j]);
colisionables[j].recibeColision(colisionadores[i]);
}
}
}
}

Tenemos dos bucles anidados, en los que estamos recorriendo la lista de colisionadores y comprobando con cada uno de los objetos de la lista colisionables si se produjo alguna colisión. Si así fuere, llamaríamos a las funciones correspondientes: colisionadores[i].colisionaba(colisionable[j]) y colisionables[j].recibeColision(colisionadores[i]).


En verdad, como de momento sólo tenemos bolas y pongs, esto se podría traducir por bola.colisiona(pong) y pong.recibeColision(bola).


La bola que colisiona...

Pues ya estamos preparados para implementar la funcion colisiona en la clase Bola que dejamos vacía en el capítulo anterior. Pero antes, vamos a a modificar un par de casas en la clase Bola:


1) En los atributos. Esto son todos los atributos que tiene, por el momento, Bola:


  private var vx:Number = 5;
private var vy:Number = 0;
private var max_velocidad:Number = 15;
private var acc:Number = 0.25;

private var stageRef:Stage;

Como se puede ver, aparece acc, que es la aceleración de la bola, que sumaremos cada vez que colisione. Y desaparece el atributo colisionadores, ya que es ahora el escenario quien se encarga de las colisiones de la bola. No olvides eliminar también el bucle en el que se testean las colisiones en la función loop. En consecuencia, éste es el nuevo constructor de Bola:


  public function Bola(stage:Stage) 
{
stageRef = stage;
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
iniciaPosicion();
}

Ahora sí, el código de colisiona. Recuerda que esta función se ejecutará siempre que la bola colisione:


public function colisiona(objeto:IColisionable):void
{
var vel_x:Number = Math.abs(vx);
if (vel_x < max_velocidad)
{
vel_x += acc;
}
if (vx > 0)
{
vx = -vel_x;
}
else
{
vx = vel_x;
}
vy = ((y - objeto.getY()) / objeto.getHeight() * 2) * vel_x;

}

En esencia, estamos cambiando la dirección de la bola y aumentando su aceleración. Siempre que su velocidad no supere la máxima permitida.


Un detalle importante, es que no podemos poner max_velocidad el valor que deseemos. Si la ponemos demasiado alta, la bola atravesará al pong. En el futuro intentaremos solucionar este problema.








Anterior Índice Siguiente

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