miércoles, 4 de mayo de 2016

Cómo escribir juegos para el ZX Spectrum. Capítulo 14

Índice de entradas

Esta serie de artículos han sido traducidos a partir del documento "How to Write ZX Spectrum Games" con permiso de su autor, Jonathan Cauldwell, un gran desarrollador de juegos para el Spectrum, os recomiendo visitar su Web donde está el texto original. El documento original, y por tanto esta traducción, tiene © Jonathan Cauldwell y solo puede duplicarse con permiso expreso por escrito de su autor.

Movimiento sofisticado

Hasta ahora hemos movido sprites arriba, abajo, a la izquierda y a la derecha por píxeles. Sin embargo, muchos juegos requieren una manipulación de sprites más sofisticada. Los juegos de plataformas requieren manejar la gravedad, los juegos de carreras top-down utilizan el movimiento de rotación, otros juegos utilizan la inercia.

Tablas de salto y de inercia

La forma más sencilla de manejar la gravedad o la inercia es disponer de una tabla de valores. Por ejemplo, los Egghead games hacen uso de una tabla de saltos y mantienen un puntero a la posición actual. Dicha tabla puede tener un aspecto como la siguiente.

; Tabla de saltos.
; Valores mayores de 128 son de subida, menores de 128 de bajada.
; Conforme el valor se aleja de 128 se aumenta la velocidad.

jptr   defw jtabu
jtabu  defb 250,251,252
       defb 253,254,254
       defb 255,255,255
       defb 0,255,0
jtabd  defb 1,0,1,1,1,2
       defb 2,3,4,5,6,6
       defb 6,6,128

Con el puntero almacenado en jptr, podríamos hacer algo como esto:

       ld hl,(jptr)        ; recuperar el puntero del salto.
       ld a,(hl)           ; siguiente valor.
       cp 128              ; ¿alcanzado el final de la tabla?
       jr nz,skip          ; no, estamos bien.
       dec hl              ; atrás aumentamos la velocidad.
       ld a,(hl)           ; recuperar la velocidad máxima.
skip   inc hl              ; avanzar el puntero.
       ld (jptr),hl        ; establecer la siguiente posición del puntero.
       ld hl,verpos        ; posición vertical del jugador.
       add a,(hl)          ; agregar la cantidad correspondiente.
       ld (hl),a           ; Establecer la nueva posición del jugador.

Para iniciar un salto, debemos establecer jptr a jtabu. Para empezar a caer, hay que configurarlo con jtabd.

Bien, esta descripción es muy simple. En la práctica, debemos utilizar el valor de la tabla de saltos como un contador de bucles, mover al jugador arriba o hacia abajo un píxel cada vez, comprobar colisiones con plataformas, paredes, elementos mortales, etc. como siempre. También podríamos utilizar el elemento final (128) para indicar que el jugador ha caído desde demasiado lejos, y establecer un indicador para que la próxima vez que el jugador golpee contra algo sólido pierda una vida. Considerando esto, ya tienes tu rutina.

Coordenadas fraccionarias

Si queremos manejar de forma más sofisticada la gravedad, la inercia o el movimiento de rotación, necesitamos coordenadas fraccionarias. Hasta ahora, con la resolución del Spectrum de 256x192 píxeles, sólo hemos tenido que utilizar un byte por coordenadas. Si por el contrario utilizamos un par de registros de dos bytes, el byte alto para la parte entera y el byte bajo para la fraccionaria, se abre un nuevo mundo de posibilidades. Esto nos da 8 bits para cifras decimales binarias, lo que permite movimientos muy precisos y sutiles. Con una coordenada en el par hl, podemos configurar el desplazamiento en de, y unir los dos. Al presentar nuestros sprites, simplemente usamos los bytes altos como nuestra coordenadas X e Y para nuestro cálculo de la dirección de la pantalla, y desechamos los bytes inferiores que contienen las fracciones. El efecto de sumar una fracción a una coordenada no será visible cada cuadro, pero incluso la fracción más pequeña, 1/256, moverá lentamente un sprite con el tiempo.

