¿Qué queremos que haga nuestro Escenario?

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


Y sí, lamentablemente y por falta de tiempo, tuve que prescindir de las imágenes para las cabeceras de cada capítulo.


Definición del Escenario

En previsión de lo que va a ser un aumento exponencial de las clases de nuestro juego, deberíamos comenzar a organizarlas de manera estructurada para que nuestro proyecto Flash no se acabe convirtiendo en una amalgama de código intratable.


Todo lo relacionado con los escenarios parece tener la suficiente importancia como para poder considerarlo como un bloque separado de Pong y Bola. Así, en nuestro proyecto de FlashDevelop, dentro de la carpeta "pong", vamos a añadir una subcarpeta (un nuevo paquete) a la que llamaremos "escenario". Y dentro de esta carpeta, creamos nuestra clase Escenario.


Antes de escribir código, vamos a pensar que representa esta clase Escenario y qué queremos hacer con ella.


Ya nombramos el encapsulamiento de la Programación Orientada a Objetos. Pero vamos a definirlo bien para que no nos perdamos.


¿Qué es el encapsulamiento?

En POO, es el proceso por el que ocultamos la implementación interna de una clase (o concepto) y sólo podemos acceder y modificar su estado por métodos definidos por la propia clase. Es decir, sabemos como interactuar con él y lo que podemos esperar que haga, pero no sabemos "cómo funciona por dentro".


Lo que queremos es encapsular el Escenario (bueno, el Escenario y muchas cosas más...) y que sea él quién se encargue de colocar los pongs, de controlar las colisiones y los marcadores y de otras muchas cosas que ya veremos. Y queremos que lo haga bien. Y queremos que desde cualquier otro sitio (la clase Main, por ahora), cuándo un objeto de la clase Escenario sea creado todo funcione correctamente. Creamos el Escenario y nos olvidamos.


Bien, pues pensemos qué tareas tendrá asignada nuestra maravillosa clase Escenario:


- Creación y colocación de los pongs, bolas y otros objetos.


- Control de colisiones.


- Control de puntuación.


Bueno, esto de momento.


Control de colisiones: Colisionadores y colisionables

Hasta ahora, el control de colisiones había sido muy sencillo. Le decíamos a la bola con que cosas podía colisionar y ella se encargaba de ver si chocaba o no. Esta información se le transmitíamos a la bola en su creación. Pero hacerlo así tiene una consecuencia: el número de objetos con los que la bola podía colsionar no puede aumentar ni disminuir. Y, aunque de momento esto no sucede, sucederá. Y en previsión de ello, en nuestro escenario vamos a definir dos tipos de objetos:


- Colisionadores: aquellos objetos que pueden lanzarse a la búsqueda del choque de otros objetos. Ej: la bola, misiles lanzados desde los pongs, etc.


- Colisionables: objetos que esperan ser embestidos por los objetos colisionadores. Ej: los pongs, los ítems que aparezcan en el escenario, etc.


Y ahora llega el gran problema: ¿cómo le decimos a ActionScript 3 que un pong es un colisionable y una bola un colisionador? Una solución factible podría ser definir dos clases: Colisionadores y Colisionables, y que cada una de las clases necesarias heredase de su respectiva clase padre.


Lamentablemente, esta solución no es viable debido a que ActionScript 3 no soporta la herencia múltiple (heredar de varias clases simultáneamente) como por ejemplo C++. Sin embargo existe una solución parecida, que nos servirá perfectamente para la realización de nuestro propósito: las interfaces.


¿Qué es una interfaz?

Simplificando mucho, una interfaz es un conjunto de métodos sin cuerpo (sin definición). Se dice que una clase implementa una interfaz cuándo define todas sus funciones.


Para concretizar un poco más, utilicemos un ejemplo. Por ejemplo, supongamos que tenemos una interfaz Volador. Y esta interfaz define los siguientes métodos: despegar, volar y aterrizar.


