3.I. Controlando el pong desde teclado

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


Controlando desde el teclado nuestro pong


Dejamos atrás la POO y nos centramos en el desarrollo puro y duro de nuestro juego Flash.


Colocando los pongs

Para que nuestro juego tenga algo de sentido, necesitamos al menos dos pongs colocados en el campo de juego, uno al lado izquierdo y el oponente en el lado derecho. Para controlar su posición en el escenario vamos a utilizar los atributos "x" e "y" que nuestra clase Pong ha heredado de MovieClip.


¿Cómo accedo a los métodos y atributos de un objeto?

Utilizamos el punto "." para acceder a los miembros de un objeto.



// Acceso a atributos
miObjeto.atributo = valorAtributo;
// Acceso a métodos
miObjeto.cualquierMetodo(parametros);

Para simplificar la colocación de los pongs en el campo de juego, vamos a hacer a nuestra clase Pong "inteligente", añadiéndole un atributo "num_jugador" que contendrá el número jugador. Éste indicará si el jugador debe situarse a la izquierda (jugador 0) o a la derecha (jugador 1). Añadimos a la clase Pong el atributo:



...
private var num_jugador:uint;
...

- uint es el tipo de variable que engloba todos los números >= 0.


Para el cálculo de las coordenadas que ha de ocupar el pong, necesitamos saber el tamaño del escenario. ¿Quién tiene esta información? La clase Main, por supuesto. La clase Main ha heredado de MovieClip un atributo "stage" de la clase "Stage" que contiene las dimensiones del escenario.


stage


De hecho, estos valores coincidirán con los valores que especifices en las Propiedades del Documento (click derecho sobre el fondo -> Propiedades del Documento...)


Propiedades del documento


La ventaja de especificar la colocación de los pongs respecto a las dimensiones del escenario es que, si éstas cambian, todo permanecerá colocado en su sitio. Porque en el futuro será útil, vamos a añadir a la clase Pong un atributo "stageRef" de tipo "Stage" que contendrá los datos del escenario. Recuerda que has de importar Stage para evitar fallos en la compilación (import flash.display.Stage;):



...
private var num_jugador:uint;
private var stageRef:Stage;
...

Vamos a hacer saber a la clase Pong de su número de jugador y del escenario a través del constructor de la clase, añadiéndole parámetros. Borramos el viejo constructor y añadimos el siguiente:


  public function Pong(stage:Stage, n_jugador:uint) 
{
this.stageRef = stage;
this.num_jugador = n_jugador;
iniciaPosicion();
}

- stage:Stage, n_jugador:uint son los parámetros del constructor, los cuáles enviaremos cuándo creemos el objeto con new.


- this: Es una referencia al objeto actual. this da acceso a todos los miembros del objeto actual. En este caso, a los atributos stageRef y num_jugador de la clase Pong.


Cómo ves, tras asignar los atributos llamamos a la función iniciaPosicion() que tiene la siguiente especificación:


  public function iniciaPosicion()
{
var proporcion:uint = 20;
this.y = stageRef.stageHeight / 2;
if (num_jugador == 0)
{
this.x = stageRef.stageWidth / proporcion;
this.scaleX = -1;
}
else
this.x = stageRef.stageWidth - stageRef.stageWidth / proporcion;
}

Hemos utilizado algo de matemática sencilla para colocar los pongs en su lugar correspondiente. Con "this.scaleX = -1;", obtenemos la imagen espejo de nuestro pong, cosiguiendo así su orientación correcta en la pantalla. (Puedes probar ejecutar el Flash con "this.scaleX = -1" añadido y sin añadir para entender a lo que me refiero.).


La clase Pong queda tal que así:


package mi.pong 
{
import flash.display.MovieClip;
import flash.display.Stage;

/**
* ...
* @author ASL
*/

public class Pong extends MovieClip
{
private var vy:N
private var num_jugador:uint;
private var stageRef:Stage;

public function Pong(stage:Stage, n_jugador:uint) {
this.stageRef = stage;
this.num_jugador = n_jugador;
iniciaPosicion();
}

public function iniciaPosicion()
{
var proporcion:uint = 20;
this.y = stageRef.stageHeight / 2;
if (num_jugador == 0)
{
this.x = stageRef.stageWidth / proporcion;
this.scaleX = -1;
}
else
this.x = stageRef.stageWidth - stageRef.stageWidth / proporcion;
}
}
}

Y ya sólo queda añdir nuestros pongs desde la clase Main:


package
{
import flash.display.MovieClip;
import mi.pong.Pong;

/**
* ...
* @author ASL
*/
public class Main extends MovieClip
{

public function Main()
{
var miPong1:Pong = new Pong(stage, 0);
addChild(miPong1);

var miPong2:Pong = new Pong(stage, 1);
addChild(miPong2);
}

}

}

Recuerda que stage es un atributo dado por la clase Main (quién lo ha heredado de MovieClip) que representa el escenario del documento. Hubiera surtido el mismo efecto si hubiéramos utilizado "this.stage".


Controlando desde el teclado nuestros pongs

Para el control de teclado vamos a utilizar una clase ya creada de un señor que sabe bastante de ActionScript, Senocular. Puedes descargar la clase aquí. Deberías crear la jerarquía de carpetas com -> senocular -> utils y ahí crear el archivo KeyObject.as con el contenido del link.


KeyObject es una clase que nos va a permitir saber si una tecla cualquiera está pulsada en el teclado, y con esta información, mover el pong hacia un lado o hacia otro. Como tenemos un único teclado, sólo necsitamos un KeyObject para todo el juego, así que lo crearemos en la clase Main. Y además, ambos Pong necesitan saber de ése teclado, con lo cual añadiremos un parámetro más en el constructor de la clase Pong:


...

