jueves, 2 de junio de 2016

VOLCADO DEL BUFFER CON LDI (IV): CONTROL DE ENEMIGOS Y DISPARO


Siguiendo el bucle principal, nos encontramos con el control de los los enemigos:


 

NUMEN es una etiqueta en la que indicamos el número de los enemigos que vamos a controlar. En este ejemplo será de 1 a 6.

En CONTE se inicia un bucle con ese número ya que vamos a hacer lo mismo con todos.
Metemos en IX la tabla del primer enemigo.

En CONTS miramos si el bit 7 del byte 19 de la tabla está a uno. Si es así, significa que este sprite va a mitad de velocidad que el prota.
Voy a usar el bit 0 de este mismo byte para saber si hay que mover al sprite o no. Para eso ponemos el bit 7 a cero (ya que sólo nos interesa el bit 0), hacemos un XOR 1 para usarlo como contador y volvemos a poner el bit 7 a uno. Si el resultado del XOR 1 es cero saltamos a PONSP, osea no se mueve el sprite, pero sí que se imprime. En este ejemplo el sprite que va a mitad de velocidad es el esqueleto de arriba.
Al hacer XOR 1, si A vale cero pasa a valer 1 y si A vale 1 pasa a valer cero. Por eso es cero una de cada dos veces, por lo que el sprite va a mitad de velocidad. Primero ponemos a cero el bit 7 para que no influya en el resultado del XOR 1 y luego lo ponemos a 1 para que nos siga valiendo de testigo como que es un sprite que se mueve a mitad de velocidad.

EN CONTS1 mira si ha llegado al límite izquierdo o derecho del buffer. Si es así, salta a CHANGE para que cambie el sentido de la marcha del sprite. Si no, actualiza su posición con el incremento (o decremento) que está en el byte 12 de la tabla y llama a GESTFAS para que cambie de fase.

Para el moviento vertical vuelve a comprobar si está en los límites de arriba o abajo del buffer. Para el de arriba basta ver si está en la coordenada Y=0. Para el de abajo cogemos el valor del byte 3 de la tabla que es la altura del sprite dividida por 2 y la multiplicamos por 2 (SLL). La sumamos a la coordenada Y, y si se pasa del número máximo de líneas verticales del buffer salta a CHANGEV para que cambie el sentido del movimiento vertical del sprite.
En caso de que esté dentro del buffer, simplemente actualiza la coordenada Y con el incremento (o decremento) vertical (byte 11 de la tabla).

Una vez hecho esto, llamamos a la rutina que nos mete la dirección de la tabla del sprite en la cola de impresión de sprites (METEIMPS), pasamos a la siguiente tabla sumando el tamaño de la tabla a IX y continuamos con el bucle hasta que el registro B sea igual a cero.

CHANGE Y CHANGEV están en ARUTINAS.ASM



Cogemos el incremento (o decremento) de la posición horizontal del sprite (byte 12 de la tabla) y lo negamos.
Si al negarlo da un número negativo, es que pasa de moverse a la derecha a moverse a la izquierda. Por ello ponemos el JP M, NEGAT que significa algo así como "si el registro A es negativo, salta a NEGAT".
Si es positivo, la cosa queda en POSIT donde metemos el gráfico mirando a la derecha (bytes 14 y 15) y saltamos a PONSP. Aquí llamamos a METEG que ya explicamos en el control del prota y actualizamos la coordenada X con el incremento corresponsiente.



CHANGEV es muy parecida. Aquí no nos interesa si hay incremento o decremento, ya que el gráfico no cambia si sube o baja.


LA COLA DE IMPRESIÓN

Más arriba he comentado que si queremos que un sprite se imprima, hay que llamar a METEIMPS para que lo incluya en la cola de impresión.
Esto funciona de la siguiente manera: tenemos una cola de impresión FIFO (el primero que entra es el primero que sale) donde vamos metiendo las direcciones de las tablas de los sprites que queremos imprimir cada vez.

Si queremos imprimir los sprites ENEM1, EMEM4, ENEM6, el disparo y el prota, cuando llegue el turno a la rutina de impresión, ésta busca en la cola qué sprite imprimir en ese momento. Así nuestra cola quedará // ENEM1, EMEM4, ENEM6,DISPARO,PROTA//. Esta rutina los va "sacando" en orden y los imprime. Los sprites que se imprimen primero van quedando por debajo de los siguientes. En realidad no hace falta usar la cola, ya que podemos ir mandando imprimir los sprites a medida que se van actualizando, pero a efectos prácticos es más interesante usarla. Además de que teniendo los sprites en una cola, se pueden ordenar para que se impriman en orden descendente de la coordenada Y que ocupa cada uno, por lo que nos da sensación de profundidad. Esto se usa en el juego Renegade y es fundamental en los juegos isométricos. En este ejemplo no se ordenan.

METEIMPS
Está en ARUTINAS.ASM

Si recuerdas cuando comenté la estructuración de la memoria había una etiqueta que se llamaba SPIMP en la cual reservaba 20 bytes con un DS 20.
SPIMP es el inicio de la cola. Cada dato que voy a meter en la cola es de 2 bytes (las direcciones de las tablas de los sprites)