Y ahora tenemos dos clases: Pájaro y Avión. Nuestro propósito es crear una clase Cielo que contenga todo tipo de objetos voladores, pero a la clase Cielo no le interesa saber si esos objetos son pájaros o aviones. Sólo que son voladores. Y quiere tratarlos a todos de la misma manera. Por ello tendrá un conjunto de objetos que implementan la interfaz Volador.


Así, Pájaro tendrá sus propios métodos y atributos: comer, limpiarse, y además implementará la interfaz Volador, definiendo despegar, volar y aterrizar. Lo mismo sucederá con Avión, que tendrá sus métodos y atributos propios, pero que también definirá despegar, volar y aterrizar.


Y es importante destacar que la implmentación de despegar en Pájaro es distinta que la de Avión. Un pájaro despega de manera distinta a cómo lo hace un avión. Sí. Pero los dos pueden despegar, porque los dos son voladores. En este punto es dónde radica la potencia de las interfaces.


Definición de interfaces en ActionScript 3

public interface INombre
{
function metodo1([parámetros1]):tipo_devol1;
function metdoo2([parámetros2]):tipo_devol2;
...
}

Las interfaces no admiten atributos. Por una convención no escrita (o quizá sí) todas los nombres de nuestras interfaces comenzarán con "I", por aquello de Interfaz.


Pasemos, por fin, a escribir el código para nuestro escenario y vemos mejor, también, todo esto de las interfaces.








Anterior Índice Siguiente

FASE II: Objetivos

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



Resultado al finalizar la Fase II

Objetivos

- Creación de un escenario de fondo.


- Introducción de marcadores de puntuación.


- Arreglar algunos bugs.

Para realizar estos cambios vamos a introducir un poco (pero muy poco) de Ingeniería del Software, estructurando el código de nuestro juego de manera que en el futuro nos resulte lo más sencillo posible introducir cambios.


En la Programación Orientada a Objetos son comunes los diagramas UML. Esquemas gráficos en los que podemos entender de un vistazo como está estrucutrada nuestra aplicación. El diagrama UML (simplificado) que define los objetivos que nos hemos marcado en esta fase es:



No vamos a hacer un estudio en profundidad en UML, pero sí a definir las bases.


- Las clases se definen dentro de cajas. Así, en el diagrama aparecen las clases que ya teníamos en el pasado (Main, Pong, Bola) y algunas nuevas (Escenario, CampoFutbol, Marcador).


- Las agregaciones se definen con un diamente conectado a las componentes de la agregación. En Escenario, por ejemplo, tenemos un diamente al que se conectan Marcador, Pong y Bola. Esto significa que, entre otras cosas, el Escenario se compondrá de marcadores, pongs y bolas (sin especificar el número).


- Las generalizaciones (o herencia) se define con una flecha que va desde la clase más concreta a la clase más general. En esencia, CampoFutbol es una clase hija de Escenario. Hereda todas sus características y además añadirá otras propias de un campo de fútbol.


Echando un vistazo al diagrama UML podemos sacar en claro que ahora será la clase Escenario la que se encargue de interactuar con bolas, pongs y marcadores, y la clase Main, simplmente, se encargará de crear un Escenario, sin preocuparse de cómo funciona por dentro. Está separación (o encapsulación) nos será muy útil, por ejemplo, a la hora de meter un menú o una pantalla de inicio en nuestro juego. Bastará con que la clase Main llame al Menú, y el Menú al Escenario.


CampoFutbol será uno de los primeros escenarios que creemos para nuestro Pong.








Anterior Índice Siguiente

Integrar JavaScript y XML: un ejemplo sencillo, pero potente

Vamos a mostrar, a través de un ejemplo, la sencilla manera en la que podemos integrar JavaScript y XML.


Supongamos que queremos incluir en nuestra web un apartado que contenga una lista de hipervínculos (o links). El problema es que esta lista va a variar continuamente y no queremos andar modificando el HTML cada vez.


