Comunidad de diseño web y desarrollo en internet online

Mover personajes con teclado en AS3

Existe un error frecuente con el uso del teclado en la creación de juegos en AS3. Lo sé porque me ha ocurrido, y, aunque pueda parecer tonto, hizo que uno de mis juegos no sea tan bueno como pudo haber llegado a ser. Pero, ¿cuál es éste error?

He aquí un ejemplo




Una práctica errónea


El control "malo" es un código que muchos reconoceremos, que se puede poner así:

Código :

private function init(e:Event = null):void 
{
   // Código Automatizado de FlashDevelop...
         
   // Dibuja al círculo...
         
   // Escucha Eventos
               stage.addEventListener( KeyboardEvent.KEY_DOWN, alPresionarTecla );
         
} // Fin de función init

private function alPresionarTecla( k:KeyboardEvent ):void
{
      switch ( k.keyCode ) 
      {
         case Keyboard.UP:
            círculo.y -= velocidad;
            break;
                  
         case Keyboard.DOWN:
            círculo.y += velocidad;
            break;
                  
         case Keyboard.LEFT:
            círculo.x -= velocidad;
            break;
                  
         case Keyboard.RIGHT:
            círculo.x += velocidad;
            break;
               
      } // Fín switch k.keycode

} // Final de función alPresionarTecla
            


Como nos podemos dar cuenta, el personaje se mueve primero un poco, y luego comienza a moverse "uniformemente". y, peor todavía, esto ocurre a diferente velocidad dependiendo de cada computadora, y uno sólo puede moverse en una sola dirección. ¿Por qué ocurre esto? Abre Word o cualquier editor de texto. Presiona una tecla, y déjala presionada. Verás cómo primero se escribe una letra, y luego se empiezan a escribir varias seguidas. ¡Esto es lo que ocurre con nuestro juego!

Trucos para corregir el defecto


Ahora bien, ¿Cómo podemos corregirlo? Este es uno de los trucos:
  1. Creamos una Lista con las teclas que están presionadas.
  2. Cada vez que presionamos una tecla, la añadimos a la Lista si no estaba ya en ella.
  3. Cada vez que dejemos de presionar una tecla, la quitamos de la lista.
  4. En Cada Frame, si está la tecla presionada, movemos el personaje.


El paso cuatro es importante, ya que, si lo hacemos cada frame en vez de cada vez que recibimos un "estímulo" del teclado, la velocidad será la misma para todos los jugadores (Siempre y cuando el framrate sea el mismo).

Éste sería el código:

1) Primero, hacemos nuestra lista, llamada "teclas", que será de la clase "Vector.<int>". Los Vectores son básicamente Arrays con un solo tipo de objetos, en este caso, números enteros ((Recuerda que las teclas tienen un código en un número entero)).

Código :

private var teclas:Vector.<int> = new Vector.<int>();


2) En segundo lugar, añadimos las teclas que vienen de KeybordEvent.KEY_DOWN. Para hacer esto, usamos la función indexOf y push de los Vectores (y Arrays). La primera nos dice el índice del elemento que buscamos, o "-1" si el elemento no está en el Vector. La segunda añade un elemento a un Vector.

Código :

private function alPresionarTecla( k:KeyboardEvent ):void
{      
   if ( teclas.indexOf( k.keyCode ) == -1 )
   {
      teclas.push( k.keyCode );
   }
   
} // Final de función alPresionarTecla



3) En tercer lugar, quitaremos una tecla de la lista cuando es levantada, en otras palabras, cuando recibims el evento de KeyboardEvent.KEY_UP. Para eso, usaremos indexOf de nuevo, pero esta vez con "splice", que quita unos elementos dados sus índices.

Código :

private function alLevantarTecla( k:KeyboardEvent ):void
{
   teclas.splice( teclas.indexOf( k.keyCode ), 1 );
      
} // Final de función alLevantarTecla


4) Usamos la lista en cada frame para mover el círculo. Para esto, volvemos a usar indexOf para saber si está o no está una tecla en la lista.

Código :

private function alEntrarAFrame( e:Event ):void
{      
   // Mueve al círculo si la tecla eestá presionada
   if ( teclas.indexOf( Keyboard.UP    ) != -1 ) { círculo.y -= velocidad; }
   if ( teclas.indexOf( Keyboard.DOWN  ) != -1 ) { círculo.y += velocidad; }
   if ( teclas.indexOf( Keyboard.LEFT  ) != -1 ) { círculo.x -= velocidad; }
   if ( teclas.indexOf( Keyboard.RIGHT ) != -1 ) { círculo.x += velocidad; }
   
} // Final de función alEntrarAFrame


5) Hay que recordar escuchar los eventos!

Código :

private function init(e:Event = null):void 
{
        // Código Automatizado de Flash Develop
   
   // Dibuja al círculo
   
   // Escucha Eventos
   this .addEventListener( Event.ENTER_FRAME,      alEntrarAFrame   );
   stage.addEventListener( KeyboardEvent.KEY_DOWN, alPresionarTecla );
   stage.addEventListener( KeyboardEvent.KEY_UP,   alLevantarTecla  );
   
} // Fin de función init