public class Main extends MovieClip
{
private var key:KeyObject;

public function Main()
{
key = new KeyObject(stage);

var miPong1:Pong = new Pong(stage, 0, key);
addChild(miPong1);

var miPong2:Pong = new Pong(stage, 1, key);
addChild(miPong2);
}

}

...

- key = new KeyObject(stage): El único parámetro que necesita KeyObject es el escenario principal.


Ahora hay que adaptar la clase Pong a las nuevas necesidades. Necesitamos un atributo key para guardar la información de teclado que nos pasan desde el constructor, y dos atributos más: upKey y downKey que especificarán las teclas concretas que mueven nuestro pong.


...
//Control de teclado
private var key:KeyObject;
private var upKey:uint;
private var downKey:uint;
...

Y necesitamos la inicialización de todas estas variables:


 ...
public function Pong(stage:Stage, n_jugador:uint, key:KeyObject)
{
this.stageRef = stage;
this.num_jugador = n_jugador;
this.key = key;

iniciaPosicion();
iniciaTeclado();

}
...


private function iniciaTeclado()
{
if (num_jugador == 1)
{
upKey = Keyboard.UP;
downKey = Keyboard.DOWN;
}
else
{
upKey = 87; // W
downKey = 83; // S
}
}

- Observa que declaramos iniciaTeclado() como private porque no queremos que nadie pueda acceder a ella desde fuera de la clase.


- upKey = Keyboard.UP; downKey = Keyboard.DOWN: Para el caso del jugador uno, asignamos a sus teclas las constantes para la tecla UP y DOWN que contiene Keyboard (import flash.ui.Keyboard;)


- En el caso del jugador 0 asignamos las constantes a mano porque, desgraciadamente y por algún motivo que desconozco, las constantes para las letras del telcado sólo existen en el AIR runtime, que no es nuestro caso. Puedes ver los códigos de teclas aquí. Ten cuidado también cuándo previsualices el juego desde Flash, porque estas teclas no te funcionarán. Para que lo hagan, ejecuta el archivo .swf que se crea en el mismo director que tu archivo .fla cada vez que previsualizas la película.


Ahora, lo que necesitamos es procesar las pulsaciones de estas teclas. Para ello vamos a utilizar la función mover y el atributo vy que declaramos hace tiempo:


  public function mover(e:Event)
{
if (key.isDown(upKey))
{
vy -= 1;
}
else if (key.isDown(downKey))
{
vy += 1;
}

this.y += vy;
}

- mover(e:Event): ahora explicaremos porque la función mover necesita este parámetro "e".


- key.isDown(una_tecla): esta función nos devuelve un booleano indicando si una_tecla está pulsada o no.


- this.y += vy : añadimos a la posición "y" del pong la velocidad, que ha sido modificada con las pulsaciones de teclado.


Todo está ya conectado, pero nos falta un último detalle: ¿cuándo se ejecuta la función mover? Para resolver este problema, añadimos la siguiente instrucción al constructor de la clase Pong:


...
addEventListener(Event.ENTER_FRAME, mover, false, 0, true);
...

- addEventListener (Event.ENTER_FRAME, mover, false, 0, true) : Con esta función le estamos diciendo a la clase Pong que cada vez que se produzca el evento "Event.ENTER_FRAME", ejecute la función "mover". El resto de parámetros de momento no tienen relevancia.


¿Qué es un evento?

En ActionScript, un evento es algo que sucede y que podemos detectar, para realizar las acciones oportunas. Los eventos pueden ser de múltiples tipos: eventos de teclado (se pulsó una tecla), eventos de ratón (se pulsó el botón derecho), eventos de película (se entró en un nuevo frame), etc.


En este caso, nos interesa que cada vez que nuestra película Flash entre en un nuevo Frame (fotograma) se actualicen las posiciones del pong.


La función mover(e:Event) necesita el parámetro "e" de tipo Event porque será ahí dónde se guarda el evento que se produjo. Sin este parámetro, no lograremos compilar.


Después de tanto trasiego, la clase Pong queda así:


package mi.pong 
{
import flash.display.MovieClip;
import flash.display.Stage;
import com.senocular.utils.KeyObject;
import flash.ui.Keyboard;
import flash.events.Event;

/**
* ...
* @author ASL
*/

public class Pong extends MovieClip
{
private var vy:Number;

private var num_jugador:uint;
private var stageRef:Stage;

//Control de teclado
private var key:KeyObject;
private var upKey:uint;
private var downKey:uint;

public function Pong(stage:Stage, n_jugador:uint, key:KeyObject)
{
this.stageRef = stage;
this.num_jugador = n_jugador;
this.key = key;

iniciaPosicion();
iniciaTeclado();


addEventListener(Event.ENTER_FRAME, mover, false, 0, true);

}

public function iniciaPosicion():void
{
var proporcion:uint = 20;
this.y = stageRef.stageHeight / 2;
if (num_jugador == 0)
{
this.x = stageRef.stageWidth / proporcion;
this.scaleX = -1;
}
else
this.x = stageRef.stageWidth - stageRef.stageWidth / proporcion;

vy = 0;
}

private function iniciaTeclado():void
{
if (num_jugador == 1)
{
upKey = Keyboard.UP;
downKey = Keyboard.DOWN;
}
else
{
upKey = 87; // W
downKey = 83; // S
}
}

public function mover(e:Event)
{
if (key.isDown(upKey))
{
vy -= 1;
}
else if (key.isDown(downKey))
{
vy += 1;
}

this.y += vy;
}
}
}

¡Los pongs ya están en movimiento! Aunque necesitamos un movimiento más fluido que el conseguido hasta ahora para que la jugabilidad sea satisfactoria. Tema que abordamos en el siguiente capítulo.








Anterior Índice Siguiente