La solución consiste en tener la lista de hipervínculos en un archivo XML externo y cargar la lista dinámicamente ayudados de JavaScript y su parser XML.


Nuestro archivo XML tiene el siguiente formato:


<?xml version="1.0" encoding="iso-8859-1"?>
<lista>
<link>
<titulo>Google</titulo>
<href>http://www.google.es</href>
</link>
<link>
<titulo>Metafísica de Costumbres</titulo>
<href>http://metafisicadecostumbres.blogspot.com</href>
</link>
<link>
<titulo>Metafísica Informática</titulo>
<href>http://metafisicainformatica.blogspot.com</href>
</link>
</lista>

Un detalle importante es la cabecera XML. Necesitamos especificar encoding="iso-8859-1" para evitar problemas con las tildes y las ñ. De no hacerlo, nos aparecerían símbolos raros en lugar de ñ y vocales acentuadas.


El código JavaScript que necesitamos para resolver nuestro problema es el siguiente:


<html>
<body>
<div id="links"></div>
<script type="text/javascript">
// En la variable div_links obtenemos el contenedor div con el id 'links'
var div_links = document.getElementById('links');

var xmlDoc = cargarXMLDoc('links.xml');
if (xmlDoc != null)
{

// Obtenemos la lista de links
var links_tag = xmlDoc.getElementsByTagName("lista")[0].getElementsByTagName("link");

for (var i = 0; i < links_tag.length; i++)
{
// Obtenemos el título del link
var titulo = links_tag[i].getElementsByTagName("titulo")[0].childNodes[0].nodeValue;

// Obtenemos el hipervínculo del link
var href = links_tag[i].getElementsByTagName("href")[0].childNodes[0].nodeValue;

// Modificamos el contenido html del contenedor div
div_links.innerHTML += "<p><a href=" + href + ">" + titulo + "</a></p>";
}
}

function cargarXMLDoc(archivoXML)
{
var xmlDoc;

if (window.XMLHttpRequest)
{
xmlDoc = new window.XMLHttpRequest();
xmlDoc.open("GET", archivoXML, false);
xmlDoc.send("");
return xmlDoc.responseXML;
}
// para IE 5 y IE 6
else if (ActiveXObject("Microsoft.XMLDOM"))
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.load(archivoXML);
return xmlDoc;
}
alert("Error cargando el documento.");
return null;
}
</script>
</body>
</html>

Puedes encontrar información detallada sobre el parser XML (en inglés) aquí.


Fase I: Conclusión

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


Fase I: Conclusión

Hemos llegado al final de la Fase I.


Puedes descargar aquí los archivos fuente del proyecto.


También me gustaría utilizar este post a modo de feedback, y que en los comentarios dejarais vuestra opinión sobre lo que llevamos de guía. Sugerencias de mejora, si hay cosas poco (o mal) explicadas, fallos, e incluso vuestras propias versiones del juego.


A partir de aquí, puedes continuar con la Fase II de esta guía, en la que introduciremos una serie de mejoras.








Anterior Índice Siguiente

Tutorial ActionScript 3 - Juego Pong - Principiantes

ActionScript 3.0 - Guía para principiantes


Objetivo

Click en la ventana Flash para jugar. Controles: w, s, flecha arriba, flecha abajo.



¿De qué va la Guía?

Bienvenidos al primer curso de esta Metafísica Informática: ActionScript 3.0 - Guía para principiantes.


ActionScript 3.0 es un poderosísimo lenguaje de script integrado en Flash y que permite realizar todo tipo de virguerías, desde películas de animación hasta juegos realmente complejos.


¿Cuál es el objetivo?

