Desarrollo de Juegos 2D con HTML5: Manipulación del Tiempo, Parte 1

Implementación de salto con movimiento lineal

En esta serie, el experto en HTML5 David Geary muestra cómo implementar un videojuego 2D en HTML5 paso a paso. En la primera de dos entregas consecutivas, usted implementará el comportamiento de salto del sprite de la corredora.

David Geary, Autor y conferencista, Clarity Training, Inc.

David GearyEl autor de Core HTML5 Canvas, David Geary, es también el cofundador del Grupo de usuarios de HTML5 de Denver y autor de ocho libros sobre Java, que incluyen los best sellers Swing y JavaServer Faces. David es disertante frecuente en conferencias, entre ellas JavaOne, Devoxx, Strange Loop, NDC y OSCON, y ganó el premio JavaOne Rock Star tres veces. Escribió la serie de artículosJSF 2 fu y GWT fu para developerWorks. Puede seguir a David en Twitter en @davidgeary.



13-05-2013

En el último artículo de esta serie, me ocupé de cómo encapsular las acciones que llevan a cabo los sprites — como correr, caerse, caminar o explotar— en objetos acoplables conocidos como comportamientos. Durante la ejecución, es posible adornar fácilmente un sprite con el conjunto de comportamientos que desee. Entre los diversos beneficios, la flexibilidad fomenta la exploración de aspectos del juego que de otra manera permanecerían inactivos.

En este artículo, continuo con los comportamientos de sprites, con un par de variantes. En principio, este es el primero de dos artículos consecutivos de la serie dedicada a un solo comportamiento de sprite: el salto de la corredora. Al final de "Manipulación del Tiempo, Parte 2," Snail Bait llegará a la secuencia natural de salto ilustrada en la Figura 1:

Figura 1. Secuencia de salto natural
Screen capture of Snail Bait showing the runner's natural jump sequence

Aplazamiento de la detección de colisión

Estoy aplazando el tratamiento de la detección de colisión de Snail Bait para concentrarme en el movimiento de la corredora mientras está saltando. Con la detección de colisión detectada, la corredora aterriza en plataformas, y por lo tanto interrumpe el salto. Sin la detección de colisión, los saltos continúan hasta completarse. Para obtener el efecto completo del salto, descargue el código del artículo y pruébelo usted mismo.

Segundo, el comportamiento de salto, a diferencia de los comportamientos que desarrollé enel artículo anterior, no se repite indefinidamente. Debido a esa sencilla diferencia, Snail Bait debe realizar un seguimiento del tiempo mientras se desarrollan los saltos. Ese requisito implica la necesidad de algo similar a un cronómetro, por lo que implementaré un cronómetro de JavaScript y lo usaré para calcular el ascenso y el descenso de la corredora cuando salta.

Pistas y cimas de las plataformas

Las plataformas de Snail Bait se mueven horizontalmente sobre tres pistas, como se muestra en la Figura 2:

Figura 2. Pistas de la plataforma
Screen capture showing Snail Bait's three platform tracks

El espacio entre las plataformas es de 100 píxeles. Esto le da a la corredora, cuya altura es de 60 píxeles, espacio más que suficiente para maniobrar.

El Listado 1 muestra cómo Snail Bait establece la altura de la corredora y las posiciones verticales de la plataforma. También detalla un método de conveniencia— calculatePlatformTop()— que, dada una pista (1, 2 o 3), devuelve la línea de base correspondiente de la pista.

Listado 1. Cómo calcular cimas de plataformas en función de líneas de base de pista
var SnailBait = function () {
   // Height of the runner's animation cells:

   this.RUNNER_CELLS_HEIGHT = 60, // pixels

   // Track baselines:

   this.TRACK_1_BASELINE = 323, // pixels
   this.TRACK_2_BASELINE = 223,
   this.TRACK_3_BASELINE = 123,
   ...
};
...

