Comunidad de diseño web y desarrollo en internet

Juegos en Flash: Disparo vertical en Actionscript 3

Esta es la traducción y actualización del tutorial que escribí para Kirupa sobre la creación de un Disparador Vertical en Flash.

Por cierto, cuando hice dicho tutorial, en el 2005, el Disparador Vertical (o Vertical Shooter en inglés) era considerado un género de video-juegos aunque en estos días es considerado una sub-categoría de los disparadores en general.

En este tutorial crearémos un juego en el que un caballero tiene que matar los dragones que aparecen a un lado de la pantalla. Este tutorial es más bien una explicación del código y no el desarrollo completo del juego, o sea, no tendrá 'vidas' ni muchas otras cosas necesarias para que sea un juego completo.

El siguiente flash es un ejemplo del disparador vertical que haremos. Haz clic dentro de la animación para que tenga el foco. Usa las teclas izquierda y derecha para mover al caballero, y presiona la tecla arriba para disparar flechas.



Preparación de los gráficos


En flash, crearemos un .fla que utilizaremos solamente para guardar los assets; escribiremos todo el código en una clase externa. Este tutorial no es sobre programación avanzada por lo tanto el código que escribiremos será muy simple y facil de entender.

Necesitamos 3 assets, un dragón, una flecha y el caballero. Agregaremos un fondo pero solo para que el juego en este tutorial no se vea tan aburrido.

El dragón lo hice en Macromedia Freehand MX (si, es algo viejo) y he lo he importado a Flash, al archivo que tendrá nuestros assets. Este es un acercamiento a dicho dragón:


Ya que fue creado originalmente usando vectores, este acercamiento permite ver algunos detalles. En flash, lo he convertido en MovieClip y sus coordenadas son 0, 0 conocido como 'el orígen' (cero en x y cero en y).

La flecha la dibujé en Adobe Photoshop y la guardé como .png. Este es un acercamiento a la flecha:


Al igual que el dragón, he convertido la flecha en MovieClip y sus coordenadas son similares, se encuentra en el orígen 0, 0.

El último asset es el caballero. Este es un poco mas complejo ya que esta formado por dos movieclips, el cuerpo y las piernas, eso es porque hay animaciones en ambos y correrán dependiendo de los eventos que sucedan en la aplicación.

Los gráficos tambien fueron creados en Macromedia Freehand MX pero podrian haber sido creados en cualquier otro programa de diseño incluyendo el mismo Flash. Este es un acercamiento al caballero a un 150%:


En la imagen, el movieclip cuerpo está seleccionado, dicho clip tiene como nombre de instancia 'cuerpo', el clip de las piernas tiene como nombre de instancia 'piernas'. Dentro de cada clip hay pequeñas animaciones que simulan movimiento de piernas y del brazo disparando la flecha.

Por el momento lo único que necesitamos en el escenario sera el caballero. Pasemos al código.

Código del Disparador Vertical


Como mencioné antes, el código lo escribiremos en una clase. Podríamos ponerlo en el primer fotograma de flash, pero la idea es evitar poner código en el .fla para promover la separación de ocupaciones, aunque a muchos les de un poco de miedo, usar clases no tiene nada de especial, es solo aprender un par de palabras clave y listo el pollo.
Como ejemplo, podríamos detener la animación de las piernas y la del cuerpo si ponemos esto en la linea del tiempo:

Código :

caballero.cuerpo.stop();
caballero.piernas.stop();

También lo podríamos poner en una función:

Código :

function detenerAnimaciones() : void
{
   caballero.cuerpo.stop();
   caballero.piernas.stop();
}
// y llamar la función despues de declararla:
detenerAnimaciones();

O lo podríamos poner en una clase que es lo que haremos a continuación. En Flash creamos un nuevo documento Actionscript File, dicho documento estará en blanco por defecto.
Este es el código que pondremos:

Código :