El enfoque de la guía será doble:


  • Teórico: Aprender y entender los pormenores de ActionScript 3.0 y los conceptos básicos (y no tan básicos) de la Programación Orientadas a Objetos.

  • Práctico: Aplicar la teoría en un proyecto práctico: El desarrollo desde cero del clásico juego Pong. Un juego, en esencia, bastante simple (en su desarrollo y juego), pero al que iremos añadiendo difrentes items y funcionalidades para darle mayor gracia.


¿Qué necesito saber para seguirla?

No vendría mal algo de experiencia con la parte gráfica de Flash: trabajo con capas, formas, colores, dregadados... Aunque si no es el caso, no importa demasiado. Cuándo utilicemos Flash para desarrollar la parte visual de nuestro Pong, se procurará explicar detalladamente todos los pasos.


Respecto a conocimientos de programación, lo básico: Qué es una variable, un bucle, una condición...


No es necesario ningún conocimiento sobre Programación Orientada a Objetos.


Últimas indicaciones

No necesitas haber trabajado con la versión antigua de ActionScript, ActionScript 2. Aunque comparten puntos comunes, ActionScript 2 y ActionScript 3 son lenguajes que trabajan de manera distinta.


La guía se va a realizar sobre Flash CS4, aunque en principio puede seguirse con cualquier otra versión de Flash que admita ActionScript 3.


Algo importante que quiero recalcar es que esta guía está escrita de aprendiz a aprendiz. Mi trabajo con ActionScript 3 y Flash es relativamente reciente, y cualquier comentario o corrección será bien recibido.



Puedes comenzar la guía de ActionScript 3, o echar un vistazo al índice que viene a continuación.
Índice




  1. Primeros pasos

    1. Lo primero, organización

    2. Introducción. FlashDevelop: organizando nuestros archivos .as. Jerarquía de carpetas

    3. La clase del Documento

    4. ¿Qué es la clase del Documento? Creando la clase del documento. Comentarios en AS3. Conectando nuestro Flash con Main.as.


  2. ActionScript y la Programación Orientada a Objetos.

    1. Concepto de clase. Clases en ActionScript 3

    2. Un mundo lleno de objetos... Concepto de Clase. ¿Qué es un atributo? ¿Qué es un método? ¿Cuál es la diferencia entre Clase y Objeto? Clases en ActionScript 3. Definición de paquetes. Definición de clases. Añadiendo atributos y métodos a nuestra clase Pong. Definición de variables. Visibilidad de atributos y funciones. Definición de atributos. Definición de funciones. El constructor de la clase. Creando un objeto de la clase Pong.

    3. Herencia en ActionScript 3

    4. Diseño gráfico de nuestro pong. Enlazando la clase Pong con su representación gráfica. Añadiendo el pong a la escena. ¿Cuál es la idea básica tras la herencia?


  3. Finalizando nuestro Pong básico

    1. Controlando el pong desde teclado

    2. Colocando los pongs. ¿Cómo accedo a los métodos y atributos de un objeto? Controlando desde el teclado nuestros pongs. ¿Qué es un evento?

    3. Movimiento avanzado

    4. Control de velocidad realista. Acotando los límites del movimiento.

    5. Detección de colisiones

    6. La clase Bola. Detección de colisiones. ¿Cómo detecto colisiones en ActionScript?. Toques finales.


  4. Conclusión y archivos fuente Pong 1.0



FASE II: Pong 1.1 - Pong con escenarios y marcadores



  1. Objetivos

  2. Creando el Escenario

    1. ¿Qué queremos que haga nuestro escenario?

    2. Definición del Escenario. ¿Qué es el encapsulamiento? Control de colisiones: Colisionadores y colisionables. ¿Qué es una interfaz? Definición de interfaces en ActionScript 3.

    3. La Bola y el Pong como IColisionador e IColisionable

    4. Un pequeño problema... IColisionable e IColisionador. Bola como IColisionador. Pong como IColisionable.

    5. El código del Escenario
      La nueva clase Main. El código del Escenario. creaPongs, creaBolas, creaEscenario, bucle. La bola que colisiona...



  3. Bug (I): La Bola loca

  4. Añadiendo los marcadores

    1. Produciendo y detectando los goles

    2. ¡Gol! ¿Qué es un evento? Definición de GolEvent. ¿Qué es un atributo estático (static) o de clase? ¿Para qué sirve la palabra clave "super"? ¿Quién produce el GolEvent? ¿Quién detecta el GolEvent?

    3. Añadiendo los marcadores

    4. Nuestro marcador Flash. Código del marcador. Añadiendo los marcadores al Escenario

    5. Un campo de fútbol

    6. La clase CampoFutbol


  5. Conclusiones y archivos fuente del Pong 1.1