SnailBait.prototype = {
   ...
   calculatePlatformTop: function (track) {
      var top;
   
      if      (track === 1) { top = this.TRACK_1_BASELINE; }
      else if (track === 2) { top = this.TRACK_2_BASELINE; }
      else if (track === 3) { top = this.TRACK_3_BASELINE; }

      return top;
   ...
};

Snail Bait usa calculatePlatformTop() para posicionar casi todos los sprites del juego.


Implementación del salto inicial

Como se implementó al final del último artículo, Snail Bait cuenta con el algoritmo más simple para el salto, como se muestra en el Listado 2:

Listado 2. Manejo del teclado para saltos
window.onkeydown = function (e) {
   var key = e.keyCode;
   ...
   
   if (key === 74) { // 'j'
      if (snailBait.runner.track === 3) { // At the top; nowhere to go
         return;
      }

      snailBait.runner.track++;
      snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) -
                              snailBait.RUNNER_CELLS_HEIGHT;
   }
};
...

Cuando el jugador presiona la tecla j , Snail Bait inmediatamente pone los pies de la corredora en la pista que se encuentra arriba de este (siempre que la corredora no se encuentre ya en la pista superior), como se muestra en la Figura 3:

Figura 3. Secuencia de salto entrecortado: fácil de implementar pero no natural
Screen capture of the Snail Bait runner's jerky jump sequence, implemented by the initial simple algorithm in Listing 2

La implementación de salto que se muestra en el Listado 2 tiene dos inconvenientes graves. Primero, la manera en que se mueve la corredora de un nivel al otro — instantáneamente — está lejos de ser el efecto deseado. Segundo, la implementación del salto está en el nivel de abstracción incorrecto. A un manejador de eventos de ventana no le corresponde la manipulación directa de los atributos de la corredora: por el contrario, la corredora es responsable del salto.


Cambio de responsabilidad del salto a la corredora

El Listado 3 muestra una implementación refactorizada del manejador de eventos de la ventana onkeydown . Es mucho más simple que la implementación del Listado 2, y traslada la responsabilidad del salto del manejador de eventos a la corredora.

Listado 3. El manejador de eventos de la ventana, delegando a la corredora
window.onkeydown = function (e) {
   var key = e.keyCode;
   ...
   
   if (key === 74) { // 'j'
      runner.jump();
   }
};

Cuando empieza el juego, Snail Bait invoca un método denominado equipRunner(), como se muestra en el Listado 4:

Listado 4. Cómo equipar a la corredora al comienzo del juego
SnailBait.prototype = {
   ...
   start: function () {
      this.createSprites();
      this.initializeImages();
      this.equipRunner();
      this.splashToast('Good Luck!');
   },
};

El método equipRunner() , que se muestra en el Listado 5, añade atributos y un método jump() a la corredora:

Listado 5. Cómo equipar a la corredora: el método jump() de la corredora
SnailBait.prototype = {
   equipRunner: function () {
      // This function sets runner attributes:

      this.runner.jumping = false; // 'this' is snailBait
      this.runner.track = this.INITIAL_RUNNER_TRACK;

      ... // More runner attributes omitted for brevity

      // This function also implements the runner's jump() method:

      this.runner.jump = function () {
         if ( ! this.jumping) {    // 'this' is the runner.
            this.jumping = true; // Start the jump
         } 
      };
   },
},

Vistas y controladores

El comportamiento de salto de la corredora y su método jump() correspondiente se parecen a un par vista/controlador. El modo en que Snail Bait dibuja a la corredora mientras está saltando se implementa en el comportamiento, mientras que el método jump() actúa como un simple controlador que determina si la corredora está saltando actualmente o no.

La corredora tiene atributos que representan, entre otras cosas, su pista actual y si está saltando actualmente o no.

Si la corredora no está saltando actualmente, el método runner.jump() simplemente establece el atributo de salto jumping en true. Snail Bait implementa el acto del salto en un objeto de comportamiento independiente, como hace con todos los demás comportamientos de la corredora como correr y caerse, — y de hecho con todos los comportamientos del sprite. Cuando crea a la corredora, Snail Bait añade ese objeto en el array de comportamientos de la corredora, como se muestra en el Listado 6:

Listado 6. Cómo crear a la corredora con sus comportamientos
var SnailBait = function () {
   ...
   this.jumpBehavior = {
      execute: function(sprite, time, fps) {

         // Implement jumping here

      },
      ...
   };
   ...

   this.runner = new Sprite('runner',          // type
                            this.runnerArtist,  // artist
                            [ this.runBehavior, // behaviors
                              this.jumpBehavior,
                              this.fallBehavior
                            ]); 
   ...
};

Ahora que la infraestructura está lista para iniciar un salto, puedo concentrarse únicamente en el comportamiento de salto.


Comportamiento de salto