Metemos IX en la cola e incrementamos el puntero de la cola en 2. Este incremento se hace de una manera "especial". Si es el primer sprite, DIRAMET+2 vale SPIMP, ya que es el principio de la cola. Para incrementarlo 2 veces deberíamos hacer INC HL, INC HL y luego volver a guardar este dato (SPIMP+2). Por qué hago INC L, INC L? Pues porque he colocado SPIMP en la dirección F900h+192=F9C0h. La cola nunca va a llegar a FA00h (F9C0h+20=F9D4h) así que podemos incrementar sólo el byte bajo de HL que es más rápido que incrementar HL entero.

A ver si me explico. Imagina que llegamos a un punto que SPIMP vale F9FEh. Tenemos que incrementar dos veces. Si hacemos INC HL, INC HL, el valor será F9FFh,FA00h. Hemos pasado de F9FEh a FA00h al incrementar HL dos veces. Si queremos usar INC L dos veces porque es más rápido, si partimos de SPIMP=F9FEh qué valor nos daría? F9FFh con el primer incremento y al incrementar otra vez el registro L sería F900h ya que H no lo actualiza, por lo que lo estaríamos haciendo mal.
Por eso es fundamental estar seguros de que al incrementar direcciones de una tabla no se nos incremente nunca el byte alto si queremos incrementar sólo el byte bajo para ganar velocidad.

Aquí no es muy importante, pero en rutinas de impresión que se mueven mucho datos, la diferencia de velocidad es significativa. Lo veremos.

Después de actualizar SPIMP, incrementamos el contador de sprites a imprimir (CSPIM).  Este contador lo utiliza la rutina de impresión para saber cuántos sprites tiene que imprimir y ese contador lo carga en el registro A:

CSPIM LD A,0

Para incrementarlo lo que hacemos es LD HL,CSPIM+1 ya que la instrucción LD A,n ocupa dos bytes e incrementamos el valor que contiene la dirección de HL; osea, hacemos INC (HL).

Por último, decir que cada vez que se ejecuta el bucle principal, hay que poner a cero el número de sprites a imprimir y poner la cola de impresión de los sprites al principio (SPIMP):



Esto se hace en INICIO justo antes de leer las teclas.



CONTROL DEL DISPARO



El control del disparo se realiza de la siguiente manera:
Si el disparo no está activado, miramos si se ha pulsado la tecla de disparo. Si lo está, metemos el opcode de JP DISPON en PONQDIS, establecemos la duración del disparo (el disparo está activo siempre que su contador de duración sea mayor que cero o no llegue a los límites del buffer), y dependiendo de la dirección a la que mira el prota ponemos el disparo a su derecha o a su izquierda y le damos el sentido del movimiento que corresponda.
Cuando el disparo está activado, decrementamos la duración del mismo y si es ésta llega a cero, lo desactiva llamando a FINDISP que está en ARUTINAS.ASM. Si se nos sale de los límites del buffer lo desactivamos también, y si no, actualizamos su coordenada X (el disparo se mueve de 4 en cuatro pixeles). Por último lo mandamos a la cola de impresión.

Lo interesante de esta rutina es la manera en que se comprueba si el disparo está activado o no.
Lo más normal sería comprobar si (DISPON+1)=0, ya que en ese caso el disparo estaría desactivado y procederíamos a comprobar la tecla. Pero las comprobaciones no son demasiado demasiado rápidas. Lo que hago es cambiar el código del programa directamente.

En CONTENIDA.ASM, antes de saltar a INICIO he metido este código para desactivar el disparo, que es el mismo que FINDISP:



¿Qué hace esto?

Si te das cuenta, para saber si se ha pulsado la tecla del disparo tenemos que meter en el registro A el valor de (JOY+1) o lo que es lo mismo, que se ejecute esta instrucción: LD A,(JOY+1). El código de esta instrucción no es otro que 3Ah,JOY+1. Pues eso es lo que metemos en PONQDIS. Así, cuando el disparo está desactivado, directamente se ejecuta la lectura de la tecla.

Cuando la tecla se pulsa, ponemos (DISPON+1)=20 con lo que se activa el disparo y sabemos que en ese caso no hay que comprobar la tecla hasta que (DISPON+1)=0.
Pues volvemos a cambiar el código y en PONQDIS ponemos directamente JP DISPON poniendo el disparo con:



Esto lo que hace es poner C3h en (PONQDIS) y DISPON en (PONQDIS+1) con lo que en PONQDIS nos queda JP DISPON y directamente pasa a controlar el disparo sin leer la tecla.

Esto de cambiar el código es muy práctico para ganar velocidad, pero éste se complica y puede llegar a ser muy lioso de depurar. Joffa lo usa mucho en Cobra, por ejemplo.

Para terminar, después de la gestión del disparo, mandamos al prota a la cola de impresión. Lo meto el último para que quede por encima de todos los sprites al imprimirlos:




En esta entrada hemos visto algunos conceptos un poco complicados (más de explicar que de entender).
Prueba a cambiar parámetros en las tablas, como coordenadas de los sprites, sentido de la marcha al principio, velocidades, gráficos...
Ten en cuenta que la comprobación de si un sprite llega al límite izquierdo o superior del buffer no es demasiado fina. Simplemente mira si ha llegado a la coordenada cero. Esto puede dar problemas dependiendo del incremento que se le de a la coordenada. Por ello, sitúa al sprite en una coordenada múltiplo de la velocidad que le des.






No hay comentarios:

Publicar un comentario