3.III. Detección de colisiones

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

Resultado al final del capítulo (Click sobre Flash para jugar; Controles: w, s, flecha arriba, flecha abajo):




La clase Bola


Algo fundamental para que nuestro juego tenga algo de sentido es una bola que rebote de pong en pong. Breve recordatorio (y último detallado) de los pasos a seguir a la hora de crear una nueva clase que tenga una representación gráfica:


1) Creación de la clase ActionScript: Con nuestro proyecto Pong abierto en FlashDevelop, click derecho en la carpeta pong -> Add... -> New Class... -> Bola.as. Y para empezar, escribimos el siguiente código en el archivo:


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

/**
* ...
* @author ASL
*/
public class Bola extends MovieClip
{
private var vx:Number = 5;
private var vy:Number = 0;
private var max_velocidad:Number = 2;

private var stageRef:Stage;

public function Bola(stage:Stage)
{
stageRef = stage;

addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
iniciaPosicion();
}

public function iniciaPosicion()
{
x = stageRef.stageWidth / 2;
y = stageRef.stageHeight / 2;
}

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();
vx = 5;
vy = 0;
}
else if (x > stageRef.stageWidth)
{
iniciaPosicion();
vx = -5;
vy = 0;
}
}

}

}


Espero que no resulte demasiado extraño al lector el código escrito para la clase Bola. Tiene bastantes puntos comunes con el código de la clase Pong. Al igual que los pongs, la bola también tiene una velocidad, aunque esta vez formada por dos componentes (ya que puede moverse hacia todas partes), la del eje x "vx" y la del eje y "vy". También una velocidad máxima "max_velocidad", que utilizaremos en el futuro para "calibrar" su velocidad.


El último bloque de la función loop (que es la función que se ejecuta cada vez que comienza un nuevo frame) controla que la bola no salga de los límites del eje x de la película, devolviendo a ésta al centro del campo.


2) Creación del símbolo Flash: En la ventana Flash -> Insertar -> Nuevo símbolo... y utilizamos las siguientes opciones:



y para el símbolo dibujamos un simple círculo blanco (ya habrá tiempo para mejorar la apariencia de nuestro juego) con las propiedades de la imagen:


Para añadir nuestra bola al juego añadimos lo siguiente en la función Main:

...
var bola:Bola = new Bola(stage);
addChild(bola);
...


Empiezo a ir un poco rápido porque entiendo que después de todo el rollo soltado en capítulos anteriores, el lector entiende más o menos todo lo que estamos haciendo. Si queda cualquier duda, siempre puedes preguntarla en los comentarios.


Bien, si ejecutamos nuestro Flash veremos nuestra bola moviéndose de un lado otro sin mucho sentido. Ahora, lo que necesitamos, es que colisione con los pongs.


Detección de colisiones


En el problema de las colisiones entre objetos es uno de los problemas tradicionales en la programación de cualquier tipo de videojuego. Son muchas las maneras en las que puede ser abordado y resuelto, y en esta guía he optado por una en particular, que para este caso y desde mi punto de vista, es la más sencillo y adecuado.


La técnica consiste en definir un área, dentro del objeto, que sea "capaz de colisionar". Es decir, que aunque gráficamente dos objetos estén superpuestos, sólo se produzca colisión si sus áreas de colisión están superpuestas.