El Listado 7, que muestra una implementación inicial del comportamiento de salto de la corredora, es funcionalmente equivalente al código del Listado 2. Si el atributo de salto jumping de la corredora — que se establece mediante el método jump() de la corredora (consulte el Listado 5) — es falso false, el comportamiento no hace nada. El comportamiento tampoco hace nada si la corredora está en la pista superior.

Listado 7. Cómo implementar un comportamiento de salto no realista
var SnailBait =  function () {
   ...

   this.jumpBehavior = {
      ...

      ejecutar: function(sprite, time, fps) {
         if ( ! sprite.jumping || sprite.track === 3) {
            return;
         }

         sprite.track++;

         sprite.top = snailBait.calculatePlatformTop(sprite.track) -
                      snailBait.RUNNER_CELLS_HEIGHT;

         sprite.jumping = false;
      } 
   },
   ...
};

Un Bucle Continuo

Recuerde que Snail Bait es esencialmente un bucle continuo que ejecuta constantemente todos los comportamientos de todos los sprites visibles. El método jump() de la corredora inicia un salto simplemente estableciendo el atributo de salto jumping en true. La próxima vez que Snail Bait ejecuta el comportamiento de salto de la corredora, ese valor hace que el comportamiento tenga efecto.

Si la corredora está saltando y no se encuentra en la pista superior, el comportamiento de salto implementado en el Listado 7 lo traslada a la pista siguiente y completa el salto estableciendo su atributo de salto jumping en falso: false.

Al igual que la implementación de salto en el Listado 2, la implementación en el Listado 7 traslada instantáneamente a la corredora de una pista a la otra. Para obtener un movimiento de salto realista, debe mover gradualmente la corredora de una pista a la otra en un periodo de tiempo específico.


Animaciones temporizadas: cronómetros

El movimiento que he implementado hasta ahora en Snail Bait fue constante. Por ejemplo, todos los sprites del juego, excepto la corredora, se mueven continuamente en dirección horizontal, y los botones y los caracoles avanzan y retroceden constantemente en sus plataformas. (Consulte la sección Desplazamiento del fondo del segundo artículo de esta serie para ver cómo se implementa ese movimiento.) Las monedas, los zafiros y los rubíes también pueden moverse hacia arriba y hacia abajo lentamente sin detenerse.

El salto, sin embargo, no es constante; tiene un inicio y un final definidos. Para implementar el salto, por lo tanto, debo encontrar una manera de supervisar constantemente cuánto tiempo transcurrió desde el comienzo de un salto. Lo que necesito es un cronómetro.

El Listado 8 muestra la implementación de un objeto de JavaScript Stopwatch :

Listado 8. Un objeto Stopwatch (cronómetro)
// Stopwatch..................................................................
//
// You can start and stop a stopwatch and you can find out the elapsed
// time the stopwatch has been running. After you stop a stopwatch,
// its getElapsedTime() method returns the elapsed time
// between the start and stop.

Stopwatch = function ()  {
   this.startTime = 0;
   this.running = false;
   this.elapsed = undefined;

   this.paused = false;
   this.startPause = 0;
   this.totalPausedTime = 0;
};

// You can get the elapsed time while the stopwatch is running, or after it's
// stopped.

Stopwatch.prototype = {
   iniciar: function () {
      this.startTime = +new Date();
      this.running = true;
      this.totalPausedTime = 0;
      this.startPause = 0;
   },

   stop: function () {
      if (this.paused) {
         this.unpause();
      }
      
      this.elapsed = (+new Date()) - this.startTime -
                                     this.totalPausedTime;
      this.running = false;
   },

   pause: function () {
      this.startPause = +new Date(); 
      this.paused = true;
   },

   unpause: function () {
      if (!this.paused) {
         return;
      }

      this.totalPausedTime += (+new Date()) - this.startPause; 
      this.startPause = 0;
      this.paused = false;
   },
   
   getElapsedTime: function () {
      if (this.running) {
         return (+new Date()) - this.startTime - this.totalPausedTime;
      }
      else {
        return this.elapsed;

      }
   },


   isPaused: function() {
      return this.paused;
   },

   isRunning: function() {
      return this.running;
   },

   reset: function() {
     this.elapsed = 0;
     this.startTime = +new Date();
     this.running = false;
     this.totalPausedTime = 0;
     this.startPause = 0;
   }
};

