Í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.
Matemáticas
Sumar y restar es bastante sencillo para la CPU del Spectrum, tenemos una gran
cantidad de instrucciones para llevar a cabo estas tareas. Pero a diferencia
de algunos procesadores posteriores en su misma serie, el programador del Z80A tiene
que hacer por si mismo la multiplicación y la división. Aunque estos cálculos
son poco frecuentes, tienen sus usos en ciertos tipos de juego y hasta que tengas
rutinas para hacerlo, ciertas cosas son difíciles de hacer. Por ejemplo,
sin el teorema de Pitágoras puede ser difícil programar para que un sprite enemigo
dispare al jugador con cierto grado de precisión.
Supongamos que un sprite A necesita hacer un disparo al sprite B.
Tenemos que encontrar el ángulo en el que el sprite A debe disparar y necesitamos
algo de trigonometría para hacerlo. Sabemos las coordenadas de los sprites,
Ax, Ay, Bx y By, y las distancias entre estos,
Bx-Ax y By-Ay, eso nos darán la longitud de los catetos opuesto y
contiguo. Por desgracia, la única manera de calcular el ángulo entre opuesto
y contiguo es el uso del arco tangente, y las tangentes solo son adecuadas para
ciertos ángulos, por lo que es mejor usar senos o cosenos en su lugar. Así que
con el fin de encontrar el ángulo entre el sprite A y el sprite B,
tenemos que encontrar la longitud de la hipotenusa.
La hipotenusa se calcula elevando al cuadrado la distancia en x, añadiéndola al
cuadrado de la distancia en y, para a continuación encontrar la raíz cuadrada de
la suma. Existen rutinas en la ROM de Sinclair para hacer todo esto, pero hay
un serio inconveniente: como puede decirte cualquier persona que haya usado Sinclair
BASIC alguna vez, las rutinas matemáticas son increíblemente lentas como para usarlas
en la escritura de juegos. Así que tenemos que poner a trabajar los dedos y escribir
nuestras propias rutinas.
Los cuadrados de nuestras distancias x e y se consiguen usando
una rutina de multiplicación, multiplicando los números por sí mismos. Afortunadamente,
esta parte es relativamente indolora. La multiplicación se logra de la misma
manera como se haría la multiplicación larga en papel, aunque esta vez estamos
trabajando en binario. Todo lo que se requiere es rotaciones, pruebas de bits y
sumas. Cuando un bit exista en nuestro primer factor, añadimos el segundo factor
al total. Luego desplazamos el segundo factor a la izquierda, y probamos el siguiente
bit de nuestro primer factor. La rutina de abajo, tomada del juego Kuiper
Persecution, demuestra esta técnica, multiplica h por d y
devuelve el resultado en hl.
imul ld e,d ; HL = H * D ld a,h ; poner en el acumulador el primer multiplicando. ld hl,0 ; poner a cero el resultado. ld d,h ; poner a cero el byte alto ya que de=multiplicador. ld b,8 ; repetir 8 veces. imul1 rra ; rotar el bit mas a la derecha en el acarreo. jr nc,imul2 ; no se estableció. add hl,de ; se estableció el bit, por lo que añadir a de. and a ; restablecer acarreo. imul2 rl e ; aumentar 1 bit izquierdo. rl d djnz imul1 ; repetir 8 veces. ret
Ahora necesitamos una raíz cuadrada, que es donde empiezan los problemas. Las
raíces cuadradas son mucho más complicadas. Esto significa hacer una gran cantidad
de divisiones, por lo que primero necesitamos una rutina de división. Esto se
puede ver como trabajar de manera opuesta a la multiplicación, desplazando y
restando. La siguiente rutina, también del juego Kuiper Persecución,
divide hl por d y devuelve el resultado en hl.
idiv ld b,8 ; bits a comprobar. ld a,d ; número por el que dividir. idiv3 rla ; comprobar bit de la izquierda. jr c,idiv2 ; no requiere más desplazamientos. inc b ; necesita desplazamientos extra. cp h jr nc,idiv2 jp idiv3 ; repetir. idiv2 xor a ld e,a ld c,a ; resultado. idiv1 sbc hl,de ; restarlo. jr nc,idiv0 ; no hay acarreo, mantener el resultado. add hl,de ; restaurar el valor original de hl. idiv0 ccf ; invertir bit de acarreo. rl c ; rotar en ac. rla rr d ; dividir por 2. rr e djnz idiv1 ; repetir. ld h,a ; copiar el resultado a hl. ld l,c ret
De la misma manera que la multiplicación se compone de desplazamiento y suma,
y la división se realiza mediante desplazamiento y resta, las raíces cuadradas
se pueden calcular por desplazamiento y división. Simplemente estamos tratando
de encontrar el número que "mejor se ajuste", de forma que multiplicado por sí mismo
nos da el número con el que empezamos. No voy a entrar en la explicación detallada
de cómo trabaja la siguiente rutina, si estás muy interesado sigue mis comentarios
y haz una ejecución paso a paso con un depurador. Tomado del juego Blizzard's
Rift, devuelve la raíz cuadrada de hl en el acumulador.
isqr ld (sqbuf0),hl ; número del que queremos encontrar la raíz cuadrada. xor a ; poner a cero el acumulador. ld (sqbuf2),a ; buffer resultado. ld a,128 ; comenzar con la división con el bit alto. ld (sqbuf1),a ; siguiente divisor. ld b,8 ; 8 bits a dividir. isqr1 push bc ; guardar contador de bucles. ld a,(sqbuf2) ; resultado actual. ld d,a ld a,(sqbuf1) ; siguiente bit a comprobar. or d ; combinar con divisor. ld d,a ; almacenar byte bajo. xor a ; HL = HL / D ld c,a ; poner a cero c. ld e,a ; poner a cero e. push de ; recordar divisor. ld hl,(sqbuf0) ; número original. call idiv4 ; dividir el número por d. pop de ; restaurar divisor. cp d ; ¿el divisor es mayor que el resultado? jr c,isqr0 ; sí, no guardar este bit entonces. ld a,d ld (sqbuf2),a ; guardar nuevo divisor. isqr0 ld hl,sqbuf1 ; bit que comprobamos. and a ; limpiar bandera de acarreo. rr (hl) ; siguiente bit a la derecha. pop bc ; restaurar el contador del bucle. djnz isqr1 ; repetir. ld a,(sqbuf2) ; retornar resultado en hl. ret sqbuf0 defw 0 sqbuf1 defb 0 sqbuf2 defb 0
Con la longitud de la hipotenusa calculada, podemos dividir simplemente el cateto
opuesto por la hipotenusa para encontrar el coseno del ángulo. Una búsqueda
rápida en nuestra tabla de senos nos dará el ángulo. ¡Uf!
Este es todo el cálculo tomado del juego Blizzard's Rift. Debes notar que usa
la longitud del cateto contiguo en lugar del opuesto, por lo que se encuentra
el arco coseno en lugar del arco seno. Solo se utiliza cuando la nave está por
encima del cañón de la torreta, dando al jugador la oportunidad de acercarse
sigilosamente y atacar desde abajo. Sin embargo demuestra cómo un sprite puede
disparar a otros con precisión mortal. Si has jugado alguna vez al Blizzard's
Rift, sabrás exactamente lo letales que pueden ser los cañones de las torretas.
; La nave debe estar por encima del cañón para que podamos emplear trigonometría ; básica para apuntar. ; Tenemos que encontrar el ángulo, para ello dividimos el cateto contiguo por ; la hipotenusa y encontramos el arco coseno. ; En primer lugar ponemos la distancia al oponente en la pila: mgunx ld a,(nshipy) ; coordenada y de la nave. ld hl,guny ; coordenada y del cañón. sub (hl) ; encontrar la diferencia. jr nc,mgun0 ; el resultado fue positivo. neg ; negativo, hacerlo positivo. mgun0 cp 5 ; ¿diferencia y menor de 5? jr c,mgunu ; sí, apuntar hacia arriba. push af ; colocar longitud del oponente en la pila. ; A continuación se necesita la longitud de la hipotenusa para lo que ; podemos usar el viejo teorema de Pitágoras. ld h,a ; copiar a en h. ld d,h ; copiar h en d. call imul ; multiplicar parte entera obtener resultado 16 bits. push hl ; guardar el valor al cuadrado. ld hl,nshipx ; cañón coordenada x. ld a,(gunx) ; nave coordenada x. sub (hl) ; encontrar la diferencia, siempre será positiva. ld h,a ; poner la diferencia de x en h. ld d,h ; copiar h en d. call imul ; multiplicar h por d para obtener el cuadrado. pop de ; obtener el último resultado al cuadrado. add hl,de ; necesitamos la suma de los dos. call isqr ; encontrar la raíz cuadrada, hipotenusa en a. pop de ; cateto opuesto ahora en el registro d.. ld h,a ; longitud de la hipotenusa. ld l,0 ; ninguna fracción o signo. ex de,hl ; intercambiarlos. ; Cateto opuesto e hipotenusa se encuentran ahora en de y hl. ; Ahora dividimos la primera por la segunda y encontramos el arco seno. ; Recuerda: seno = cateto opuesto sobre la hipotenusa. call div ; la división nos dará el seno. ex de,hl ; queremos el resultado en de. call asn ; obtener arco seno para encontrar el ángulo. push af ; Bien, tenemos el ángulo, pero está sólo entre 0 y PI/2 radianes (64 ángulos) ; por lo que tenemos que hacer un ajuste en base al cuadrante del círculo. ; Podemos establecer en qué cuadrante del círculo está nuestro ángulo ; examinando la diferencia entre las coordenadas de la nave y el arma. ld a,(guny) ; cañón posición y. ld hl,shipy ; nave y. cp (hl) ; ¿está la nave a la derecha? jr nc,mgun2 ; jugador a la izquierda, ángulo en segundo cuadrante. ; Ángulo del jugador en el primer cuadrante, por lo que debemos restar de 64. ld a,64 ; pi/2 radianes = 64 ángulos. pop bc ; ángulo en b. sub b ; restarlos. ld (ix+1),a ; nuevo ángulo. ret ; tenemos nuestro ángulo. ; Segundo cuadrante, añadir el literal 64 a nuestro ángulo. mgun2 pop af ; ángulo original. add a,192 ; sumar pi/2 radianes. ld (ix+1),a ; nuevo ángulo. ret ; ¡buen trabajo!
No hay comentarios:
Publicar un comentario