Las colisiones, de manera gráfica (la zona más oscurá representa la zona de colisión de cada objeto):



En la primera, obviamente, no hay colisión. En la segunda, la bola está sobre el gráfico que representa el pong, pero no sobre su zona de colisión. En la última, sendas zonas de colisiones se superponen, produciéndose el choque.


¿Y cómo vamos a representar esta zona de colisiones? Pues, saliéndonos un poco (muy poco, ¿eh?) de aquello que dijimos de que "todo aquello que pueda hacerse desde ActionScript será hecho desde ActionScript".


Nuestra zona de colisiones va a ser un nuevo símbolo al que vamos a llamar "HitBox" cuyo único contenido va a ser un cuadrado sin borde y de un color semitransparente. Este símbolo no va a estar asociado a ninguna clase ActionScript.



Ahora necesitamos definar la zona de colisión en los símbolos para la bola y el pong.


1) En la ventana de Biblioteca (Ctrl+L), click derecho en el símbolo Pong -> Edición... Crea una nueva capa llamada hitBox y selecciónala. Ahora arrastra desde la biblioteca un símbolo "HitBox" hasta el centro del Pong.


2) Con la herramienta de transformación libre (Q) ajustamos el cuadrado a la zona de colisión buscada.


3) Nombramos a la instancia del símbolo como "hitBox". Este paso es muy importante, porque será el que nos dé acceso a la zona de colisión desde el código ActionScript.


Seguimos el mismo proceso para la bola.


¿Cómo detecto colisiones en ActionScript?
object1.hitTestObject(object2)


Que devuelve true si los objetos object1 y object2 han colisionado.


Vamos a pensar un poco la lógica que queremos detras de nuestras colisiones: De momento, sólo tenemos un objeto que puede colisionar: la bola, y dos objetos sobre los que colisionar, los pongs. Entonces, sólo necesitamos que la bola sepa sobre que objetos puede colisionar. Esto lo vamos a conseguir pasándole un Array (una "lista") con ambos pongs, en la función Main, por supuesto. Nuestra función tendrá este aspecto:


  public function Main()
{
key = new KeyObject(stage);
var colisionadores:Array = new Array();

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

var miPong2:Pong = new Pong(stage, 1, key);
addChild(miPong2);
colisionadores.push(miPong2);

var bola:Bola = new Bola(stage, colisionadores);
addChild(bola);
}


- colisionadores.push(miPong1); Con la función push añadimos un elemento al Array.


En verdad, estamos repitiendo código, y podemos agrupar la creación de pongs en un bucle:


  public function Main()
{
key = new KeyObject(stage);
var colisionadores:Array = new Array();

for (var i:uint; i < 2; i++)
{
var miPong:Pong = new Pong(stage, i, key);
addChild(miPong);
colisionadores.push(miPong);
}

var bola:Bola = new Bola(stage, colisionadores);
addChild(bola);
}


Ahora debemos modificar la clase Bola para que procesa las colisiones. Añadimos un atributo para guardar la lista de colisionadores:


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


Y el procesamiento de colisiones en la función loop:


public function loop(e:Event)
{
for (var i:uint = 0; i < colisionadores.length; i++)
{
if (hitBox.hitTestObject(colisionadores[i].hitBox))
{
vx = -vx;
vy = ((y - colisionadores[i].y) / colisionadores[i].width*2) * max_velocidad;
}
}

x += vx;
y += vy;
...

Y aquí entra en juego el nombre de instancia "hitBox", que fue cómo nombramos a nuestro cuadradito semitransparente. Nombrando la instancia como hitBox conseguimos tener un nuevo atributo en la clase correspondiente que representa nuestra zona de colisión. Por eso realizamos el test sobre ellas.


En hitBox.hitTestObject(colisionadores[i].hitBox) ,el primer "hitBox" está haciendo referencia a la zona de colisión de la bola, y el segundo, al del colisionador, en este caso, cualquiera de los pongs.


Si se produce una colisión, cambiamos las componentes de velocidad de la bola.


Toques finales

Ya podríamos ejecutar nuestro juego pong y sería completamente funcional. Pero vamos a añadir un par de detalles para darle algo de vistosidad:


1) Seleccionar un color totalmente transparante (con Alfa a 0%) para el símbolo HitBox y evitar que aparezca en pantalla.