Es posible iniciar, detener, pausar, reiniciar y restablecer el objeto de cronómetro en el Listado 8. También es posible obtener el tiempo transcurrido, y determinar si un cronómetro está funcionando o si está en pausa.

En la sección Bloqueo del juego del tercer artículo de esta serie, expliqué cómo reanudar un juego en pausa exactamente donde quedó dando cuenta del tiempo por el cual estuvo en pausa el juego. Al igual que el juego en sí, los cronómetros en pausa deben reanudarse exactamente donde quedaron, de modo que también dan cuenta del tiempo por el que estuvieron en pausa.

La implementación del cronómetro, si bien es sencilla, es de gran importancia porque le permite implementar comportamientos que duran por un tiempo limitado — en este caso, el salto más natural.


Refinamiento del comportamiento de salto

Ahora que tengo cronómetros, los usaré para refinar el comportamiento de salto. Primero modifico el método equipRunner() del Listado 5 , como se muestra en el Listado 9:

Listado 9. Método equipRunner() revisado
SnailBait.prototype = {
   ...

   this.RUNNER_JUMP_HEIGHT = 120,    // pixels
   this.RUNNER_JUMP_DURATION = 1000, // milliseconds

   equipRunnerForJumping: function () {
      this.runner.JUMP_HEIGHT = this.RUNNER_JUMP_HEIGHT;
      this.runner.JUMP_DURATION = this.RUNNER_JUMP_DURATION;

      this.runner.jumping = false;


      this.runner.ascendStopwatch  = new Stopwatch(this.runner.JUMP_DURATION/2);
      this.runner.descendStopwatch = new Stopwatch(this.runner.JUMP_DURATION/2);

      this.runner.jump = function () {
         if (this.jumping) // 'this' is the runner
            return;

         this.jumping = true;
         this.runAnimationRate = 0; // Freeze the runner while jumping
         this.verticalLaunchPosition = this.top;
         this.ascendStopwatch.start();
      };
   },
  
   equipRunner: function () {
      ...

      this.equipRunnerForJumping();
   },
   ...
};

La implementación revisada de equipRunner() invoca un nuevo método: equipRunnerForJumping(). Tal como indica el nombre, equipa a la corredora para el salto. Ese método crea dos cronómetros: runner.ascendStopwatch para el ascenso del salto y runner.descendStopwatch para el descenso.

Cuando comienza el salto, el método jump() inicia el cronómetro de ascenso de la corredora, como puede verse en el Listado 9. Además, ese método establece el ritmo de animación de carrera de la corredora — que determina cuán rápido es el progreso de la corredora durante la animación de carrera — a cero para detener a la corredora mientras está en el aire. El método run() también registra la posición vertical de la corredora de modo que pueda volver a esa posición cuando finaliza el salto.

Todos los atributos de la corredora establecidos en el Listado 9 están resumidos en la Tabla 1:

Tabla 1. Atributos de la corredora relacionados con el salto
AtributoDescripción
JUMP_DURATIONUna constante que representa la duración del salto en milisegundos: 1000.
JUMP_HEIGHTUna constante que representa la altura del salto en píxeles: 120. En la altura máxima del salto, la corredora tiene 20 píxeles por sobre el nivel siguiente.
ascendStopwatchUn cronómetro que controla el ascenso de la corredora durante un salto.
descendStopwatchUn cronómetro que controla el descenso de la corredora durante un salto.
jumpApexEl punto más alto durante el salto de la corredora; el comportamiento de salto usa el punto máximo para determinar la distancia de caída de la corredora para cada trama durante el descenso de un salto.
jumpingUna marca que indica true mientras la corredora está saltando.
verticalLaunchPositionLa posición de la corredora (la esquina superior izquierda del sprite de la corredora) cuando comienza el salto. La corredora vuelve a esa posición al final de un salto completo.

Luego, en el Listado 10, refactorizo el comportamiento de salto implementado originalmente en el Listado 7:

