lunes, 2 de mayo de 2016

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

Í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.

Temporización

El tiempo lo es todo. Un juego se arruina fácilmente si se ejecuta demasiado rápido o demasiado lento. La mayoría de los juegos de Spectrum se ejecutarán con demasiada rapidez si lo único que hacen es manipular unos sprites, y necesitan ser ralentizados.

La instrucción Halt

Medimos la velocidad de un juego de Spectrum por la cantidad de tiempo que tarda un recorrido completo del bucle principal, incluyendo todos los trabajos realizados por las rutinas llamadas dentro de este bucle. La forma más sencilla de introducir un retardo es insertar instrucciones halt, que espera por una interrupción, en ciertos puntos del bucle principal para que espere hasta una interrupción. Como el Spectrum genera 50 interrupciones por segundo, esto significa que el bucle principal que tenga 1, 2 ó 3 de tales pausas se ejecutará a 50, 25 o 17 cuadros por segundo respectivamente, en tanto que el resto de procesamiento no ocupa más de un cuadro para completarse. En términos generales, no es buena idea tener el sprite del jugador moviendo más lentamente de 17 cuadros por segundo.

En realidad, la instrucción halt puede ser muy útil. En efecto, espera a que la línea de exploración del televisión llegue al final de la pantalla. Esto significa que un buen momento para borrar, mover y volver a mostrar un sprite es inmediatamente después de un halt, porque la línea de exploración no va a encontrarse con la imagen y no hay posibilidad de parpadeo. Si tienes un panel de estado de tu juego en la parte superior de la pantalla, esto significa que hay aún más tiempo para que la línea de exploración viaje antes de que alcance la zona de sprites, y a menudo puedes manipular un par de sprites después de una interrupción sin mucho peligro de parpadeo.

La instrucción halt también se puede utilizar en un bucle para hacer una pausa durante períodos más largos. El siguiente código hará una pausa de 100 cincuentavos de segundo (lo que son dos segundos).

       ld b,100            ; duración de la pausa.
delay  halt                ; esperar una interrupción.
       djnz delay          ; repetir.

El reloj del Spectrum y la rutina Vsync

Por desgracia, halt es un instrumento sin punta. Siempre espera a la siguiente interrupción independientemente del tiempo que quede para la siguiente. Imagina una situación en la que el bucle principal tarda 3/4 del tiempo del cuadro para hacer su procesamiento la mayor parte del tiempo, pero de vez en cuando tiene períodos en los que hay un procesamiento adicional que tarda medio tiempo de cuadro adicional. En estas circunstancias, un halt mantendrá el juego a la constantes de 50 cuadrod por segundo la mayoría de las veces, pero cuando el procesamiento adicional entra en acción, la primera interrupción ha pasado y halt esperará hasta la siguiente, lo que significa que el juego se ralentiza a 25 cuadros por segundo periódicamente.

Hay una manera de evitar este problema, consiste en contar el número de cuadros que han transcurrido desde la última iteración del bucle principal. En el Spectrum, la rutina de servicio de interrupción de la ROM actualiza el contador de cuadros de 24 bits del Spectrum 50 veces por segundo, y tambien hace otras cosas. Este contador es almacenado en las variables del sistema en la dirección 23672, por lo que mediante la comprobación de esta ubicación una vez en cada iteración del bucle, podemos saber cuántas interrupciones se han producido desde la última vez que estuvimos en ese mismo punto. Naturalmente, si quieres escribir tu propia rutina de manejo de interrupción, puedes utilizar o bien en primer lugar rst 56 para actualizar el reloj, o bien incrementar un contador de cuadros por ti mismo si deseas utilizar este método.

La siguiente rutina vsync está diseñada para estabilizar un juego y hacerlo funcionar a un nivel más o menos constantes de 25 cuadros por segundo:

wait   ld hl,pretim        ; carga el temporizador anterior.
       ld a,(23672)        ; carga el temporizador actual.
       sub (hl)            ; diferencia entre los dos.
       cp 2                ; ¿ya han transcurrido dos cuadros?
       jr nc,wait0         ; sí, no más demora.
       jp wait
wait0  ld a,(23672)        ; carga el temporizador actual.
       ld (hl),a           ; guardar su valor como anterior.
       ret
pretim defb 0

En lugar de simplemente esperar en un bucle, se podría realizar alguna procesamiento adicional no esencial. Este es un buen punto en el que poner cualquier efecto de sonido del altavoz. Una buena idea es cuando lo necesites establecer un byte para indicar el tipo de efecto de sonido a reproducir, y a continuación comprobar este octeto en tu rutina vsync y llamar a la rutina correspondiente al efecto de sonido. Tus rutinas de efectos de sonido también necesitarán controles periódicos para ver si el contador de cuadros ha llegado a su fin, y salir cuando lo ha hecho.

Hay otras cosas que tal vez quiera hacer con este tiempo de CPU. Yo a veces renuevo mis sprites en la tabla que los mantienen, cambiando el orden en que se muestran en cada bucle para ayudar a prevenir el parpadeo.

Semilla de Números Aleatorios

El contador de tramas del Spectrum es útil para otra cosa: se puede utilizar para inicializar la semilla de los números aleatorios. Utilizando el generador de números aleatorios propuesto en el capítulo de números aleatorios, podemos hacer esto:

       ld a,(23672)        ; temporizador actual.
       ld (seed),a         ; establece primer byte de la semilla de aleatorios.

Esto va bien si estamos trabajando con el hardware real y nos asegurará que un juego no comience con la misma secuencia de números aleatorios cada vez que se juega. Por desgracia, los autores de los emuladores tienen la mala costumbre de cargar automáticamente los archivos de cinta una vez abiertos, una práctica que no sólo hace difícil el desarrollo, da lugar a que la máquina esté siempre en el mismo estado cada vez que el juego se cargue, es decir, los números aleatorios pueden seguir la misma secuencia cada vez que se juega a ese juego. La solución para el programador de juegos es esperar a que se pulse una tecla tan pronto como nuestro juego ha cargado, después de lo cual podemos configurar nuestra semilla. Esto introduce un elemento humano y asegura que el generador de números aleatorios es diferente cada vez.

No hay comentarios:

Publicar un comentario