2) Elegir un color verde para el fondo de la película (puedes hacerlo desde Propiedades del Documento).


3) Ajustar la velocidad de la película a 30 FPS (con el documento seleccionado, en la ventana de propiedades, o en la línea de tiempo). Esta velocidad dará una mejor sensación de fluidez.


4) Y cómo extra, he añadido en una nueva capa una sombra para el Pong, copiando la forma del pong y desplazándola ligeramente, borrando el contorno y dándole un color grisáceo semitransparente.

Hasta aquí el desarrollo de nuestro Pong Básico.


En el siguiente capítulo encontrarás los archivos fuente del proyecto.









Anterior Índice Siguiente

3. II. Movimiento avanzado

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



Control de velocidad realista

Vamos a realizar un control de velocidad basado en términos de aceleración. Es decir, cada vez que pulsemos una tecla, vamos a propulsar nuestro pong, sumándole una aceleración la velocidad que ya tuviere. Además, para que la cosa no se vaya de madre y el pong se vuelva medio loco, vamos a simular fricción con el aire, que frenará nuestro pong. Nuestras variables para el control de velocidad serán:


  // Control de velocidad
private var vy:Number = 0; // Velocidad actual
private var acc:Number = 1; // Aceleración
private var friccion:Number = 0.95; // Fricción con el aire

Inicializar los atributos en su declaración surge el mismo efecto que si los iniciaramos en el constructor de clase.


Para hacer uso de estas nuevos atributos, modificamos la función mover:


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

y += vy;
vy *= friccion;
}

Si probamos la película, veremos que nuestros pongs ahora se desplazan de manera más suave y realista.


Acotando los límites del movimiento

Aún así, seguimos teniendo un problema importante: Cuándo el pong llega a alguno de los límites de película, desaparece. Podemos controlar esto de manera sencilla, añadiendo el siguiente bloque al final de la función mover:


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

Con este trozo de código, controlamos que la posición "y" de nuestro pong (su punto central) no salga de los límites de la película (Recuerda que stageRef era una referencia al escenario principal de la película). Si el pong intenta salir de los límites, restablecemos su posición al límite más cercano y revertimos la dirección de su velocidad, consiguiendo un efecto de rebote bastante resultón.








Anterior Índice Siguiente

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

2.II. Herencia en ActionScript 3

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


Herencia en ActionScript 3


Diseño gráfico de nuestro pong

Antes de explicar el concepto de herencia, vamos a colocar, por fin, nuestro pong en la ventana Flash. Lo primero que necesitamos es una representación gráfica para él. Con la ventana Flash activa: menú Insertar -> Nuevo símbolo... En el diálogo que nos aparece, elegimos como nombre para el símbolo Pong. Y en el tipo, Clip de película. Puedes elegir el diseño que quieras. Aquí, el que he seguido yo:


Creando nuestro pong


1) Dibujamos un rectángulo con la Herramienta Rectángulo (R)


2) Con la herramienta de Tansformación Libre (Q), con Ctrl+Shitf pulsados, clickamos sobre la esquina inferior derecha del rectángulo y arrastramos hacia arriba, hasta conseguir la forma adecuada.


3) Con todo lo que acabamos de dibujar seleccionado, en la ventana Color (Shift + F9) uilizamos los siguientes valores:


Color para el Pong