package
{
   import flash.display.MovieClip;
   
   public class DisparadorVertical extends MovieClip
   {
      public function DisparadorVertical()
      {
         detenerAnimaciones();
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
   }
}

Package y otras palabras clave están explicadas en esta introducción a ActionScript 3. Todas las clases pertenecen a un package, todas tienen nombre de clase, en este caso la nuestra se llama DisparadorVertical y extiende MovieClip ya que cualquier pelicula flash, su escenario y la linea del tiempo principal, es un MovieClip.

Guardamos nuestra clase como DisparadorVertical.as al lado del .fla y agregamos dicha clase como la 'document class' en las propiedades de la pelicula flash como se muestra a continuación:


Moviendo al Personaje


El movimiento del heroe en los disparadores verticales esta limitado al eje x, o sea, los personajes se mueven horizontalmente. Agreguemos el código para mover al caballero.

Código :

stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);

Es un 'listener' donde el escenario (stage) escucha cuando el jugador presiona cualquier tecla, en dado caso, llamará a la función revisarInteraccion.

Código :

private function revisarInteraccion(evento : KeyboardEvent) : void
{
   if(evento.keyCode == Keyboard.LEFT)
   {
      caballero.x -= 5;
   }
   else if(evento.keyCode == Keyboard.RIGHT)
   {
      caballero.x += 5;
   }
}

La función revisarInteraccion revisa si la tecla presionada es la flecha derecha o izquierda, si es asi, el caballero se movera 5 pixeles en el eje x.

El código actualizado es:

Código :

package
{
   import flash.display.MovieClip;
   import flash.ui.Keyboard;
   import flash.events.KeyboardEvent;
   
   public class DisparadorVertical extends MovieClip
   {
      private const PASOS : uint = 5;
      
      public function DisparadorVertical()
      {
         detenerAnimaciones();
         agregarListeners();
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
      
      private function agregarListeners() : void
      {
         stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);
      }

      private function revisarInteraccion(evento : KeyboardEvent) : void
      {
         if(evento.keyCode == Keyboard.LEFT)
         {
            caballero.x -= PASOS;
         }
         else if(evento.keyCode == Keyboard.RIGHT)
         {
            caballero.x += PASOS;
         }
      }
   }
}

También he agregado la constante PASOS cuyo valor es un numero entero (int) igual a 5. Es el numero de pixeles que el caballero debe moverse. Esto es considerado buena practica ya que al tener el valor declarado en una constante, es facil encontrarlo y actualizarlo.

El juego actualizado quedaría asi:



El caballero se mueve hacia ambos lados, el problema es que no mueve las piernas y se sale del escenario :oops:
Para arreglar esos 'bugs', agreguemos límites y movimiento a las piernas.

Declaramos los límites como constantes:

Código :

private const LIMITE_IZQUIERDA : uint = 0;
private const LIMITE_DERECHA : uint = 475;

Al declarar valores constantes es considerado otra buena practica, que sus nombres sean escritos con letras mayúsculas.
El valor de LIMITE_DERECHA es 475 ya que es el ancho de nuestra pelicula menos el ancho del caballero (550 menos 75 pixeles).

Necesitamos un nuevo listener para que el escenario escuche cuando cualquier tecla es liberada y cuando suceda, llame a una función detenerCaballero para que la animación de las piernas del caballero regresen al fotograma 1.

Código :

stage.addEventListener(KeyboardEvent.KEY_UP, detenerCaballero);

Y la función detenerCaballero es:

Código :

private function detenerCaballero(evento : KeyboardEvent) : void
{
   caballero.piernas.gotoAndStop(1);
}


Agreguemos también los límites.

Código :

private function moverCaballero(distancia : int) : void
{
   var nuevaPosicion : int = caballero.x + distancia;
   if(nuevaPosicion > LIMITE_IZQUIERDA && nuevaPosicion < LIMITE_DERECHA)
   {
      caballero.x += distancia;
      caballero.piernas.play();
   }
   else
   {
      detenerCaballero();
   }
}

