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