Un último detalle importante que has de tener en cuenta, es que el gráfico ha de estar lo más centrado posible en el clip de película. Si te fijas, una pequeña cruz es la que marca este centro. Si tu pong no estuviera centrado, trasládalo de modo que su centro (que se marca cuándo tienes seleccionada la Heramienta de Transformación Libre (Q)), quede alineado con el del clip.


Enlazando la clase Pong con su representación gráfica

Nuestro objetivo es que la clase Pong que tenemos definida en el archivo Pong.as esté asociada a la representación gráfica que le hemos creado. Para ello debemos seguir dos pasos:


1) Especificar, en ActionScript, que la clase Pong tiene una representación gráfica en forma de clip de película (MovieClip). Y hacemos esto mediante la herencia de MovieClip en la clase Pong.


package mi.pong 
{

import flash.display.MovieClip;

/**
* ...
* @author ASL
*/
public class Pong extends MovieClip
{
private var vy:int;

public function Pong()
{
trace("¡Objeto de la clase Pong creado!");
}

public function mover():void
{
// Vacío. Aún no sabemos cómo vamos a mover nuestro Pong
}

}

}

- public class Pong extends MovieClip: Aquí estamos especificando que la clase Pong hereda de la clase MovieClip de Flash. Es decir, que Pong, además de tener sus atributos y sus métodos propios, además también hereda todas las características MovieClip, entre ellos, la representación gráfica. MovieClip es la clase que Flash utiliza para las representaciones gráficas con línea temporla.


2) Indicar que el símbolo Pong que hemos creado en Flash está relacionado con la clase Pong del archivo Pong.as. Para ello, en la ventana Biblioteca, click derecho sobre el símbolo Pong -> Propiedades, y en caso de no estar extendido, pulsamos Avanzado. Ahí, selecionamos Exportar para ActionScript. En clase, la clase con la que queremos vincular el símbolo (y su ubicación en su respectivo paquete) "mi.pong.Pong", y en clase base, si no estuviera ya, "flash.display.MovieClip".


Tras estos dos pasos, cada vez que creemos un objeto de la clase Pong, este objeto tendrá asociado el símbolo Pong que creamos al inicio del capítulo.


Añadiendo el pong a la escena

Ahora cobra sentido aquel "extends MovieClip" que añadimos a la clase Main. El documento Flash es, en esencia, un clip de película, un MovieClip.Y, al igual que con el Pong, a este MovieClip podemos asociarle una clase ActionScript, en este caso, Main.


Por ser Main la clase que representa al MovieClip principal de nuestro archivo Flash, es él quién controla todo lo que sucede en la parte gráfica de la aplicación. Por eso debe ser en él en el que añadamos nuestro pong.


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

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

public function Main()
{
var miPong:Pong = new Pong();
addChild(miPong);
miPong.x = stage.stageWidth / 2;
miPong.y = stage.stageHeight / 2;
}

}

}

- addChild(miPong): añadimos a nuestra película miPong.


- miPong.x = stage.stageWidth / 2: "x" es un atributo heredado desde MovieClip. Indica la posición, en el eje de las x, del objeto dentro de la película. stage.stageWidth indica el ancho de la película. Lo dividimos entre 2 para centrar. Análogo para miPong.y.


Si ahora pruebas la película, verás a nuestro Pong, en el centro.


¿Cuál es la idea básica tras la herencia?

La herencia nos permite crear clases generales de las que poder heredar en clases más concretas. Por ejemplo, podríamos tener una clase padre Transporte, con atributos generales como "velocidad" o "precio" y unas clases heredades, más concretas, como Avión, Tren, Automóvil...


Para especificar que una clase hereda de otra utilizamos la palabra clave extends:



public class miClase extends ClasePadre
{
// Definición de clase
}

En capítulos posteriores revisaremos esta definición y todas sus aplicaciones. De momento, nos vale con saber que para poder asociar clases ActionScript con símbolos Flash necesitamos que éstas hereden de MovieClip.








Anterior Índice Siguiente