Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
cursos:ensamblador:gfx3_sprites_lowres [18-11-2010 13:13] – [Trazado de Sprites de 16x16] sromero | cursos:ensamblador:gfx3_sprites_lowres [19-01-2024 08:21] (actual) – sromero | ||
---|---|---|---|
Línea 2: | Línea 2: | ||
En este capítulo crearemos rutinas específica de impresión de sprites de 8x8 píxeles en posiciones exáctas de carácter (gráficos de bloques) y la extenderemos a la impresión de sprites de 16x16 píxeles (2x2 bloques). Además de estas rutinas de tamaños específicos, | En este capítulo crearemos rutinas específica de impresión de sprites de 8x8 píxeles en posiciones exáctas de carácter (gráficos de bloques) y la extenderemos a la impresión de sprites de 16x16 píxeles (2x2 bloques). Además de estas rutinas de tamaños específicos, | ||
+ | |||
+ | | ||
El capítulo hará uso intensivo de los algoritmos de cálculo de direcciones de memoria a partir de coordenadas y del movimiento relativo descritos en 2 anteriores capítulos, aunque las rutinas mostradas a continuación podrían ser directamente utilizadas incluso sin conocimientos sobre la organización de la videomemoria del Spectrum. | El capítulo hará uso intensivo de los algoritmos de cálculo de direcciones de memoria a partir de coordenadas y del movimiento relativo descritos en 2 anteriores capítulos, aunque las rutinas mostradas a continuación podrían ser directamente utilizadas incluso sin conocimientos sobre la organización de la videomemoria del Spectrum. | ||
| | ||
- | |||
\\ | \\ | ||
Línea 47: | Línea 48: | ||
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
- | //Sprite set de Pacman -(c) NAMCO- coloreado por Simon Owen\\ para la versión SAM. Gráficos en formato rectangular.\\ Las rutinas de impresión requieren\\ coordenadas (xorg, | + | //Sprite set de Pacman -(c) NAMCO- coloreado por Simon Owen\\ |
;#; | ;#; | ||
\\ | \\ | ||
Línea 56: | Línea 57: | ||
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
- | //Parte del sprite set de Sokoban: gráficos en formato\\ lineal vertical. Las rutinas de impresión\\ requieren un identificador de sprite (0-NumSprites).// | + | //Parte del sprite set de Sokoban: gráficos en formato\\ |
;#; | ;#; | ||
\\ | \\ | ||
Línea 63: | Línea 64: | ||
\\ | \\ | ||
- | | + | |
\\ | \\ | ||
Línea 71: | Línea 72: | ||
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
- | //Tilemap: componiendo un mapa en pantalla\\ a partir de tiles de un tileset/ | + | //Tilemap: componiendo un mapa en pantalla\\ |
;#; | ;#; | ||
\\ | \\ | ||
Línea 98: | Línea 99: | ||
A la hora de crear una rutina de impresión de sprites tenemos que tener en cuenta el formato del Sprite de Origen. Casi se podría decir que más bien, la rutina de impresión de sprites debemos escribirla o adaptarla al formato de sprites que vayamos a utilizar en el juego. | A la hora de crear una rutina de impresión de sprites tenemos que tener en cuenta el formato del Sprite de Origen. Casi se podría decir que más bien, la rutina de impresión de sprites debemos escribirla o adaptarla al formato de sprites que vayamos a utilizar en el juego. | ||
- | + | ||
Dicho formato puede ser: | Dicho formato puede ser: | ||
Línea 124: | Línea 125: | ||
\\ | \\ | ||
* El cálculo de posición en memoria de las coordenadas (c,f) en las que vamos a dibujar el Sprite. | * El cálculo de posición en memoria de las coordenadas (c,f) en las que vamos a dibujar el Sprite. | ||
- | * El dibujado de cada scanline del sprite en pantalla, ya sea con LD/LDIR o con operaciones lógicas tipo OR/XOR. | + | * El dibujado de cada scanline del sprite en pantalla, ya sea con LD/ldir o con operaciones lógicas tipo OR/XOR. |
* El avance a través del sprite para acceder a otros scanlines del mismo. | * El avance a través del sprite para acceder a otros scanlines del mismo. | ||
* El avance diferencial en pantalla para movernos hacia la derecha (por cada bloque de anchura del sprite), y hacia abajo (por cada scanline de cada bloque y por cada bloque de altura del sprite). | * El avance diferencial en pantalla para movernos hacia la derecha (por cada bloque de anchura del sprite), y hacia abajo (por cada scanline de cada bloque y por cada bloque de altura del sprite). | ||
Línea 139: | Línea 140: | ||
| | ||
- | En este sentido, en alguna de las rutinas utilizaremos variables en memoria para alojar datos de entrada o datos temporales o intermedios. Aunque acceder a la memoria es " | + | En este sentido, en alguna de las rutinas utilizaremos variables en memoria para alojar datos de entrada o datos temporales o intermedios. Aunque acceder a la memoria es " |
- | Las instrucciones | + | Las instrucciones |
\\ | \\ | ||
+ | |< 50% 30% 20% >| | ||
^ Instrucción ^ Tiempo en t-estados ^ | ^ Instrucción ^ Tiempo en t-estados ^ | ||
- | | PUSH rr | 11 | | + | | push rr | 11 | |
- | | PUSH IX o PUSH IY | 16 | | + | | push ix o push iy | 16 | |
- | | POP rr | 10 | | + | | pop rr | 10 | |
- | | POP IX o POP IY | 14 | | + | | pop ix o pop iy | 14 | |
- | | LD (NN), A | 13 | | + | | ld (NN), a | 13 | |
- | | LD A, (NN) | 13 | | + | | ld a, (NN) | 13 | |
- | | LD rr, (NN) | 20 | | + | | ld rr, (NN) | 20 | |
- | | LD (NN), rr | 20 | | + | | ld (NN), rr | 20 | |
- | | LD (NN), HL | 16 | | + | | ld (NN), hl | 16 | |
\\ | \\ | ||
- | | + | |
En nuestro caso utilizaremos algunas variables de memoria para facilitar la lectura de las rutinas: serán más sencillas de seguir y más intuitivas a costa de algunos ciclos de reloj. No deja de ser cierto también que los programadores en ocasiones nos obsesionamos por utilizar sólo registros y acabamos realizando combinaciones de intercambios de valores en registros y PUSHes/POPs que acaban teniendo más coste que la utilización de variables de memoria. | En nuestro caso utilizaremos algunas variables de memoria para facilitar la lectura de las rutinas: serán más sencillas de seguir y más intuitivas a costa de algunos ciclos de reloj. No deja de ser cierto también que los programadores en ocasiones nos obsesionamos por utilizar sólo registros y acabamos realizando combinaciones de intercambios de valores en registros y PUSHes/POPs que acaban teniendo más coste que la utilización de variables de memoria. | ||
- | El programador profesional tendrá que adaptar cada rutina a cada caso específico de su programa y en este proceso de optimización podrá (o no) sustituir dichas variables de memoria por combinaciones de código que eviten su uso, aunque no siempre es posible dado el reducido juego de registros del Z80A. | + | El programador profesional tendrá que adaptar cada rutina a cada caso específico de su programa y en este proceso de optimización podrá (o no) sustituir dichas variables de memoria por combinaciones de código que eviten su uso, aunque no siempre es posible dado el reducido juego de registros del Z80A. |
- | | + | |
Línea 179: | Línea 181: | ||
\\ | \\ | ||
- | El **formato de organización del tileset** no debería requerir mucho tiempo de decisión: la organización del tileset en formato lineal es mucho más eficiente para las rutinas de impresión de sprites que el almacenamiento en una " | + | El **formato de organización del tileset** no debería requerir mucho tiempo de decisión: la organización del tileset en formato lineal es mucho más eficiente para las rutinas de impresión de sprites que el almacenamiento en una " |
De esta forma, el " | De esta forma, el " | ||
Línea 228: | Línea 230: | ||
< | < | ||
Tabla_Sprites: | Tabla_Sprites: | ||
- | | + | |
- | DB L1, M1, N1, O1, P1, A2, B2, C2, D2, E2, F2 | + | DB L1, M1, N1, O1, P1, A2, B2, C2, D2, E2, F2 |
- | DB G2, H2, I2, J2, K2, L2, M2, N2, O2, P2 | + | DB G2, H2, I2, J2, K2, L2, M2, N2, O2, P2 |
- | + | ||
Tabla_Atributos: | Tabla_Atributos: | ||
- | | + | |
</ | </ | ||
Línea 241: | Línea 243: | ||
< | < | ||
Tabla_Sprites: | Tabla_Sprites: | ||
- | | + | |
- | DB L1, M1, N1, O1, P1, S1_Attr1, S1_Attr2, A2 | + | DB L1, M1, N1, O1, P1, S1_Attr1, S1_Attr2, A2 |
- | DB B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2 | + | DB B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2 |
- | DB M2, N2, O2, P2, S2_Attr1, S2_Attr2 | + | DB M2, N2, O2, P2, S2_Attr1, S2_Attr2 |
</ | </ | ||
- | | + | |
Si denominamos " | Si denominamos " | ||
Línea 254: | Línea 256: | ||
; Formato: Una única tabla: | ; Formato: Una única tabla: | ||
Tabla_Sprites: | Tabla_Sprites: | ||
- | | + | |
- | DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 | + | DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 |
- | DB XX, O1, XX, P1, S1_Attr1, S1_Attr2, YY, A2, YY, B2, YY | + | DB XX, O1, XX, P1, S1_Attr1, S1_Attr2 |
- | DB C2, YY, D2, YY, E2, YY, F2, YY, G2, YY, H2, YY, I2, YY | + | DB YY, A2, YY, B2, YY, C2, YY, D2, YY, E2, YY, F2, YY, G2 |
- | DB J2, YY, K2, YY, L2, YY, M2, YY, N2, YY, O2, YY, P2 | + | DB YY, H2, YY, I2, YY, J2, YY, K2, YY, L2, YY, M2, YY, N2 |
- | DB S2_Attr1, S2_Attr2 | + | DB YY, O2, YY, P2, S2_Attr1, S2_Attr2 |
; Formato: Dos tablas: | ; Formato: Dos tablas: | ||
Tabla_Sprites: | Tabla_Sprites: | ||
- | | + | |
- | DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 | + | DB XX, H1, XX, I1, XX, J1, XX, K1, XX, L1, XX, M1, XX, N1 |
- | DB XX, O1, XX, P1, YY, A2, YY, B2, YY, C2, YY, D2, YY, E2 | + | DB XX, O1, XX, P1, YY, A2, YY, B2, YY, C2, YY, D2, YY, E2 |
- | DB YY, F2, YY, G2, YY, H2, YY, I2, YY, J2, YY, K2, YY, L2 | + | DB YY, F2, YY, G2, YY, H2, YY, I2, YY, J2, YY, K2, YY, L2 |
- | DB YY, M2, YY, N2, YY, O2, YY, P2 | + | DB YY, M2, YY, N2, YY, O2, YY, P2 |
Tabla_Atributos: | Tabla_Atributos: | ||
- | | + | |
</ | </ | ||
\\ | \\ | ||
Línea 312: | Línea 314: | ||
Toggle Pixel/ | Toggle Pixel/ | ||
El botón izquierdo cambia el valor de los pixels entre 0 y 1. | El botón izquierdo cambia el valor de los pixels entre 0 y 1. | ||
- | El botón derecho controla la selección. Para seleccionar una zona, | + | El botón derecho controla la selección. Para seleccionar una zona, |
se hace click-derecho en una esquina, click-derecho en la opuesta y ya | se hace click-derecho en una esquina, click-derecho en la opuesta y ya | ||
tenemos una porción seleccionada. La zona seleccionada será algo mas | tenemos una porción seleccionada. La zona seleccionada será algo mas | ||
Línea 320: | Línea 322: | ||
patrón en el relleno con textura. Un tercer click-derecho quita la | patrón en el relleno con textura. Un tercer click-derecho quita la | ||
selección. Atajo de teclado: 2 | selección. Atajo de teclado: 2 | ||
- | |||
Copy | Copy | ||
Línea 360: | Línea 361: | ||
* **Interleave**: | * **Interleave**: | ||
- | | + | |
\\ | \\ | ||
Línea 367: | Línea 368: | ||
* Data Outputted: Gfx | * Data Outputted: Gfx | ||
* Mask: No | * Mask: No | ||
- | * Múltiples sprites en formato vertical sin máscara y con atributos en 1 array: | + | * Múltiples sprites en formato vertical sin máscara y con atributos en 1 array: |
* Sort Priorities: X char, Char line, Y char | * Sort Priorities: X char, Char line, Y char | ||
* Data Outputted: Gfx+Attr | * Data Outputted: Gfx+Attr | ||
* Mask: No | * Mask: No | ||
- | * Múltiples sprites en formato vertical sin máscara y con atributos en 2 arrays: | + | * Múltiples sprites en formato vertical sin máscara y con atributos en 2 arrays: |
* Sort Priorities: X char, Char line, Y char | * Sort Priorities: X char, Char line, Y char | ||
* Data Outputted: Primero exportamos Gfx y luego Attr | * Data Outputted: Primero exportamos Gfx y luego Attr | ||
* Mask: No | * Mask: No | ||
- | * Múltiples sprites en formato vertical con máscara intercalada y con atributos: | + | * Múltiples sprites en formato vertical con máscara intercalada y con atributos: |
* Sort Priorities: Mask, X char, Char line, Y char | * Sort Priorities: Mask, X char, Char line, Y char | ||
* Data Outputted: Gfx+Attr | * Data Outputted: Gfx+Attr | ||
* Mask: Yes, before graphic | * Mask: Yes, before graphic | ||
- | * Múltiples sprites en formato vertical con máscara intercalada y con atributos en 2 arrays: | + | * Múltiples sprites en formato vertical con máscara intercalada y con atributos en 2 arrays: |
* Sort Priorities: Mask, X char, Char line, Y char | * Sort Priorities: Mask, X char, Char line, Y char | ||
* Data Outputted: Primero exportamos Gfx y luego Attr | * Data Outputted: Primero exportamos Gfx y luego Attr | ||
Línea 529: | Línea 530: | ||
\\ | \\ | ||
- | Como puede verse, SevenuP nos permite realizar la exportación tal y como la necesitemos en | + | Como puede verse, SevenuP nos permite realizar la exportación tal y como la necesitemos en |
nuestras rutinas de impresión. Nosotros utilizaremos principalmente los 3 formatos que acabamos de ver, pero otras rutinas pueden necesitar de otra organización diferente, la cual podemos lograr alterando estos parámetros. | nuestras rutinas de impresión. Nosotros utilizaremos principalmente los 3 formatos que acabamos de ver, pero otras rutinas pueden necesitar de otra organización diferente, la cual podemos lograr alterando estos parámetros. | ||
Línea 537: | Línea 538: | ||
| | ||
+ | |||
+ | |||
+ | \\ | ||
+ | \\ | ||
+ | **INCBIN vs INCLUDE vs DB:** | ||
+ | |||
+ | En este y los siguientes capítulos, para incluir los gráficos en nuestros ejemplos los hemos exportado como secuencias de bytes DEFB con el propio SevenuP. El ASM exportado por SevenuP puede ser " | ||
+ | |||
+ | No obstante, si tenemos los gráficos y atributos por separado en formato BINARIO (no como ristras de números en un fichero de texto), podríamos incluirlo con '' | ||
+ | |||
+ | <code z80> | ||
+ | bicho_gfx: | ||
+ | INCBIN " | ||
+ | |||
+ | bicho_attr: | ||
+ | INCBIN " | ||
+ | </ | ||
+ | |||
+ | Es exactamente equivalente a incluir los bytes como números con DB, pero nos evita tener que convertir el fichero binario a texto. Eso implica que si estamos editando constantemente por ejemplo, un fichero gráfico, y lo editamos en un editor que trabaje directamente con el formato binario, no tenemos que volver a convertir a texto su contenido. | ||
+ | |||
+ | SevenuP permite exportar también como binario en "File > Export Data > Raw Binary (*.BIN)" | ||
+ | |||
+ | |||
+ | Esto se puede hacer también con las pantallas completas o las fuentes de texto, entre otros. | ||
+ | |||
\\ | \\ | ||
===== Paso de parámetros a las rutinas ===== | ===== Paso de parámetros a las rutinas ===== | ||
- | Al programar esta rutina, y cualquiera de las que veremos en este capítulo, debemos decidir cómo pasar los parámetros de entrada a la misma, ya que en estas rutinas manejaremos bastantes parámetros y en alguno de los casos no tendremos suficientes registros libres para establecerlos antes del CALL. | + | Al programar esta rutina, y cualquiera de las que veremos en este capítulo, debemos decidir cómo pasar los parámetros de entrada a la misma, ya que en estas rutinas manejaremos bastantes parámetros y en alguno de los casos no tendremos suficientes registros libres para establecerlos antes del call. |
En este sentido, podemos utilizar para el paso de los parámetros, | En este sentido, podemos utilizar para el paso de los parámetros, | ||
\\ | \\ | ||
- | * Registros de 8 y 16 bits, allá donde sea posible, especialmente en rutinas con pocos parámetros de entrada y que sean llamadas en el programa en momentos críticos o gran cantidad de veces. | + | * **Registros de 8 y 16 bits**, allá donde sea posible, especialmente en rutinas con pocos parámetros de entrada y que sean llamadas en el programa en momentos críticos o gran cantidad de veces. |
- | * La Pila: realizando PUSH de los parámetros de entrada en un orden concreto. La rutina, en su punto inicial, hará POP de dichos valores en los registros adecuados. Tiene la ventaja de que podemos ir recuperando los valores conforme los vayamos necesitando y tras haber realizado cálculos con los parámetros anteriores que nos hayan dejado libres registros para los siguientes cálculos. | + | |
- | * El Stack del Calculador: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante | + | * **La Pila**: realizando |
- | * Variables de memoria: podemos establecer los valores de entrada en variables de memoria con LD y recuperarlos dentro de la rutina también con instrucciones de carga LD. Esta técnica tiene una desventaja: la " | + | |
+ | * **El Stack del Calculador**: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante | ||
+ | |||
+ | * **Variables de memoria**: podemos establecer los valores de entrada en variables de memoria con LD y recuperarlos dentro de la rutina también con instrucciones de carga LD. Esta técnica tiene una desventaja: la " | ||
* Los registros quedan libres para realizar todo tipo de operaciones. | * Los registros quedan libres para realizar todo tipo de operaciones. | ||
* No tenemos que preservar los valores de los parámetros de entrada al realizar operaciones con los registros, y podemos recuperarlos en cualquier otro punto de la rutina para realizar nuevos cálculos. | * No tenemos que preservar los valores de los parámetros de entrada al realizar operaciones con los registros, y podemos recuperarlos en cualquier otro punto de la rutina para realizar nuevos cálculos. | ||
- | * Nos permite llamar a las rutinas desde BASIC, estableciendo los parámetros con POKE y después realizando el RANDOMIZE USR direccion_rutina. | + | * Nos permite llamar a las rutinas desde BASIC (si usamos posiciones de memoria de direcciones fijas y conocidas), estableciendo los parámetros con POKE y después realizando el '' |
\\ | \\ | ||
- | Lo normal en rutinas de este tipo sería utilizar en la medida de lo posible el paso mediante registros (programa en ASM puro) o mediante la pila (programas en C y ASM), pero en nuestros ejemplos utilizaremos el último método: paso de variables en direcciones de memoria, con el objetivo de que las rutinas puedan llamarse desde BASIC y para que sean lo suficientemente sencillas de leer para que cada programador pueda adaptar la entrada de una rutina concreta a las necesidades de su programa utilizando otro de los métodos de paso de parámetros descrito. | + | Lo normal en rutinas de este tipo sería utilizar en la medida de lo posible el paso mediante registros (programa en ASM puro) o mediante la pila (programas en C y ASM), pero en nuestros ejemplos utilizaremos el último método: paso de variables en direcciones de memoria, con el objetivo de que las rutinas puedan llamarse desde BASIC y para que sean lo suficientemente sencillas de leer para que cada programador pueda adaptar la entrada de una rutina concreta a las necesidades de su programa utilizando otro de los métodos de paso de parámetros descrito. |
Es habitual incluso que en los programas ni siquiera se le pase a las rutinas las direcciones origen de Sprites y Atributos sino que dichas direcciones estén " | Es habitual incluso que en los programas ni siquiera se le pase a las rutinas las direcciones origen de Sprites y Atributos sino que dichas direcciones estén " | ||
Por otra parte, el paso de parámetros es sólo un pequeño porcentaje del tiempo total de la rutina: el interés de optimización residirá en la impresión del sprite en sí, ya que esta recogida de parámetros se realiza una sola vez por sprite independientemente del número de bloques que lo compongan (en cuyo dibujado será donde realicemos el mayor gasto de tiempo). | Por otra parte, el paso de parámetros es sólo un pequeño porcentaje del tiempo total de la rutina: el interés de optimización residirá en la impresión del sprite en sí, ya que esta recogida de parámetros se realiza una sola vez por sprite independientemente del número de bloques que lo compongan (en cuyo dibujado será donde realicemos el mayor gasto de tiempo). | ||
+ | |||
\\ | \\ | ||
Línea 596: | Línea 626: | ||
;Sort Priorities: Char line | ;Sort Priorities: Char line | ||
- | cara_gfx: | + | cara_gfx: |
- | DEFB 28, 62,107,127, 93, 99, 62, 28 | + | DEFB 28, 62,107,127, 93, 99, 62, 28 |
cara_attrib: | cara_attrib: | ||
- | | + | |
</ | </ | ||
+ | |||
+ | |||
+ | \\ | ||
+ | ==== Sobreescribiendo el fondo (impresión con LD) ==== | ||
+ | |||
+ | La primera de las rutinas que veremos realizará transferencias directas de datos entre el origen (el sprite) y el destino (la pantalla). | ||
Para nuestra rutina utilizaremos el siguiente esquema de paso de parámetros: | Para nuestra rutina utilizaremos el siguiente esquema de paso de parámetros: | ||
\\ | \\ | ||
- | ^ Dirección | + | |< 50% >| |
- | | 50000 | Dirección de la tabla de Sprites | | + | ^ Variable ^ Tamaño (Bytes) |
- | | 50002 | Dirección de la tabla de Atributos | | + | | DS_SPRITES | 2 | Dirección de la tabla de Sprites | |
- | | 50004 | Coordenada X en baja resolución | | + | | DS_ATTRIBS | 2 | Dirección de la tabla de Atributos | |
- | | 50005 | Coordenada Y en baja resolución | | + | | DS_COORD_X | 1 | Coordenada X en baja resolución | |
- | | 50006 | Numero de sprite a dibujar (0-N) | | + | | DS_COORD_Y | 1 | Coordenada Y en baja resolución | |
+ | | DS_NUMSPR | 1 | Numero de sprite a dibujar (0-N) | | ||
\\ | \\ | ||
Línea 627: | Línea 664: | ||
; Bajar a siguiente scanline en pantalla (HL). | ; Bajar a siguiente scanline en pantalla (HL). | ||
- | ; Si base_atributos == 0 -> RET | + | ; Si base_atributos == 0 -> ret |
; Calcular posicion origen de los atributos array_attr+NUM_SPRITE en HL. | ; Calcular posicion origen de los atributos array_attr+NUM_SPRITE en HL. | ||
; Calcular posicion destino en area de atributos en DE. | ; Calcular posicion destino en area de atributos en DE. | ||
Línea 633: | Línea 670: | ||
</ | </ | ||
- | La rutina debe de comenzar calculando la direcciones origen y destino para las transferencias de datos. | + | La rutina debe de comenzar calculando la direcciones origen y destino para las transferencias de datos. |
La dirección origen es el primer scanline del sprite a dibujar. Dada una tabla de sprites 8x8 en formato vertical, y teniendo en cuenta que cada sprite 8x8 ocupa 8 bytes (1 byte por cada scanline, 8 scanlines), nos posicionaremos en el sprite correcto " | La dirección origen es el primer scanline del sprite a dibujar. Dada una tabla de sprites 8x8 en formato vertical, y teniendo en cuenta que cada sprite 8x8 ocupa 8 bytes (1 byte por cada scanline, 8 scanlines), nos posicionaremos en el sprite correcto " | ||
Línea 653: | Línea 690: | ||
La misma rutina que vamos a crear servirá para dibujar gráficos con atributos o sin atributos. Nos puede interesar el dibujado de gráficos sin atributos en juegos monocolor donde los atributos de fondo ya están establecidos en pantalla y no sea necesario re-escribirlos, | La misma rutina que vamos a crear servirá para dibujar gráficos con atributos o sin atributos. Nos puede interesar el dibujado de gráficos sin atributos en juegos monocolor donde los atributos de fondo ya están establecidos en pantalla y no sea necesario re-escribirlos, | ||
- | En lugar de crear 2 rutinas diferentes, una que imprima un sprite con atributos y otra que lo haga sin ellos, vamos a utilizar en este capítulo una única rutina indicando en el parámetro de la dirección de atributos si queremos dibujarlos o no. La rutina comprobará si la dirección de atributos es 0 (basta con comprobar su parte alta) y si es así, saldrá con un RET sin realizar los cálculos de atributos (dirección origen, destino, y dibujado). | + | En lugar de crear 2 rutinas diferentes, una que imprima un sprite con atributos y otra que lo haga sin ellos, vamos a utilizar en este capítulo una única rutina indicando en el parámetro de la dirección de atributos si queremos dibujarlos o no. La rutina comprobará si la dirección de atributos es 0 (basta con comprobar su parte alta) y si es así, saldrá con un ret sin realizar los cálculos de atributos (dirección origen, destino, y dibujado). |
- | | + | |
- | | + | |
< | < | ||
Línea 677: | Línea 714: | ||
; | ; | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
- | ; Direccion | + | ; Direccion |
- | ; 50000 Direccion de la tabla de Sprites | + | ; (DS_SPRITES) |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_ATTRIBS) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_Y) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_NUMSPR) |
; | ; | ||
DrawSprite_8x8_LD: | DrawSprite_8x8_LD: | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | |
- | LD BC, (DS_COORD_X) | + | ld bc, (DS_COORD_X) |
- | ;;; Calculamos las coordenadas destino de pantalla en DE: | + | |
- | LD A, B | + | ld a, b |
- | AND $18 | + | |
- | ADD A, $40 | + | add a, $40 |
- | LD D, A | + | ld d, a ; Ya tenemos la parte alta calculada (010TT000) |
- | LD A, B | + | ld a, b ; Ahora calculamos la parte baja |
- | AND 7 | + | |
- | RRCA | + | rrca |
- | RRCA | + | rrca |
- | RRCA | + | |
- | | + | add a, c ; Sumamos COLUMNA -> A = NNNCCCCCb |
- | LD E, A ; DE contiene ahora la direccion destino. | + | ld e, a |
+ | | ||
- | PUSH DE ; Lo guardamos para luego, lo usaremos para | + | |
- | | + | ;;; |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | ;;; | + | ld a, (DS_NUMSPR) |
- | + | ld h, 0 | |
- | LD BC, (DS_SPRITES) | + | ld l, a ; HL = DS_NUMSPR |
- | LD A, (DS_NUMSPR) | + | add hl, hl ; HL = HL * 2 |
- | LD H, 0 | + | add hl, hl ; HL = HL * 4 |
- | LD L, A ; HL = DS_NUMSPR | + | add hl, hl ; HL = HL * 8 = DS_NUMSPR * 8 |
- | ADD HL, HL ; HL = HL * 2 | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) |
- | ADD HL, HL ; HL = HL * 4 | + | ; HL contiene la direccion de inicio en el sprite |
- | ADD HL, HL ; HL = HL * 8 = DS_NUMSPR * 8 | + | |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) | + | |
- | | + | |
- | EX DE, HL ; Intercambiamos DE y HL (DE=origen, HL=destino) | + | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) |
- | ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | + | |
- | | + | ;;; Incrementar scanline del sprite (DE) |
+ | |||
+ | ld b, 8 ; 8 scanlines -> 8 iteraciones | ||
- | LD B, 8 ; 8 scanlines -> 8 iteraciones | ||
- | |||
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | LD A, (DE) ; Tomamos el dato del sprite | + | ld a, (de) ; Tomamos el dato del sprite |
- | LD (HL), A ; Establecemos el valor en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite | + | inc de ; Incrementamos puntero en sprite |
- | INC H ; Incrementamos puntero en pantalla (scanline+=1) | + | inc h ; Incrementamos puntero en pantalla (scanline+=1) |
- | DJNZ drawsp8x8_loopLD | + | |
- | + | ||
- | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | + | |
- | POP BC ; Recuperamos | + | ;;; En este punto, los 8 scanlines del sprite estan dibujados. |
+ | ld a, h | ||
+ | sub 8 | ||
+ | ld b, a ; scanline | ||
+ | ld c, l ; BC = HL - 8 | ||
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | |
- | LD HL, (DS_ATTRIBS) | + | ld hl, (DS_ATTRIBS) |
- | XOR A ; A = 0 | + | xor a ; A = 0 |
- | ADD A, H ; A = 0 + H = H | + | add a, h ; A = 0 + H = H |
- | RET Z ; Si H = 0, volver (no dibujar atributos) | + | ret z ; Si H = 0, volver (no dibujar atributos) |
- | ;;; Calcular posicion destino en area de atributos en DE. | + | |
- | LD A, B ; Codigo de Get_Attr_Offset_From_Image | + | ld a, b ; Codigo de Get_Attr_Offset_From_Image |
- | RRCA ; Obtenemos dir de atributo a partir de | + | |
- | RRCA ; dir de zona de imagen. | + | |
- | RRCA ; Nos evita volver a obtener X e Y | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | ld d, a |
- | LD E, C ; DE tiene el offset del attr de HL | + | ld e, c ; DE tiene el offset del attr de HL |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD C, A | + | ld c, a |
- | LD B, 0 | + | ld b, 0 |
- | ADD HL, BC ; HL = HL+DS_NUMSPR = Origen de atributo | + | add hl, bc ; HL = HL+DS_NUMSPR = Origen de atributo |
- | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | + | |
- | LD A, (HL) | + | ld a, (hl) |
- | LD (DE), A ; Mas rapido que LDI (7+7 vs 16 t-estados) | + | |
- | RET ; porque no necesitamos incrementar HL y DE | + | |
</ | </ | ||
Al respecto del código de la rutina, caben destacar las siguientes consideraciones: | Al respecto del código de la rutina, caben destacar las siguientes consideraciones: | ||
- | * Nótese que en la rutina se emplean las subrutinas | + | * Nótese que en la rutina se emplean las subrutinas |
- | * Para realizar la transferencia de datos entre el sprite (apuntado por DE) y la pantalla (apuntada por HL) hemos utilizado 2 instrucciones de transferencia LD usando A como registro intermedio en lugar de utilizar una instrucción | + | * Para realizar la transferencia de datos entre el sprite (apuntado por DE) y la pantalla (apuntada por HL) hemos utilizado 2 instrucciones de transferencia LD usando A como registro intermedio en lugar de utilizar una instrucción |
- | * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar | + | * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar |
- | * La rutina que hemos visto, por simplicar el código, utiliza un bucle de 8 iteraciones para dibujar los 8 scanlines. Esto ahorra espacio (ocupación de la rutina) pero implica un testeo del contador y salto por cada iteración (excepto en la última). En una rutina crítica, si tenemos suficiente espacio libre, y si conocemos de antemano el número de iteraciones exacto de un bucle, lo óptimo sería **desenrollar el bucle**, es decir, repetir 8 veces el código de impresión. De este modo evitamos el **LD B, 8** y el **DJNZ | + | * La rutina que hemos visto, por simplicar el código, utiliza un bucle de 8 iteraciones para dibujar los 8 scanlines. Esto ahorra espacio (ocupación de la rutina) pero implica un testeo del contador y salto por cada iteración (excepto en la última). En una rutina crítica, si tenemos suficiente espacio libre, y si conocemos de antemano el número de iteraciones exacto de un bucle, lo óptimo sería **desenrollar el bucle**, es decir, repetir 8 veces el código de impresión. De este modo evitamos el '' |
En el caso de nuestra rutina de ejemplo, cambiaríamos... | En el caso de nuestra rutina de ejemplo, cambiaríamos... | ||
<code z80> | <code z80> | ||
- | LD B, 8 ; 8 scanlines | + | ld b, 8 ; 8 scanlines |
- | | + | |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | LD A, (DE) ; Tomamos el dato del sprite | + | ld a, (de) ; Tomamos el dato del sprite |
- | LD (HL), A ; Establecemos el valor en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite | + | inc de ; Incrementamos puntero en sprite |
- | INC H ; Incrementamos puntero en pantalla (scanline+=1) | + | inc h ; Incrementamos puntero en pantalla (scanline+=1) |
- | DJNZ drawsp8x8_loopLD | + | |
</ | </ | ||
Línea 790: | Línea 827: | ||
<code z80> | <code z80> | ||
- | LD A, (DE) ; Scanline 0 | + | ld a, (de) ; Scanline 0 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 1 | + | ld a, (de) ; Scanline 1 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 2 | + | ld a, (de) ; Scanline 2 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | + | ||
- | LD A, (DE) ; Scanline 3 | + | |
- | LD (HL), A | + | |
- | INC DE | + | |
- | INC H | + | |
- | LD A, (DE) ; Scanline | + | ld a, (de) ; Scanline |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline | + | ld a, (de) ; Scanline |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline | + | ld a, (de) ; Scanline |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 7 | + | ld a, (de) ; Scanline 6 |
- | LD (HL), A | + | ld (hl), a |
- | INC DE | + | inc de |
- | INC H | + | inc h |
+ | |||
+ | ld a, (de) ; Scanline 7 | ||
+ | | ||
+ | inc de | ||
+ | ;;; | ||
</ | </ | ||
+ | |||
+ | | ||
+ | |||
+ | Al no ser necesario el '' | ||
+ | |||
+ | <code z80> | ||
+ | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | ||
+ | ld a, h | ||
+ | sub 7 ; Recuperamos la posicion de memoria del | ||
+ | ld b, a ; scanline inicial donde empezamos a dibujar | ||
+ | ld c, l ; BC = HL - 7 | ||
+ | </ | ||
+ | |||
+ | | ||
+ | |||
+ | <code z80> | ||
+ | REPT 8 | ||
+ | ld a, (de) ; Tomamos el dato del sprite | ||
+ | ld (hl), a ; Establecemos el valor en videomemoria | ||
+ | inc de ; Incrementamos puntero en sprite | ||
+ | inc h ; Incrementamos puntero en pantalla (scanline+=1) | ||
+ | ENDM | ||
+ | </ | ||
+ | |||
+ | O, si queremos ahorrarnos el '' | ||
Lo normal es desenrollar sólo aquellas rutinas lo suficiente críticas e importantes como para compensar el mayor espacio en memoria con un menor tiempo de ejecución. En esta rutina evitaríamos la pérdida de ciclos de reloj en el establecimiento del contador, en el testeo de condición de salida y en el salto, a cambio de una mayor ocupación de espacio tras el ensamblado. | Lo normal es desenrollar sólo aquellas rutinas lo suficiente críticas e importantes como para compensar el mayor espacio en memoria con un menor tiempo de ejecución. En esta rutina evitaríamos la pérdida de ciclos de reloj en el establecimiento del contador, en el testeo de condición de salida y en el salto, a cambio de una mayor ocupación de espacio tras el ensamblado. | ||
- | Una rutina de impresión de sprites de tamaños fijos (8x8, 16x16, 8x16, etc) en la que conocemos el número de iteraciones verticales y horizontales para la impresión es uno de los casos típicos en los que usaremos esta técnica. Si la rutina fuera para sprites de tamaño variable (NxM), no podríamos aplicarla porque necesitamos los bucles de N y M iteraciones que no podemos sustituir de antemano. | + | Una rutina de impresión de sprites de tamaños fijos (8x8, 16x16, 8x16, etc) en la que conocemos el número de iteraciones verticales y horizontales para la impresión es uno de los casos típicos en los que usaremos esta técnica. Si la rutina fuera para sprites de tamaño variable (NxM), no podríamos aplicarla porque necesitamos los bucles de N y M iteraciones que no podemos sustituir de antemano. |
El programa de ejemplo que veremos a continuación utiliza la rutina anterior (no incluída en el listado) para imprimir el sprite de ejemplo 8x8 que hemos visto: | El programa de ejemplo que veremos a continuación utiliza la rutina anterior (no incluída en el listado) para imprimir el sprite de ejemplo 8x8 que hemos visto: | ||
<code z80> | <code z80> | ||
- | | + | ; Ejemplo impresion sprites 8x8 |
- | ORG 32768 | + | ORG 35000 |
- | DS_SPRITES | + | call ClearScreen_Pattern |
- | DS_ATTRIBS | + | |
- | DS_COORD_X | + | |
- | DS_COORD_Y | + | |
- | DS_NUMSPR | + | |
- | CALL ClearScreen_Pattern | + | ; Establecemos los parametros de entrada a la rutina |
+ | ; Los 2 primeros se pueden establecer una unica vez | ||
+ | ld hl, cara_gfx | ||
+ | ld (DS_SPRITES), | ||
+ | ld hl, cara_attrib | ||
+ | ld (DS_ATTRIBS), | ||
+ | ld a, 15 | ||
+ | ld (DS_COORD_X), | ||
+ | ld a, 8 | ||
+ | ld (DS_COORD_Y), | ||
+ | xor a | ||
+ | ld (DS_NUMSPR), | ||
- | ; Establecemos los parametros de entrada a la rutina | + | call DrawSprite_8x8_LD |
- | ; Los 2 primeros se pueden establecer una unica vez | + | |
- | LD HL, cara_gfx | + | |
- | LD (DS_SPRITES), | + | |
- | LD HL, cara_attrib | + | |
- | LD (DS_ATTRIBS), | + | |
- | LD A, 15 | + | |
- | LD (DS_COORD_X), | + | |
- | LD A, 8 | + | |
- | LD (DS_COORD_Y), | + | |
- | XOR A | + | |
- | LD (DS_NUMSPR), | + | |
- | + | ||
- | CALL DrawSprite_8x8_LD | + | |
loop: | loop: | ||
- | JR loop | + | jr loop |
- | | + | ret |
+ | |||
+ | ; Variables que usaremos como parámetros | ||
+ | DS_SPRITES | ||
+ | DS_ATTRIBS | ||
+ | DS_COORD_X | ||
+ | DS_COORD_Y | ||
+ | DS_NUMSPR | ||
; | ; | ||
Línea 873: | Línea 936: | ||
; | ; | ||
ClearScreen_Pattern: | ClearScreen_Pattern: | ||
- | LD B, 191 ; Numero de lineas a rellenar | + | ld b, 191 ; Numero de lineas a rellenar |
cs_line_loop: | cs_line_loop: | ||
- | LD C, 0 | + | ld c, 0 |
- | LD A, B | + | ld a, b |
- | LD B, A | + | ld b, a |
- | CALL $22B1 ; ROM (Pixel-Address) | + | |
- | LD A, B | + | ld a, b |
- | AND 1 | + | |
- | JR Z, cs_es_par | + | jr z, cs_es_par |
- | LD A, 170 | + | ld a, 170 |
- | JR cs_pintar | + | |
cs_es_par: | cs_es_par: | ||
- | LD A, 85 | + | ld a, 85 |
cs_pintar: | cs_pintar: | ||
- | LD D, B ; Salvar el contador del bucle | + | ld d, b ; Salvar el contador del bucle |
- | LD B, 32 ; Imprimir 32 bytes | + | ld b, 32 ; Imprimir 32 bytes |
cs_x_loop: | cs_x_loop: | ||
- | | + | ld (hl), a |
- | INC HL | + | inc hl |
- | DJNZ cs_x_loop | + | |
- | + | ||
- | LD B, D ; Recuperamos el contador externo | + | |
- | DJNZ cs_line_loop | + | |
- | RET | + | |
+ | ld b, d ; Recuperamos el contador externo | ||
+ | djnz cs_line_loop | ||
+ | ret | ||
; | ; | ||
Línea 912: | Línea 974: | ||
cara_gfx: | cara_gfx: | ||
- | | + | |
cara_attrib: | cara_attrib: | ||
- | | + | |
; | ; | ||
DrawSprite_8x8_LD: | DrawSprite_8x8_LD: | ||
- | | + | |
+ | |||
+ | END 35000 | ||
</ | </ | ||
Línea 926: | Línea 989: | ||
\\ | \\ | ||
- | {{ : | + | {{ : |
\\ | \\ | ||
+ | |||
+ | Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, | ||
+ | |||
+ | <code z80> | ||
+ | DS_SPRITES | ||
+ | DS_ATTRIBS | ||
+ | DS_COORD_X | ||
+ | DS_COORD_Y | ||
+ | DS_NUMSPR | ||
+ | </ | ||
+ | |||
+ | Así, podríamos establecer la coordenada X e Y con '' | ||
+ | |||
+ | En nuestro caso, como el esqueleto del programa está en ensamblador, | ||
\\ | \\ | ||
- | **Transferencia por LDI vs LD+INC** | + | **Transferencia por ldi vs LD+INC** |
\\ | \\ | ||
En nuestra rutina de impresión hemos utilizado instrucciones de carga entre memoria para transferir bytes desde la dirección apuntada por DE (el origen; el Sprite) a la dirección apuntada por HL (el destino; la pantalla). | En nuestra rutina de impresión hemos utilizado instrucciones de carga entre memoria para transferir bytes desde la dirección apuntada por DE (el origen; el Sprite) a la dirección apuntada por HL (el destino; la pantalla). | ||
- | Para trazar los píxeles en pantalla podríamos haber utilizado la instrucción LDI, que con 16 t-estados realiza una transferencia de 1 byte entre la dirección de memoria apuntada por HL (origen) y la apuntada por DE (destino), y además incrementa HL y DE. | + | Para trazar los píxeles en pantalla podríamos haber utilizado la instrucción |
El bucle principal de impresión de nuestro programa es el siguiente: | El bucle principal de impresión de nuestro programa es el siguiente: | ||
Línea 941: | Línea 1018: | ||
<code z80> | <code z80> | ||
drawsp8x8_loop: | drawsp8x8_loop: | ||
- | LD A, (DE) ; A = (DE) = leer dato del sprite | + | ld a, (de) ; A = (DE) = leer dato del sprite |
- | LD (HL), A ; (HL) = A = escribir dato a la pantalla | + | |
- | INC DE ; Incrementamos DE (puntero sprite) | + | inc de ; Incrementamos DE (puntero sprite) |
- | INC H ; Incrementamos scanline HL (HL+=256) | + | inc h ; Incrementamos scanline HL (HL+=256) |
- | DJNZ drawsp8x8_loop | + | |
</ | </ | ||
- | Las instrucciones antes del DJNZ tienen un coste de ejecución de 7, 7, 6 y 4 t-estados respectivamente (empezando por el **LD A, (DE)** y acabando por el **INC H**). Esto suma un total de 24 t-estados por cada byte transferido. | + | Las instrucciones antes del '' |
Si invertimos el uso de los punteros y utilizamos HL como puntero al Sprite (origen) y DE como puntero a pantalla (destino), el bucle anterior podría haberse reescrito de la siguiente forma: | Si invertimos el uso de los punteros y utilizamos HL como puntero al Sprite (origen) y DE como puntero a pantalla (destino), el bucle anterior podría haberse reescrito de la siguiente forma: | ||
Línea 954: | Línea 1031: | ||
<code z80> | <code z80> | ||
drawsp8x8_loop: | drawsp8x8_loop: | ||
- | | + | ldi ; Copia (HL) en (DE) y HL++ DE++ |
- | INC D ; Sumamos 256 (+1=257) | + | inc d ; Sumamos 256 (+1=257) |
- | DEC E ; Restamos 1 (+=256) | + | dec e ; Restamos 1 (+=256) |
- | DJNZ drawsp8x8_loop | + | |
</ | </ | ||
| | ||
- | Entre las 2 posibles formas de realizar la impresión (LD+INC vs LDI), utilizaremos la primera porque, como veremos a continuación, | + | Entre las 2 posibles formas de realizar la impresión ('' |
+ | |||
+ | | ||
Línea 974: | Línea 1053: | ||
\\ | \\ | ||
- | | + | |
< | < | ||
Línea 981: | Línea 1060: | ||
Valor en Videomemoria (HL): | Valor en Videomemoria (HL): | ||
Valor en el sprite - reg. A: 00111110 | Valor en el sprite - reg. A: 00111110 | ||
- | Operación: | + | Operación: |
Resultado en VRAM: 00111110 | Resultado en VRAM: 00111110 | ||
</ | </ | ||
- | La primera de las soluciones a este problema es la de escribir los píxeles con una operación lógica OR entre el scanline y el valor actual en memoria. Con la operación OR mezclaremos los bits de ambos elementos: | + | La primera de las soluciones a este problema es la de escribir los píxeles con una operación lógica |
< | < | ||
Línea 996: | Línea 1075: | ||
</ | </ | ||
- | La operación OR nos permitirá respetar el fondo en aquellos juegos en que los sprites no tengan zonas a 0 dentro del contorno del mismo (que, como veremos más adelante, no es el caso de nuestro pequeño sprite). | + | La operación |
- | Para modificar la rutina de impresión de Sprites de forma que utilice OR (u otra operación lógica con otras aplicaciones como XOR), sólo necesitamos añadir la correspondiente instrucción lógica entre A y el contenido de la memoria: | + | Para modificar la rutina de impresión de Sprites de forma que utilice |
| | ||
<code z80> | <code z80> | ||
- | LD B, 8 | + | ld b, 8 ; 8 scanlines -> 8 iteraciones |
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | LD A, (DE) ; Tomamos el dato del sprite | + | ld a, (de) ; Tomamos el dato del sprite |
- | LD (HL), A ; Establecemos el valor en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite | + | inc de ; Incrementamos puntero en sprite |
- | INC H ; Incrementamos puntero en pantalla | + | inc h ; Incrementamos puntero en pantalla |
- | DJNZ drawsp8x8_loopLD | + | |
</ | </ | ||
Línea 1016: | Línea 1095: | ||
<code z80> | <code z80> | ||
- | LD B, 8 ; 8 scanlines -> 8 iteraciones | + | ld b, 8 ; 8 scanlines -> 8 iteraciones |
- | + | ||
- | drawsp8x8_loop: | + | drawsp8x8_loop_or: |
- | LD A, (DE) ; Tomamos el dato del sprite | + | ld a, (de) ; Tomamos el dato del sprite |
- | OR (HL) ; NUEVO: Hacemos un OR del scanline con el fondo | + | |
- | LD (HL), A ; Establecemos el valor del OR en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite (DE+=1) | + | inc de ; Incrementamos puntero en sprite (DE+=1) |
- | INC H ; Incrementamos puntero en pantalla (HL+=256) | + | inc h ; Incrementamos puntero en pantalla (HL+=256) |
- | DJNZ drawsp8x8_loop | + | djnz drawsp8x8_loop_or |
</ | </ | ||
- | A continuación, | + | A continuación, |
<code z80> | <code z80> | ||
Línea 1035: | Línea 1114: | ||
; | ; | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
- | ; Direccion | + | ; Direccion |
- | ; 50000 Direccion de la tabla de Sprites | + | ; (DS_SPRITES) |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_ATTRIBS) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_Y) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_NUMSPR) |
; | ; | ||
DrawSprite_8x8_OR: | DrawSprite_8x8_OR: | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | |
- | LD BC, (DS_COORD_X) | + | ld bc, (DS_COORD_X) |
- | ;;; Calculamos las coordenadas destino de pantalla en DE: | + | |
- | LD A, B | + | ld a, b |
- | AND $18 | + | |
- | ADD A, $40 | + | add a, $40 |
- | LD D, A ; Ya tenemos la parte alta calculada (010TT000) | + | ld d, a ; Ya tenemos la parte alta calculada (010TT000) |
- | LD A, B ; Ahora calculamos la parte baja | + | ld a, b ; Ahora calculamos la parte baja |
- | AND 7 | + | |
- | RRCA | + | rrca |
- | RRCA | + | rrca |
- | RRCA ; A = NNN00000b | + | |
- | ADD A, C ; Sumamos COLUMNA -> A = NNNCCCCCb | + | add a, c ; Sumamos COLUMNA -> A = NNNCCCCCb |
- | LD E, A ; Lo cargamos en la parte baja de la direccion | + | ld e, a ; Lo cargamos en la parte baja de la direccion |
- | | + | ; DE contiene ahora la direccion destino. |
- | PUSH DE ; Lo guardamos para luego, lo usaremos para | + | |
- | | + | ;;; |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | ;;; | + | ld a, (DS_NUMSPR) |
- | + | ld h, 0 | |
- | LD BC, (DS_SPRITES) | + | ld l, a ; HL = DS_NUMSPR |
- | LD A, (DS_NUMSPR) | + | add hl, hl ; HL = HL * 2 |
- | LD H, 0 | + | add hl, hl ; HL = HL * 4 |
- | LD L, A ; HL = DS_NUMSPR | + | add hl, hl ; HL = HL * 8 = DS_NUMSPR * 8 |
- | ADD HL, HL ; HL = HL * 2 | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) |
- | ADD HL, HL ; HL = HL * 4 | + | ; HL contiene la direccion de inicio en el sprite |
- | ADD HL, HL ; HL = HL * 8 = DS_NUMSPR * 8 | + | |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) | + | |
- | | + | |
- | EX DE, HL ; Intercambiamos DE y HL para el OR | + | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) |
- | ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline | + | |
- | | + | ;;; Incrementar scanline del sprite (DE) |
- | LD B, 8 ; 8 scanlines -> 8 iteraciones | + | |
- | + | ||
- | drawsp8x8_loop: | + | |
- | LD A, (DE) ; Tomamos el dato del sprite | + | |
- | OR (HL) ; Hacemos un OR del scanline con el fondo | + | |
- | LD (HL), A ; Establecemos el valor del OR en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite | + | |
- | INC H ; Incrementamos puntero en pantalla | + | |
- | DJNZ drawsp8x8_loop | + | |
- | ;;; En este punto, los 8 scanlines | + | ld b, 8 ; 8 scanlines |
- | POP BC ; Recuperamos | + | drawsp8x8_loop_or: |
+ | ld a, (de) ; Tomamos | ||
+ | or (hl) ; NUEVO: Hacemos un OR del scanline | ||
+ | ld (hl), a ; Establecemos el valor en videomemoria | ||
+ | inc de ; Incrementamos puntero en sprite | ||
+ | inc h ; Incrementamos puntero en pantalla (scanline+=1) | ||
+ | djnz drawsp8x8_loop_or | ||
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | |
- | LD HL, (DS_ATTRIBS) | + | ld a, h |
+ | sub 8 ; Recuperamos la posicion de memoria del | ||
+ | ld b, a ; scanline inicial donde empezamos a dibujar | ||
+ | ld c, l ; BC = HL - 8 | ||
- | XOR A | + | |
- | ADD A, H ; A = 0 + H = H | + | ld hl, (DS_ATTRIBS) |
- | RET Z | + | |
- | ;;; Calcular posicion destino en area de atributos en DE. | + | xor a |
- | | + | |
- | RRCA ; Obtenemos dir de atributo | + | ret z |
- | | + | |
- | RRCA ; Nos evita volver | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | |
- | LD E, C ; DE tiene el offset del attr de HL | + | |
- | LD A, (DS_NUMSPR) | + | ;;; Calcular posicion destino en area de atributos en DE. |
- | LD C, A | + | ld a, b |
- | LD B, 0 | + | rrca ; Obtenemos dir de atributo |
- | ADD HL, BC ; HL = HL+DS_NUMSPR = Origen | + | |
+ | | ||
+ | and 3 ; y hacer el calculo completo de la | ||
+ | or $58 ; direccion en zona de atributos | ||
+ | ld d, a | ||
+ | ld e, c | ||
- | ;;; Copiar | + | ld a, (DS_NUMSPR) ; Cogemos el numero |
- | LD A, (HL) | + | ld c, a |
- | LD (DE), A ; Mas rapido que LDI (7+7 vs 16 t-estados) | + | ld b, 0 |
- | | + | add hl, bc ; HL = HL+DS_NUMSPR = Origen de atributo |
- | END 32768 | + | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla |
+ | ld a, (hl) | ||
+ | ld (de), a ; Mas rapido que ldi (7+7 vs 16 t-estados) | ||
+ | ret ; porque no necesitamos incrementar HL y DE | ||
</ | </ | ||
Línea 1130: | Línea 1207: | ||
\\ | \\ | ||
- | {{ : | + | {{ : |
\\ | \\ | ||
- | | + | |
\\ | \\ | ||
Línea 1139: | Línea 1216: | ||
\\ | \\ | ||
- | | + | |
En tal caso, ¿qué hacemos para imprimir nuestro sprite respetando el fondo pero que a su vez podamos disponer de zonas que no sean transparentes? | En tal caso, ¿qué hacemos para imprimir nuestro sprite respetando el fondo pero que a su vez podamos disponer de zonas que no sean transparentes? | ||
Línea 1146: | Línea 1223: | ||
\\ | \\ | ||
- | ==== Usándo | + | ==== Impresión 8x8 usándo |
- | Como hemos visto, las operaciones con OR nos permiten respetar el fondo pero a su vez provocan zonas transparentes en nuestro sprite. En sistemas más modernos se utiliza un "color transparente" | + | Como hemos visto, las operaciones con '' |
La solución para respetar el fondo y sólo tener transparencias en los sprites en las zonas que nosotros deseemos es la utilización de máscaras. | La solución para respetar el fondo y sólo tener transparencias en los sprites en las zonas que nosotros deseemos es la utilización de máscaras. | ||
Línea 1154: | Línea 1231: | ||
La máscara es un bitmap del mismo tamaño que el sprite al que está asociada. Tiene un contorno similar al del sprite, donde colocamos a 1 todos los pixeles del fondo que necesitamos respetar (zonas transparentes) y a 0 todos los píxeles que se transferirán desde el sprite (píxeles opacos o píxeles del sprite) y que deben de ser borrados del fondo. | La máscara es un bitmap del mismo tamaño que el sprite al que está asociada. Tiene un contorno similar al del sprite, donde colocamos a 1 todos los pixeles del fondo que necesitamos respetar (zonas transparentes) y a 0 todos los píxeles que se transferirán desde el sprite (píxeles opacos o píxeles del sprite) y que deben de ser borrados del fondo. | ||
- | A la hora de dibujar el sprite en pantalla, se realiza un AND entre el valor del pixel y el valor del fondo (de los 8 píxeles, en el caso del Spectrum), con lo cual " | + | A la hora de dibujar el sprite en pantalla, se realiza un AND entre el valor del pixel y el valor del fondo (de los 8 píxeles, en el caso del Spectrum), con lo cual " |
De esta forma, podemos respetar todos los píxeles del fondo alrededor de la figura del personaje, así como algunas zonas de mismo que podamos querer que sean transparentes, | De esta forma, podemos respetar todos los píxeles del fondo alrededor de la figura del personaje, así como algunas zonas de mismo que podamos querer que sean transparentes, | ||
- | | + | |
\\ | \\ | ||
Línea 1164: | Línea 1241: | ||
\\ | \\ | ||
- | El resultado es que los ojos y la boca del sprite, que en la máscara están a 0, son borrados del fondo y por lo tanto no se produce el efecto de " | + | El resultado es que los ojos y la boca del sprite, que en la máscara están a 0, son borrados del fondo y por lo tanto no se produce el efecto de " |
No obstante, nuestro sprite sigue teniendo un problema relacionado con el sistema de atributos del Spectrum, y es que los píxeles de nuestro personaje se " | No obstante, nuestro sprite sigue teniendo un problema relacionado con el sistema de atributos del Spectrum, y es que los píxeles de nuestro personaje se " | ||
Línea 1187: | Línea 1264: | ||
\\ | \\ | ||
- | * El uso de máscaras requiere utilizar el doble de memoria para el spriteset gráfico (no para el de atributos), ya que por cada byte del sprite necesitamos el correspondiente byte de máscara. | + | * El uso de máscaras requiere utilizar el doble de memoria para el spriteset gráfico (no para el de atributos), ya que por cada byte del sprite necesitamos el correspondiente byte de máscara. |
* Las rutinas de impresión con máscaras son más lentas que sin ellas, porque tienen que recoger datos de la máscara, realizar operaciones lógicas entre los datos de la misma y el fondo, y realizar incrementos adicionales del puntero al sprite (DE). | * Las rutinas de impresión con máscaras son más lentas que sin ellas, porque tienen que recoger datos de la máscara, realizar operaciones lógicas entre los datos de la misma y el fondo, y realizar incrementos adicionales del puntero al sprite (DE). | ||
Línea 1194: | Línea 1271: | ||
La rutina de impresión de máscaras tiene ahora que recoger un dato extra por cada byte del sprite: la máscara que se corresponde con ese byte. Para no utilizar un puntero de memoria adicional en un array de máscara, lo ideal es exportar cada byte de máscara junto al byte del sprite correspondiente, | La rutina de impresión de máscaras tiene ahora que recoger un dato extra por cada byte del sprite: la máscara que se corresponde con ese byte. Para no utilizar un puntero de memoria adicional en un array de máscara, lo ideal es exportar cada byte de máscara junto al byte del sprite correspondiente, | ||
- | El pseudocódigo de la nueva rutina | + | El pseudocódigo de la nueva rutina |
< | < | ||
Línea 1220: | Línea 1297: | ||
; | ; | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
- | ; Direccion | + | ; Direccion |
- | ; 50000 Direccion de la tabla de Sprites | + | ; (DS_SPRITES) |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_ATTRIBS) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_Y) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_NUMSPR) |
; | ; | ||
DrawSprite_8x8_MASK: | DrawSprite_8x8_MASK: | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | |
- | LD BC, (DS_COORD_X) | + | ld bc, (DS_COORD_X) |
+ | |||
+ | ;;; Calculamos las coordenadas destino de pantalla en DE: | ||
+ | ld a, b | ||
+ | and $18 | ||
+ | add a, $40 | ||
+ | ld d, a ; Ya tenemos la parte alta calculada (010TT000) | ||
+ | ld a, b ; Ahora calculamos la parte baja | ||
+ | and 7 | ||
+ | rrca | ||
+ | rrca | ||
+ | rrca ; A = NNN00000b | ||
+ | add a, c ; Sumamos COLUMNA -> A = NNNCCCCCb | ||
+ | ld e, a ; Lo cargamos en la parte baja de la direccion | ||
+ | ; DE contiene ahora la direccion destino. | ||
- | ;;; Calculamos las coordenadas destino de pantalla | + | |
- | LD A, B | + | ;;; direccion |
- | AND $18 | + | |
- | ADD A, $40 | + | |
- | LD D, A ; Ya tenemos la parte alta calculada (010TT000) | + | |
- | LD A, B ; Ahora calculamos la parte baja | + | |
- | AND 7 | + | |
- | | + | |
- | | + | |
- | | + | |
- | ADD A, C ; Sumamos COLUMNA -> A = NNNCCCCCb | + | |
- | LD E, A ; Lo cargamos en la parte baja de la direccion | + | |
- | ; DE contiene ahora la direccion destino. | + | |
- | PUSH DE ; Lo guardamos para luego, lo usaremos para | + | ld bc, (DS_SPRITES) |
- | | + | ld a, (DS_NUMSPR) |
+ | ld h, 0 | ||
+ | ld l, a | ||
+ | add hl, hl ; HL = HL * 2 | ||
+ | add hl, hl ; HL = HL * 4 | ||
+ | add hl, hl ; HL = HL * 8 | ||
+ | add hl, hl ; HL = HL * 16 = DS_NUMSPR * 16 | ||
+ | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) | ||
+ | | ||
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | ;;; | + | |
- | + | ||
- | LD BC, (DS_SPRITES) | + | |
- | LD A, (DS_NUMSPR) | + | |
- | LD H, 0 | + | |
- | LD L, A ; HL = DS_NUMSPR | + | |
- | ADD HL, HL ; HL = HL * 2 | + | |
- | ADD HL, HL ; HL = HL * 4 | + | |
- | ADD HL, HL ; HL = HL * 8 | + | |
- | ADD HL, HL ; HL = HL * 16 = DS_NUMSPR * 16 | + | |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) | + | |
- | ; HL contiene la direccion | + | |
- | EX DE, HL ; Intercambiamos DE y HL para el AND+OR | + | |
- | ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | + | |
- | LD B, 8 | + | ld b, 8 |
drawspr8x8m_loop: | drawspr8x8m_loop: | ||
- | LD A, (DE) | + | ld a, (de) |
- | AND (HL) | + | |
- | LD C, A | + | ld c, a |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de ; Avanzamos al siguiente byte (el dato grafico) |
- | LD A, (DE) | + | ld a, (de) |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c ; A = A or c = A OR (MASK and fONDO) |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de ; Avanzamos al siguiente dato del sprite |
- | INC H | + | inc h |
- | DJNZ drawspr8x8m_loop | + | |
- | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | + | |
+ | ld a, h | ||
+ | sub 8 ; Recuperamos la posicion de memoria del | ||
+ | ld b, a ; scanline inicial donde empezamos a dibujar | ||
+ | ld c, l ; BC = HL - 8 | ||
- | POP BC ; Recuperamos | + | |
+ | ld hl, (DS_ATTRIBS) | ||
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | xor a ; A = 0 |
- | LD HL, (DS_ATTRIBS) | + | add a, h ; A = 0 + H = H |
+ | ret z | ||
- | XOR A | + | |
- | ADD A, H ; A = 0 + H = H | + | ld a, b |
- | RET Z | + | |
+ | rrca ; dir de zona de imagen. | ||
+ | rrca ; Nos evita volver | ||
+ | and 3 ; y hacer el calculo completo de la | ||
+ | or $58 ; direccion en zona de atributos | ||
+ | ld d, a | ||
+ | ld e, c ; DE tiene el offset del attr de HL | ||
- | ;;; Calcular posicion destino en area de atributos en DE. | + | ld a, (DS_NUMSPR) |
- | LD A, B ; Codigo de Get_Attr_Offset_From_Image | + | ld c, a |
- | | + | ld b, 0 |
- | | + | add hl, bc ; HL = HL+DS_NUMSPR = Origen de atributo |
- | | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | |
- | LD E, C ; DE tiene el offset del attr de HL | + | |
- | + | ||
- | LD A, (DS_NUMSPR) | + | |
- | LD C, A | + | |
- | LD B, 0 | + | |
- | ADD HL, BC ; HL = HL+DS_NUMSPR = Origen de atributo | + | |
- | + | ||
- | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | + | |
- | LD A, (HL) | + | |
- | LD (DE), A ; Mas rapido que LDI (7+7 vs 16 t-estados) | + | |
- | | + | |
+ | ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla | ||
+ | ld a, (hl) | ||
+ | ld (de), a ; Mas rapido que ldi (7+7 vs 16 t-estados) | ||
+ | ret ; porque no necesitamos incrementar HL y DE | ||
</ | </ | ||
Línea 1326: | Línea 1402: | ||
</ | </ | ||
- | El código para el cálculo agrega un "**ADD HL, HL**" | + | El código para el cálculo agrega un '' |
<code z80> | <code z80> | ||
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD H, 0 | + | ld h, 0 |
- | LD L, A ; HL = DS_NUMSPR | + | ld l, a ; HL = DS_NUMSPR |
- | ADD HL, HL ; HL = HL * 2 | + | add hl, hl ; HL = HL * 2 |
- | ADD HL, HL ; HL = HL * 4 | + | add hl, hl ; HL = HL * 4 |
- | ADD HL, HL ; HL = HL * 8 | + | add hl, hl ; HL = HL * 8 |
- | ADD HL, HL ; HL = HL * 16 = DS_NUMSPR * 16 | + | add hl, hl ; HL = HL * 16 = DS_NUMSPR * 16 |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) |
- | | + | ; HL contiene la direccion de inicio en el sprite |
</ | </ | ||
Línea 1344: | Línea 1420: | ||
<code z80> | <code z80> | ||
- | LD B, 8 | + | ld b, 8 |
drawspr8x8m_loop: | drawspr8x8m_loop: | ||
- | LD A, (DE) | + | ld a, (de) ; Obtenemos un byte del sprite (el byte de mascara) |
- | AND (HL) | + | |
- | LD C, A | + | ld c, a ; Nos guardamos el valor del AND |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de |
- | LD A, (DE) | + | ld a, (de) ; Obtenemos el byte grafico |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de |
- | INC H | + | inc h ; Incrementamos puntero en pantalla (siguiente scanline) |
- | DJNZ drawspr8x8m_loop | + | |
</ | </ | ||
Línea 1384: | Línea 1460: | ||
cara_gfx: | cara_gfx: | ||
- | DEFB 227, 28, 193, 62, 128, 107, 128, 127 | + | |
- | | + | DEFB 128, 93, 128, 99, 193, 62, 227, 28 |
cara_attrib: | cara_attrib: | ||
- | DEFB 56 | + | |
</ | </ | ||
Línea 1394: | Línea 1470: | ||
\\ | \\ | ||
- | {{ : | + | {{ : |
\\ | \\ | ||
Línea 1405: | Línea 1481: | ||
| | ||
- | Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando DS_NUMSPR=0, | + | Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando |
\\ | \\ | ||
===== Trazado de Sprites de 16x16 ===== | ===== Trazado de Sprites de 16x16 ===== | ||
+ | |||
+ | \\ | ||
+ | ==== Impresión 16x16 con Transferencia por LD ==== | ||
+ | |||
La impresión de sprites de 16x16 sin máscaras es esencialmente idéntica a la de 8x8, ajustando las funciones que hemos visto hasta ahora a las nuevas dimensiones del sprite: | La impresión de sprites de 16x16 sin máscaras es esencialmente idéntica a la de 8x8, ajustando las funciones que hemos visto hasta ahora a las nuevas dimensiones del sprite: | ||
Línea 1418: | Línea 1498: | ||
\\ | \\ | ||
- | | + | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 2 bytes y no 1, y tenemos 2 bloques de altura en el sprite y no uno. Antes calculábamos la dirección origen como BASE+(DS_NUMSPR*8), |
- | * Para multiplicar DS_NUMSPR por 32 vamos a utilizar desplazamientos a la derecha de un pseudo-registro de 16 bits formado por A y L en lugar de utilizar sumas sucesivas **ADD HL, HL**. Esta técnica requiere menos ciclos de reloj para su ejecución. | + | |
- | * La impresión de datos debe imprimir todo un scanline horizontal del Sprite (2 bytes) antes de avanzar al siguiente scanline de pantalla. | + | * Para multiplicar DS_NUMSPR por 32 vamos a utilizar desplazamientos a la derecha de un pseudo-registro de 16 bits formado por A y L en lugar de utilizar sumas sucesivas **add hl, hl**. Esta técnica requiere menos ciclos de reloj para su ejecución. |
- | * El avance al siguiente scanline cambia ligeramente, | + | |
- | * El bucle vertical es de 16 iteraciones en lugar de 8, ya que el sprite tiene 2 caracteres verticales. | + | * La impresión de datos debe imprimir todo un scanline horizontal del Sprite (2 bytes) antes de avanzar al siguiente scanline de pantalla. |
- | * El cálculo de la dirección origen en los atributos cambia, ya que ahora tenemos 4 bytes de atributos por cada sprite. Así, la posición ya no se calcula como BASE+(DS_NUMSPR*1) sino como BASE+(DS_NUMSPR*4). | + | |
- | * La impresión de los atributos también cambia, ya que ahora hay que imprimir 4 atributos (2x2 bloques) en lugar de sólo 1. | + | * El avance al siguiente scanline cambia ligeramente, |
+ | |||
+ | * El bucle vertical es de 16 iteraciones en lugar de 8, ya que el sprite tiene 2 caracteres verticales. | ||
+ | |||
+ | * El cálculo de la dirección origen en los atributos cambia, ya que ahora tenemos 4 bytes de atributos por cada sprite. Así, la posición ya no se calcula como BASE+(DS_NUMSPR*1) sino como BASE+(DS_NUMSPR*4). | ||
+ | |||
+ | * La impresión de los atributos también cambia, ya que ahora hay que imprimir 4 atributos (2x2 bloques) en lugar de sólo 1. | ||
Línea 1440: | Línea 1526: | ||
; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ||
; Incrementar DE | ; Incrementar DE | ||
- | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (INC H/DEC L). | + | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (inc h/dec l). |
; Avanzar puntero de pantalla (HL) a la posicion de la segunda | ; Avanzar puntero de pantalla (HL) a la posicion de la segunda | ||
Línea 1450: | Línea 1536: | ||
; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ; Dibujar byte (DE) -> (HL), trazando el scanline del 2o bloque | ||
; Incrementar DE | ; Incrementar DE | ||
- | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (INC H/DEC L). | + | ; Bajar a siguiente scanline en pantalla (HL), sumando 256 (inc h/dec l). |
- | ; Si base_atributos == 0 -> RET | + | ; Si base_atributos == 0 -> ret |
; Calcular posicion origen de los atributos array_attr+(NUM_SPRITE*4) en HL. | ; Calcular posicion origen de los atributos array_attr+(NUM_SPRITE*4) en HL. | ||
; Calcular posicion destino en area de atributos en DE. | ; Calcular posicion destino en area de atributos en DE. | ||
Línea 1476: | Línea 1562: | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
; Direccion | ; Direccion | ||
- | ; 50000 Direccion de la tabla de Sprites | + | ; Direccion |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_SPRITES) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_ATTRIBS) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_COORD_Y) |
+ | ; (DS_NUMSPR) | ||
; | ; | ||
DrawSprite_16x16_LD: | DrawSprite_16x16_LD: | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | |
- | LD BC, (DS_COORD_X) | + | ld bc, (DS_COORD_X) |
- | + | ||
- | ;;; Calculamos las coordenadas destino de pantalla en DE: | + | |
- | LD A, B | + | |
- | AND $18 | + | |
- | ADD A, $40 | + | |
- | LD D, A | + | |
- | LD A, B | + | |
- | AND 7 | + | |
- | | + | |
- | | + | |
- | | + | |
- | ADD A, C | + | |
- | LD E, A | + | |
- | + | ||
- | PUSH DE ; Lo guardamos para luego, lo usaremos para | + | |
- | ; calcular la direccion del atributo | + | |
- | + | ||
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | ;;; | + | |
- | ;;; Multiplicamos con desplazamientos, | + | |
- | LD BC, (DS_SPRITES) | + | |
- | LD A, (DS_NUMSPR) | + | |
- | LD L, 0 ; AL = DS_NUMSPR*256 | + | |
- | SRL A ; Desplazamos a la derecha para dividir por dos | + | |
- | RR L ; AL = DS_NUMSPR*128 | + | |
- | | + | |
- | RR L ; AL = DS_NUMSPR*64 | + | |
- | | + | |
- | RR L ; AL = DS_NUMSPR*32 | + | |
- | LD H, A ; HL = DS_NUMSPR*32 | + | |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) | + | |
- | ; HL contiene la direccion de inicio en el sprite | + | |
- | | + | ;;; Calculamos las coordenadas destino de pantalla en DE: |
+ | ld a, b | ||
+ | and $18 | ||
+ | add a, $40 | ||
+ | ld d, a | ||
+ | ld a, b | ||
+ | and 7 | ||
+ | rrca | ||
+ | rrca | ||
+ | rrca | ||
+ | add a, c | ||
+ | ld e, a | ||
+ | |||
+ | push de ; Lo guardamos para luego, lo usaremos para | ||
+ | ; calcular la direccion del atributo | ||
+ | |||
+ | ;;; Calcular posicion origen (array sprites) en HL como: | ||
+ | ;;; | ||
+ | ;;; Multiplicamos con desplazamientos, | ||
+ | ld bc, (DS_SPRITES) | ||
+ | ld a, (DS_NUMSPR) | ||
+ | ld l, 0 ; AL = DS_NUMSPR*256 | ||
+ | srl a ; Desplazamos a la derecha para dividir por dos | ||
+ | rr l ; AL = DS_NUMSPR*128 | ||
+ | rra ; Rotamos, ya que el bit que salio de L al CF fue 0 | ||
+ | rr l ; AL = DS_NUMSPR*64 | ||
+ | rra ; Rotamos, ya que el bit que salio de L al CF fue 0 | ||
+ | rr l ; AL = DS_NUMSPR*32 | ||
+ | ld h, a ; HL = DS_NUMSPR*32 | ||
+ | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) | ||
+ | ; HL contiene la direccion de inicio en el sprite | ||
+ | |||
+ | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) | ||
+ | |||
+ | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
+ | ld b, 8 | ||
- | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
- | LD B, 8 | ||
- | |||
drawsp16x16_loop1: | drawsp16x16_loop1: | ||
- | LD A, (DE) ; Bloque 1: Leemos dato del sprite | + | ld a, (de) ; Bloque 1: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
- | LD A, (DE) ; Bloque 2: Leemos dato del sprite | + | ld a, (de) ; Bloque 2: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC H ; Hay que sumar 256 para ir al siguiente scanline | + | inc h ; Hay que sumar 256 para ir al siguiente scanline |
- | DEC L ; pero hay que restar el INC L que hicimos. | + | dec l ; pero hay que restar el inc l que hicimos. |
- | DJNZ drawsp16x16_loop1 | + | |
+ | |||
+ | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
+ | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
+ | |||
+ | ;;;inc h ; No hay que hacer inc h, lo hizo en el bucle | ||
+ | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que | ||
+ | ;;;and 7 ; no hay salto (es un cambio de bloque) | ||
+ | ;;;jr nz, drawsp16_nofix_abajop | ||
+ | ld a, l | ||
+ | add a, 32 | ||
+ | ld l, a | ||
+ | jr c, drawsp16_nofix_abajop | ||
+ | ld a, h | ||
+ | sub 8 | ||
+ | ld h, a | ||
- | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
- | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
- | |||
- | ;;; | ||
- | LD A, H | ||
- | AND 7 | ||
- | JR NZ, drawsp16_nofix_abajop | ||
- | LD A, L | ||
- | ADD A, 32 | ||
- | LD L, A | ||
- | JR C, drawsp16_nofix_abajop | ||
- | LD A, H | ||
- | SUB 8 | ||
- | LD H, A | ||
- | |||
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | ;;; Repetir 8 veces (segundos 2 bloques horizontales): | + | |
- | LD B, 8 | + | ld b, 8 |
- | + | ||
drawsp16x16_loop2: | drawsp16x16_loop2: | ||
- | LD A, (DE) ; Bloque 1: Leemos dato del sprite | + | ld a, (de) ; Bloque 1: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
- | LD A, (DE) ; Bloque 2: Leemos dato del sprite | + | ld a, (de) ; Bloque 2: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC H ; Hay que sumar 256 para ir al siguiente scanline | + | inc h ; Hay que sumar 256 para ir al siguiente scanline |
- | DEC L ; pero hay que restar el INC L que hicimos. | + | dec l ; pero hay que restar el inc l que hicimos. |
- | DJNZ drawsp16x16_loop2 | + | |
- | + | ||
- | | + | ;;; En este punto, los 16 scanlines del sprite estan dibujados. |
- | + | ||
- | POP BC ; Recuperamos el offset del primer scanline | + | pop bc ; Recuperamos el offset del primer scanline |
- | + | ||
- | | + | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> ret) |
- | LD HL, (DS_ATTRIBS) | + | ld hl, (DS_ATTRIBS) |
- | + | ||
- | XOR A ; A = 0 | + | xor a ; A = 0 |
- | ADD A, H ; A = 0 + H = H | + | add a, h ; A = 0 + H = H |
- | RET Z ; Si H = 0, volver (no dibujar atributos) | + | ret z ; Si H = 0, volver (no dibujar atributos) |
- | + | ||
- | | + | ;;; Calcular posicion destino en area de atributos en DE. |
- | LD A, B ; Codigo de Get_Attr_Offset_From_Image | + | ld a, b ; Codigo de Get_Attr_Offset_From_Image |
- | RRCA ; Obtenemos dir de atributo a partir de | + | |
- | RRCA ; dir de zona de imagen. | + | |
- | RRCA ; Nos evita volver a obtener X e Y | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | ld d, a |
- | LD E, C ; DE tiene el offset del attr de HL | + | ld e, c ; DE tiene el offset del attr de HL |
- | + | ||
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD C, A | + | ld c, a |
- | LD B, 0 | + | ld b, 0 |
- | ADD HL, BC ; HL = HL+DS_NUMSPR | + | add hl, bc ; HL = HL+DS_NUMSPR |
- | ADD HL, BC ; HL = HL+DS_NUMSPR*2 | + | add hl, bc ; HL = HL+DS_NUMSPR*2 |
- | ADD HL, BC ; HL = HL+DS_NUMSPR*3 | + | add hl, bc ; HL = HL+DS_NUMSPR*3 |
- | ADD HL, BC ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo | + | add hl, bc ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo |
- | + | ||
- | LDI | + | ldi |
- | LDI ; Imprimimos las 2 primeras filas de atributo | + | |
- | ;;; Avance diferencial a la siguiente linea de atributos | + | |
- | LD A, E ; A = L | + | ld a, e ; A = L |
- | ADD A, 30 ; Sumamos A = A + 30 mas los 2 INCs de LDI. | + | add a, 30 ; Sumamos A = A + 30 mas los 2 INCs de ldi. |
- | LD E, A ; Guardamos en L (L = L+30 + 2 por LDI=L+32) | + | ld e, a ; Guardamos en L (L = L+30 + 2 por ldi=L+32) |
- | JR NC, drawsp16x16_attrab_noinc | + | jr nc, drawsp16x16_attrab_noinc |
- | INC D | + | inc d |
drawsp16x16_attrab_noinc: | drawsp16x16_attrab_noinc: | ||
- | LDI | + | ldi |
- | LDI | + | ldi |
- | | + | |
</ | </ | ||
\\ | \\ | ||
- | Lo primero que nos llama la atención de la rutina es la forma de multiplicar por 32 el valor de DS_NUMSPR. Una primera aproximación de multiplicación de HL = NUM_SPR * 32 podría ser aumentar el número de sumas **ADD HL, HL** tal y como se realizan en las rutinas de 8x8: | + | Lo primero que nos llama la atención de la rutina es la forma de multiplicar por 32 el valor de '' |
<code z80> | <code z80> | ||
- | ;;; Multiplicar DS_SPRITES por 32 con sumas | + | |
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD H, 0 ; H = 0 | + | ld h, 0 ; H = 0 |
- | LD L, A ; HL = DS_NUMSPR | + | ld l, a ; HL = DS_NUMSPR |
- | ADD HL, HL ; HL = HL * 2 | + | add hl, hl ; HL = HL * 2 |
- | ADD HL, HL ; HL = HL * 4 | + | add hl, hl ; HL = HL * 4 |
- | ADD HL, HL ; HL = HL * 8 | + | add hl, hl ; HL = HL * 8 |
- | ADD HL, HL ; HL = HL * 16 | + | add hl, hl ; HL = HL * 16 |
- | ADD HL, HL ; HL = HL * 32 | + | add hl, hl ; HL = HL * 32 |
- | ADD HL, BC ; HL = DS_SPRITES + (DS_NUMSPR * 32) | + | add hl, bc ; HL = DS_SPRITES + (DS_NUMSPR * 32) |
</ | </ | ||
- | Esta porción de código tarda 11 t-estados por cada ADD de 16 bits, más 7 t-estados de **LD H, 0**, más 4 de **LD L, A**, lo que da un total de 77 t-estados para realizar la multiplicación. | + | Esta porción de código tarda 11 t-estados por cada '' |
La técnica empleada en el listado, proporcionada por **metalbrain**, | La técnica empleada en el listado, proporcionada por **metalbrain**, | ||
Línea 1640: | Línea 1727: | ||
<code z80> | <code z80> | ||
- | ;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> | + | |
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD L, 0 ; AL = DS_NUMSPR*256 | + | ld l, 0 ; AL = DS_NUMSPR*256 |
- | SRL A ; Desplazamos a la derecha para dividir por dos | + | srl a ; Desplazamos a la derecha para dividir por dos |
- | RR L ; AL = DS_NUMSPR*128 | + | rr l ; AL = DS_NUMSPR*128 |
- | RRA ; Rotamos, ya que el bit que salio de L al CF fue 0 | + | |
- | RR L ; AL = DS_NUMSPR*64 | + | rr l ; AL = DS_NUMSPR*64 |
- | RRA ; Rotamos, ya que el bit que salio de L al CF fue 0 | + | |
- | RR L ; AL = DS_NUMSPR*32 | + | rr l ; AL = DS_NUMSPR*32 |
- | LD H, A ; HL = DS_NUMSPR*32 | + | ld h, a ; HL = DS_NUMSPR*32 |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) |
- | | + | ; HL contiene la direccion de inicio en el sprite |
</ | </ | ||
- | Esta porción de código tiene un coste de 11 (ADD) + 8 + 8 + 8 (RR) + 4 + 4 + 4 (RRA) + 7 + 4 (LD) = 58 t-estados, 29 ciclos de reloj menos que la rutina con ADD. | + | Esta porción de código tiene un coste de 11 ('' |
- | Si tuvieramos que multiplicar por 64 (realizar un RRA/RL menos), el coste sería todavía menor: 12 t-estados menos con un total de 46 t-estados. | + | Si tuvieramos que multiplicar por 64 (realizar un '' |
- | Otra parte interesante de la rutina de dibujado de sprites está en la impresión de los datos gráficos. En esta ocasión hay que imprimir 2 bytes horizontales en cada scanline, y sumar 256 para avanzar a la siguiente línea de pantalla. Esto nos obliga a decrementar HL en 1 unidad (con **DEC L**) para compensar el avance horizontal utilizado para posicionarnos en el lugar de dibujado del segundo bloque del sprite. Tras esto, ya podemos hacer el avance de scanline con un simple | + | Otra parte interesante de la rutina de dibujado de sprites está en la impresión de los datos gráficos. En esta ocasión hay que imprimir 2 bytes horizontales en cada scanline, y sumar 256 para avanzar a la siguiente línea de pantalla. Esto nos obliga a decrementar HL en 1 unidad (con '' |
- | Una vez finalizado el bucle de 8 iteraciones que imprime los datos de los 2 bloques de la fila 1 del sprite, debemos avanzar al siguiente scanline de pantalla (8 más abajo de la posicion Y inicial) para trazar los 2 bloques restantes (los bloques "de abajo" | + | Una vez finalizado el bucle de 8 iteraciones que imprime los datos de los 2 bloques de la fila 1 del sprite, debemos avanzar al siguiente scanline de pantalla (8 más abajo de la posicion Y inicial) para trazar los 2 bloques restantes (los bloques "de abajo" |
Tras ajustar HL tenemos que dibujar los 2 últimos bloques del sprite, con un bucle similar al que dibujó los 2 primeros. | Tras ajustar HL tenemos que dibujar los 2 últimos bloques del sprite, con un bucle similar al que dibujó los 2 primeros. | ||
Línea 1670: | Línea 1757: | ||
<code z80> | <code z80> | ||
- | LD B, 16 ; 16 iteraciones | + | ld b, 16 ; 16 iteraciones |
- | | + | |
drawsp16x16_loop: | drawsp16x16_loop: | ||
- | LD A, (DE) ; Bloque 1: Leemos dato del sprite | + | ld a, (de) ; Bloque 1: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
- | LD A, (DE) ; Bloque 2: Leemos dato del sprite | + | ld a, (de) ; Bloque 2: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | DEC L ; Decrementamos el avance realizado | + | dec l ; Decrementamos el avance realizado |
- | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | + | |
- | INC H | + | ;;; |
- | LD A, H | + | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que |
- | AND 7 | + | ;;; |
- | JR NZ, drawsp16_nofix_abajop | + | ;;;jr nz, drawsp16_nofix_abajop |
- | LD A, L | + | ld a, l |
- | ADD A, 32 | + | add a, 32 |
- | LD L, A | + | ld l, a |
- | JR C, drawsp16_nofix_abajop | + | jr c, drawsp16_nofix_abajop |
- | LD A, H | + | ld a, h |
- | SUB 8 | + | |
- | LD H, A | + | ld h, a |
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | | + | djnz drawsp16x16_loop |
</ | </ | ||
- | Este código es más pequeño en tamaño que el uso de 2 bucles, pero estamos efectuando un JR innecesario en 14 de las 16 iteraciones, | + | Este código es más pequeño en tamaño que el uso de 2 bucles, pero estamos efectuando un '' |
- | | + | |
| | ||
Línea 1716: | Línea 1803: | ||
bicho_gfx: | bicho_gfx: | ||
- | DEFB 8, | + | |
- | | + | DEFB 15, 80, 15, 80, 15, |
- | | + | DEFB 0, 0, 1, 64, 0, 32, 2, 0 |
- | | + | DEFB 104, 22,112, 14, 56, 28, 0, 0 |
bicho_attrib: | bicho_attrib: | ||
- | DEFB 70, 71, 67, 3 | + | |
</ | </ | ||
Línea 1728: | Línea 1815: | ||
<code z80> | <code z80> | ||
- | LD HL, bicho_gfx | + | ld hl, bicho_gfx |
- | | + | |
- | LD HL, bicho_attrib | + | ld hl, bicho_attrib |
- | | + | |
- | LD A, 13 | + | ld a, 13 |
- | | + | |
- | LD A, 8 | + | ld a, 8 |
- | | + | |
- | XOR A | + | xor a |
- | | + | |
- | | + | |
- | </ | + | </ |
Este es el aspecto del sprite impreso en pantalla sobre la trama de píxeles alternos que estamos utilizando hasta el momento: | Este es el aspecto del sprite impreso en pantalla sobre la trama de píxeles alternos que estamos utilizando hasta el momento: | ||
\\ | \\ | ||
- | {{ : | + | {{ : |
\\ | \\ | ||
Línea 1754: | Línea 1841: | ||
\\ | \\ | ||
- | **Sprites de 16x16 usando operaciones lógicas** | ||
\\ | \\ | ||
+ | ==== Impresión 16x16 usándo operaciones lógicas ==== | ||
- | | + | |
| | ||
<code z80> | <code z80> | ||
- | LD A, (DE) ; Bloque 1: Leemos dato del sprite | + | ld a, (de) ; Bloque 1: Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
</ | </ | ||
Línea 1771: | Línea 1858: | ||
<code z80> | <code z80> | ||
- | LD A, (DE) ; Bloque 1: Leemos dato del sprite | + | ld a, (de) ; Bloque 1: Leemos dato del sprite |
- | OR (HL) ; Realizamos operación lógica | + | |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
</ | </ | ||
- | El resto de la rutina es esencialmente idéntico. | + | El resto de la rutina es esencialmente idéntico |
\\ | \\ | ||
\\ | \\ | ||
- | **Sprites de 16x16 usando | + | ==== Impresión |
- | \\ | + | |
La rutina para dibujar sprites de 16x16 (2x2) con transparencia usando máscaras también sería una mezcla entre la rutina de 8x8 con máscara y a la de 16x16 sin máscara, con el siguiente cambio: | La rutina para dibujar sprites de 16x16 (2x2) con transparencia usando máscaras también sería una mezcla entre la rutina de 8x8 con máscara y a la de 16x16 sin máscara, con el siguiente cambio: | ||
\\ | \\ | ||
- | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 4 bytes (2 del sprite y 2 de la máscara) y no 2, y tenemos 2 bloques de altura en el sprite y no uno. Como tenemos el doble de datos gráficos por cada sprite, si antes habíamos calculado la dirección origen como BASE+(DS_NUMSPR*32), | + | * El cálculo de la dirección origen en el sprite cambia, ya que ahora cada scanline ocupa 4 bytes (2 del sprite y 2 de la máscara) y no 2, y tenemos 2 bloques de altura en el sprite y no uno. Como tenemos el doble de datos gráficos por cada sprite, si antes habíamos calculado la dirección origen como BASE+(DS_NUMSPR*32), |
\\ | \\ | ||
Línea 1800: | Línea 1887: | ||
; | ; | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
- | ; Direccion | + | ; Direccion |
- | ; 50000 Direccion de la tabla de Sprites | + | ; (DS_SPRITES) |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_ATTRIBS) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_Y) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_NUMSPR) |
; | ; | ||
DrawSprite_16x16_MASK: | DrawSprite_16x16_MASK: | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | |
- | LD BC, (DS_COORD_X) | + | ld bc, (DS_COORD_X) |
- | ;;; Calculamos las coordenadas destino de pantalla en DE: | + | |
- | LD A, B | + | ld a, b |
- | AND $18 | + | |
- | ADD A, $40 | + | add a, $40 |
- | LD D, A | + | ld d, a |
- | LD A, B | + | ld a, b |
- | AND 7 | + | |
- | RRCA | + | rrca |
- | RRCA | + | rrca |
- | RRCA | + | rrca |
- | ADD A, C | + | add a, c |
- | LD E, A | + | ld e, a |
- | PUSH DE ; Lo guardamos para luego, lo usaremos para | + | push de ; Lo guardamos para luego, lo usaremos para |
- | | + | ; calcular la direccion del atributo |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | | + | ;;; |
- | | + | ;;; Multiplicamos con desplazamientos, |
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD L, 0 ; AL = DS_NUMSPR*256 | + | ld l, 0 ; AL = DS_NUMSPR*256 |
- | SRL A ; Desplazamos a la derecha para dividir por dos | + | srl a ; Desplazamos a la derecha para dividir por dos |
- | RR L ; AL = DS_NUMSPR*128 | + | rr l ; AL = DS_NUMSPR*128 |
- | RRA ; Rotamos, ya que el bit que salio de L al CF fue 0 | + | |
- | RR L ; AL = DS_NUMSPR*64 | + | rr l ; AL = DS_NUMSPR*64 |
- | LD H, A ; HL = DS_NUMSPR*64 | + | ld h, a ; HL = DS_NUMSPR*64 |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) |
- | | + | ; HL contiene la direccion de inicio en el sprite |
- | EX DE, HL ; Intercambiamos DE y HL para las OP LOGICAS | + | ex de, hl ; Intercambiamos DE y HL para las OP LOGICAS |
- | ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR | + | |
- | LD B, 8 | + | ld b, 8 |
drawspr16m_loop1: | drawspr16m_loop1: | ||
- | LD A, (DE) ; Obtenemos un byte del sprite (el byte de mascara) | + | ld a, (de) ; Obtenemos un byte del sprite (el byte de mascara) |
- | AND (HL) ; A = A AND (HL) | + | |
- | LD C, A ; Nos guardamos el valor del AND | + | ld c, a ; Nos guardamos el valor del AND |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de ; Avanzamos al siguiente byte (el dato grafico) |
- | LD A, (DE) ; Obtenemos el byte grafico | + | ld a, (de) ; Obtenemos el byte grafico |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c ; A = A or c = A OR (MASK and fONDO) |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de ; Avanzamos al siguiente dato del sprite |
- | INC L ; Avanzamos al segundo bloque en pantalla | + | inc l ; Avanzamos al segundo bloque en pantalla |
- | LD A, (DE) ; Obtenemos un byte del sprite (el byte de mascara) | + | ld a, (de) ; Obtenemos un byte del sprite (el byte de mascara) |
- | AND (HL) ; A = A AND (HL) | + | |
- | LD C, A ; Nos guardamos el valor del AND | + | ld c, a ; Nos guardamos el valor del AND |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de ; Avanzamos al siguiente byte (el dato grafico) |
- | LD A, (DE) ; Obtenemos el byte grafico | + | ld a, (de) ; Obtenemos el byte grafico |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c ; A = A or c = A OR (MASK and fONDO) |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de ; Avanzamos al siguiente dato del sprite |
- | DEC L ; Volvemos atras del valor que incrementamos | + | dec l ; Volvemos atras del valor que incrementamos |
- | INC H ; Incrementamos puntero en pantalla (siguiente scanline) | + | inc h ; Incrementamos puntero en pantalla (siguiente scanline) |
- | DJNZ drawspr16m_loop1 | + | |
- | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | + | |
- | | + | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 |
- | + | ||
- | | + | ;;;inc h ; No hay que hacer inc h, lo hizo en el bucle |
- | LD A, H | + | ;;;ld a, h ; No hay que hacer esta prueba, sabemos que |
- | AND 7 | + | ;;; |
- | JR NZ, drawsp16m_nofix_abajop | + | ;;;jr nz, drawsp16_nofix_abajop |
- | LD A, L | + | ld a, h |
- | ADD A, 32 | + | and 7 |
- | LD L, A | + | jr nz, drawsp16m_nofix_abajop |
- | JR C, drawsp16m_nofix_abajop | + | ld a, l |
- | LD A, H | + | add a, 32 |
- | SUB 8 | + | ld l, a |
- | LD H, A | + | jr c, drawsp16m_nofix_abajop |
+ | ld a, h | ||
+ | | ||
+ | ld h, a | ||
drawsp16m_nofix_abajop: | drawsp16m_nofix_abajop: | ||
- | ;;; Repetir 8 veces (segundos 2 bloques horizontales): | + | |
- | LD B, 8 | + | ld b, 8 |
drawspr16m_loop2: | drawspr16m_loop2: | ||
- | LD A, (DE) ; Obtenemos un byte del sprite (el byte de mascara) | + | ld a, (de) ; Obtenemos un byte del sprite (el byte de mascara) |
- | AND (HL) ; A = A AND (HL) | + | |
- | LD C, A ; Nos guardamos el valor del AND | + | ld c, a ; Nos guardamos el valor del AND |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de ; Avanzamos al siguiente byte (el dato grafico) |
- | LD A, (DE) ; Obtenemos el byte grafico | + | ld a, (de) ; Obtenemos el byte grafico |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c ; A = A or c = A OR (MASK and fONDO) |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de ; Avanzamos al siguiente dato del sprite |
- | INC L ; Avanzamos al segundo bloque en pantalla | + | inc l ; Avanzamos al segundo bloque en pantalla |
- | LD A, (DE) ; Obtenemos un byte del sprite (el byte de mascara) | + | ld a, (de) ; Obtenemos un byte del sprite (el byte de mascara) |
- | AND (HL) ; A = A AND (HL) | + | |
- | LD C, A ; Nos guardamos el valor del AND | + | ld c, a ; Nos guardamos el valor del AND |
- | INC DE ; Avanzamos al siguiente byte (el dato grafico) | + | inc de ; Avanzamos al siguiente byte (el dato grafico) |
- | LD A, (DE) ; Obtenemos el byte grafico | + | ld a, (de) ; Obtenemos el byte grafico |
- | OR C ; A = A OR C = A OR (MASK AND FONDO) | + | or c ; A = A or c = A OR (MASK and fONDO) |
- | LD (HL), A ; Imprimimos el dato tras aplicar operaciones logicas | + | |
- | INC DE ; Avanzamos al siguiente dato del sprite | + | inc de ; Avanzamos al siguiente dato del sprite |
- | DEC L ; Volvemos atras del valor que incrementamos | + | dec l ; Volvemos atras del valor que incrementamos |
- | INC H ; Incrementamos puntero en pantalla (siguiente scanline) | + | inc h ; Incrementamos puntero en pantalla (siguiente scanline) |
- | DJNZ drawspr16m_loop2 | + | |
- | ;;; En este punto, los 16 scanlines del sprite estan dibujados. | + | |
- | POP BC ; Recuperamos el offset del primer scanline | + | pop bc ; Recuperamos el offset del primer scanline |
- | ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET) | + | |
- | LD HL, (DS_ATTRIBS) | + | ld hl, (DS_ATTRIBS) |
- | XOR A ; A = 0 | + | xor a ; A = 0 |
- | ADD A, H ; A = 0 + H = H | + | add a, h ; A = 0 + H = H |
- | RET Z ; Si H = 0, volver (no dibujar atributos) | + | ret z ; Si H = 0, volver (no dibujar atributos) |
- | ;;; Calcular posicion destino en area de atributos en DE. | + | |
- | LD A, B ; Codigo de Get_Attr_Offset_From_Image | + | ld a, b ; Codigo de Get_Attr_Offset_From_Image |
- | RRCA ; Obtenemos dir de atributo a partir de | + | |
- | RRCA ; dir de zona de imagen. | + | |
- | RRCA ; Nos evita volver a obtener X e Y | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | ld d, a |
- | LD E, C ; DE tiene el offset del attr de HL | + | ld e, c ; DE tiene el offset del attr de HL |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD C, A | + | ld c, a |
- | LD B, 0 | + | ld b, 0 |
- | ADD HL, BC ; HL = HL+DS_NUMSPR | + | add hl, bc ; HL = HL+DS_NUMSPR |
- | ADD HL, BC ; HL = HL+DS_NUMSPR*2 | + | add hl, bc ; HL = HL+DS_NUMSPR*2 |
- | ADD HL, BC ; HL = HL+DS_NUMSPR*3 | + | add hl, bc ; HL = HL+DS_NUMSPR*3 |
- | ADD HL, BC ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo | + | add hl, bc ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo |
- | LDI | + | ldi |
- | LDI ; Imprimimos las 2 primeras filas de atributo | + | |
- | ;;; Avance diferencial a la siguiente linea de atributos | + | |
- | LD A, E ; A = L | + | ld a, e ; A = L |
- | ADD A, 30 ; Sumamos A = A + 30 mas los 2 INCs de LDI. | + | add a, 30 ; Sumamos A = A + 30 mas los 2 INCs de ldi. |
- | LD E, A ; Guardamos en L (L = L+30 + 2 por LDI=L+32) | + | ld e, a ; Guardamos en L (L = L+30 + 2 por ldi=L+32) |
- | JR NC, drawsp16m_attrab_noinc | + | jr nc, drawsp16m_attrab_noinc |
- | INC D | + | inc d |
drawsp16m_attrab_noinc: | drawsp16m_attrab_noinc: | ||
- | LDI | + | ldi |
- | LDI | + | ldi |
- | | + | |
</ | </ | ||
Datos a destacar sobre el código: | Datos a destacar sobre el código: | ||
- | * Al igual que en el caso de la rutina con LD y con OR, el bucle de 16 iteraciones de la rutina de impresión con máscaras, se debería desenrollar si el trazado de sprites es una rutina prioritaria en nuestro programa (que suele ser el caso, especialmente en juegos). No obstante, hay que tener en cuenta que desenrollar estos 2 bucles supone añadir a la rutina 14 veces el tamaño de cada iteración (hay 16 iteraciones, | + | * Al igual que en el caso de la rutina con '' |
| | ||
Línea 1983: | Línea 2073: | ||
bicho_gfx: | bicho_gfx: | ||
- | DEFB 247, | + | |
- | | + | DEFB 240, 15, 15, 80,240, 15, 15, 80, 240, 15, 15, |
- | | + | DEFB 255, |
- | | + | DEFB 151, |
- | | + | DEFB 251, |
- | | + | DEFB 240, 15, 15, 80,240, 15, 15,240, 248, 7, 31, |
- | | + | DEFB 255, |
- | | + | DEFB 247, |
- | | + | DEFB 253, |
- | | + | DEFB 248, |
- | | + | DEFB 255, |
- | | + | DEFB 255, |
- | | + | DEFB 254, |
- | | + | DEFB 248, |
- | | + | DEFB 255, |
- | | + | DEFB 151, |
bicho_attrib: | bicho_attrib: | ||
- | DEFB 70, 71, 67, 3, 70, 71, 67, 3, 70, 71, 3, 67, 70, 71, 3, 67 | + | |
</ | </ | ||
Línea 2007: | Línea 2097: | ||
\\ | \\ | ||
- | {{ : | + | {{ : |
\\ | \\ | ||
Línea 2035: | Línea 2125: | ||
\\ | \\ | ||
\\ | \\ | ||
- | **Impresión con transferencia (LD/LDI):** | + | **Impresión con transferencia (LD/ldi):** |
La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento " | La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento " | ||
Línea 2052: | Línea 2142: | ||
**Impresión por operación lógica OR:** | **Impresión por operación lógica OR:** | ||
- | La impresión por OR es adecuada en juegos donde el personaje no tenga pixeles a cero dentro de su contorno, de forma que no se vean píxeles del fondo en el interior de nuestro personaje, alterando el sprite. | + | La impresión por '' |
La segunda opción (y la más habitual) es su uso en juegos donde (sea como sea el personaje del jugador) el fondo sea plano (sin tramas) y el color del mismo sea idéntico al color de PAPER de los sprites del juego. De esta forma nos podemos mover en el juego sin causar colisión de atributos con el fondo (ya que no tiene pixeles activos y el PAPER del fondo es igual al PAPER de nuestro Sprite) y podemos ubicar en la pantalla objetos multicolor en posiciones de carácter que no provoquen colisión de atributos con nuestro personaje o si la provocan sea imperceptible (elemento muy ajustado a tamaño de carácter) o durante un período de tiempo mínimo (recogida de un objeto). | La segunda opción (y la más habitual) es su uso en juegos donde (sea como sea el personaje del jugador) el fondo sea plano (sin tramas) y el color del mismo sea idéntico al color de PAPER de los sprites del juego. De esta forma nos podemos mover en el juego sin causar colisión de atributos con el fondo (ya que no tiene pixeles activos y el PAPER del fondo es igual al PAPER de nuestro Sprite) y podemos ubicar en la pantalla objetos multicolor en posiciones de carácter que no provoquen colisión de atributos con nuestro personaje o si la provocan sea imperceptible (elemento muy ajustado a tamaño de carácter) o durante un período de tiempo mínimo (recogida de un objeto). | ||
Línea 2067: | Línea 2157: | ||
**Impresión por operación lógica XOR:** | **Impresión por operación lógica XOR:** | ||
- | Más que juegos adecuados para esta técnica, podemos hablar más bien de sprites adecuados para ella. Nos referimos a los cursores, que suelen ser dibujados con XOR para no confundirse con el fondo (permitiendo una visión " | + | Más que juegos adecuados para esta técnica, podemos hablar más bien de sprites adecuados para ella. Nos referimos a los cursores, que suelen ser dibujados con '' |
\\ | \\ | ||
Línea 2100: | Línea 2190: | ||
La rutina, al ser genérica, y para que pueda ser legible por el lector, no es especialmente eficiente: debido a la necesidad de realizar multiplicaciones de 16 bits, se utilizan los registros HL, DE y BC y nos vemos obligados a hacer continuos PUSHes y POPs de estos registros, así como a apoyarnos en variables de memoria. La rutina podría ser optimizada en cuanto a algoritmo y/o en cuanto a reordenación de código y uso de shadow-registers para tratar de ganar todos los t-estados posibles. | La rutina, al ser genérica, y para que pueda ser legible por el lector, no es especialmente eficiente: debido a la necesidad de realizar multiplicaciones de 16 bits, se utilizan los registros HL, DE y BC y nos vemos obligados a hacer continuos PUSHes y POPs de estos registros, así como a apoyarnos en variables de memoria. La rutina podría ser optimizada en cuanto a algoritmo y/o en cuanto a reordenación de código y uso de shadow-registers para tratar de ganar todos los t-estados posibles. | ||
- | | + | |
<code z80> | <code z80> | ||
Línea 2110: | Línea 2200: | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
; Direccion | ; Direccion | ||
- | ; 50000 Direccion de la tabla de Sprites | + | ; Direccion |
- | ; 50002 Direccion de la tabla de Atribs | + | ; (DS_SPRITES) |
- | ; 50004 Coordenada X en baja resolucion | + | ; (DS_ATTRIBS) |
- | ; 50005 Coordenada Y en baja resolucion | + | ; (DS_COORD_X) |
- | ; 50006 Numero de sprite a dibujar (0-N) | + | ; (DS_COORD_Y) |
- | ; 50010 Ancho del sprite | + | ; (DS_NUMSPR) |
- | ; 50011 Alto del sprite | + | ; (DS_WIDTH) |
+ | ; (DS_HEIGHT) | ||
; | ; | ||
DrawSprite_MxN_LD: | DrawSprite_MxN_LD: | ||
- | | ||
- | ;;; Calcular posicion origen (array sprites) en HL como: | ||
- | ;;; | ||
- | ;;;; Multiplicamos ancho por alto (en bloques) | + | |
- | LD A, (DS_WIDTH) | + | ;;; direccion = base_sprites + (NUM_SPRITE*ANCHO*ALTO) |
- | LD C, A | + | |
- | LD A, (DS_HEIGHT) | + | |
- | | + | |
- | | + | |
- | | + | |
- | LD (drawsp_height), A | + | |
- | ;;; Multiplicamos Ancho_bloques * Alto_pixeles: | + | ;;;; Multiplicamos ancho por alto (en bloques) |
- | LD B, A | + | ld a, (DS_WIDTH) |
- | XOR A ; A = 0 | + | ld c, a |
+ | ld a, (DS_HEIGHT) | ||
+ | rlca ; Multiplicamos por 8, necesitamos | ||
+ | rlca ; la altura en pixeles (FILAS*8) | ||
+ | rlca ; Y la guardamos porque la necesitaremos: | ||
+ | ld (drawsp_height), | ||
+ | |||
+ | ;;; Multiplicamos Ancho_bloques * Alto_pixeles: | ||
+ | ld b, a | ||
+ | xor a ; A = 0 | ||
drawsp_mul1: | drawsp_mul1: | ||
- | ADD A, C ; A = A + C (B veces) | + | add a, c ; A = A + C (B veces) |
- | DJNZ drawsp_mul1 | + | |
- | ; Ahora A = Ancho*Alto (maximo 255!!!) | + | ; Ahora A = Ancho*Alto (maximo 255!!!) |
- | ;;; Multiplicamos DS_NUMSPR por (Ancho_bloques*Alto_pixeles) | + | |
- | LD B, A ; Repetimos Ancho * Alto veces | + | ld b, a ; Repetimos Ancho * Alto veces |
- | LD A, (DS_NUMSPR) | + | ld hl, 0 |
- | LD E, A ; | + | ld d, h ; |
- | LD D, 0 | + | ld a, (DS_NUMSPR) |
- | LD H, D ; | + | ld e, a ; |
- | LD L, H | + | |
drawsp_mul2: | drawsp_mul2: | ||
- | ADD HL, DE ; HL = HL+DS_NUMSPR | + | add hl, de ; HL = HL+DS_NUMSPR |
- | DJNZ drawsp_mul2 | + | |
- | | + | |
- | LD (drawsp_width_by_height), | + | |
- | ;;; Calculamos direccion origen copia en el sprite | + | |
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) |
- | ; HL contiene la direccion de inicio en el sprite | + | |
- | + | ||
- | ;;; Calculamos las coordenadas destino de pantalla en DE: | + | |
- | LD BC, (DS_COORD_X) | + | ;;; Calculamos las coordenadas destino de pantalla en DE: |
- | LD A, B | + | |
- | AND $18 | + | |
- | ADD A, $40 | + | |
- | LD D, A | + | |
- | LD A, B | + | |
- | AND 7 | + | |
- | | + | |
- | | + | |
- | | + | |
- | ADD A, C | + | |
- | LD E, A | + | |
- | PUSH DE | + | |
- | | + | |
- | | + | |
+ | ld bc, (DS_COORD_X) | ||
+ | ld a, b | ||
+ | and $18 | ||
+ | add a, $40 | ||
+ | ld d, a | ||
+ | ld a, b | ||
+ | and 7 | ||
+ | rrca | ||
+ | rrca | ||
+ | rrca | ||
+ | add a, c | ||
+ | ld e, a | ||
+ | push de ; Lo guardamos para luego, lo usaremos para | ||
+ | ; calcular la direccion del atributo | ||
+ | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) | ||
- | ;;; Bucle de impresión vertical | + | |
- | ; Recogemos de nuevo la altura en pixeles | + | ;;; Bucle de impresión vertical |
- | LD A, (drawsp_height) | + | ; Recogemos de nuevo la altura en pixeles |
- | LD B, A ; Contador del bucle exterior del bucle | + | ld a, (drawsp_height) |
+ | ld b, a ; Contador del bucle exterior del bucle | ||
drawsp_yloop: | drawsp_yloop: | ||
- | LD C, B ; Nos guardamos el contador | + | ld c, b ; Nos guardamos el contador |
- | ;;; Bucle de impresion horizontal | + | |
- | LD A, (DS_WIDTH) | + | ld a, (DS_WIDTH) |
- | LD B, A | + | ld b, a |
- | + | ||
- | PUSH HL ; Guardamos en pila inicio de scanline | + | push hl ; Guardamos en pila inicio de scanline |
- | ; para poder volver a el luego | + | ; para poder volver a el luego |
drawsp_xloop: | drawsp_xloop: | ||
- | LD A, (DE) ; Leemos dato del sprite | + | ld a, (de) ; Leemos dato del sprite |
- | LD (HL), A ; Copiamos dato a pantalla | + | |
- | INC DE ; Incrementar puntero en sprite | + | inc de ; Incrementar puntero en sprite |
- | INC L ; Incrementar puntero en pantalla | + | inc l ; Incrementar puntero en pantalla |
- | DJNZ drawsp_xloop | + | |
- | POP HL ; Recuperamos de pila inicio de scanline | + | pop hl ; Recuperamos de pila inicio de scanline |
- | ;;; Avanzamos al siguiente scanline de pantalla | + | |
- | INC H | + | inc h |
- | LD A, H | + | ld a, h |
- | AND 7 | + | |
- | JR NZ, drawspNM_nofix | + | jr nz, drawspNM_nofix |
- | LD A, L | + | ld a, l |
- | ADD A, 32 | + | add a, 32 |
- | LD L, A | + | ld l, a |
- | JR C, drawspNM_nofix | + | jr c, drawspNM_nofix |
- | LD A, H | + | ld a, h |
- | SUB 8 | + | |
- | LD H, A | + | ld h, a |
drawspNM_nofix: | drawspNM_nofix: | ||
- | LD B, C | + | ld b, c |
- | DJNZ drawsp_yloop | + | |
- | ;;; Aqui hemos dibujado todo el sprite, vamos a los attributos | + | |
- | POP BC ; Recuperamos el offset del primer scanline | + | pop bc ; Recuperamos el offset del primer scanline |
- | ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> RET) | + | |
- | LD HL, (DS_ATTRIBS) | + | |
- | + | | |
- | XOR A | + | ret z |
- | ADD A, H ; A = 0 + H = H | + | |
- | RET Z | + | |
- | + | ||
- | ;;; Calcular posicion destino en area de atributos en DE. | + | |
- | LD A, B ; Codigo de Get_Attr_Offset_From_Image | + | |
- | | + | |
- | | + | |
- | | + | |
- | AND 3 ; y hacer el calculo completo de la | + | |
- | OR $58 ; direccion en zona de atributos | + | |
- | LD D, A | + | |
- | LD E, C ; DE tiene el offset del attr de HL | + | |
- | PUSH DE ; Guardamos una copia | + | |
- | + | ||
- | ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR | + | |
- | ; para ahorrarnos repetir otra vez dos multiplicaciones: | + | |
- | LD HL, (drawsp_width_by_height) | + | |
- | ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR | + | ;;; Calcular posicion destino en area de atributos en DE. |
- | | + | ld a, b ; Codigo de Get_Attr_Offset_From_Image |
- | SRL H ; Desplazamos H a la derecha | + | rrca ; Obtenemos dir de atributo a partir de |
- | RR L ; Rotamos L a la derecha introduciendo CF | + | rrca ; dir de zona de imagen. |
- | SRL H ; | + | rrca ; Nos evita volver a obtener X e Y |
- | RR L ; | + | and 3 ; y hacer el calculo completo de la |
- | SRL H ; | + | or $58 ; direccion en zona de atributos |
- | RR L ; Resultado : HL = HL >> 3 = HL / 8 | + | ld d, a |
- | | + | ld e, c ; DE tiene el offset del attr de HL |
- | | + | push de ; Guardamos una copia |
- | LD C, L | + | |
- | LD B, H | + | ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR |
- | LD HL, (DS_ATTRIBS) | + | ; para ahorrarnos repetir otra vez dos multiplicaciones: |
- | ADD HL, BC ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) | + | ld hl, (drawsp_width_by_height) |
- | + | ||
- | POP DE ; Recuperamos direccion destino | + | ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR |
+ | ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> | ||
+ | srl h ; Desplazamos H a la derecha | ||
+ | rr l ; Rotamos L a la derecha introduciendo CF | ||
+ | srl h ; | ||
+ | rr l ; | ||
+ | srl h ; | ||
+ | rr l ; Resultado : HL = HL >> 3 = HL / 8 | ||
+ | |||
+ | ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR | ||
+ | ld c, l | ||
+ | ld b, h | ||
+ | ld hl, (DS_ATTRIBS) | ||
+ | add hl, bc ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) | ||
+ | |||
+ | pop de ; Recuperamos direccion destino | ||
- | LD A, (DS_HEIGHT) | + | ld a, (DS_HEIGHT) |
- | LD B, A | + | ld b, a |
- | ;;; Bucle impresion vertical de atributos | + | |
drawsp_attyloop: | drawsp_attyloop: | ||
- | LD C, B | + | ld c, b |
- | PUSH DE ; Guardamos inicio de linea de atributos | + | push de ; Guardamos inicio de linea de atributos |
- | LD A, (DS_WIDTH) | + | ld a, (DS_WIDTH) |
- | LD B, A | + | ld b, a |
- | ;;; Bucle impresion horizontal de atributos | + | |
drawsp_attxloop: | drawsp_attxloop: | ||
- | LD A, (HL) ; Leer atributo del sprite | + | ld a, (hl) ; Leer atributo del sprite |
- | INC HL | + | inc hl |
- | LD (DE), A ; Escribir atributo | + | |
- | INC E | + | inc e |
- | DJNZ | + | |
- | POP DE ; Recuperamos inicio de linea de atributos | + | pop de ; Recuperamos inicio de linea de atributos |
- | ;;; Avance diferencial a la siguiente linea de atributos | + | |
- | LD A, E | + | ld a, e |
- | ADD A, 32 | + | add a, 32 |
- | LD E, A | + | ld e, a |
- | JR NC, drawsp_attrab_noinc | + | jr nc, drawsp_attrab_noinc |
- | INC D | + | inc d |
drawsp_attrab_noinc: | drawsp_attrab_noinc: | ||
- | LD B, C | + | ld b, c |
- | DJNZ drawsp_attyloop | + | |
- | RET | + | ret |
drawsp_height | drawsp_height | ||
Línea 2303: | Línea 2391: | ||
<code z80> | <code z80> | ||
- | | + | |
- | DS_SPRITES | + | ld hl, bicho_gfx |
- | DS_ATTRIBS | + | ld (DS_SPRITES), hl |
- | DS_COORD_X | + | ld hl, bicho_attrib |
- | DS_COORD_Y | + | ld (DS_ATTRIBS), hl |
- | DS_NUMSPR | + | ld a, 13 |
- | DS_WIDTH | + | ld (DS_COORD_X), a |
- | DS_HEIGHT | + | ld a, 8 |
+ | ld (DS_COORD_Y), a | ||
+ | ld a, 2 | ||
+ | ld (DS_WIDTH), a | ||
+ | ld a, 2 | ||
+ | ld (DS_HEIGHT), a | ||
+ | xor a | ||
+ | ld (DS_NUMSPR), | ||
- | LD HL, bicho_gfx | + | call DrawSprite_MxN_LD |
- | LD (DS_SPRITES), | + | ret |
- | LD HL, bicho_attrib | + | |
- | LD (DS_ATTRIBS), | + | |
- | LD A, 13 | + | |
- | LD (DS_COORD_X), | + | |
- | LD A, 8 | + | |
- | LD (DS_COORD_Y), | + | |
- | LD A, 2 | + | |
- | LD (DS_WIDTH), A | + | |
- | LD A, 2 | + | |
- | LD (DS_HEIGHT), | + | |
- | XOR A | + | |
- | LD (DS_NUMSPR), | + | |
- | | + | ; Variables que usaremos como parámetros |
- | | + | DS_SPRITES |
+ | DS_ATTRIBS | ||
+ | DS_COORD_X | ||
+ | DS_COORD_Y | ||
+ | DS_NUMSPR | ||
+ | DS_WIDTH | ||
+ | DS_HEIGHT | ||
</ | </ | ||
- | La rutina anterior trabaja mediante transferencia de datos sprite -> pantalla. Para realizar una operación lógica, bastará modificar el bucle horizontal de impresión y añadir el correspondiente OR / XOR contra (HL). | + | La rutina anterior trabaja mediante transferencia de datos sprite -> pantalla. Para realizar una operación lógica, bastará modificar el bucle horizontal de impresión y añadir el correspondiente |
La rutina para trabajar con máscaras implicar modificar el bucle principal para gestionar los datos de la máscara y modificar también las multiplicaciones iniciales de parámetros (*2 en el caso de los datos gráficos, sin modificación en los atributos). | La rutina para trabajar con máscaras implicar modificar el bucle principal para gestionar los datos de la máscara y modificar también las multiplicaciones iniciales de parámetros (*2 en el caso de los datos gráficos, sin modificación en los atributos). | ||
Línea 2364: | Línea 2453: | ||
< | < | ||
El buffer al que me refiero puedes verlo como una segunda capa de atributos, | El buffer al que me refiero puedes verlo como una segunda capa de atributos, | ||
- | que es la forma más fácil de implementar. Es una zona de memoria de | + | que es la forma más fácil de implementar. Es una zona de memoria de |
32x24 = 768 bytes (como el area de atributos) en la que se marcan los caracteres | 32x24 = 768 bytes (como el area de atributos) en la que se marcan los caracteres | ||
- | ocupados en cada momento. | + | ocupados en cada momento. |
Algunas pistas para implementarlo: | Algunas pistas para implementarlo: | ||
- cuando vayas a imprimir un caracter consulta primero su entrada en el buffer. | - cuando vayas a imprimir un caracter consulta primero su entrada en el buffer. | ||
- | Si está libre puedes volcar el gráfico directamente con LD (HL),A... porque es | + | Si está libre puedes volcar el gráfico directamente con ld (hl),a... porque es |
más rápido y sabes *seguro* que puedes machacar lo que haya en pantalla. Si por | más rápido y sabes *seguro* que puedes machacar lo que haya en pantalla. Si por | ||
el contrario está ocupado (o sea, hay otro sprite en esa posición) sabes que tienes | el contrario está ocupado (o sea, hay otro sprite en esa posición) sabes que tienes | ||
que imprimir mezclando para preservar el fondo (p.e. con XOR). De esta forma tu | que imprimir mezclando para preservar el fondo (p.e. con XOR). De esta forma tu | ||
rutina de impresión imprimirá siempre de la forma más rápida posible, excepto en | rutina de impresión imprimirá siempre de la forma más rápida posible, excepto en | ||
- | los caracteres que se superpongan. | + | los caracteres que se superpongan. |
- con este método puedes borrar pintando atributos en lugar de volcar blancos con | - con este método puedes borrar pintando atributos en lugar de volcar blancos con | ||
Línea 2401: | Línea 2490: | ||
**Borrado (e impresión) con XOR** | **Borrado (e impresión) con XOR** | ||
- | | + | |
- | | + | |
+ | |< 30% >| | ||
^ Bit Pantalla ^ Bit Sprite ^ XOR ^ | ^ Bit Pantalla ^ Bit Sprite ^ XOR ^ | ||
- | | 0 | 0 | 0 | | + | | 0 | 0 | 0 | |
- | | 0 | 1 | 1 | | + | | 0 | 1 | 1 | |
- | | 1 | 0 | 1 | | + | | 1 | 0 | 1 | |
- | | 1 | 1 | 0 | | + | | 1 | 1 | 0 | |
- | A continuación tenemos un ejemplo de cómo restaurar el fondo gracias a XOR: | + | A continuación tenemos un ejemplo de cómo restaurar el fondo gracias a '' |
< | < | ||
Impresión del sprite con XOR: | Impresión del sprite con XOR: | ||
----------------------------- | ----------------------------- | ||
- | Fondo: | + | Fondo: |
Sprite: | Sprite: | ||
Tras XOR: 01101101 | Tras XOR: 01101101 | ||
Línea 2428: | Línea 2518: | ||
</ | </ | ||
- | La impresión de un sprite con XOR produce una especie de " | + | La impresión de un sprite con '' |
Línea 2441: | Línea 2531: | ||
| | ||
- | Para evitar esto, podemos adaptar las rutinas de impresión para que salvaguarden el fondo en nuestro array temporal antes de escribir los datos en pantalla. | + | Para evitar esto, podemos adaptar las rutinas de impresión para que salvaguarden el fondo en nuestro array temporal antes de escribir los datos en pantalla. |
Como tenemos ya utilizados DE y HL para la transferencia del Sprite, necesitamos un registro de 16 bits adicional. En este caso utilizaremos el registro IX para apuntar al array temporal. | Como tenemos ya utilizados DE y HL para la transferencia del Sprite, necesitamos un registro de 16 bits adicional. En este caso utilizaremos el registro IX para apuntar al array temporal. | ||
Línea 2451: | Línea 2541: | ||
; DrawSprite_8x8_Restore: | ; DrawSprite_8x8_Restore: | ||
; Imprime un sprite de 8x8 pixeles salvaguardando el fondo | ; Imprime un sprite de 8x8 pixeles salvaguardando el fondo | ||
- | ; en la direccion de memoria indicada por (50008). | + | ; en la direccion de memoria indicada por (DS_TEMPBUF). |
; | ; | ||
; Entrada (paso por parametros en memoria): | ; Entrada (paso por parametros en memoria): | ||
- | ; Direccion | + | ; Direccion |
- | ; 50000 | + | ; ... |
- | ; 50002 | + | ; (DS_TEMPBUF) Direccion del array temporal para el fondo |
- | ; 50004 | + | |
- | ; 50005 | + | |
- | ; 50006 | + | |
- | ; 50008 Direccion del array temporal para el fondo | + | |
; | ; | ||
- | DS_TEMPBUF EQU 50008 | ||
- | ;;; Recogida de datos y parametros | + | |
- | | + | (...) |
- | LD IX, (DS_TEMPBUF) | + | ld ix, (DS_TEMPBUF) |
- | | + | (...) |
- | | + | |
- | | + | ;;; Bucle de impresion del Sprite: |
- | LD B, 8 ; 8 scanlines | + | ld b, 8 ; 8 scanlines |
- | + | ||
drawsp8x8_loopLD: | drawsp8x8_loopLD: | ||
- | LD A, (HL) ; NUEVO: Leemos el valor actual del fondo | + | ld a, (hl) ; NUEVO: Leemos el valor actual del fondo |
- | LD (IX), A ; NUEVO: Lo almacenamos en el array temporal | + | |
- | INC IX ; NUEVO: Incrementamos IX (puntero array fondo) | + | inc ix ; NUEVO: Incrementamos IX (puntero array fondo) |
+ | |||
+ | ; Ya podemos imprimir el sprite | ||
+ | ld a, (de) ; Tomamos el dato del sprite | ||
+ | ld (hl), a ; Establecemos el valor en videomemoria | ||
+ | inc de ; Incrementamos puntero en sprite | ||
+ | inc h ; Incrementamos puntero en pantalla (scanline+=1) | ||
+ | djnz drawsp8x8_loopLD | ||
+ | |||
+ | (...) | ||
+ | ;;; Impresion de atributos | ||
+ | ld a, (de) ; NUEVO: Leemos el atributo actual | ||
+ | ld (ix), a ; NUEVO: Lo guardamos en el array temporal | ||
- | ; Ya podemos imprimir el sprite | + | ld a, (hl) ; |
- | LD A, (DE) ; | + | |
- | LD (HL), A ; Establecemos el valor en videomemoria | + | ret |
- | INC DE ; Incrementamos puntero en sprite | + | |
- | INC H ; Incrementamos puntero en pantalla (scanline+=1) | + | |
- | DJNZ drawsp8x8_loopLD | + | |
- | | + | DS_TEMPBUF |
- | | + | |
- | LD A, (DE) ; NUEVO: Leemos el atributo actual | + | |
- | LD (IX), A ; NUEVO: Lo guardamos en el array temporal | + | |
- | + | ||
- | LD A, (HL) ; Ya podemos imprimir el atributo | + | |
- | LD (DE), A | + | |
- | RET | + | |
</ | </ | ||
- | El registro IX no es especialmente rápido (10 t-estados el **INC IX** y 19 t-estados el **LD (IX), A** o **LD (IX+0), A**, pero es más rápido utilizarlo que escribir y ejecutar una rutina adicional para almacenar el fondo. | + | El registro IX no es especialmente rápido (10 t-estados el '' |
| | ||
Línea 2520: | Línea 2607: | ||
Por otra parte, si no tenemos intención de llamar a estas funciones desde BASIC, lo más óptimo sería eliminar el paso de parámetros por variables de memoria y utilizar registros y/o pila allá donde sea posible, puesto que estas operaciones son más rápidas que los acceso a memoria. | Por otra parte, si no tenemos intención de llamar a estas funciones desde BASIC, lo más óptimo sería eliminar el paso de parámetros por variables de memoria y utilizar registros y/o pila allá donde sea posible, puesto que estas operaciones son más rápidas que los acceso a memoria. | ||
- | No obstante, nótese que las 2 variables que más tiempo cuesta de establecer y de leer (DS_SPRITES y DS_ATTRIBS) se pueden establecer como constantes (y no como variables) directamente dentro de la rutina de impresión. Eso quiere decir que si tenemos un único tileset, podríamos cambiar partes de la rutina, como: | + | No obstante, nótese que las 2 variables que más tiempo cuesta de establecer y de leer ('' |
<code z80> | <code z80> | ||
- | LD BC, (DS_SPRITES) | + | ld bc, (DS_SPRITES) |
</ | </ | ||
Línea 2529: | Línea 2616: | ||
<code z80> | <code z80> | ||
- | LD BC, Tabla_Sprites | + | ld bc, Tabla_Sprites |
</ | </ | ||
- | | + | |
Línea 2538: | Línea 2625: | ||
**Deshabilitar las interrupciones** | **Deshabilitar las interrupciones** | ||
- | Si nuestra rutina de dibujado de interrupciones es crítica, puede que nos resulte necesario en algunas circunstancias realizar un **DI** (Disable Interrupts) al principio de la misma y un **EI** (Enable Interrupts) al acabar, para evitar que una interrupción del Z80 sea lanzada durante la ejecución de la misma. | + | Si nuestra rutina de dibujado de interrupciones es crítica, puede que nos resulte necesario en algunas circunstancias realizar un '' |
- | | + | |
- | No obstante, si no estamos sincronizados con las interrupciones o si estamos usando | + | No obstante, si no estamos sincronizados con las interrupciones o si estamos usando |
| | ||
Línea 2551: | Línea 2638: | ||
**Uso de la pila para acceso a datos** | **Uso de la pila para acceso a datos** | ||
- | Una opción realmente ingeniosa para optimizar el acceso al sprite sería la de utilizar la pila para realizar operaciones de lectura de 2 bytes del sprite con una única instrucción (POP). | + | Una opción realmente ingeniosa para optimizar el acceso al sprite sería la de utilizar la pila para realizar operaciones de lectura de 2 bytes del sprite con una única instrucción ('' |
- | | + | |
- | Como desventaja, debemos deshabilitar las interrupciones con **DI** al principio del programa y habilitarlas de nuevo antes del RET con **EI** ya que no podemos permitir la ejecución de una ISR estando SP apuntando al Sprite. Un "RST", " | + | Como desventaja, debemos deshabilitar las interrupciones con **di** al principio del programa y habilitarlas de nuevo antes del ret con '' |
| | ||
< | < | ||
- | ; DI | + | ; di |
; Calcular dirección destino en pantalla | ; Calcular dirección destino en pantalla | ||
; Calcular dirección origen de sprite | ; Calcular dirección origen de sprite | ||
Línea 2572: | Línea 2659: | ||
; Recuperar puntero de pila | ; Recuperar puntero de pila | ||
- | ; EI | + | ; ei |
- | ; RET | + | ; ret |
</ | </ | ||
- | A continuación vamos a ver una versión " | + | A continuación vamos a ver una versión " |
- | + | ||
- | En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, | + | |
+ | En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, | ||
<code z80> | <code z80> | ||
; | ; | ||
; DrawSprite_16x16_LD_STACK_no_attr: | ; DrawSprite_16x16_LD_STACK_no_attr: | ||
- | ; Imprime un sprite de 16x16 pixeles sin atributos | + | ; Imprime un sprite de 16x16 pixeles sin atributos |
; usando la pila para obtener datos del sprite. | ; usando la pila para obtener datos del sprite. | ||
; | ; | ||
Línea 2591: | Línea 2677: | ||
DrawSprite_16x16_LD_STACK_no_attr: | DrawSprite_16x16_LD_STACK_no_attr: | ||
- | |||
- | ;;; Deshabilitar interrupciones (vamos a cambiar SP). | ||
- | DI | ||
- | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X | + | ;;; Deshabilitar interrupciones (vamos a cambiar SP). |
- | LD BC, (DS_COORD_X) | + | di |
- | + | ||
- | | + | ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X |
- | LD A, B | + | ld bc, (DS_COORD_X) |
- | AND $18 | + | |
- | ADD A, $40 | + | ;;; Calculamos las coordenadas destino de pantalla en DE: |
- | LD D, A | + | ld a, b |
- | LD A, B | + | |
- | AND 7 | + | add a, $40 |
- | RRCA | + | ld d, a |
- | RRCA | + | ld a, b |
- | RRCA | + | |
- | ADD A, C | + | rrca |
- | LD E, A | + | rrca |
+ | rrca | ||
+ | add a, c | ||
+ | ld e, a | ||
;;; Guardamos el valor actual y correcto de SP | ;;; Guardamos el valor actual y correcto de SP | ||
- | | + | ld (DS_TEMP_sp), sp |
- | + | ||
- | ;;; Calcular posicion origen (array sprites) en HL: | + | |
- | LD BC, (DS_SPRITES) | + | |
- | LD A, (DS_NUMSPR) | + | |
- | LD L, 0 ; AL = DS_NUMSPR*256 | + | |
- | SRL A ; Desplazamos a la derecha para dividir por dos | + | |
- | RR L ; AL = DS_NUMSPR*128 | + | |
- | | + | |
- | RR L ; AL = DS_NUMSPR*64 | + | |
- | | + | |
- | RR L ; AL = DS_NUMSPR*32 | + | |
- | LD H, A ; HL = DS_NUMSPR*32 | + | |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) | + | |
- | ; HL contiene la direccion de inicio en el sprite | + | |
- | + | ||
- | ;;; Establecemos el valor de SP apuntando al sprite | + | |
- | LD SP, HL | + | |
- | EX DE, HL ; Intercambiamos DE y HL (DE=origen, HL=destino) | + | ;;; Calcular posicion origen (array sprites) en HL: |
+ | ld bc, (DS_SPRITES) | ||
+ | ld a, (DS_NUMSPR) | ||
+ | ld l, 0 ; AL = DS_NUMSPR*256 | ||
+ | srl a ; Desplazamos a la derecha para dividir por dos | ||
+ | rr l ; AL = DS_NUMSPR*128 | ||
+ | rra ; Rotamos, ya que el bit que salio de L al CF fue 0 | ||
+ | rr l ; AL = DS_NUMSPR*64 | ||
+ | rra ; Rotamos, ya que el bit que salio de L al CF fue 0 | ||
+ | rr l ; AL = DS_NUMSPR*32 | ||
+ | ld h, a ; | ||
+ | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) | ||
+ | ; HL contiene la direccion de inicio en el sprite | ||
+ | |||
+ | ;;; Establecemos el valor de SP apuntando al sprite | ||
+ | ld sp, hl | ||
+ | |||
+ | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) | ||
+ | |||
+ | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
+ | ld b, 16 | ||
- | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | ||
- | LD B, 16 | ||
- | |||
drawsp16x16_stack_loop: | drawsp16x16_stack_loop: | ||
- | POP DE ; Recuperamos 2 bytes del sprite | + | pop de ; Recuperamos 2 bytes del sprite |
- | ; Y ademas no hace falta sumar 2 a DE | + | ; Y ademas no hace falta sumar 2 a DE |
- | LD (HL), E ; Ahora imprimimos los 2 bytes en pantalla | + | |
- | INC L | + | inc l |
- | LD (HL), D | + | |
- | DEC L | + | dec l |
- | + | ||
- | | + | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) |
- | | + | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 |
- | + | ||
- | INC H ; Siguiente scanline | + | inc h ; Siguiente scanline |
- | LD A, H ; Ajustar tercio/ | + | ld a, h ; Ajustar tercio/ |
- | AND 7 | + | |
- | JR NZ, drawsp16_nofix_abajop | + | jr nz, drawsp16_nofix_abajop |
- | LD A, L | + | ld a, l |
- | ADD A, 32 | + | add a, 32 |
- | LD L, A | + | ld l, a |
- | JR C, drawsp16_nofix_abajop | + | jr c, drawsp16_nofix_abajop |
- | LD A, H | + | ld a, h |
- | SUB 8 | + | |
- | LD H, A | + | ld h, a |
- | + | ||
drawsp16_nofix_abajop: | drawsp16_nofix_abajop: | ||
- | | + | djnz drawsp16x16_stack_loop |
- | + | ||
- | ;;; En este punto, los 16 scanlines del sprite estan dibujados. | + | |
- | + | ||
- | ;;; Recuperamos el valor de SP | + | |
- | LD HL, (DS_TEMP_SP) | + | |
- | LD SP, HL | + | |
- | EI ; Habilitar de nuevo interrupciones | + | ;;; En este punto, los 16 scanlines del sprite estan dibujados. |
- | RET | + | |
+ | ;;; Recuperamos el valor de SP | ||
+ | ld hl, (DS_TEMP_SP) | ||
+ | ld sp, hl | ||
+ | |||
+ | ei ; Habilitar de nuevo interrupciones | ||
+ | ret | ||
</ | </ | ||
+ | Al igual que en el caso de las rutinas anteriores, si desenrollamos el bucle de 16 iteraciones en 2 bucles de 8, podemos utilizar **inc h** dentro de los bucles y el código de // | ||
+ | |||
+ | <code z80> | ||
+ | drawsp16x16_stack_loop1: | ||
+ | pop de ; Recuperamos 2 bytes del sprite | ||
+ | ; Y ademas no hace falta sumar 2 a DE | ||
+ | ld (hl), e ; Ahora imprimimos los 2 bytes en pantalla | ||
+ | inc l ; de la parte superior del Sprite | ||
+ | ld (hl), d | ||
+ | dec l | ||
+ | inc h | ||
+ | djnz drawsp16x16_stack_loop1 | ||
+ | |||
+ | ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) | ||
+ | ; desde el septimo scanline de la fila Y+1 al primero de la Y+2 | ||
+ | ; No hay que comprobar si (H & $07) == 0 porque sabemos que lo es | ||
+ | ld a, l | ||
+ | add a, 32 | ||
+ | ld l, a | ||
+ | jr c, drawsp16_nofix_abajop | ||
+ | ld a, h | ||
+ | sub 8 | ||
+ | ld h, a | ||
+ | drawsp16_nofix_abajop: | ||
+ | |||
+ | ld b,8 | ||
+ | drawsp16x16_stack_loop2: | ||
+ | pop de ; Recuperamos 2 bytes del sprite | ||
+ | ; Y ademas no hace falta sumar 2 a DE | ||
+ | ld (hl), e ; Ahora imprimimos los 2 bytes en pantalla | ||
+ | inc l ; de la parte inferior del Sprite | ||
+ | ld (hl), d | ||
+ | dec l | ||
+ | inc h | ||
+ | djnz drawsp16x16_stack_loop2 | ||
+ | </ | ||
+ | |||
+ | La opción inversa sería utilizar '' | ||
+ | |||
+ | \\ | ||
+ | \\ | ||
+ | **Organización de los datos gráficos** | ||
+ | |||
+ | A la hora de renderizar gráficos, es muy importante decidir cómo queremos alojar esos gráficos en el código (y por tanto, en memoria), y pensar las rutinas de forma que sean más eficientes gracias a esa organización. | ||
+ | |||
+ | Por ejemplo, si tenemos un personaje de un juego de 24x24, estará formado por 9 bloques de 8x8: | ||
+ | |||
+ | < | ||
+ | ABC | ||
+ | DEF | ||
+ | GHI | ||
+ | </ | ||
+ | |||
+ | A la hora de almacenar los datos en memoria, tenemos que decidir cómo poner los datos de los scanlines. Podríamos guardarlos tal y como aparecen los pixeles, es decir: | ||
+ | |||
+ | < | ||
+ | Linea 0: 8_pixeles_linea_0_A | ||
+ | Linea 1: 8_pixeles_linea_1_A | ||
+ | ... | ||
+ | Linea 8: 8_pixeles_linea_0_D | ||
+ | Linea 9: 8_pixeles_linea_1_D | ||
+ | ... | ||
+ | Linea 22: 8_pixeles_linea_6_G | ||
+ | Linea 23: 8_pixeles_linea_7_G | ||
+ | </ | ||
+ | |||
+ | Y después de los datos de cada sprites, tener los datos de la máscara de dicho sprite. | ||
+ | |||
+ | Pero eso nos obligaría a tener un puntero para las máscaras. | ||
+ | |||
+ | Podría ser más interesante alojar la máscara junto al mismo sprite, estando cada byte precedido de su byte de máscara, para leerlos con un | ||
+ | |||
+ | <code z80> | ||
+ | ; SP apunta a nuestro sprite | ||
+ | ; HL apunta ya a la pantalla, lugar de dibujado | ||
+ | ld b, ANCHO | ||
+ | |||
+ | LOOP: | ||
+ | pop de ; Leemos mascara y datos graficos | ||
+ | ld a, (hl) ; Leemos dato en la memoria | ||
+ | and e | ||
+ | or d | ||
+ | ld (hl), a ; Aplicamos la mascara y guardamos en pantalla | ||
+ | inc hl ; Avanzamos puntero de dibujado en pantalla | ||
+ | djnz LOOP | ||
+ | </ | ||
+ | |||
+ | De la misma forma, tenemos que pensar cómo queremos organizar los atributos para después renderizarlos. Según cómo creemos la rutina, puede interesaros tener los datos de atributos de un sprite después de cada Sprite, o en un tabla aparte. Incluso puede darse el caso de que si los tenemos guardados en orden inverso se nos ocurra una rutina de impresión más eficiente. | ||
+ | |||
+ | Recordemos que imprimir los gráficos del juego es una de las tareas que más recursos de la CPU (tiempo de proceso) consumirá por lo que este es un caso especial en el que se recomienda optimizar al extremo. | ||
\\ | \\ | ||
Línea 2685: | Línea 2861: | ||
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
- | * {{cursos: | + | * {{cursos: |
\\ | \\ | ||
===== Enlaces ===== | ===== Enlaces ===== | ||
Línea 2694: | Línea 2870: | ||
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
- | * [[http:// | ||
- | |||
\\ | \\ | ||
+ | **[ [[.: |