La función moverCaballero necesita un número entero como parámetro que es la distancia que nuestro caballero se moverá. Podemos llamarla desde revisarInteraccion y pasarle PASOS como parametro con valor negativo o positivo. Revisamos también si la nueva posición del caballero esta dentro de los limites que hemos declarado y si es asi, movemos al caballero, de lo contrario llamamos a la función detenerCaballero.

Este es el código actualizado:

Código :

package
{
   import flash.display.MovieClip;
   import flash.ui.Keyboard;
   import flash.events.KeyboardEvent;
   
   public class DisparadorVertical extends MovieClip
   {
      private const PASOS : uint = 5;
      private const LIMITE_IZQUIERDA : uint = 0;
      private const LIMITE_DERECHA : uint = 475;
      
      public function DisparadorVertical()
      {
         detenerAnimaciones();
         agregarListeners();
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
      
      private function agregarListeners() : void
      {
         stage.addEventListener(KeyboardEvent.KEY_UP, detenerCaballero);
         stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);      
      }

      private function revisarInteraccion(evento : KeyboardEvent) : void
      {
         if(evento.keyCode == Keyboard.LEFT)
         {
            moverCaballero(-PASOS);
         }
         else if(evento.keyCode == Keyboard.RIGHT)
         {
            moverCaballero(PASOS);
         }
      }
      
      private function moverCaballero(distancia : int) : void
      {
         var nuevaPosicion : int = caballero.x + distancia;
         if(nuevaPosicion > LIMITE_IZQUIERDA && nuevaPosicion < LIMITE_DERECHA)
         {
            caballero.x += distancia;
            caballero.piernas.play();
         }
         else
         {
            detenerCaballero();
         }
      }
      
      private function detenerCaballero(evento : KeyboardEvent = null) : void
      {
         caballero.piernas.gotoAndStop(1);
      }
   }
}


Y este es el juego actualizado:


Agregando el fondo


Como mencioné antes, para que el ejemplo no se vea tan aburrido, le agregaremos un fondo. Al igual que el dragón y el caballero, el fondo esta hecho en Macromedia Freehand MX e importado a flash como vectores, aunque en este caso no es necesario convertirlo a MovieClip, con solo agregarlo a nuestra película es suficiente. Para que cubra toda la película, el tamaño es 550 x 400 pixeles y sus coordenadas son 0, 0.



Disparando


Nuestro personaje se mueve hacia la izquierda y hacia la derecha sin salirse del escenario. El caballero ya esta listo para hacer su siguiente tarea: disparar flechas.

Antes de seguir con el código, veamos un poco de historia. El Disparador Vertical es una de las categorias de video-juegos mas viejas que existen y en aquellas doradas épocas cuando los video-juegos nacieron, las consolas tenían muchisimas limitaciones y por ende no era posible disparar mas de una sola vez cada cierto tiempo, usualmente se esperaba a que el disparo llegase al final de la pantalla o que le pegara a algo para que el jugador pudiese disparar nuevamente.

Siguiendo con el desarrollo del juego, he agregado una instancia de la flecha en el escenario y le he puesto 'flecha' como nombre de instancia. El código lo adaptaremos un poco para que el caballero dispare la flecha:

Código :