Ahora podemos echar un vistazo a la gravedad. Esta es una fuerza constante, en la práctica acelera un objeto hacia el suelo a 9,8 m/s2. Para simularlo en un juego de Spectrum, haremos que nuestra coordenada vertical sea una palabra de 16 bits. A continuación, establecemos una segunda palabra de 16 bits para la inercia. Cada cuadro añadimos una pequeña fracción a la inercia, y a continuación añadimos la inercia a la posición vertical. Por ejemplo:

       ld hl,(vermom)      ; inercia.
       ld de,2             ; fracción pequeña, 1/128.
       add hl,de           ; incrementar inercia.
       ld (vermom),hl      ; guardar inercia.
       ld de,(verpos)      ; posición vertical.
       add hl,de           ; añadir inercia.
       ld (verpos),hl      ; guardar nueva posición.
       ret
verpos defw 0              ; posición vertical.
vermom defw 0              ; inercia vertical.

A continuación, para mostrar nuestros sprites, simplemente tomamos el byte alto de nuestra posición vertical, verpos+1, que nos dan el número de píxeles desde la parte superior de la pantalla. Diferentes valores de de variarán la fuerza de la gravedad, de hecho, incluso podemos cambiar la dirección restando de con hl, o añadiendo una distancia negativa (65536-distancia). Podemos aplicar lo misma también a la coordenada y para mantener el sprite sujeto a la inercia en todas direcciones. Así es como nos gustaría ir escribiendo un juego de estilo similar a Thrust.

Movimiento rotatorio

Otro tema que podemos necesitar para juegos del estilo al Thrust, carreras top-down, u otros en los que círculos o trigonometría básica están involucrados es una tabla de senos y cosenos. Las matemáticas no son del agrado de todo el mundo, y si tu trigonometría está un poco oxidada te sugiero leer sobre senos y cosenos antes de continuar con el resto del capítulo.

En matemáticas, podemos encontrar la distancia x e y desde el centro de un círculo dado el radio y el ángulo mediante el uso de senos y cosenos. Sin embargo, mientras que en matemáticas un círculo se compone de 360 grados o lo que es lo mismo de 2 PI radianes, es más conveniente para el programador de Spectrum representar un ángulo como, por ejemplo, un valor de 8 bits de 0 a 255, o incluso usar menos bits, dependiendo del número de posiciones que el sprite del jugador pueda tomar. Podemos utilizar este valor para mirar su valor fraccionario de 16 bits para el seno y el coseno de una tabla. Suponiendo que tenemos un ángulo de 8 bits almacenado en el acumulador y deseamos encontrar el seno, simplemente accedemos a la tabla de manera similar a la siguiente:

       ld de,2             ; fracción pequeña - 1/128.
       ld l,a              ; ángulo en el byte bajo.
       ld h,0              ; ponemos a cero byte alto.
       add hl,hl           ; doble desplazamiento las entradas son de 16 bits.
       ld de,sintab        ; dirección de tabla de senos.
       add hl,de           ; añadir el desplazamiento de este ángulo.
       ld e,(hl)           ; fracción del seno.
       inc hl              ; apunto a la segunda mitad.
       ld d,(hl)           ; parte entera.
       ret                 ; volver con el seno en DE.

Realmente el BASIC de Sinclair ya nos proporciona los valores que necesitamos, con sus funciones SIN y COS. Usándolas, podemos almacenar con POKE en la memoria RAM, o bien guardarlos en cinta, o guardarlos en binario utilizando un emulador como SPIN. Alternativamente, es posible que prefieras utilizar otro lenguaje de programación en el PC para generar una tabla formateada de valores de senos, para importarlos en tu archivo de origen o incluirlos como valores binarios. Para una tabla de senos con 256 ángulos equidistantes se necesitaría un total de 512 bytes, pero debemos que tener cuidado para convertir el número devuelto por SIN en uno que nuestro juego reconozca. Multiplicando el seno por 256 nos dará nuestros valores positivos, pero cuando SIN devuelve un resultado negativo, podrías tener que multiplicar el valor absoluto del seno por 256, y luego restarlo de 65536 o poner el bit d7 del byte a uno para indicar que el número debe ser restado en lugar de sumado a nuestras coordenadas. Con una tabla de senos construida de esta manera no necesitamos una tabla separada para los cosenos, solo hemos de añadir o restar 64, o un cuarto de vuelta, al ángulo antes de obtener el valor en nuestra tabla. Para mover un sprite en un ángulo de A, se añade el seno de A a una coordenadas, y el coseno de A a la otra coordenada. Cambiando si sumamos o restamos un cuarto de vuelta para obtener el coseno, y viendo que cuadrante utiliza senos y cual cosenos, podemos empezar nuestro círculo en cualquiera de los 4 puntos cardinales y hacer que se mueva en sentido horario o anti horario.

No hay comentarios:

Publicar un comentario