Listado 10. El comportamiento de salto, revisado
var SnailBait =  function () {
   this.jumpBehavior = {
      ...

      execute: function(sprite, context, time, fps) {
         if ( ! sprite.jumping) {
            return;
         }

         if (this.isJumpOver(sprite)) {
            sprite.jumping = false;
            return;
         }

         if (this.isAscending(sprite)) {
            if ( ! this.isDoneAscending(sprite)) this.ascend(sprite);
            else                               this.finishAscent(sprite);
         }
         else if (this.isDescending(sprite)) {
            if ( ! this.isDoneDescending(sprite)) this.descend(sprite); 
            else                                this.finishDescent(sprite);
         }
      } 
   },
   ...

El comportamiento de salto en el Listado 10 es la implementación de una abstracción de alto nivel que deja los detalles del salto a otros métodos como ascend() y isDescending(). Ahora, solo falta completar los detalles usando los cronómetros de ascenso y descenso de la corredora para implementar los métodos siguientes:

  • isJumpOver()
  • ascend()
  • isAscending()
  • isDoneAscending()
  • finishAscent()
  • descend()
  • isDescending()
  • isDoneDescending()
  • finishDescent()

Movimiento lineal

Por el momento, los métodos que enumero arriba producen un movimiento lineal, lo que significa que la corredora asciende y desciende a una velocidad constante, como se muestra en la Figura 4:

Figura 4. Secuencia de salto lineal suave
Screen capture of the Snail Bait runner's smooth jump sequence

El movimiento lineal tiene como resultado un movimiento de salto poco natural. Esto se debe a que la gravedad debe acelerar o desacelerar constantemente a la corredora cuando asciende o desciende, respectivamente. En la próxima etapa, volveré a implementar aquellos métodos para obtener movimientos no lineales, como se muestra en la Figura 1. Por el momento, me quedaré con el caso más simple de movimiento lineal.

Primero, el Listado 11 muestra la implementación del método isJumpOver() del comportamiento de salto, que es el mismo sea el movimiento lineal o no lineal: un salto está terminado si ninguno de los cronómetros está en funcionamiento.

Listado 11. Cómo determinar la finalización del salto
SnailBait.prototype = {
   this.jumpBehavior = {
      isJumpOver: function (sprite) {
         return !sprite.ascendStopwatch.isRunning() &&
                !sprite.descendStopwatch.isRunning();
      },
      ...
   },
   ...
};

Los métodos de comportamiento de salto que se ocupan del ascenso se muestran en el Listado 12:

Listado 12. Ascenso
SnailBait.prototype = {
   ...

   this.jumpBehavior = {
      isAscending: function (sprite) {
         return sprite.ascendStopwatch.isRunning();
      },

      ascend: function (sprite) {
         var elapsed = sprite.ascendStopwatch.getElapsedTime(),
             deltaY  = elapsed / (sprite.JUMP_DURATION/2) * sprite.JUMP_HEIGHT;

         sprite.top = sprite.verticalLaunchPosition - deltaY; // Moving up
      },

      isDoneAscending: function (sprite) {
         return sprite.ascendStopwatch.getElapsedTime() > sprite.JUMP_DURATION/2;
      },
      
      finishAscent: function (sprite) {
         sprite.jumpApex = sprite.top;
         sprite.ascendStopwatch.stop();
         sprite.descendStopwatch.start();
      }
   },
   ...
};

Los métodos del Listado 12 están resumidos en la Tabla 2:

Tabla 2. Métodos jumpBehaviorde ascenso
MétodoDescripción
isAscending()Devuelve el valor true si el cronómetro de ascenso de la corredora está en funcionamiento.
ascend()Mueve a la corredora hacia arriba en función del tiempo transcurrido del último cuadro de animación y de la duración y la altura del salto.
isDoneAscending()Devuelve el valor true si el tiempo transcurrido en el cronómetro de ascenso de la corredora es mayor que la mitad de la duración del salto.
finishAscent()

Completa el ascenso deteniendo el cronómetro de ascenso de la corredora e iniciando el cronómetro de descenso.

El jumpBehavior llama a este método cuando la corredora se encuentra en el punto más alto del salto, por lo que finishAscent() almacena la posición de la corredora en el jumpApex de la corredora. El método descend() usa ese atributo.

Recuerde que el método jump() de la corredora, que se muestra en el Listado 9, inicia el cronómetro de descenso de la corredora. Posteriormente, ese cronómetro en funcionamiento hace que el método isAscending() de comportamiento de salto devuelva el valor true temporalmente. Hasta que la corredora termine de ascender — es decir que el salto se encuentre en la mitad de su curso— el comportamiento de salto de la corredora llama constantemente al método ascend() , como puede verse en el Listado 10.

Ascenso y descenso

El método ascend() mueve a la corredora hacia arriba gradualmente. Calcula la cantidad de píxeles que debe mover a la corredora para cada cuadro de animación dividiendo el tiempo transcurrido del cronómetro (milisegundos) por la mitad de la duración del salto (milisegundos) y multiplicando ese valor por la altura del salto (píxeles). Los milisegundos se cancelan entre sí, dejando los píxeles como unidad de medida del valor deltaY . Por lo tanto, ese valor representa la cantidad de píxeles que debe moverse la corredora en dirección vertical para el cuadro de animación actual.

Cuando la corredora completa el ascenso, el método finishAscent() del comportamiento de salto registra la posición del sprite como punto máximo del salto, detiene el cronómetro de ascenso e inicia el cronómetro de descenso.

Los métodos del comportamiento de salto relacionados con el descenso se muestran en el Listado 13:

Listado 13. Descenso
SnailBait.prototype = {
   this.jumpBehavior = {
      isDescending: function (sprite) {
         return sprite.descendStopwatch.isRunning();
      },

      descend: function (sprite, verticalVelocity, fps) {
         var elapsed = sprite.descendStopwatch.getElapsedTime(),
             deltaY  = elapsed / (sprite.JUMP_DURATION/2) * sprite.JUMP_HEIGHT;

         sprite.top = sprite.jumpApex + deltaY; // Moving down
      },
      
      isDoneDescending: function (sprite) {
         return sprite.descendStopwatch.getElapsedTime() > sprite.JUMP_DURATION/2;
      },

      finishDescent: function (sprite) {
         sprite.top = sprite.verticalLaunchPosition;
         sprite.descendStopwatch.stop();
         sprite.jumping = false;
         sprite.runAnimationRate = snailBait.RUN_ANIMATION_RATE;
      }
   },
   ...
};

Los métodos del Listado 13 están resumidos en la Tabla 3:

Tabla 3. jumpBehavior: métodos de descenso
isDescending()Devuelve el valor true si el cronómetro de descenso de la corredora está en funcionamiento.
descend()Mueve a la corredora hacia abajo en función del tiempo transcurrido del último cuadro de animación y de la duración y la altura del salto.
isDoneDescending()Devuelve el valor true si la corredora cayó por debajo de su posición previa al salto.
finishDescent()

Detiene el descenso y el salto deteniendo el cronómetro de descenso de la corredora y estableciendo la marca de salto jumping de la corredora en false, respectivamente.

Después del descenso, la corredora puede no estar a exactamente la misma altura que cuando inició el salto, por lo que finishDescent() establece la posición de la corredora en esa ubicación vertical previa al salto.

Finalmente, finishDescent() establece la velocidad de animación de la corredora en su valor normal, lo que hace que la corredora empiece a correr.

Hay mucha simetría entre los métodos de ascenso del Listado 12 y los métodos de descenso del Listado 13. Tanto ascend() como descend() calculan la cantidad de píxeles que deben mover la corredora en dirección vertical en el cuadro actual exactamente de la misma manera. No obstante, el método descend() añade ese valor al punto máximo del salto, mientras que ascend() lo resta de la posición de lanzamiento. (Recuerde que el eje Y de Canvas aumenta de arriba hacia abajo.)

Cuando finaliza el descenso del salto, finishDescent() vuelve a poner a la corredora en la misma posición vertical en que comenzó el salto y reinicia la animación de carrera.


Próximamente

En el próximo artículo de esta serie mostraré cómo implementar un movimiento no lineal para producir el movimiento de salto realista que se muestra en la Figura 1. A su vez, le mostraré cómo deformar el tiempo para producir efectos no lineales para cualquier otra derivación del tiempo, como el cambio de color. Hasta la próxima.


Descargar

DescripciónNombretamaño
Sample codej-html5-game6.zip1.2MB

Recursos

Aprender

Obtener los productos y tecnologías

  • Replica Island: puede descargar el código de este conocido videojuego de plataforma de código abierto para Android. La mayoría de los sprites de Snail Bait son de Replica Island (con permisos).

Comentar

  • Participe en la Comunidad developerWorks. Conéctese con otros usuarios de developerWorks mientras explora los blogs conducidos por desarrolladores, foros, grupos y wikis.

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=929539
ArticleTitle=Desarrollo de Juegos 2D con HTML5: Manipulación del Tiempo, Parte 1
publish-date=05132013