package
{
   import flash.display.MovieClip;
   import flash.ui.Keyboard;
   import flash.events.KeyboardEvent;
   import fl.transitions.Tween;
   import fl.transitions.easing.*;
   
   public class DisparadorVertical extends MovieClip
   {
      private var estaDisparando : Boolean;
      
      private const PASOS : uint = 5;
      private const ANCHO_DEL_CABALLERO : uint = 75;
      private const LIMITE_IZQUIERDA : uint = 0;
      private const LIMITE_DERECHA : uint = 475;
      
      public function DisparadorVertical()
      {
         detenerAnimaciones();
         agregarListeners();
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
      
      private function agregarListeners() : void
      {
         stage.addEventListener(KeyboardEvent.KEY_UP, detenerCaballero);
         stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);      
      }

      private function revisarInteraccion(evento : KeyboardEvent) : void
      {
         if(evento.keyCode == Keyboard.LEFT)
         {
            moverCaballero(-PASOS);
         }
         else if(evento.keyCode == Keyboard.RIGHT)
         {
            moverCaballero(PASOS);
         }
         if(evento.keyCode == Keyboard.UP)
         {
            if(!estaDisparando) dispararFlecha();
         }
      }
      
      private function moverCaballero(distancia : int) : void
      {
         var nuevaPosicion : int = caballero.x + distancia;
         if(nuevaPosicion > LIMITE_IZQUIERDA && nuevaPosicion < LIMITE_DERECHA)
         {
            caballero.x += distancia;
            caballero.piernas.play();
         }
         else
         {
            detenerCaballero();
         }
      }
      
      private function detenerCaballero(evento : KeyboardEvent = null) : void
      {
         caballero.piernas.gotoAndStop(1);
      }
      
      private function dispararFlecha() : void
      {
         estaDisparando = true;
         caballero.cuerpo.play();
         flecha.x = caballero.x + ANCHO_DEL_CABALLERO / 2;
         var disparar : Tween = new Tween(flecha, "y", None.easeIn, caballero.y, -50, 1, true);
      }
   }
}

La variable estaDisparando es un booleano, solamente acepta dos valores, true y false (verdadero y falso) y al declararlo es falso por defecto. Esta variable nos ayudará a evitar que el caballero dispare muy seguido.
ANCHO_DEL_CABALLERO es un número entero que nos ayudará a saber donde posicionar la flecha.
La función revisarInteraccion ha sido actualizado para que detecte si la tecla 'arriba' ha sido presionada y si de ser asi, revisa si el caballero no esta disparando para hacerlo disparar.
La función dispararFlecha cambia el valor de estaDisparando para que el caballero no dispare nuevamente. Posiciona la flecha e inicia la animación del disparo y usando la clase Tween, dispara la flecha hacia -50 para que desaparezca de la pantalla.
Para mayor información sobre la clase Tween, te aconsejo leer este tip.



Ahora tenemos otro problema, no es posible disparar mas de una flecha! El código para hacer que el caballero pueda disparar mas flechas es el siguiente:

Código :

package
{
   import flash.display.MovieClip;
   import flash.ui.Keyboard;
   import flash.events.KeyboardEvent;
   import fl.transitions.Tween;
   import fl.transitions.easing.*;
   import fl.transitions.TweenEvent;
   
   public class DisparadorVertical extends MovieClip
   {
      private var estaDisparando : Boolean;
      
      private const PASOS : uint = 5;
      private const ANCHO_DEL_CABALLERO : uint = 75;
      private const LIMITE_IZQUIERDA : uint = 0;
      private const LIMITE_DERECHA : uint = 475;
      
      public function DisparadorVertical()
      {
         detenerAnimaciones();
         agregarListeners();
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
      
      private function agregarListeners() : void
      {
         stage.addEventListener(KeyboardEvent.KEY_UP, detenerCaballero);
         stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);      
      }

      private function revisarInteraccion(evento : KeyboardEvent) : void
      {
         if(evento.keyCode == Keyboard.LEFT)
         {
            moverCaballero(-PASOS);
         }
         else if(evento.keyCode == Keyboard.RIGHT)
         {
            moverCaballero(PASOS);
         }
         if(evento.keyCode == Keyboard.UP)
         {
            if(!estaDisparando) dispararFlecha();
         }
      }
      
      private function moverCaballero(distancia : int) : void
      {
         var nuevaPosicion : int = caballero.x + distancia;
         if(nuevaPosicion > LIMITE_IZQUIERDA && nuevaPosicion < LIMITE_DERECHA)
         {
            caballero.x += distancia;
            caballero.piernas.play();
         }
         else
         {
            detenerCaballero();
         }
      }
      
      private function detenerCaballero(evento : KeyboardEvent = null) : void
      {
         caballero.piernas.gotoAndStop(1);
      }
      
      private function dispararFlecha() : void
      {
         estaDisparando = true;
         caballero.cuerpo.play();
         flecha.x = caballero.x + ANCHO_DEL_CABALLERO / 2;
         var disparar : Tween = new Tween(flecha, "y", None.easeIn, caballero.y, -50, 1, true);
         disparar.addEventListener(TweenEvent.MOTION_FINISH, flechaDisparada);
      }
   
      private function flechaDisparada(evento : TweenEvent) : void
      {
         estaDisparando = false;
      }
   }
}

