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 [06-01-2024 15:01] – [Enlaces] 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 7: | Línea 9: | ||
| | ||
- | + | \\ | |
- | \\ | + | |
===== Teoría sobre el trazado de sprites ===== | ===== Teoría sobre el trazado de sprites ===== | ||
| | ||
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ cursos: | {{ cursos: | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ cursos: | {{ cursos: | ||
- | \\ | + | \\ |
Estos programas gráficos tratan los sprites como pequeños rectángulos (con o sin zonas transparentes en ellos) del ancho y alto deseado y se convierten en mapas de bits (una matriz de píxeles activos o no activos agrupados) que se almacenan en los programas como simples ristras de bytes, preparados para ser volcados en la pantalla con //rutinas de impresión de sprites//. | Estos programas gráficos tratan los sprites como pequeños rectángulos (con o sin zonas transparentes en ellos) del ancho y alto deseado y se convierten en mapas de bits (una matriz de píxeles activos o no activos agrupados) que se almacenan en los programas como simples ristras de bytes, preparados para ser volcados en la pantalla con //rutinas de impresión de sprites//. | ||
- | \\ | + | \\ |
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
//Sprite en editor de sprites, su bitmap, y su conversión a datos binarios.// | //Sprite en editor de sprites, su bitmap, y su conversión a datos binarios.// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
| | ||
El sistema de sprites en formato rectangular suele ser utilizado en sistemas más potentes que el Spectrum, permitiendo además sprites de diferentes tamaños en el mismo tileset. Las rutinas que imprimen estos sprites a lo largo del juego requieren como parámetros, | El sistema de sprites en formato rectangular suele ser utilizado en sistemas más potentes que el Spectrum, permitiendo además sprites de diferentes tamaños en el mismo tileset. Las rutinas que imprimen estos sprites a lo largo del juego requieren como parámetros, | ||
- | \\ | + | \\ |
{{ 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\\ |
;#; | ;#; | ||
- | \\ | + | \\ |
En el caso del Spectrum, nos interesa mucho más el sistema de almacenamiento lineal dentro de un " | En el caso del Spectrum, nos interesa mucho más el sistema de almacenamiento lineal dentro de un " | ||
- | \\ | + | \\ |
{{ 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\\ |
;#; | ;#; | ||
- | \\ | + | \\ |
En un juego donde todos los sprites son de 16x16 y los fondos están formados por sprites o tiles de 8x8, se podría tener un " | En un juego donde todos los sprites son de 16x16 y los fondos están formados por sprites o tiles de 8x8, se podría tener un " | ||
- | \\ | + | \\ |
- | | + | |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
- | //Tilemap: componiendo un mapa en pantalla\\ a partir de tiles de un tileset/ | + | //Tilemap: componiendo un mapa en pantalla\\ |
;#; | ;#; | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ cursos: | {{ cursos: | ||
;#; | ;#; | ||
//Un Sprite y su máscara aplicados sobre el fondo.// | //Un Sprite y su máscara aplicados sobre el fondo.// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
Las máscaras son necesarias para saber qué partes del sprite son transparentes: | Las máscaras son necesarias para saber qué partes del sprite son transparentes: | ||
- | \\ | + | \\ |
===== Diseño de una rutina de impresión de sprites ===== | ===== Diseño de una rutina de impresión de sprites ===== | ||
En microordenadores como el Spectrum existe un vínculo especial entre los " | En microordenadores como el Spectrum existe un vínculo especial entre los " | ||
- | \\ | + | \\ |
==== El diseño gráfico del Sprite ==== | ==== El diseño gráfico del Sprite ==== | ||
Línea 101: | Línea 102: | ||
Dicho formato puede ser: | Dicho formato puede ser: | ||
- | \\ | + | \\ |
* Sprite con atributos de color (multicolor) o sin atributos de color (monocolor). | * Sprite con atributos de color (multicolor) o sin atributos de color (monocolor). | ||
* Si el sprite tiene atributos de color, los atributos pueden ir: | * Si el sprite tiene atributos de color, los atributos pueden ir: | ||
Línea 109: | Línea 110: | ||
* Sprite que altere o no altere el fondo: | * Sprite que altere o no altere el fondo: | ||
* Si no debe alterarlo, se tiene que decidir si será mediante impresión por operación lógica o si será mediante máscaras (y dibujar y almacenar estas). | * Si no debe alterarlo, se tiene que decidir si será mediante impresión por operación lógica o si será mediante máscaras (y dibujar y almacenar estas). | ||
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
==== La creación de la rutina de impresión ==== | ==== La creación de la rutina de impresión ==== | ||
Línea 122: | Línea 123: | ||
Para crear estas rutinas necesitamos conocer la teoría relacionada con: | Para crear estas rutinas necesitamos conocer la teoría relacionada con: | ||
- | \\ | + | \\ |
* 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). | ||
* El cálculo de posición en memoria de atributos del bloque (0,0) del sprite. | * El cálculo de posición en memoria de atributos del bloque (0,0) del sprite. | ||
* El avance diferencial en la zona de atributos para imprimir los atributos de los sprites de más de 1x1 bloques. | * El avance diferencial en la zona de atributos para imprimir los atributos de los sprites de más de 1x1 bloques. | ||
- | \\ | + | \\ |
| | ||
Línea 141: | Línea 142: | ||
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% >| | |< 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. | ||
Línea 163: | Línea 164: | ||
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. | ||
- | | + | |
- | \\ | + | \\ |
===== Organización de los sprites en memoria ===== | ===== Organización de los sprites en memoria ===== | ||
Línea 173: | Línea 174: | ||
Hay 4 decisiones principales que tomar al respecto: | Hay 4 decisiones principales que tomar al respecto: | ||
- | \\ | + | \\ |
* Formato de organización del tileset (lineal o en forma de matriz/ | * Formato de organización del tileset (lineal o en forma de matriz/ | ||
* Formato de almacenamiento de cada tile (por bloques, por scanlines). | * Formato de almacenamiento de cada tile (por bloques, por scanlines). | ||
* Formato de almacenamiento de los atributos (después de los sprites, intercalados con ellos). | * Formato de almacenamiento de los atributos (después de los sprites, intercalados con ellos). | ||
* Formato de almacenamiento de las máscaras de los sprites si las hubiera. | * Formato de almacenamiento de las máscaras de los sprites si las hubiera. | ||
- | \\ | + | \\ |
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 " | ||
Línea 224: | Línea 225: | ||
Al organizar los datos gráficos y de atributos en disco, podemos hacerlo de 2 formas: | Al organizar los datos gráficos y de atributos en disco, podemos hacerlo de 2 formas: | ||
- | \\ | + | \\ |
* **Utilizando 2 arrays**: uno con los datos gráficos y otro con los atributos, organizando la información horizontal por scanlines del sprite. Todos los datos gráficos o de atributo de un mismo sprite son consecutivos en memoria y el " | * **Utilizando 2 arrays**: uno con los datos gráficos y otro con los atributos, organizando la información horizontal por scanlines del sprite. Todos los datos gráficos o de atributo de un mismo sprite son consecutivos en memoria y el " | ||
< | < | ||
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: | ||
- | | + | |
</ | </ | ||
- | \\ | + | \\ |
* **Utilizando un único array**: Se intercalan los atributos dentro del array de gráficos, detrás de cada Sprite. La rutina de impresión calculará en el array el inicio del sprite a dibujar y encontrará todos los datos gráficos de dicho sprite seguidos a partir de este punto. Al acabar de trazar los datos gráficos, nos encontramos directamente en el vector con los datos de atributo del sprite que estamos tratando. | * **Utilizando un único array**: Se intercalan los atributos dentro del array de gráficos, detrás de cada Sprite. La rutina de impresión calculará en el array el inicio del sprite a dibujar y encontrará todos los datos gráficos de dicho sprite seguidos a partir de este punto. Al acabar de trazar los datos gráficos, nos encontramos directamente en el vector con los datos de atributo del sprite que estamos tratando. | ||
< | < | ||
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 |
</ | </ | ||
Línea 255: | 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 | + | DB XX, O1, XX, P1, S1_Attr1, S1_Attr2 |
- | DB YY, A2, YY, B2, YY, C2, YY, D2, YY, E2, YY, F2, YY, G2 | + | DB YY, A2, YY, B2, YY, C2, YY, D2, YY, E2, YY, F2, YY, G2 |
- | DB YY, H2, YY, I2, YY, J2, YY, K2, YY, L2, YY, M2, YY, N2 | + | DB YY, H2, YY, I2, YY, J2, YY, K2, YY, L2, YY, M2, YY, N2 |
- | DB YY, O2, YY, P2, 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: | ||
- | | + | |
</ | </ | ||
- | \\ | + | \\ |
Para las rutinas que crearemos como ejemplo utilizaremos el **formato lineal horizontal mediante 2 tablas**, una con los gráficos y otra con los atributos de dichos gráficos. En las rutinas con máscara, intercalaremos los datos de máscara antes de cada dato del sprite, como acabamos de ver. Es el formato más sencillo para la generación de los gráficos y para los cálculos en las rutinas, y por tanto el elegido para mostrar rutinas comprensibles por el lector. | Para las rutinas que crearemos como ejemplo utilizaremos el **formato lineal horizontal mediante 2 tablas**, una con los gráficos y otra con los atributos de dichos gráficos. En las rutinas con máscara, intercalaremos los datos de máscara antes de cada dato del sprite, como acabamos de ver. Es el formato más sencillo para la generación de los gráficos y para los cálculos en las rutinas, y por tanto el elegido para mostrar rutinas comprensibles por el lector. | ||
Línea 281: | Línea 282: | ||
A continuación hablaremos sobre el editor de Sprites SevenuP y veremos de una forma gráfica el formato de organización lineal-horizontal de datos en memoria, y cómo un gráfico de ejemplo se traduce de forma efectiva en un array de datos con el formato deseado. | A continuación hablaremos sobre el editor de Sprites SevenuP y veremos de una forma gráfica el formato de organización lineal-horizontal de datos en memoria, y cómo un gráfico de ejemplo se traduce de forma efectiva en un array de datos con el formato deseado. | ||
- | \\ | + | \\ |
===== Conversion de datos graficos a códigos dibujables ===== | ===== Conversion de datos graficos a códigos dibujables ===== | ||
Línea 292: | Línea 293: | ||
Para el propósito de este capítulo (y, en general durante el proceso de creación de un juego), dibujaremos en SevenuP nuestro spriteset con los sprites distribuídos verticalmente (cada sprite debajo del anterior). Crearemos un nuevo " | Para el propósito de este capítulo (y, en general durante el proceso de creación de un juego), dibujaremos en SevenuP nuestro spriteset con los sprites distribuídos verticalmente (cada sprite debajo del anterior). Crearemos un nuevo " | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
Por ejemplo, para guardar la información de 10 sprites de 16x16 crearíamos un nuevo sprite de 16x160 píxeles. Si nos vemos en la necesidad de ampliar el sprite para alojar más sprites podremos " | Por ejemplo, para guardar la información de 10 sprites de 16x16 crearíamos un nuevo sprite de 16x160 píxeles. Si nos vemos en la necesidad de ampliar el sprite para alojar más sprites podremos " | ||
Línea 321: | 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 346: | Línea 346: | ||
Antes de exportar los datos a ASM, debemos definir las opciones de exportación en //File -> Output Options//: | Antes de exportar los datos a ASM, debemos definir las opciones de exportación en //File -> Output Options//: | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
Este menú permite especificar diferentes opciones de exportación: | Este menú permite especificar diferentes opciones de exportación: | ||
Línea 363: | Línea 363: | ||
| | ||
- | \\ | + | \\ |
* Múltiples sprites en formato vertical sin máscara y sin atributos en 1 array: | * Múltiples sprites en formato vertical sin máscara y sin atributos en 1 array: | ||
* Sort Priorities: X char, Char line, Y char | * Sort Priorities: X char, Char line, Y char | ||
Línea 384: | Línea 384: | ||
* Data Outputted: Primero exportamos Gfx y luego Attr | * Data Outputted: Primero exportamos Gfx y luego Attr | ||
* Mask: Yes, before graphic | * Mask: Yes, before graphic | ||
- | \\ | + | \\ |
Tras establecer las opciones adecuadas para el gráfico en cuestión, seleccionamos //File -> Export Data:// para generar un fichero de texto de extensión .asm con los datos en el formato elegido. | Tras establecer las opciones adecuadas para el gráfico en cuestión, seleccionamos //File -> Export Data:// para generar un fichero de texto de extensión .asm con los datos en el formato elegido. | ||
Línea 390: | Línea 390: | ||
| | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
El spriteset es, pues, de 16x32 píxeles, o lo que es lo mismo, 2 sprites de 2x2 bloques colocados verticalmente. | El spriteset es, pues, de 16x32 píxeles, o lo que es lo mismo, 2 sprites de 2x2 bloques colocados verticalmente. | ||
Línea 412: | Línea 412: | ||
| | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Multiples sprites en formato vertical sin máscara y sin atributos: | **Multiples sprites en formato vertical sin máscara y sin atributos: | ||
Línea 435: | Línea 435: | ||
</ | </ | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Multiples sprites en formato vertical sin máscara y con atributos al final:** | **Multiples sprites en formato vertical sin máscara y con atributos al final:** | ||
Línea 461: | Línea 461: | ||
El array de datos resultante es esencialmente igual al anterior, salvo que se añaden los 8 bytes de atributos (2 sprites de 16x16 = 2x2 bytes por cada sprite = 8 bytes de atributo). | El array de datos resultante es esencialmente igual al anterior, salvo que se añaden los 8 bytes de atributos (2 sprites de 16x16 = 2x2 bytes por cada sprite = 8 bytes de atributo). | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Multiples sprites en formato vertical con máscara y con atributos al final:** | **Multiples sprites en formato vertical con máscara y con atributos al final:** | ||
Línea 495: | Línea 495: | ||
En este export, hemos habilitado el uso de máscaras y el byte de máscara de cada scanline aparece justo antes del byte de datos del mismo. Los atributos permanecen al final del array. | En este export, hemos habilitado el uso de máscaras y el byte de máscara de cada scanline aparece justo antes del byte de datos del mismo. Los atributos permanecen al final del array. | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Multiples sprites en formato vertical separando GFX y ATTR en 2 tablas:** | **Multiples sprites en formato vertical separando GFX y ATTR en 2 tablas:** | ||
Línea 528: | Línea 528: | ||
DEFB 57, 58, 59, 60, 61, 62, 57, 56 | DEFB 57, 58, 59, 60, 61, 62, 57, 56 | ||
</ | </ | ||
- | \\ | + | \\ |
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 | ||
Línea 539: | Línea 539: | ||
| | ||
- | \\ | + | |
+ | \\ | ||
+ | \\ | ||
+ | **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. | + | * **La Pila**: realizando |
- | * **El Stack del Calculador**: | + | * **El Stack del Calculador**: |
* **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 " | * **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 (si usamos posiciones de memoria de direcciones fijas y conocidas), 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. | ||
Línea 566: | Línea 591: | ||
- | \\ | + | \\ |
===== Trazado de Sprites de 8x8 ===== | ===== Trazado de Sprites de 8x8 ===== | ||
Línea 573: | Línea 598: | ||
El sprite a utilizar como " | El sprite a utilizar como " | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
El bitmap con el que se corresponde este sprite y los valores binarios de los scanlines son los siguientes: | El bitmap con el que se corresponde este sprite y los valores binarios de los scanlines son los siguientes: | ||
Línea 602: | Línea 627: | ||
cara_gfx: | cara_gfx: | ||
- | | + | |
cara_attrib: | cara_attrib: | ||
- | | + | |
</ | </ | ||
- | \\ | + | \\ |
==== Sobreescribiendo el fondo (impresión con LD) ==== | ==== Sobreescribiendo el fondo (impresión con LD) ==== | ||
Línea 616: | Línea 641: | ||
Para nuestra rutina utilizaremos el siguiente esquema de paso de parámetros: | Para nuestra rutina utilizaremos el siguiente esquema de paso de parámetros: | ||
- | \\ | + | \\ |
|< 50% >| | |< 50% >| | ||
^ Variable ^ Tamaño (Bytes) ^ Parámetro ^ | ^ Variable ^ Tamaño (Bytes) ^ Parámetro ^ | ||
Línea 624: | Línea 649: | ||
| DS_COORD_Y | 1 | Coordenada Y en baja resolución | | | DS_COORD_Y | 1 | Coordenada Y en baja resolución | | ||
| DS_NUMSPR | 1 | Numero de sprite a dibujar (0-N) | | | DS_NUMSPR | 1 | Numero de sprite a dibujar (0-N) | | ||
- | \\ | + | \\ |
El pseudocódigo de la rutina es el siguiente: | El pseudocódigo de la rutina es el siguiente: | ||
Línea 639: | 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 665: | 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 698: | Línea 723: | ||
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 ; 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. |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | | + | ;;; |
- | 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 = DS_NUMSPR * 8 | + | add hl, hl ; HL = HL * 8 = DS_NUMSPR * 8 |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) |
- | | + | ; HL contiene la direccion de inicio en el sprite |
- | 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. | + | |
- | LD A, H | + | ld a, h |
- | SUB 8 ; Recuperamos la posicion de memoria del | + | |
- | LD B, A ; scanline inicial donde empezamos a dibujar | + | ld b, a ; scanline inicial donde empezamos a dibujar |
- | LD C, L ; BC = HL - 8 | + | 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 802: | 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 a, (de) ; Scanline 3 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 4 | + | ld a, (de) ; Scanline 4 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 5 | + | ld a, (de) ; Scanline 5 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 6 | + | ld a, (de) ; Scanline 6 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | INC H | + | inc h |
- | LD A, (DE) ; Scanline 7 | + | ld a, (de) ; Scanline 7 |
- | LD (HL), A | + | |
- | INC DE | + | inc de |
- | | + | ;;;inc h ; no es necesario el ultimo |
</ | </ | ||
- | | + | |
- | Al no ser necesario el **INC H**, en la versión desenrollada del bucle tenemos que cambiar la resta de HL - 8 por HL - 7: | + | Al no ser necesario el '' |
<code z80> | <code z80> | ||
- | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | + | |
- | LD A, H | + | ld a, h |
- | SUB 7 ; Recuperamos la posicion de memoria del | + | |
- | LD B, A ; scanline inicial donde empezamos a dibujar | + | ld b, a ; scanline inicial donde empezamos a dibujar |
- | LD C, L ; BC = HL - 7 | + | 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. | ||
Línea 863: | Línea 901: | ||
<code z80> | <code z80> | ||
; Ejemplo impresion sprites 8x8 | ; Ejemplo impresion sprites 8x8 | ||
- | ORG 35000 | + | |
- | CALL ClearScreen_Pattern | + | call ClearScreen_Pattern |
- | | + | |
- | ; Los 2 primeros se pueden establecer una unica vez | + | ; Los 2 primeros se pueden establecer una unica vez |
- | LD HL, cara_gfx | + | ld hl, cara_gfx |
- | | + | |
- | LD HL, cara_attrib | + | ld hl, cara_attrib |
- | | + | |
- | LD A, 15 | + | ld a, 15 |
- | | + | |
- | LD A, 8 | + | ld a, 8 |
- | | + | |
- | XOR A | + | xor a |
- | | + | |
- | CALL DrawSprite_8x8_LD | + | call DrawSprite_8x8_LD |
loop: | loop: | ||
- | JR loop | + | jr loop |
- | RET | + | ret |
; Variables que usaremos como parámetros | ; Variables que usaremos como parámetros | ||
Línea 892: | Línea 930: | ||
DS_COORD_Y | DS_COORD_Y | ||
DS_NUMSPR | DS_NUMSPR | ||
- | |||
; | ; | ||
Línea 899: | 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 938: | Línea 974: | ||
cara_gfx: | cara_gfx: | ||
- | | + | |
cara_attrib: | cara_attrib: | ||
- | | + | |
; | ; | ||
DrawSprite_8x8_LD: | DrawSprite_8x8_LD: | ||
- | | + | |
- | END 35000 | + | |
</ | </ | ||
El resultado de la ejecución del anterior programa es el siguiente: | El resultado de la ejecución del anterior programa es el siguiente: | ||
- | \\ | + | \\ |
- | {{ : | + | {{ : |
- | \\ | + | \\ |
Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, | Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, | ||
Línea 966: | Línea 1002: | ||
</ | </ | ||
- | Así, podríamos establecer la coordenada X e Y con **POKE 65003, x** y **POKE 65004, y** y después hacer el randomize USR a nuestra rutina de impresión del Sprite ya cargada en memoria. | + | Así, podríamos establecer la coordenada X e Y con '' |
En nuestro caso, como el esqueleto del programa está en ensamblador, | 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 982: | 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 995: | 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 ('' |
- | | + | |
- | \\ | + | \\ |
==== Respetando el fondo (impresión con OR) ==== | ==== Respetando el fondo (impresión con OR) ==== | ||
| | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
- | | + | |
< | < | ||
Línea 1024: | 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 1039: | 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 1059: | Línea 1095: | ||
<code z80> | <code z80> | ||
- | LD B, 8 ; 8 scanlines -> 8 iteraciones | + | ld b, 8 ; 8 scanlines -> 8 iteraciones |
drawsp8x8_loop_or: | 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_or | + | |
</ | </ | ||
- | A continuación, | + | A continuación, |
<code z80> | <code z80> | ||
Línea 1087: | Línea 1123: | ||
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. |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | | + | ;;; |
- | 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 = DS_NUMSPR * 8 | + | add hl, hl ; HL = HL * 8 = DS_NUMSPR * 8 |
- | ADD HL, BC ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) |
- | | + | ; HL contiene la direccion de inicio en el sprite |
- | 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_loop_or: | drawsp8x8_loop_or: | ||
- | LD A, (DE) | + | ld a, (de) |
- | OR (HL) ; NUEVO: Hacemos un OR del scanline con el fondo | + | |
- | LD (HL), A ; Establecemos el valor en videomemoria | + | |
- | INC DE ; Incrementamos puntero en sprite | + | inc de ; Incrementamos puntero en sprite |
- | INC H | + | inc h |
- | DJNZ drawsp8x8_loop_or | + | |
- | ;;; En este punto, los 8 scanlines del sprite estan dibujados. | + | |
- | LD A, H | + | ld a, h |
- | SUB 8 ; Recuperamos la posicion de memoria del | + | |
- | LD B, A ; scanline inicial donde empezamos a dibujar | + | ld b, a ; scanline inicial donde empezamos a dibujar |
- | LD C, L ; BC = HL - 8 | + | 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 | + | |
</ | </ | ||
Línea 1170: | Línea 1206: | ||
| | ||
- | \\ | + | \\ |
- | {{ : | + | {{ : |
- | \\ | + | \\ |
- | | + | |
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
- | | + | |
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 1186: | Línea 1222: | ||
La respuesta es: mediante **máscaras**. | La respuesta es: mediante **máscaras**. | ||
- | \\ | + | \\ |
==== Impresión 8x8 usándo máscaras ==== | ==== Impresión 8x8 usándo máscaras ==== | ||
- | 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 1195: | 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, | ||
- | | + | |
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
- | 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 1213: | Línea 1249: | ||
La siguiente imagen ilustra lo que acabamos de comentar: | La siguiente imagen ilustra lo que acabamos de comentar: | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
Así pues, con la máscara podemos conseguir 2 cosas: | Así pues, con la máscara podemos conseguir 2 cosas: | ||
- | \\ | + | \\ |
* Evitar transparencias en el interior de nuestro Sprite: bastará con que la máscara sea una copia del contorno del sprite, pero " | * Evitar transparencias en el interior de nuestro Sprite: bastará con que la máscara sea una copia del contorno del sprite, pero " | ||
* Conseguir un contorno de pixeles "a cero" alrededor de nuestro Sprite, un borde que lo haga visualmente más identificable y no mezcle sus píxeles con los de la pantalla. Para eso, basta con que el contorno del sprite en la máscara sea ligeramente más grande que el del sprite. | * Conseguir un contorno de pixeles "a cero" alrededor de nuestro Sprite, un borde que lo haga visualmente más identificable y no mezcle sus píxeles con los de la pantalla. Para eso, basta con que el contorno del sprite en la máscara sea ligeramente más grande que el del sprite. | ||
- | \\ | + | \\ |
Por contra, el uso de máscaras tiene también desventajas: | Por contra, el uso de máscaras tiene también desventajas: | ||
- | \\ | + | \\ |
* 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). | ||
- | \\ | + | \\ |
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 1270: | Línea 1306: | ||
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 | + | 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. |
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | | + | ;;; |
- | 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 |
- | ADD HL, HL | + | add hl, hl ; HL = HL * 2 |
- | ADD HL, HL | + | add hl, hl ; HL = HL * 4 |
- | ADD HL, HL | + | add hl, hl ; HL = HL * 8 |
- | ADD HL, HL | + | add hl, hl ; HL = HL * 16 = DS_NUMSPR * 16 |
- | ADD HL, BC | + | add hl, bc ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) |
- | | + | |
- | EX DE, HL ; Intercambiamos DE y HL para el OR | + | ex de, hl |
- | ;;; 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 | + | ld a, h |
- | SUB 8 ; Recuperamos la posicion de memoria del | + | |
- | LD B, A ; scanline inicial donde empezamos a dibujar | + | ld b, a ; scanline inicial donde empezamos a dibujar |
- | LD C, L ; BC = HL - 8 | + | 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 | + | |
</ | </ | ||
Línea 1366: | 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 1384: | 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 1404: | Línea 1440: | ||
En el módo de visualización de máscara, podemos generar la máscara de nuestro sprite produciendo una versión invertida del mismo. Para ayudarnos en esa tarea, SevenuP nos muestra con diferentes colores el estado de los píxeles del sprite, e incluso tiene una opción de "// | En el módo de visualización de máscara, podemos generar la máscara de nuestro sprite produciendo una versión invertida del mismo. Para ayudarnos en esa tarea, SevenuP nos muestra con diferentes colores el estado de los píxeles del sprite, e incluso tiene una opción de "// | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
;#; | ;#; | ||
//La máscara de nuestro pequeño Sprite en SevenuP// | //La máscara de nuestro pequeño Sprite en SevenuP// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
En la anterior imagen, tenemos en " | En la anterior imagen, tenemos en " | ||
Línea 1424: | 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 | + | |
</ | </ | ||
| | ||
- | \\ | + | \\ |
- | {{ : | + | {{ : |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
| | ||
- | 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 ==== | ==== Impresión 16x16 con Transferencia por LD ==== | ||
Línea 1457: | Línea 1493: | ||
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: | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
+ | |||
+ | * 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. | ||
+ | |||
+ | * 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). | ||
- | * 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), | + | * 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. |
- | * 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. | + | |
- | * 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). | + | |
- | | + | |
Línea 1484: | 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 1494: | 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 1529: | Línea 1571: | ||
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 | + | 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 |
- | 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 |
- | EX DE, HL ; Intercambiamos DE y HL (DE=origen, HL=destino) | + | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) |
- | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | + | |
- | LD B, 8 | + | 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 | + | 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: | ||
- | ;;; 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 1685: | 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. En el caso de las rutinas de 8x8, resultaba más rápido realizar la multiplicación por medio de sumas que por desplazamientos, | + | 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 1715: | 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 ; 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 | + | 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 1761: | 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 1773: | 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: | ||
- | \\ | + | \\ |
- | {{ : | + | {{ : |
- | \\ | + | \\ |
| | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
- | \\ | + | \\ |
==== Impresión 16x16 usándo 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 1816: | 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 a la versión con transferencias de datos LD. | + | El resto de la rutina es esencialmente idéntico a la versión con transferencias de datos '' |
- | \\ | + | \\ |
- | \\ | + | \\ |
==== Impresión 16x16 usándo máscaras ==== | ==== Impresión 16x16 usándo máscaras ==== | ||
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), |
- | \\ | + | \\ |
El código quedaría de la siguiente forma: | El código quedaría de la siguiente forma: | ||
Línea 1854: | Línea 1896: | ||
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 ; 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, H | + | ld a, h |
- | AND 7 | + | |
- | JR NZ, drawsp16m_nofix_abajop | + | jr nz, drawsp16m_nofix_abajop |
- | LD A, L | + | ld a, l |
- | ADD A, 32 | + | add a, 32 |
- | LD L, A | + | ld l, a |
- | JR C, drawsp16m_nofix_abajop | + | jr c, drawsp16m_nofix_abajop |
- | LD A, H | + | ld a, h |
- | SUB 8 | + | |
- | LD H, A | + | 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 2031: | 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 | + | |
</ | </ | ||
La siguiente captura muestra la impresión de uno de los sprites de 2x2 bloques del tileset usando su correspondiente máscara sobre nuestro fondo de píxeles alternos: | La siguiente captura muestra la impresión de uno de los sprites de 2x2 bloques del tileset usando su correspondiente máscara sobre nuestro fondo de píxeles alternos: | ||
- | \\ | + | \\ |
- | {{ : | + | {{ : |
- | \\ | + | \\ |
En ella, resulta evidente el problema del **colour clash** o **colisión de atributos**. | En ella, resulta evidente el problema del **colour clash** o **colisión de atributos**. | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
Por desgracia, debido a las características de color en baja resolución del Spectrum, el uso de máscaras con sprites multicolor sobre fondos con patrones provoca este tipo de resultados. Los sprites multicolor con máscara deben utilizarse en otras circunstancias / formatos de juego. | Por desgracia, debido a las características de color en baja resolución del Spectrum, el uso de máscaras con sprites multicolor sobre fondos con patrones provoca este tipo de resultados. Los sprites multicolor con máscara deben utilizarse en otras circunstancias / formatos de juego. | ||
Línea 2068: | Línea 2110: | ||
Por ejemplo, si el juego fuera monocolor y no imprimieramos los atributos del sprite, el resultado sería mucho más agradable a la vista: | Por ejemplo, si el juego fuera monocolor y no imprimieramos los atributos del sprite, el resultado sería mucho más agradable a la vista: | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
- | \\ | + | \\ |
El resultado en esta ocasión es mucho mejor, aunque este sprite sigue necesitando claramente un reborde generado vía máscara para una impresión mucho más agradable a la vista, aún en un fondo tan " | El resultado en esta ocasión es mucho mejor, aunque este sprite sigue necesitando claramente un reborde generado vía máscara para una impresión mucho más agradable a la vista, aún en un fondo tan " | ||
Línea 2076: | Línea 2118: | ||
Y es que, como vamos a ver a continuación, | Y es que, como vamos a ver a continuación, | ||
- | \\ | + | \\ |
===== Técnicas de impresión adecuadas a cada tipo de juego ===== | ===== Técnicas de impresión adecuadas a cada tipo de juego ===== | ||
Hemos visto en este capítulo 3 técnicas diferentes de impresión de Sprites, las cuales nos dan 4 posibilidades: | Hemos visto en este capítulo 3 técnicas diferentes de impresión de Sprites, las cuales nos dan 4 posibilidades: | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
- | **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 " | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
;#; | ;#; | ||
//Tetris, Plotting, Maziacs y Boulder Dash// | //Tetris, Plotting, Maziacs y Boulder Dash// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
En estos juegos no suele ser necesario tener transparecia con el fondo (al ser plano) y no coinciden 2 sprites en la misma cuadrícula (si lo hacen, es, por ejemplo, para recoger un objeto). | En estos juegos no suele ser necesario tener transparecia con el fondo (al ser plano) y no coinciden 2 sprites en la misma cuadrícula (si lo hacen, es, por ejemplo, para recoger un objeto). | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**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). | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
;#; | ;#; | ||
//Manic Miner, Dizzy, Dynamite Dan y Fred// | //Manic Miner, Dizzy, Dynamite Dan y Fred// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
- | \\ | + | \\ |
**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 '' |
- | \\ | + | \\ |
- | \\ | + | \\ |
**Impresión con máscaras: | **Impresión con máscaras: | ||
Línea 2125: | Línea 2167: | ||
Una segunda opción es que el fondo sea multicolor y nuestro personaje se imprima sin atributos, adaptandose el personaje al color del fondo sin cambiar éste. En la captura de abajo tenemos al personaje principal de //Target: Renegade// adoptando los atributos de las posiciones de pantalla donde es dibujado. | Una segunda opción es que el fondo sea multicolor y nuestro personaje se imprima sin atributos, adaptandose el personaje al color del fondo sin cambiar éste. En la captura de abajo tenemos al personaje principal de //Target: Renegade// adoptando los atributos de las posiciones de pantalla donde es dibujado. | ||
- | \\ | + | \\ |
{{ : | {{ : | ||
;#; | ;#; | ||
//H.A.T.E., Arkanoid, Rick Dangerous y Target: Renegade// | //H.A.T.E., Arkanoid, Rick Dangerous y Target: Renegade// | ||
;#; | ;#; | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
Así pues, a la hora de realizar un juego, deberemos elegir la técnica más adecuada al tipo de juego que vamos a programar. | Así pues, a la hora de realizar un juego, deberemos elegir la técnica más adecuada al tipo de juego que vamos a programar. | ||
- | \\ | + | \\ |
===== Rutina genérica trazado de Sprites multicarácter ===== | ===== Rutina genérica trazado de Sprites multicarácter ===== | ||
Línea 2148: | 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 2169: | Línea 2211: | ||
DrawSprite_MxN_LD: | DrawSprite_MxN_LD: | ||
- | ;;; Calcular posicion origen (array sprites) en HL como: | + | |
- | | + | ;;; |
- | ;;;; Multiplicamos ancho por alto (en bloques) | + | |
- | LD A, (DS_WIDTH) | + | ld a, (DS_WIDTH) |
- | LD C, A | + | ld c, a |
- | LD A, (DS_HEIGHT) | + | 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 | + | ld b, a |
- | XOR A ; A = 0 | + | 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 HL, 0 | + | ld hl, 0 |
- | LD D, H ; HL = 0 | + | ld d, h ; HL = 0 |
- | LD A, (DS_NUMSPR) | + | ld a, (DS_NUMSPR) |
- | LD E, A ; DE = DS_NUMSPR | + | ld e, a ; DE = DS_NUMSPR |
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) | + | ld bc, (DS_COORD_X) |
- | 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 | + | |
- | EX DE, HL ; Intercambiamos DE y HL (DE=origen, HL=destino) | + | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) |
- | ;;; Bucle de impresión vertical | + | |
- | ; Recogemos de nuevo la altura en pixeles | + | ; Recogemos de nuevo la altura en pixeles |
- | LD A, (drawsp_height) | + | ld a, (drawsp_height) |
- | LD B, A ; Contador del bucle exterior del bucle | + | 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 A, | + | |
- | OR A ; para comprobar si es 0 | + | |
- | 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 |
- | PUSH DE ; Guardamos una copia | + | 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) | + | ld hl, (drawsp_width_by_height) |
- | ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR | + | |
- | | + | ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> |
- | SRL H ; Desplazamos H a la derecha | + | srl h ; Desplazamos H a la derecha |
- | RR L ; Rotamos L a la derecha introduciendo CF | + | rr l ; Rotamos L a la derecha introduciendo CF |
- | SRL H ; | + | srl h ; |
- | RR L ; | + | rr l ; |
- | SRL H ; | + | srl h ; |
- | RR L ; Resultado : HL = HL >> 3 = HL / 8 | + | rr l ; Resultado : HL = HL >> 3 = HL / 8 |
- | ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR | + | |
- | LD C, L | + | ld c, l |
- | LD B, H | + | ld b, h |
- | LD HL, (DS_ATTRIBS) | + | ld hl, (DS_ATTRIBS) |
- | ADD HL, BC ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) | + | add hl, bc ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO) |
- | POP DE ; Recuperamos direccion destino | + | 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 2349: | Línea 2391: | ||
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | 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 |
- | | + | |
- | LD A, 2 | + | ld a, 2 |
- | | + | |
- | LD A, 2 | + | ld a, 2 |
- | | + | |
- | XOR A | + | xor a |
- | | + | |
- | CALL DrawSprite_MxN_LD | + | call DrawSprite_MxN_LD |
- | RET | + | ret |
; Variables que usaremos como parámetros | ; Variables que usaremos como parámetros | ||
Línea 2379: | Línea 2421: | ||
</ | </ | ||
- | 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). | ||
- | \\ | + | \\ |
===== Borrado de Sprites y Restauración del fondo ===== | ===== Borrado de Sprites y Restauración del fondo ===== | ||
Línea 2391: | Línea 2433: | ||
Hay diferentes técnicas de borrado de sprites, en las que profundizaremos cuando tratemos las // | Hay diferentes técnicas de borrado de sprites, en las que profundizaremos cuando tratemos las // | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Borrado de sprite por sobreimpresión: | **Borrado de sprite por sobreimpresión: | ||
Línea 2399: | Línea 2441: | ||
En juegos de fondo monocolor (típicamente fondo negro) se suele reservar el bloque 0 del SpriteSet para alojar un " | En juegos de fondo monocolor (típicamente fondo negro) se suele reservar el bloque 0 del SpriteSet para alojar un " | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Borrado de sprite por borrado de atributos: | **Borrado de sprite por borrado de atributos: | ||
Línea 2418: | Línea 2460: | ||
- 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 | ||
Línea 2444: | Línea 2486: | ||
</ | </ | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**Borrado (e impresión) con XOR** | **Borrado (e impresión) con XOR** | ||
- | | + | |
- | | + | |
|< 30% >| | |< 30% >| | ||
Línea 2459: | Línea 2501: | ||
| 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 '' |
< | < | ||
Línea 2476: | Línea 2518: | ||
</ | </ | ||
- | La impresión de un sprite con XOR produce una especie de " | + | La impresión de un sprite con '' |
- | \\ | + | \\ |
- | \\ | + | \\ |
**Almacenamiento en buffer temporal** | **Almacenamiento en buffer temporal** | ||
Línea 2507: | Línea 2549: | ||
; | ; | ||
- | ;;; 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) |
- | | + | |
- | 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 | + | |
- | (...) | + | |
- | | + | ;;; Impresion de atributos |
- | LD A, (DE) ; NUEVO: Leemos el atributo actual | + | 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 a, (hl) ; Ya podemos imprimir el atributo |
- | LD (DE), A | + | |
- | RET | + | ret |
DS_TEMPBUF | DS_TEMPBUF | ||
</ | </ | ||
- | 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 2549: | Línea 2591: | ||
No olvidemos que las operaciones de borrado y reimpresión deben de ser lo más rápidas posibles para evitar parpadeos debidos a que el ojo humano pueda ver el momento del borrado si la ULA refresca la pantalla tras el " | No olvidemos que las operaciones de borrado y reimpresión deben de ser lo más rápidas posibles para evitar parpadeos debidos a que el ojo humano pueda ver el momento del borrado si la ULA refresca la pantalla tras el " | ||
- | \\ | + | \\ |
===== Optimizar las rutinas ===== | ===== Optimizar las rutinas ===== | ||
- | \\ | + | \\ |
**Adaptación de las rutinas al juego** | **Adaptación de las rutinas al juego** | ||
Línea 2560: | Línea 2602: | ||
- | \\ | + | \\ |
**Adaptación de los parámetros de entrada** | **Adaptación de los parámetros de entrada** | ||
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 2574: | Línea 2616: | ||
<code z80> | <code z80> | ||
- | LD BC, Tabla_Sprites | + | ld bc, Tabla_Sprites |
</ | </ | ||
- | | + | |
- | \\ | + | \\ |
**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 |
| | ||
- | \\ | + | \\ |
- | \\ | + | \\ |
**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 2617: | 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> | ||
Línea 2637: | Línea 2678: | ||
DrawSprite_16x16_LD_STACK_no_attr: | DrawSprite_16x16_LD_STACK_no_attr: | ||
- | ;;; Deshabilitar interrupciones (vamos a cambiar SP). | + | |
- | DI | + | di |
- | ; 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 |
;;; 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 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 |
- | ;;; Establecemos el valor de SP apuntando al sprite | + | |
- | LD SP, HL | + | ld sp, hl |
- | EX DE, HL ; Intercambiamos DE y HL (DE=origen, HL=destino) | + | ex de, hl ; Intercambiamos DE y HL (DE=origen, HL=destino) |
- | ;;; Repetir 8 veces (primeros 2 bloques horizontales): | + | |
- | LD B, 16 | + | 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 hl, (DS_TEMP_SP) |
- | LD SP, HL | + | ld sp, hl |
- | | + | ei ; Habilitar de nuevo interrupciones |
- | RET | + | 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 // | + | 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> | <code z80> | ||
drawsp16x16_stack_loop1: | drawsp16x16_stack_loop1: | ||
- | POP DE ; Recuperamos 2 bytes del sprite | + | 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 | + | inc l ; de la parte superior del Sprite |
- | LD (HL), D | + | |
- | DEC L | + | dec l |
- | INC H | + | 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 | + | 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: | ||
- | LD B,8 | + | ld b,8 |
drawsp16x16_stack_loop2: | drawsp16x16_stack_loop2: | ||
- | POP DE ; Recuperamos 2 bytes del sprite | + | 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 | + | inc l ; de la parte inferior del Sprite |
- | LD (HL), D | + | |
- | DEC L | + | dec l |
- | INC H | + | 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. | ||
+ | |||
+ | \\ | ||
===== Ficheros ===== | ===== Ficheros ===== | ||
Línea 2767: | Línea 2862: | ||
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
- | \\ | + | \\ |
===== Enlaces ===== | ===== Enlaces ===== | ||