Para concluir


Me gustaría comentar que no hay que usar este código para todos los casos. Por ejemplo, si es para una cosa puntual (como atacar, saltar((en algunos juegos)), poner pausa o enmudecer el juego), este código es más contraproducente que beneficioso, ya que pondría pausa y la quitaría alternativamente a un ritmo incontrolable, por ejemplo. Es importante saber, entonces, cuándo usar este código, y cuándo usar el de siempre.

Aquí está el código completo. Acabo de comentar todo a excepción de los modos (versión buena o mala), cómo dibujar el círculo y cómo crear el botón (y cambiar de modo al hacerle click), lo cual debería ser o fácil de entender, o quizá ya haya sido explicado en otro tutorial.

Código :

package 
{
   import flash.display.Shape;
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.events.KeyboardEvent;
   import flash.events.MouseEvent;
   import flash.text.TextField;
   import flash.ui.Keyboard;
   
   /**
    * ...
    * @author Agecaf
    */
   public class Main extends Sprite 
   {
      // Base
      private var modo:int = 0;
      
      // Gráficos
      private var círculo:Shape;
      private var botón:TextField;
      
      // Movimiento
      private var velocidad:int = 5;
      
      // Teclas
      private var teclas:Vector.<int> = new Vector.<int>();
      
      
      public function Main():void 
      {
         if (stage) init();
         else addEventListener(Event.ADDED_TO_STAGE, init);
         
      } // Fin de constructor
      
      private function init(e:Event = null):void 
      {
         removeEventListener(Event.ADDED_TO_STAGE, init);
         // entry point
         
         // Dibuja al círculo
         círculo = new Shape();
         círculo.graphics.beginFill( 0x000000 );
         círculo.graphics.drawCircle(0, 0, 30);
         círculo.graphics.endFill();
         círculo.x = 200;
         círculo.y = 200;
         addChild( círculo );
         
         // Crea Botón
         botón = new TextField();
         botón.text = "Cambiar!";
         botón.scaleX = 4;
         botón.scaleY = 4;
         botón.selectable = false;
         botón.x = 250;
         botón.y = 300;
         addChild( botón );
         botón.addEventListener( MouseEvent.CLICK, alCambiarDeModo );
         
         // Escucha Eventos
         this .addEventListener( Event.ENTER_FRAME,      alEntrarAFrame   );
         stage.addEventListener( KeyboardEvent.KEY_DOWN, alPresionarTecla );
         stage.addEventListener( KeyboardEvent.KEY_UP,   alLevantarTecla  );
         
      } // Fin de función init

      private function alEntrarAFrame( e:Event ):void
      {
         if ( modo == 1 ) { // BUENO!!!
            
            // Mueve al círculo si la tecla está presionada
            if ( teclas.indexOf( Keyboard.UP    ) != -1 ) { círculo.y -= velocidad; }
            if ( teclas.indexOf( Keyboard.DOWN  ) != -1 ) { círculo.y += velocidad; }
            if ( teclas.indexOf( Keyboard.LEFT  ) != -1 ) { círculo.x -= velocidad; }
            if ( teclas.indexOf( Keyboard.RIGHT ) != -1 ) { círculo.x += velocidad; }
            
         } // Fín de "si modo igual a 1"
         
      } // Final de función alEntrarAFrame
      
      private function alPresionarTecla( k:KeyboardEvent ):void
      {
         if ( modo == 0 ) { // MALO!!!
            switch ( k.keyCode ) 
            {
               case Keyboard.UP:
                  círculo.y -= velocidad;
                  break;
                  
               case Keyboard.DOWN:
                  círculo.y += velocidad;
                  break;
                  
               case Keyboard.LEFT:
                  círculo.x -= velocidad;
                  break;
                  
               case Keyboard.RIGHT:
                  círculo.x += velocidad;
                  break;
               
            } // Fín switch k.keycode
            
         } // Fín "si modo igual a 0"
         
         if ( modo == 1 ) { // BUENO!!!
            
            if ( teclas.indexOf( k.keyCode ) == -1 )
            {
               teclas.push( k.keyCode );
            }
            
         } // Fín "si modo igual a 1"
         
      } // Final de función alPresionarTecla
      
      private function alLevantarTecla( k:KeyboardEvent ):void
      {
         if ( modo == 1 ) { // BUENO!!!
            
            teclas.splice( teclas.indexOf( k.keyCode ), 1 );
            
         } // Fín de "si modo igual a 1"
         
      } // Final de función alLevantarTecla
      
      private function alCambiarDeModo( e:MouseEvent ):void
      {
         // Cambia de Modo
         if ( modo == 0 ) modo = 1;
         else if ( modo == 1 ) modo = 0;
         
         // Limpia Teclas
         teclas.length = 0;
         
         // Mueve el círculo
         círculo.x = 200;
         círculo.y = 200;
         
      } // Final de función alCambiarDeModo
      
   } // Fin de clase
   
} // Fin de package

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

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