Por cierto, no son mas flechas, es la misma. Soy un mentiroso :P
Lo único que he agregado es un 'listener' al tween disparar que escucha cuando el movimiento ha terminado y llama a la función flechaDisparada.
La función flechaDisparada solamente regresa el valor del booleano a 'false' que nos permite volver a disparar otra flecha (ok, la misma flecha...).



El Enemigo


Nuestro caballero puede desplazarse y ya dispara flechas pero disparar flechas al aire no tiene mucho sentido. Tenemos que agregar algún enemigo asi que agregaremos un dragón.

Al igual que con el resto de los assets, lo mas recomendable es llamar el asset del dragón desde la biblioteca pero como la idea de este tutorial es hacerlo de una forma básica, basta con poner manualmente una instancia del dragón en el escenario y llamarlo 'dragon'.

Para mover al dragón utilizamos un simple Tween:

Código :

var dragonVolando : Tween = new Tween(dragon, "x", None.easeIn, 550, -dragon.width, 5, true);

Desde 550, que es el ancho de la pelicula, hasta -dragon.width que es el valor negativo del ancho del dragón, o sea, fuera del escenario. Se tardará 5 segundos en cruzar todo el escenario.
Ese código lo pondremos dentro de una función que llamaremos desde el constructor de la clase:

Código :

private function iniciarVuelo(evento : TweenEvent = null) : void
{
   var dragonVolando : Tween = new Tween(dragon, "x", None.easeIn, 550, -dragon.width, 5, true);
}


Para aparentar que un nuevo dragón aparece a la derecha cuando el anterior ha llegado al otro lado, comenzaremos el tween nuevamente llamando a la misma función iniciarVuelo(). Para ello agregamos un listener al tween dragonVolando en el evento MOTION_FINISH, o sea, cuando termine el movimiento.

Código :

dragonVolando.addEventListener(TweenEvent.MOTION_FINISH, iniciarVuelo);


Asi se crea un bucle con el que engañaremos a los jugadores haciendoles creer que hay miles de dragones.

Nuevo problema: el dragón no toma nota de la flecha disparada. Solución: agregar un detector de colisiones, o sea, una función que nos avisará si el dragón ha sido alcanzado por la flecha. El listener lo agregamos también al tween 'dragonVolando', que escuchará cada vez que el dragón se mueva un poco. El MOTION_CHANGE funciona mas o menos como el ENTER_FRAME de un tween.

Código :

dragonVolando.addEventListener(TweenEvent.MOTION_CHANGE, detectarDisparo);

El detector de colisiones se llama detectarDisparo donde usaremos el método hitTestObject para ver si un objeto a chocado con otro.

Código :

private function detectarDisparo(evento : TweenEvent) : void
{
   if(flecha.hitTestObject(dragon))
   {
      iniciarVuelo();
   }
}


Para evitar fugas de memoria innecesarias, declararemos el tween dragonVolando en nuestra clase y no dentro de la función porque sino, una nueva variable dragonVolando va a ser creada cada vez que llamemos a dicha función.

El código final es:

Código :

package
{
   import flash.display.MovieClip;
   import flash.ui.Keyboard;
   import flash.events.KeyboardEvent;
   import fl.transitions.Tween;
   import fl.transitions.easing.*;
   import fl.transitions.TweenEvent;
   
   public class DisparadorVertical extends MovieClip
   {
      private var estaDisparando : Boolean;
      private var dragonVolando : Tween;
      
      private const PASOS : uint = 5;
      private const ANCHO_DEL_CABALLERO : uint = 75;
      private const LIMITE_IZQUIERDA : uint = 0;
      private const LIMITE_DERECHA : uint = 475;
      
      public function DisparadorVertical()
      {
         iniciarVuelo();
         iniciarDragon();
         detenerAnimaciones();
         agregarListeners();
      }
      
      private function iniciarVuelo() : void
      {
         dragonVolando = new Tween(dragon, "x", None.easeIn, 550, -dragon.width, 5, true);
      }
      
      private function iniciarDragon() : void
      {
         dragonVolando.stop();
         moverDragon();
      }
      
      private function moverDragon(evento : TweenEvent = null) : void
      {
         dragonVolando.start();
         dragonVolando.addEventListener(TweenEvent.MOTION_FINISH, moverDragon);
         dragonVolando.addEventListener(TweenEvent.MOTION_CHANGE, detectarDisparo);
      }
      
      private function detectarDisparo(evento : TweenEvent) : void
      {
         if(flecha.hitTestObject(dragon))
         {
            iniciarDragon();
         }
      }
      
      private function detenerAnimaciones() : void
      {
         caballero.cuerpo.stop();
         caballero.piernas.stop();
      }
      
      private function agregarListeners() : void
      {
         stage.addEventListener(KeyboardEvent.KEY_UP, detenerCaballero);
         stage.addEventListener(KeyboardEvent.KEY_DOWN, revisarInteraccion);      
      }

      private function revisarInteraccion(evento : KeyboardEvent) : void
      {
         if(evento.keyCode == Keyboard.LEFT)
         {
            moverCaballero(-PASOS);
         }
         else if(evento.keyCode == Keyboard.RIGHT)
         {
            moverCaballero(PASOS);
         }
         if(evento.keyCode == Keyboard.UP)
         {
            if(!estaDisparando) dispararFlecha();
         }
      }
      
      private function moverCaballero(distancia : int) : void
      {
         var nuevaPosicion : int = caballero.x + distancia;
         if(nuevaPosicion > LIMITE_IZQUIERDA && nuevaPosicion < LIMITE_DERECHA)
         {
            caballero.x += distancia;
            caballero.piernas.play();
         }
         else
         {
            detenerCaballero();
         }
      }
      
      private function detenerCaballero(evento : KeyboardEvent = null) : void
      {
         caballero.piernas.gotoAndStop(1);
      }
      
      private function dispararFlecha() : void
      {
         estaDisparando = true;
         caballero.cuerpo.play();
         flecha.x = caballero.x + ANCHO_DEL_CABALLERO / 2;
         var disparar : Tween = new Tween(flecha, "y", None.easeIn, caballero.y, -50, 1, true);
         disparar.addEventListener(TweenEvent.MOTION_FINISH, flechaDisparada);
      }
      
      private function flechaDisparada(evento : TweenEvent) : void
      {
         estaDisparando = false;
      }
   }
}


Y este es el juego final:


El código, al igual que el juego, se puede mejorar muchisimo por ejemplo usando ENTER_FRAME para hacer que el caballero se mueva de una forma menos rara (en mi navegador se queda quieto por un momento y luego camina) pero como mencioné antes, este tutorial es solo un ejemplo básico de un disparador vertical.

Les dejo como tarea agregar puntuación que, por ejemplo, aumente 5 puntos cada vez que se le pegue al dragón.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate