cursos:ensamblador:gfx3_sprites_lowres

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:gfx3_sprites_lowres [06-01-2024 16:35] – [Impresión 16x16 usándo máscaras] sromerocursos: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, analizaremos una rutina más genérica que permita imprimir sprites de tamaños múltiples del anterior (16x16, 24x16, etc).  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, analizaremos una rutina más genérica que permita imprimir sprites de tamaños múltiples del anterior (16x16, 24x16, etc).
 +
 + Imprimir en posiciones exactas de carácter (de 0 a 31 para la coordenada X, y de 0 a 23 para la coordenada Y), coincidiendo con las posiciones posibles de la rejilla de 32x24 en pantalla es lo que se conoce como "gráficos en baja resolución". Los gráficos, aunque sean muy detallados, se mueven "carácter a carácter".
  
  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:
  Estudiaremos también los métodos de impresión sobre pantalla: transferencia directa, operaciones lógicas y uso de máscaras, y la generación de los datos gráficos y de atributos a partir de la imagen formada en un editor de sprites.  Estudiaremos también los métodos de impresión sobre pantalla: transferencia directa, operaciones lógicas y uso de máscaras, y la generación de los datos gráficos y de atributos a partir de la imagen formada en un editor de sprites.
  
- +\\ 
-\\+
 ===== Teoría sobre el trazado de sprites ===== ===== Teoría sobre el trazado de sprites =====
  
  Comencemos con las definiciones básicas de la terminología que se usará en este capítulo.  Comencemos con las definiciones básicas de la terminología que se usará en este capítulo.
  
-\\+\\ 
  **Sprite**: Se utiliza el término anglosajón //sprite// (traducido del inglés: "duendecillo") para designar en un juego o programa a cualquier gráfico que tenga movimiento. En el caso del Spectrum, que no tiene como otros sistemas hardware dedicado a la impresión de Sprites, aplicamos el término a cualquier **mapa de bits** (del inglés //bitmap//) que podamos utilizar en nuestros programas: el gráfico del personaje protagonista, los de los enemigos, los gráficos de cualquier item o incluso las propias fuentes de texto de los marcadores.  **Sprite**: Se utiliza el término anglosajón //sprite// (traducido del inglés: "duendecillo") para designar en un juego o programa a cualquier gráfico que tenga movimiento. En el caso del Spectrum, que no tiene como otros sistemas hardware dedicado a la impresión de Sprites, aplicamos el término a cualquier **mapa de bits** (del inglés //bitmap//) que podamos utilizar en nuestros programas: el gráfico del personaje protagonista, los de los enemigos, los gráficos de cualquier item o incluso las propias fuentes de texto de los marcadores.
  
-\\+\\ 
 {{ cursos:ensamblador:gfx3_sprite.png | Sprite de Spectrum, 16x16 }} {{ cursos:ensamblador:gfx3_sprite.png | Sprite de Spectrum, 16x16 }}
-\\+\\ 
  
-\\+\\ 
  **Editor de Sprites**: Los sprites se diseñan en editores de sprites, que son aplicaciones diseñadas para crear Sprites teniendo en cuenta la máquina destino para la que se crean. Por ejemplo, un editor de Sprites para Spectrum tomará en cuenta, a la hora de aplicar colores, el sistema de tinta/papel en bloques de 8x8 píxeles y no nos permitirá dibujar colores saltándonos dicha limitación que después existirá de forma efectiva en el hardware destino.  **Editor de Sprites**: Los sprites se diseñan en editores de sprites, que son aplicaciones diseñadas para crear Sprites teniendo en cuenta la máquina destino para la que se crean. Por ejemplo, un editor de Sprites para Spectrum tomará en cuenta, a la hora de aplicar colores, el sistema de tinta/papel en bloques de 8x8 píxeles y no nos permitirá dibujar colores saltándonos dicha limitación que después existirá de forma efectiva en el hardware destino.
  
-\\+\\ 
 {{ cursos:ensamblador:gfx3_sevenup.png | Editor de Sprites SevenuP }} {{ cursos:ensamblador:gfx3_sevenup.png | Editor de Sprites SevenuP }}
-\\+\\ 
  
  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:ensamblador:gfx3_sprite_bitmap.png | Sprite de Spectrum convertido a bitmap y a ristra de bits }} {{ cursos:ensamblador:gfx3_sprite_bitmap.png | Sprite de Spectrum convertido a bitmap y a ristra de bits }}
 ;#; ;#;
 //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.//
 ;#; ;#;
-\\+\\ 
  
-\\+\\ 
  **Rutinas de impresión de Sprites**: son rutinas que reciben como parámetro la dirección en memoria del Sprite y sus Atributos y las coordenadas (x,y) destino, y vuelcan esta información gráfica en la pantalla.  **Rutinas de impresión de Sprites**: son rutinas que reciben como parámetro la dirección en memoria del Sprite y sus Atributos y las coordenadas (x,y) destino, y vuelcan esta información gráfica en la pantalla.
  
-\\+\\ 
  **Sprite Set**: Normalmente todos los Sprites de un juego se agrupan en un "**sprite set**" (o "**tile set**"), que es una imagen rectangular o un array lineal que almacena todos los datos gráficos del juego de forma que la rutina de impresión de Sprites pueda volcar uno de ellos mediante unas coordenadas origen y un ancho y alto (caso del tileset rectangular) o mediante un identificador dentro del array de sprites (caso del tileset lineal).  **Sprite Set**: Normalmente todos los Sprites de un juego se agrupan en un "**sprite set**" (o "**tile set**"), que es una imagen rectangular o un array lineal que almacena todos los datos gráficos del juego de forma que la rutina de impresión de Sprites pueda volcar uno de ellos mediante unas coordenadas origen y un ancho y alto (caso del tileset rectangular) o mediante un identificador dentro del array de sprites (caso del tileset lineal).
  
  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, además de la posición (x,y) de destino, una posición (x,y) de origen y un ancho y alto para "extraer" cada sprite de su "pantalla origen" y volcarlo a la pantalla destino.  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, además de la posición (x,y) de destino, una posición (x,y) de origen y un ancho y alto para "extraer" cada sprite de su "pantalla origen" y volcarlo a la pantalla destino.
  
-\\+\\ 
 {{ cursos:ensamblador:pacman_SimonOwen.png | Sprite set de Pacman (coloreado por Simon Owen) }} {{ cursos:ensamblador:pacman_SimonOwen.png | Sprite set de Pacman (coloreado por Simon Owen) }}
 ;#; ;#;
-//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,yorg).//+//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,yorg).//
 ;#; ;#;
-\\+\\ 
  
  En el caso del Spectrum, nos interesa mucho más el sistema de almacenamiento lineal dentro de un "vector" de datos, ya que normalmente agruparemos todos los sprites de un mismo tamaño en un mismo array. Podremos disponer de diferentes arrays para elementos de diferentes tamaños. Cuando queramos hacer referencia a uno de los sprites de dicho array, lo haremos con un identificador numérico (0-N) que indicará el número de sprite que queremos dibujar comenzando desde arriba y designando al primero como 0.  En el caso del Spectrum, nos interesa mucho más el sistema de almacenamiento lineal dentro de un "vector" de datos, ya que normalmente agruparemos todos los sprites de un mismo tamaño en un mismo array. Podremos disponer de diferentes arrays para elementos de diferentes tamaños. Cuando queramos hacer referencia a uno de los sprites de dicho array, lo haremos con un identificador numérico (0-N) que indicará el número de sprite que queremos dibujar comenzando desde arriba y designando al primero como 0.
  
-\\+\\ 
 {{ cursos:ensamblador:gfx3_tilesetsk.png | Sprite set de Sokoban }} {{ cursos:ensamblador:gfx3_tilesetsk.png | Sprite set de Sokoban }}
 ;#; ;#;
-//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\\  lineal vertical. Las rutinas de impresión\\  requieren un identificador de sprite (0-NumSprites).//
 ;#; ;#;
-\\+\\ 
  
  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 "array" para los sprites, otro para los fondos, y otro para las fuentes de texto. Durante el desarrollo del bucle del programa llamaremos a la rutina de impresión de sprites pasando como parámetro el array de sprites, el ancho y alto del sprite, y el identificador del sprite que queremos dibujar.  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 "array" para los sprites, otro para los fondos, y otro para las fuentes de texto. Durante el desarrollo del bucle del programa llamaremos a la rutina de impresión de sprites pasando como parámetro el array de sprites, el ancho y alto del sprite, y el identificador del sprite que queremos dibujar.
  
-\\ +\\  
- **Frame** (fotograma): El "sprite set" no sólo suele alojar los diferentes gráficos de cada personaje o enemigo de un juego, sino que además se suelen alojar todos los //frames// (fotogramas) de animación de cada personaje. En sistemas más modernos se suele tener un //frameset// (un array de frames) por cada personaje, y cada objeto del juego tiene asociado su frameset y su estado actual de animación y es capaz de dibujar el frame que le corresponde.\\ En el Spectrum, por limitaciones de memoria y código, lo normal es tener todo en un mismo spriteset, y tener almacenados los identificadores de animación de un personaje en lugar de su frameset. Así, sabremos que nuestro personaje andando hacia la derecha tiene una animación que consta de los frames (por ejemplo) 10, 11 y 12 dentro del spriteset.+ **Frame** (fotograma): El "sprite set" no sólo suele alojar los diferentes gráficos de cada personaje o enemigo de un juego, sino que además se suelen alojar todos los //frames// (fotogramas) de animación de cada personaje. En sistemas más modernos se suele tener un //frameset// (un array de frames) por cada personaje, y cada objeto del juego tiene asociado su frameset y su estado actual de animación y es capaz de dibujar el frame que le corresponde.\\  En el Spectrum, por limitaciones de memoria y código, lo normal es tener todo en un mismo spriteset, y tener almacenados los identificadores de animación de un personaje en lugar de su frameset. Así, sabremos que nuestro personaje andando hacia la derecha tiene una animación que consta de los frames (por ejemplo) 10, 11 y 12 dentro del spriteset.
  
-\\+\\ 
  **Tiles**: Algunos bitmaps, en lugar de ser llamados "sprites", reciben el nombre de //tiles// ("bloques"). Normalmente esto sucede con bitmaps que no van a tener movimiento, que se dibujan en posiciones exáctas de carácter, y/o que no tienen transparencia. Un ejemplo de tiles son los "bloques" que forman los escenarios y fondos de las pantallas cuando son utilizados para componer un mapa de juego en base a la repetición de los mismos. Los tiles pueden ser impresos con las mismas rutinas de impresión de Sprites (puesto que son bitmaps), aunque normalmente se diseñan rutinas específicas para trazar este tipo de bitmaps aprovechando sus características (no móviles, posición de carácter, no transparencia), con lo que dichas rutinas se pueden optimizar. Como veremos en el próximo capítulo, los tiles se utilizan normalmente para componer el área de juego mediante un **tilemap** (mapa de tiles):  **Tiles**: Algunos bitmaps, en lugar de ser llamados "sprites", reciben el nombre de //tiles// ("bloques"). Normalmente esto sucede con bitmaps que no van a tener movimiento, que se dibujan en posiciones exáctas de carácter, y/o que no tienen transparencia. Un ejemplo de tiles son los "bloques" que forman los escenarios y fondos de las pantallas cuando son utilizados para componer un mapa de juego en base a la repetición de los mismos. Los tiles pueden ser impresos con las mismas rutinas de impresión de Sprites (puesto que son bitmaps), aunque normalmente se diseñan rutinas específicas para trazar este tipo de bitmaps aprovechando sus características (no móviles, posición de carácter, no transparencia), con lo que dichas rutinas se pueden optimizar. Como veremos en el próximo capítulo, los tiles se utilizan normalmente para componer el área de juego mediante un **tilemap** (mapa de tiles):
  
-\\+\\ 
 {{ cursos:ensamblador:gfx3_tilemap.png | Tilemaps }} {{ cursos:ensamblador:gfx3_tilemap.png | Tilemaps }}
 ;#; ;#;
-//Tilemap: componiendo un mapa en pantalla\\ a partir de tiles de un tileset/spriteset + mapa.//+//Tilemap: componiendo un mapa en pantalla\\  a partir de tiles de un tileset/spriteset + mapa.//
 ;#; ;#;
-\\+\\ 
  
-\\+\\ 
  **Máscaras de Sprites**: Finalmente, cabe hablar de las //máscaras de sprites//, que son bitmaps que contienen un contorno del sprite de forma que se define qué parte del Sprite original debe sobreescribir el fondo y qué parte del mismo debe de ser transparente.  **Máscaras de Sprites**: Finalmente, cabe hablar de las //máscaras de sprites//, que son bitmaps que contienen un contorno del sprite de forma que se define qué parte del Sprite original debe sobreescribir el fondo y qué parte del mismo debe de ser transparente.
  
-\\+\\ 
 {{ cursos:ensamblador:gfx3_masks.png | Sprite, Máscara y aplicación sobre el fondo }} {{ cursos:ensamblador:gfx3_masks.png | Sprite, Máscara y aplicación sobre el fondo }}
 ;#; ;#;
 //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: sin ellas habría que testear el estado de cada bit para saber si hay que "dibujar" ese pixel del sprite o no. Gracias a la máscara, basta un AND entre la máscara y el fondo y un OR del sprite para dibujar de una sóla vez 8 píxeles sin realizar testeos individuales de bits.  Las máscaras son necesarias para saber qué partes del sprite son transparentes: sin ellas habría que testear el estado de cada bit para saber si hay que "dibujar" ese pixel del sprite o no. Gracias a la máscara, basta un AND entre la máscara y el fondo y un OR del sprite para dibujar de una sóla vez 8 píxeles sin realizar testeos individuales de bits.
  
-\\+\\ 
 ===== 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 "programadores" y los "diseñadores gráficos", ya que estos últimos deben diseñar los sprites teniendo en cuenta las limitaciones del Spectrum y a veces hacerlo tal y como los programadores los necesitan para su rutina de impresión de Sprites o para salvar las limitaciones de color del Spectrum o evitar colisiones de atributos entre personajes y fondos.  En microordenadores como el Spectrum existe un vínculo especial entre los "programadores" y los "diseñadores gráficos", ya que estos últimos deben diseñar los sprites teniendo en cuenta las limitaciones del Spectrum y a veces hacerlo tal y como los programadores los necesitan para su rutina de impresión de Sprites o para salvar las limitaciones de color del Spectrum o evitar colisiones de atributos entre personajes y fondos.
  
-\\+\\ 
 ==== 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).
-\\+\\ 
  
  Además, hay que tener las herramientas para el dibujado y la conversión de los bitmaps o spritesets en código, en el formato que hayamos decidido. Más adelante en el capítulo profundizaremos en ambos temas: la organización en memoria del Sprite (o del Spriteset completo) y las herramientas de dibujo y conversión.  Además, hay que tener las herramientas para el dibujado y la conversión de los bitmaps o spritesets en código, en el formato que hayamos decidido. Más adelante en el capítulo profundizaremos en ambos temas: la organización en memoria del Sprite (o del Spriteset completo) y las herramientas de dibujo y conversión.
  
-\\+\\ 
 ==== 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.
-\\+\\ 
  
  Gracias a los 2 últimos capítulos del curso y a nuestros conocimientos en ensamblador, ya tenemos los mecanismos para dar forma a la rutina completa.  Gracias a los 2 últimos capítulos del curso y a nuestros conocimientos en ensamblador, ya tenemos los mecanismos para dar forma a la rutina completa.
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 "lenta" comparada con tener los datos guardados en registros, cuando comenzamos a manejar muchos parámetros de entrada (y de trabajo) en una rutina y además hay que realizar cálculos con ellos, es habitual que agotemos los registros disponibles, más todavía teniendo en cuenta la necesidad de realizar dichos cálculos. En muchas ocasiones se acaba realizando uso de la pila con continuos PUSHes y POPs destinados a guardar valores y recuperarlos posteriormente a realizar los cálculos o en ciertos puntos de la rutina.  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 "lenta" comparada con tener los datos guardados en registros, cuando comenzamos a manejar muchos parámetros de entrada (y de trabajo) en una rutina y además hay que realizar cálculos con ellos, es habitual que agotemos los registros disponibles, más todavía teniendo en cuenta la necesidad de realizar dichos cálculos. En muchas ocasiones se acaba realizando uso de la pila con continuos PUSHes y POPs destinados a guardar valores y recuperarlos posteriormente a realizar los cálculos o en ciertos puntos de la rutina.
  
- Las instrucciones **PUSH** **POP** toman 11 y 10 t-estados respectivamente, mientras que escribir o leer un valor de 8 bits en memoria (**LD (NN), A** **LD A, (NN)**) requiere 13 t-estados y escribir o leer un valor de 16 bits toma 20 t-estados (**LD (NN), rr** **LD rr, (NN)**) con la excepción de **LD (NN), HL** que cuesta 16 t-estados.+ Las instrucciones ''PUSH'' ''POP'' toman 11 y 10 t-estados respectivamente, mientras que escribir o leer un valor de 8 bits en memoria (''ld (NN), a'' ''ld a, (NN)'') requiere 13 t-estados y escribir o leer un valor de 16 bits toma 20 t-estados ''ld (NN), rr'' ''ld rr, (NN)'') con la excepción de ''ld (NN), hl'' que cuesta 16 t-estados.
  
-\\+\\ 
 |< 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 PUSH IY | 16 | +push ix push iy | 16 | 
-POP rr | 10 | +pop rr | 10 | 
-POP IX POP IY | 14 | +pop ix pop iy | 14 | 
-LD (NN), | 13 | +ld (NN), | 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 | 
-\\+\\ 
  
- Aunque es una diferencia apreciable, no siempre podemos obtener una "linealidad" de uso de la pila que requiera POP por cada PUSH, por lo que en ocasiones se hace realmente cómodo y útil el aprovechar variables de memoria para diseñar las rutinas.+ Aunque es una diferencia apreciable, no siempre podemos obtener una "linealidad" de uso de la pila que requiera un ''POP'' por cada ''PUSH'', por lo que en ocasiones se hace realmente cómodo y útil el aprovechar variables de memoria para diseñar las rutinas.
  
  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.
  
- Finalmente, recordar que las rutinas que veremos en este capítulo pueden ser ubicadas en memoria y llamadas desde BASIC. Una vez ensambladas y POKEadas en memoria, podemos hacer uso de ellas utilizando POKE para establecer los parámetros de llamada y RANDOMIZE USR DIR_RUTINA para ejecutarlas. A lo largo de la vida de revistas como Microhobby se publicaron varios paquetes de rutinas de gestión de Sprites en ensamblador que utilizan este método y que estaban pensadas para ser utilizadas tanto desde código máquina como desde BASIC.+ Finalmente, recordar que las rutinas que veremos en este capítulo pueden ser ubicadas en memoria y llamadas desde BASIC. Una vez ensambladas y POKEadas en memoria, podemos hacer uso de ellas utilizando POKE para establecer los parámetros de llamada y ''RANDOMIZE USR DIR_RUTINA'' para ejecutarlas. A lo largo de la vida de revistas como Microhobby se publicaron varios paquetes de rutinas de gestión de Sprites en ensamblador que utilizan este método y que estaban pensadas para ser utilizadas tanto desde código máquina como desde BASIC.
  
  
-\\+\\ 
 ===== 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/imagen).    * Formato de organización del tileset (lineal o en forma de matriz/imagen).
    * 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 "imagen" rectangular. Teniendo todos los sprites (o tiles) en un único vector, podemos hacer referencia a cualquier bloque, tile, sprite o cuadro de animación mediante un identificador numérico.  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 "imagen" rectangular. Teniendo todos los sprites (o tiles) en un único vector, podemos hacer referencia a cualquier bloque, tile, sprite o cuadro de animación mediante un identificador numérico.
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 "salto" se hace al acabar cada scanline completo del sprite (no de cada bloque). La rutina de impresión recibe como parámetro la dirección de inicio de ambas tablas y traza primero los gráficos y después los atributos.   * **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 "salto" se hace al acabar cada scanline completo del sprite (no de cada bloque). La rutina de impresión recibe como parámetro la dirección de inicio de ambas tablas y traza primero los gráficos y después los atributos.
  
 <code> <code>
 Tabla_Sprites: Tabla_Sprites:
-  DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 +    DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 
-  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:
-  DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2+    DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2
 </code> </code>
  
-\\+\\ 
   * **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.
  
 <code> <code>
 Tabla_Sprites: Tabla_Sprites:
-  DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 +    DB A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1 
-  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
 </code> </code>
  
Línea 255: Línea 256:
 ; Formato: Una única tabla: ; Formato: Una única tabla:
 Tabla_Sprites: Tabla_Sprites:
-  DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 +    DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 
-  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, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 +    DB XX, A1, XX, B1, XX, C1, XX, D1, XX, E1, XX, F1, XX, G1 
-  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:
-  DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2+    DB S1_Attr1, S1_Attr2, S2_Attr1, S2_Attr2
 </code> </code>
-\\+\\ 
  
  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 "sprite" con //File -> New// e indicaremos el ancho de nuestro sprite en pixels y un valor para la altura que nos permita alojar suficientes sprites en nuestro tileset.  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 "sprite" con //File -> New// e indicaremos el ancho de nuestro sprite en pixels y un valor para la altura que nos permita alojar suficientes sprites en nuestro tileset.
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sevenup_vertical.png | SevenuP }} {{ :cursos:ensamblador:gfx3_sevenup_vertical.png | SevenuP }}
-\\+\\ 
  
  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 "cortar" los datos gráficos, crear una imagen nueva con un tamaño superior y posteriormente pegar los datos gráficos cortados. La documentación de SevenUp explica cómo copiar y pegar:  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 "cortar" los datos gráficos, crear una imagen nueva con un tamaño superior y posteriormente pegar los datos gráficos cortados. La documentación de SevenUp explica cómo copiar y pegar:
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//:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_opciones_export_su.png | Opciones de exportación de SevenuP}} {{ :cursos:ensamblador:gfx3_opciones_export_su.png | Opciones de exportación de SevenuP}}
-\\+\\ 
  
  Este menú permite especificar diferentes opciones de exportación:  Este menú permite especificar diferentes opciones de exportación:
Línea 363: Línea 363:
  Veamos las opciones que debemos especificar de forma predeterminada para exportar los datos de nuestro set de sprites en el formato adecuado para las rutinas utilizadas en este capítulo:  Veamos las opciones que debemos especificar de forma predeterminada para exportar los datos de nuestro set de sprites en el formato adecuado para las rutinas utilizadas en este capítulo:
  
-\\+\\ 
    * 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:
  Veamos un ejemplo bastante claro de las posibilidades de exportación utilizando dos sprites de 2x2 bloques (16x16) con valores binarios fácilmente identificables para cada uno de los 8 bloques que forman estos 2 sprites de 16x16:  Veamos un ejemplo bastante claro de las posibilidades de exportación utilizando dos sprites de 2x2 bloques (16x16) con valores binarios fácilmente identificables para cada uno de los 8 bloques que forman estos 2 sprites de 16x16:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_spriteprueba.png | Sprite de prueba para exports }} {{ :cursos:ensamblador:gfx3_spriteprueba.png | Sprite de prueba para exports }}
-\\+\\ 
  
  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:
  Veamos el resultado de la exportación del Sprite con diferentes opciones:  Veamos el resultado de la exportación del Sprite con diferentes opciones:
  
-\\ +\\  
-\\+\\ 
 **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:
 </code> </code>
  
-\\ +\\  
-\\+\\ 
 **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
 </code> </code>
-\\+\\ 
  
  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:
  Después, utilizaremos la opción de importación de SevenuP (//File -> Import//) para convertir la imagen de color "real" a un mapa de bits ya editable en SevenuP. Con la imagen importada en SevenuP, y tras realizar los retoques que consideremos oportunos, realizamos una exportación a código ASM con los parámetros de configuración de exportación correctos para nuestra rutina de impresión de Sprites.  Después, utilizaremos la opción de importación de SevenuP (//File -> Import//) para convertir la imagen de color "real" a un mapa de bits ya editable en SevenuP. Con la imagen importada en SevenuP, y tras realizar los retoques que consideremos oportunos, realizamos una exportación a código ASM con los parámetros de configuración de exportación correctos para nuestra rutina de impresión de Sprites.
  
-\\+ 
 +\\  
 +\\  
 +**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 "agregado manualmente" directamente en el código de nuestro programa, o lo podemos incluir con ''INCLUDE grafico.asm'', lo cual incluiría también las etiquetas que van dentro del fichero ASM: 
 + 
 +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 ''INCBIN grafico.bin'', de la siguiente forma: 
 + 
 +<code z80> 
 +bicho_gfx: 
 +    INCBIN "gfx_bicho.bin" 
 + 
 +bicho_attr: 
 +    INCBIN "attr_bicho.bin" 
 +</code> 
 + 
 +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, las siguientes posibilidades:  En este sentido, podemos utilizar para el paso de los parámetros, las siguientes posibilidades:
  
-\\+\\ 
    * **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 ''PUSH'' de los parámetros de entrada en un orden concreto. La rutina, en su punto inicial, hará POP de dichos valores en los registros adecuados. Tiene la ventaja de que podemos ir recuperando los valores conforme los vayamos necesitando y tras haber realizado cálculos con los parámetros anteriores que nos hayan dejado libres registros para los siguientes cálculos.
  
-   * **El Stack del Calculador**: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante //DEF FN//. Su principal desventaja es la lentitud y la no portabilidad a otros Sistemas.+   * **El Stack del Calculador**: Este método permite que programa BASIC puedan llamar a nuestras subrutinas en ensamblador mediante ''DEF FN''. Su principal desventaja es la lentitud y la no portabilidad a otros Sistemas.
  
    * **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 "lentitud" del acceso a memoria para lectura y escritura, pero también tiene sustanciales ventajas:    * **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 "lentitud" del acceso a memoria para lectura y escritura, pero también tiene sustanciales ventajas:
      * 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 ''RANDOMIZE USR'' direccion_rutina. 
-\\+\\ 
  
  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 "modelo" para desarrollar la rutina y para un posterior ejemplo de aplicación de la misma será el siguiente:  El sprite a utilizar como "modelo" para desarrollar la rutina y para un posterior ejemplo de aplicación de la misma será el siguiente:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_cara8x8.png | Cara 8x8 }} {{ :cursos:ensamblador:gfx3_cara8x8.png | Cara 8x8 }}
-\\+\\ 
  
  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:
-  DEFB  28, 62,107,127, 93, 99, 62, 28+    DEFB  28, 62,107,127, 93, 99, 62, 28
  
 cara_attrib: cara_attrib:
-  DEFB  56+    DEFB  56
 </code> </code>
  
  
-\\+\\ 
 ==== 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, ahorrando ciclos de reloj al no realizar esta tarea sin efectos en pantalla. Nótese que aunque no dibujemos los atributos de un sprite, esto no quiere decir que no tendrá color en pantalla: si no modificamos los atributos de una posición (c,f), el sprite que dibujemos en esas coordenadas adoptará los colores de tinta y papel que ya tenía esa posición de pantalla.  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, ahorrando ciclos de reloj al no realizar esta tarea sin efectos en pantalla. Nótese que aunque no dibujemos los atributos de un sprite, esto no quiere decir que no tendrá color en pantalla: si no modificamos los atributos de una posición (c,f), el sprite que dibujemos en esas coordenadas adoptará los colores de tinta y papel que ya tenía esa posición de pantalla.
  
- 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).
  
  Utilizaremos este sistema de comprobación (DIR_ATTR==0) para evitar la necesidad de crear 2 rutinas diferentes, aunque en un juego lo normal será que adaptemos la rutina al caso concreto y exacto (SIN o CON atributos) para evitar la comprobación de DIR_ATTR=0 y cualquier otro código no necesario.  Utilizaremos este sistema de comprobación (DIR_ATTR==0) para evitar la necesidad de crear 2 rutinas diferentes, aunque en un juego lo normal será que adaptemos la rutina al caso concreto y exacto (SIN o CON atributos) para evitar la comprobación de DIR_ATTR=0 y cualquier otro código no necesario.
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 +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD D          ; Ya tenemos la parte alta calculada (010TT000) +    ld d          ; Ya tenemos la parte alta calculada (010TT000) 
-   LD A          ; Ahora calculamos la parte baja +    ld a          ; Ahora calculamos la parte baja 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA              ; A = NNN00000b +    rrca              ; A = NNN00000b 
-   ADD A         ; Sumamos COLUMNA -> A = NNNCCCCCb +    add a         ; Sumamos COLUMNA -> A = NNNCCCCCb 
-   LD E          ; Lo cargamos en la parte baja de la direccion +    ld e          ; Lo cargamos en la parte baja de la direccion 
-                     ; DE contiene ahora la direccion destino.+                      ; DE contiene ahora la direccion destino.
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*8)+    ;;;     direccion = base_sprites + (NUM_SPRITE*8)
  
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD H, 0 +    ld h, 0 
-   LD L          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 = DS_NUMSPR * 8 +    add hlhl        ; HL = HL * 8 = DS_NUMSPR * 8 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline +    ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline 
-   ;;; Incrementar scanline del sprite (DE)+    ;;; 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),       ; Establecemos el valor en videomemoria +    ld (hl),       ; 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+    djnz drawsp8x8_loopLD
  
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados. +    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
-   LD AH +    ld ah 
-   SUB 8              ; Recuperamos la posicion de memoria del +    sub 8              ; Recuperamos la posicion de memoria del 
-   LD B           ; scanline inicial donde empezamos a dibujar +    ld b           ; scanline inicial donde empezamos a dibujar 
-   LD C           ; BC = HL - 8+    ld c           ; BC = HL - 8
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; 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          ; A = 0 + H = H +    add a          ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR = Origen de atributo+    add hlbc         ; HL = HL+DS_NUMSPR = Origen de atributo
  
-   ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla +    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla 
-   LD A, (HL+    ld a, (hl
-   LD (DE),         ; Mas rapido que LDI (7+7 vs 16 t-estados) +    ld (de),         ; Mas rapido que ldi (7+7 vs 16 t-estados) 
-   RET                ; porque no necesitamos incrementar HL y DE+    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
  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 //Get_Char_Offset_LR// //Attr_Offset_From_Image// con el código de las mismas embebido dentro de la rutina principal. Esto se hace con el objetivo de evitar los correspondientes CALLs RET y para poder personalizarlas (en este caso, están modificadas para devolver la dirección calculada en DE en lugar de en HL, por requerimientos del código de DrawSprite_8x8).+  * Nótese que en la rutina se emplean las subrutinas ''Get_Char_Offset_LR'' ''Attr_Offset_From_Image'' con el código de las mismas embebido dentro de la rutina principal. Esto se hace con el objetivo de evitar los correspondientes calls ret y para poder personalizarlas (en este caso, están modificadas para devolver la dirección calculada en DE en lugar de en HL, por requerimientos del código de ''DrawSprite_8x8'').
  
-  * 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 **LDI**. Más adelante veremos el por qué de esta elecció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 ''LDI''. Más adelante veremos el por qué de esta elección.
  
-  * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar **DEC HL** **INC HL** (6 t-estados), realizamos un **DEC L** **INC L** (4 t-estados). Esto es posible porque dentro de un mismo scanline de pantalla no varía el valor del byte alto de la dirección. Esta pequeña optimización no podemos realizarla con el puntero de datos del Sprite porque no tenemos la certeza de que esté dentro de una página de 256 bytes y que, por lo tanto, alguno de los incrementos del puntero deba modificar la parte alta del mismo.+  * Como ya vimos en el capítulo anterior, para avanzar o retroceder el puntero HL en pantalla, en lugar de utilizar ''dec hl'' ''inc hl'' (6 t-estados), realizamos un ''dec l'' ''inc l'' (4 t-estados). Esto es posible porque dentro de un mismo scanline de pantalla no varía el valor del byte alto de la dirección. Esta pequeña optimización no podemos realizarla con el puntero de datos del Sprite porque no tenemos la certeza de que esté dentro de una página de 256 bytes y que, por lo tanto, alguno de los incrementos del puntero deba modificar la parte alta del mismo.
  
-  * 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 bucle**.+  * 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 bucle''.
  
  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),       ; Establecemos el valor en videomemoria +    ld (hl),       ; 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+    djnz drawsp8x8_loopLD
 </code> </code>
  
Línea 802: Línea 827:
  
 <code z80> <code z80>
-   LD A, (DE)       ; Scanline 0 +    ld a, (de)       ; Scanline 0 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 1 +    ld a, (de)       ; Scanline 1 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 2 +    ld a, (de)       ; Scanline 2 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 3 +    ld a, (de)       ; Scanline 3 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 4 +    ld a, (de)       ; Scanline 4 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 5 +    ld a, (de)       ; Scanline 5 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 6 +    ld a, (de)       ; Scanline 6 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   INC H+    inc h
  
-   LD A, (DE)       ; Scanline 7 +    ld a, (de)       ; Scanline 7 
-   LD (HL), A +    ld (hl), a 
-   INC DE +    inc de 
-   ;;;INC H         ; no es necesario el ultimo INC H+    ;;;inc h         ; no es necesario el ultimo inc h
 </code> </code>
  
- Nótese cómo al desenrollar el bucle ya no es necesario el último **INC H** para avanzar al siguiente scanline de pantalla. El **INC DE** sí que es necesario ya que tenemos que avanzar en el sprite al primero de los atributos (aunque este INC también podría realizarse después del código de comprobación de la dirección de atributo, evitando hacerlo si no queremos imprimirlos).+ Nótese cómo al desenrollar el bucle ya no es necesario el último ''inc h'' para avanzar al siguiente scanline de pantalla. El ''inc de'' sí que es necesario ya que tenemos que avanzar en el sprite al primero de los atributos (aunque este INC también podría realizarse después del código de comprobación de la dirección de atributo, evitando hacerlo si no queremos imprimirlos).
  
- 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 ''inc h'', en la versión desenrollada del bucle tenemos que cambiar la resta de HL - 8 por HL - 7:
  
 <code z80> <code z80>
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados. +    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
-   LD AH +    ld ah 
-   SUB 7              ; Recuperamos la posicion de memoria del +    sub 7              ; Recuperamos la posicion de memoria del 
-   LD B           ; scanline inicial donde empezamos a dibujar +    ld b           ; scanline inicial donde empezamos a dibujar 
-   LD C           ; BC = HL - 7+    ld c           ; BC = HL - 7
 </code> </code>
 +
 + Recordemos que mediante las directivas ''REPT''/''ENDM'' de pasmo y ''REPT''/''ENDR'' de sjasmplus, podríamos escribir el bloque de 8 repeticiones con:
 + 
 +<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
 +</code>
 +
 + O, si queremos ahorrarnos el ''inc h'' final, podemos usar un ''REPT 7'' y volver a escribir las 4 instrucciones sin el ''inc h''. Este código es mucho más legible, hace crecer menos los ficheros, pero por las diferencias de directivas entre ensambladores nos ata a uno o a otro.
  
  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+    ORG 35000
  
-  CALL ClearScreen_Pattern+    call ClearScreen_Pattern
  
-  ; Establecemos los parametros de entrada a la rutina +    ; Establecemos los parametros de entrada a la rutina 
-  ; 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 (DS_SPRITES), HL +    ld (DS_SPRITES), hl 
-  LD HL, cara_attrib +    ld hl, cara_attrib 
-  LD (DS_ATTRIBS), HL +    ld (DS_ATTRIBS), hl 
-  LD A, 15 +    ld a, 15 
-  LD (DS_COORD_X), A +    ld (DS_COORD_X), a 
-  LD A, 8 +    ld a, 8 
-  LD (DS_COORD_Y), A +    ld (DS_COORD_Y), a 
-  XOR A +    xor a 
-  LD (DS_NUMSPR), A+    ld (DS_NUMSPR), 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  DEFB   0 DS_COORD_Y  DEFB   0
 DS_NUMSPR   DEFB   0 DS_NUMSPR   DEFB   0
- 
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
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 AB +    ld ab 
-   LD BA +    ld ba 
-   CALL $22B1                  ; ROM (Pixel-Address)+    call $22b1                  ; ROM (Pixel-Address)
  
-   LD AB +    ld ab 
-   AND +    and 
-   JR Z, cs_es_par +    jr z, cs_es_par 
-   LD A, 170 +    ld a, 170 
-   JR cs_pintar+    jr cs_pintar
  
 cs_es_par: cs_es_par:
-   LD A, 85+    ld a, 85
  
 cs_pintar: cs_pintar:
-   LD D                    ; Salvar el contador del bucle +    ld d                    ; 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 +    ld (hl), a 
-   INC HL +    inc hl 
-   DJNZ cs_x_loop +    djnz cs_x_loop
- +
-   LD B, D                     ; Recuperamos el contador externo +
-   DJNZ cs_line_loop           ; Repetimos 192 veces +
-   RET+
  
 +    ld b, d                     ; Recuperamos el contador externo
 +    djnz cs_line_loop           ; Repetimos 192 veces
 +    ret
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
Línea 938: Línea 974:
  
 cara_gfx: cara_gfx:
-  DEFB  28, 62,107,127, 93, 99, 62, 28+    DEFB  28, 62,107,127, 93, 99, 62, 28
  
 cara_attrib: cara_attrib:
-  DEFB  56+    DEFB  56
  
 ;-------------------------------------------------------------------- ;--------------------------------------------------------------------
 DrawSprite_8x8_LD: DrawSprite_8x8_LD:
-  ;;; codigo de la rutina...+    ;;; codigo de la rutina...
  
-END 35000+    END 35000
 </code> </code>
  
  El resultado de la ejecución del anterior programa es el siguiente:  El resultado de la ejecución del anterior programa es el siguiente:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite8x8_1.png?640 | Salida del ejemplo anterior }} {{ :cursos:ensamblador:gfx3_sprite8x8_1.png?640 | Salida del ejemplo anterior }}
-\\+\\ 
  
  Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, podríamos utilizar celdas de memoria para POKEar en ellas los parámetros de las funciones antes de hacer el USR y llamarlas. Para eso tendríamos que eliminar las variables "en memoria" del código (embebidas dentro del listado) y usar direcciones absolutas de memoria:  Si quisiéramos llamar a esta rutina desde BASIC, en lugar de utilizar variables dentro de ensamblador, podríamos utilizar celdas de memoria para POKEar en ellas los parámetros de las funciones antes de hacer el USR y llamarlas. Para eso tendríamos que eliminar las variables "en memoria" del código (embebidas dentro del listado) y usar direcciones absolutas de memoria:
Línea 966: Línea 1002:
 </code> </code>
  
-Así, podríamos establecer la coordenada X e Y con **POKE 65003, x** **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 ''POKE 65003, x'' ''POKE 65004, y'' y después hacer el randomize USR a nuestra rutina de impresión del Sprite ya cargada en memoria.
  
 En nuestro caso, como el esqueleto del programa está en ensamblador, usaremos variables y de esta forma no necesitaremos mantener un listado de diferentes direcciones de memoria y su finalidad. En nuestro caso, como el esqueleto del programa está en ensamblador, usaremos variables y de esta forma no necesitaremos mantener un listado de diferentes direcciones de memoria y su finalidad.
  
-\\ +\\  
-**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 ''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.
  
  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),         ; (HL) = A = escribir dato a la pantalla +    ld (hl),         ; (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+    djnz drawsp8x8_loop
 </code> </code>
  
- 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 ''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.
  
  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++ +    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+    djnz drawsp8x8_loop
 </code> </code>
  
  Aunque es un formato más compacto, el coste de ejecución es el mismo (16+4+4 = 24 t-estados).  Aunque es un formato más compacto, el coste de ejecución es el mismo (16+4+4 = 24 t-estados).
  
- Entre las 2 posibles formas de realizar la impresión (LD+INC vs LDI), utilizaremos la primera porque, como veremos a continuación, la impresión de sprites mediante operaciones lógicas o mediante máscaras no permite el uso de LDI y utilizar la primera técnica hace todas las rutinas muy similares entre sí y por lo tanto podremos aplicar en todas cualquier mejora u optimización de una forma más rápida y sencilla.+ Entre las 2 posibles formas de realizar la impresión (''LD''+''INC'' vs ''LDI''), utilizaremos la primera porque, como veremos a continuación, la impresión de sprites mediante operaciones lógicas o mediante máscaras no permite el uso de ldi y utilizar la primera técnica hace todas las rutinas muy similares entre sí y por lo tanto podremos aplicar en todas cualquier mejora u optimización de una forma más rápida y sencilla.
  
- Además, LDI decrementa el registro BC tras las transferencia, por lo que si lo utilizamos en un bucle tenemos que tener en cuenta que cada LDI puede alterar el valor de BC y por tanto del contador de iteraciones del bucle, lo cual es otro motivo para elegir LD+INC vs LDI.+ Además, ''LDI'' decrementa el registro BC tras las transferencia, por lo que si lo utilizamos en un bucle tenemos que tener en cuenta que cada ''LDI'' puede alterar el valor de BC y por tanto del contador de iteraciones del bucle, lo cual es otro motivo para elegir ''LD''+''INC'' vs ''LDI''.
  
  
-\\+\\ 
 ==== Respetando el fondo (impresión con OR) ==== ==== Respetando el fondo (impresión con OR) ====
  
  Veamos una ampliación del Sprite de 8x8 del ejemplo anterior impreso sobre un fondo no plano:  Veamos una ampliación del Sprite de 8x8 del ejemplo anterior impreso sobre un fondo no plano:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite8x8_2.png | Ampliación del Sprite de 8x8 }} {{ :cursos:ensamblador:gfx3_sprite8x8_2.png | Ampliación del Sprite de 8x8 }}
-\\+\\ 
  
- Nótese cómo la impresión del sprite no ha respetado el fondo en los píxeles a cero del mismo: al establecer el valor del byte en pantalla con un LD, hemos establecido a cero en pantalla los bits que estaban a cero en el sprite sin respetar el valor que hubiera en videoram para dichos bits.+ Nótese cómo la impresión del sprite no ha respetado el fondo en los píxeles a cero del mismo: al establecer el valor del byte en pantalla con un ''LD'', hemos establecido a cero en pantalla los bits que estaban a cero en el sprite sin respetar el valor que hubiera en videoram para dichos bits.
  
 <code> <code>
Línea 1024: Línea 1060:
 Valor en Videomemoria (HL):     10101010 Valor en Videomemoria (HL):     10101010
 Valor en el sprite - reg. A:    00111110 Valor en el sprite - reg. A:    00111110
-Operación:                      LD (HL), A+Operación:                      ld (hl), a
 Resultado en VRAM:              00111110 Resultado en VRAM:              00111110
 </code> </code>
  
- 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 ''OR'' entre el scanline y el valor actual en memoria. Con la operación OR mezclaremos los bits de ambos elementos:
  
 <code> <code>
Línea 1039: Línea 1075:
 </code> </code>
  
- 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 ''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).
  
- 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 ''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:
  
  Cambiamos el bucle de impresión...  Cambiamos el bucle de impresión...
  
 <code z80> <code z80>
-   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),       ; Establecemos el valor en videomemoria +    ld (hl),       ; 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+    djnz drawsp8x8_loopLD
 </code> </code>
  
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 +    or (hl)             ; NUEVO: Hacemos un OR del scanline con el fondo 
-   LD (HL),          ; Establecemos el valor del OR en videomemoria +    ld (hl),          ; 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+    djnz drawsp8x8_loop_or
 </code> </code>
  
- A continuación, el código fuente completo de la rutina de impresión de 8x8 con operación lógica OR:+ A continuación, el código fuente completo de la rutina de impresión de 8x8 con operación lógica ''OR'':
  
 <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 +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD D          ; Ya tenemos la parte alta calculada (010TT000) +    ld d          ; Ya tenemos la parte alta calculada (010TT000) 
-   LD A          ; Ahora calculamos la parte baja +    ld a          ; Ahora calculamos la parte baja 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA              ; A = NNN00000b +    rrca              ; A = NNN00000b 
-   ADD A         ; Sumamos COLUMNA -> A = NNNCCCCCb +    add a         ; Sumamos COLUMNA -> A = NNNCCCCCb 
-   LD E          ; Lo cargamos en la parte baja de la direccion +    ld e          ; Lo cargamos en la parte baja de la direccion 
-                     ; DE contiene ahora la direccion destino.+                      ; DE contiene ahora la direccion destino.
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*8)+    ;;;     direccion = base_sprites + (NUM_SPRITE*8)
  
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD H, 0 +    ld h, 0 
-   LD L          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 = DS_NUMSPR * 8 +    add hlhl        ; HL = HL * 8 = DS_NUMSPR * 8 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 8) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline +    ;;; Dibujar 8 scanlines (DE) -> (HL) y bajar scanline 
-   ;;; Incrementar scanline del sprite (DE)+    ;;; 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      ; Tomamos el dato del sprite +    ld a, (de        ; Tomamos el dato del sprite 
-   OR (HL         ; NUEVO: Hacemos un OR del scanline con el fondo +    or (hl           ; NUEVO: Hacemos un OR del scanline con el fondo 
-   LD (HL), A       ; Establecemos el valor en videomemoria +    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_loop_or+    djnz drawsp8x8_loop_or
  
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados. +    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
-   LD AH +    ld ah 
-   SUB 8              ; Recuperamos la posicion de memoria del +    sub 8              ; Recuperamos la posicion de memoria del 
-   LD B           ; scanline inicial donde empezamos a dibujar +    ld b           ; scanline inicial donde empezamos a dibujar 
-   LD C           ; BC = HL - 8+    ld c           ; BC = HL - 8
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; 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          ; A = 0 + H = H +    add a          ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR = Origen de atributo+    add hlbc         ; HL = HL+DS_NUMSPR = Origen de atributo
  
-   ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla +    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla 
-   LD A, (HL+    ld a, (hl
-   LD (DE),         ; Mas rapido que LDI (7+7 vs 16 t-estados) +    ld (de),         ; Mas rapido que ldi (7+7 vs 16 t-estados) 
-   RET                ; porque no necesitamos incrementar HL y DE+    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
Línea 1170: Línea 1206:
  Veamos qué ha ocurrido con los píxeles del fondo y la operación lógica OR:  Veamos qué ha ocurrido con los píxeles del fondo y la operación lógica OR:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite8x8_OR_1.png?640 | Sprite 8x8 impreso con OR }} {{ :cursos:ensamblador:gfx3_sprite8x8_OR_1.png?640 | Sprite 8x8 impreso con OR }}
-\\+\\ 
  
- Ampliando el sprite...+ Ampliando el sprite vemos...
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite8x8_OR_2.png | Ampliación del Sprite de 8x8 con OR }} {{ :cursos:ensamblador:gfx3_sprite8x8_OR_2.png | Ampliación del Sprite de 8x8 con OR }}
-\\+\\ 
  
- ¿Qué ha ocurrido con el sprite? ¿Por qué le faltan los "ojos" y hay un pixel activo en medio de la "boca"? Sencillamente, porque mediante el OR hemos impreso el sprite respetando el valor a 1 de los píxeles del fondo cuando el mismo pixel estaba a 0 en nuestro sprite. Eso ha hecho que alrededor de nuestro "personaje" no se haya borrado el fondo, ya que los píxeles a cero de nuestro sprite se convierten en "transparentes". Por desgracia, eso también hace que los ojos del personaje sean transparentes en lugar de estar a cero. En el caso del ejemplo anterior, los "ojos" del personaje coinciden con 2 píxeles de pantalla activos por lo que la operación OR los deja a 1. Lo mismo ocurre con el pixel en el centro de la boca, que se corresponde con un pixel activo en la pantalla.+ ¿Qué ha ocurrido con el sprite? ¿Por qué le faltan los "ojos" y hay un pixel activo en medio de la "boca"? Sencillamente, porque mediante el or hemos impreso el sprite respetando el valor a 1 de los píxeles del fondo cuando el mismo pixel estaba a 0 en nuestro sprite. Eso ha hecho que alrededor de nuestro "personaje" no se haya borrado el fondo, ya que los píxeles a cero de nuestro sprite se convierten en "transparentes". Por desgracia, eso también hace que los ojos del personaje sean transparentes en lugar de estar a cero. En el caso del ejemplo anterior, los "ojos" del personaje coinciden con 2 píxeles de pantalla activos por lo que la operación or los deja a 1. Lo mismo ocurre con el pixel en el centro de la boca, que se corresponde con un pixel activo en la pantalla.
  
  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" (que suele ser unas componentes concretas de color con un tono específico de rosa fucsia). En el caso del Spectrum, el color reside en los atributos y no en los sprites en sí, por lo que no podemos utilizar un "color transparencia".+ 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" (que suele ser unas componentes concretas de color con un tono específico de rosa fucsia). En el caso del Spectrum, el color reside en los atributos y no en los sprites en sí, por lo que no podemos utilizar un "color transparencia".
  
  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 "borramos" del fondo todos aquellos píxeles a cero en la máscara. Después se realiza un OR del Sprite en el resultado del anterior AND, activando los píxeles del Sprite.+ 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 "borramos" del fondo todos aquellos píxeles a cero en la máscara. Después se realiza un ''OR'' del Sprite en el resultado del anterior ''AND'', activando los píxeles del Sprite.
  
  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, mientras que borramos todos aquellos píxeles de pantalla que deben de ser reemplazados por el sprite.  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, mientras que borramos todos aquellos píxeles de pantalla que deben de ser reemplazados por el sprite.
  
- Apliquemos una máscara a nuestro ejemplo anterior. Al hacer el AND entre la máscara y el fondo (paso 1.-), eliminamos el entramado de pixeles de pantalla, con lo que al imprimir nuestro sprite con el OR (paso 2.-), los ojos y el centro de la boca serán de nuevo píxeles no activos:+ Apliquemos una máscara a nuestro ejemplo anterior. Al hacer el ''AND'' entre la máscara y el fondo (paso 1.-), eliminamos el entramado de pixeles de pantalla, con lo que al imprimir nuestro sprite con el ''OR'' (paso 2.-), los ojos y el centro de la boca serán de nuevo píxeles no activos:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_mask8x8.png | Aplicando máscara }} {{ :cursos:ensamblador:gfx3_mask8x8.png | Aplicando máscara }}
-\\+\\ 
  
- 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 "transparencia" que presentaba el dibujado con OR.+ 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 "transparencia" que presentaba el dibujado con ''OR''.
  
  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 "confunden" con los del fondo en los contornos del sprite, ya que todos tendrán idéntico color si están dentro de una misma celda 8x8. Para evitar esto, lo ideal sería disponer de un "borde" alrededor del mismo.  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 "confunden" con los del fondo en los contornos del sprite, ya que todos tendrán idéntico color si están dentro de una misma celda 8x8. Para evitar esto, lo ideal sería disponer de un "borde" alrededor del mismo.
Línea 1213: Línea 1249:
  La siguiente imagen ilustra lo que acabamos de comentar:  La siguiente imagen ilustra lo que acabamos de comentar:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_bordes.png | Aplicando bordes mediante máscara }} {{ :cursos:ensamblador:gfx3_bordes.png | Aplicando bordes mediante máscara }}
-\\+\\ 
  
  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 "relleno", por lo que todos los pixeles del sprite serán transferidos dentro de dicho contorno.   * Evitar transparencias en el interior de nuestro Sprite: bastará con que la máscara sea una copia del contorno del sprite, pero "relleno", por lo que todos los pixeles del sprite serán transferidos dentro de dicho contorno.
  
   * 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, de forma que podamos recoger ambos valores usando el mismo puntero (DE en nuestro caso). Para eso habría que exportar el Sprite como //Mask, X Char, Char line, Y Char// y activando "Mask before graphic".  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, de forma que podamos recoger ambos valores usando el mismo puntero (DE en nuestro caso). Para eso habría que exportar el Sprite como //Mask, X Char, Char line, Y Char// y activando "Mask before graphic".
  
- El pseudocódigo de la nueva rutina **DrawSprite_8x8_MASK** sería el siguiente:+ El pseudocódigo de la nueva rutina ''DrawSprite_8x8_MASK'' sería el siguiente:
  
 <code> <code>
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 +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD D          ; Ya tenemos la parte alta calculada (010TT000) +    ld d          ; Ya tenemos la parte alta calculada (010TT000) 
-   LD A          ; Ahora calculamos la parte baja +    ld a          ; Ahora calculamos la parte baja 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA              ; A = NNN00000b +    rrca              ; A = NNN00000b 
-   ADD A         ; Sumamos COLUMNA -> A = NNNCCCCCb +    add a         ; Sumamos COLUMNA -> A = NNNCCCCCb 
-   LD E          ; Lo cargamos en la parte baja de la direccion +    ld e          ; Lo cargamos en la parte baja de la direccion 
-                     ; DE contiene ahora la direccion destino.+                      ; DE contiene ahora la direccion destino.
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*16)+    ;;;     direccion = base_sprites + (NUM_SPRITE*16)
  
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD H, 0 +    ld h, 0 
-   LD LA           ; HL = DS_NUMSPR +    ld la            ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl         ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl         ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 +    add hlhl         ; HL = HL * 8 
-   ADD HLHL        ; HL = HL * 16 = DS_NUMSPR * 16 +    add hlhl         ; HL = HL * 16 = DS_NUMSPR * 16 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) +    add hlbc         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) 
-                     ; HL contiene la direccion de inicio en el sprite+                       ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL para el OR+    ex dehl          ; Intercambiamos DE y HL para el OR
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR +    ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR 
-   LD B, 8+    ld b, 8
  
 drawspr8x8m_loop: drawspr8x8m_loop:
-   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+    and (hl          ; A = A and (hl
-   LD CA          ; Nos guardamos el valor del AND +    ld ca            ; 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 +    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            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h              ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr8x8m_loop+    djnz drawspr8x8m_loop
  
-   ;;; En este punto, los 8 scanlines del sprite estan dibujados. +    ;;; En este punto, los 8 scanlines del sprite estan dibujados. 
-   LD AH +    ld ah 
-   SUB 8              ; Recuperamos la posicion de memoria del +    sub 8              ; Recuperamos la posicion de memoria del 
-   LD B           ; scanline inicial donde empezamos a dibujar +    ld b           ; scanline inicial donde empezamos a dibujar 
-   LD C           ; BC = HL - 8+    ld c           ; BC = HL - 8
  
-   ;;; Considerar el dibujado de los atributos (Si DS_ATTRIBS=0 -> RET+    ;;; 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          ; A = 0 + H = H +    add a          ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR = Origen de atributo+    add hlbc         ; HL = HL+DS_NUMSPR = Origen de atributo
  
-   ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla +    ;;; Copiar (HL) en (DE) -> Copiar atributo de sprite a pantalla 
-   LD A, (HL+    ld a, (hl
-   LD (DE),         ; Mas rapido que LDI (7+7 vs 16 t-estados) +    ld (de),         ; Mas rapido que ldi (7+7 vs 16 t-estados) 
-   RET                ; porque no necesitamos incrementar HL y DE+    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
Línea 1366: Línea 1402:
 </code> </code>
  
- El código para el cálculo agrega un "**ADD HLHL**" adicional para esta tarea:+ El código para el cálculo agrega un ''add hlhl'' adicional para esta tarea:
  
 <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          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 +    add hlhl        ; HL = HL * 8 
-   ADD HLHL        ; HL = HL * 16 = DS_NUMSPR * 16 +    add hlhl        ; HL = HL * 16 = DS_NUMSPR * 16 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 16) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
 </code> </code>
  
Línea 1384: Línea 1420:
  
 <code z80> <code z80>
-   LD B, 8+    ld b, 8
 drawspr8x8m_loop: drawspr8x8m_loop:
-   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+    and (hl         ; A = A and (hl
-   LD CA          ; Nos guardamos el valor del AND +    ld ca           ; 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 +    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            ; Incrementamos puntero en pantalla (siguiente scanline) +    inc h             ; Incrementamos puntero en pantalla (siguiente scanline) 
-   DJNZ drawspr8x8m_loop+    djnz drawspr8x8m_loop
 </code> </code>
  
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 "//AutoMask//" que generará una máscara básica como inversión del sprite para comenzar a trabajar con ella:  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 "//AutoMask//" que generará una máscara básica como inversión del sprite para comenzar a trabajar con ella:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_mask_1.png | La máscara de nuestro pequeño Sprite }} {{ :cursos:ensamblador:gfx3_mask_1.png | La máscara de nuestro pequeño Sprite }}
 ;#; ;#;
 //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 "negro" los píxeles a respetar en el fondo, y en blanco y amarillo (dentro de la imagen) los píxeles a borrar. SevenuP nos marca en diferente color los píxeles a cero de la máscara que coinciden con los del sprite.  En la anterior imagen, tenemos en "negro" los píxeles a respetar en el fondo, y en blanco y amarillo (dentro de la imagen) los píxeles a borrar. SevenuP nos marca en diferente color los píxeles a cero de la máscara que coinciden con los del sprite.
Línea 1424: Línea 1460:
  
 cara_gfx: cara_gfx:
-   DEFB   227, 28, 193, 62, 128, 107, 128, 127 +    DEFB   227, 28, 193, 62, 128, 107, 128, 127 
-   DEFB   128, 93, 128, 99, 193,  62, 227, 28+    DEFB   128, 93, 128, 99, 193,  62, 227, 28
  
 cara_attrib: cara_attrib:
-   DEFB   56+    DEFB   56
 </code> </code>
  
  Veamos el resultado del mismo ejemplo que hemos usado hasta ahora (fondo con patrón de píxeles alternados), pero con la rutina de impresión con máscaras. El código es igual a los 2 ejemplos anteriores, pero llamando a DrawSprite_8x8_MASK:  Veamos el resultado del mismo ejemplo que hemos usado hasta ahora (fondo con patrón de píxeles alternados), pero con la rutina de impresión con máscaras. El código es igual a los 2 ejemplos anteriores, pero llamando a DrawSprite_8x8_MASK:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_mask_2.png?640 | Impresión del Sprite con máscara }} {{ :cursos:ensamblador:gfx3_mask_2.png?640 | Impresión del Sprite con máscara }}
-\\+\\ 
  
  Ampliando la anterior captura de pantalla en la zona del sprite, podemos apreciar que los ojos y la boca de nuestro personaje ya se visualizan correctamente:  Ampliando la anterior captura de pantalla en la zona del sprite, podemos apreciar que los ojos y la boca de nuestro personaje ya se visualizan correctamente:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_mask_3.png | Ampliación del sprite impreso con máscara }} {{ :cursos:ensamblador:gfx3_mask_3.png | Ampliación del sprite impreso con máscara }}
-\\+\\ 
  
  Nuestro pequeño sprite se vería mucho mejor con un reborde vacío alrededor, ya que hay píxeles del fondo pegados a nuestro personaje. Por desgracia, en un sprite de 8x8 como el nuestro apenas nos queda espacio para este reborde en la máscara, pero se podría haber aplicado si el sprite fuera de mayores dimensiones.  Nuestro pequeño sprite se vería mucho mejor con un reborde vacío alrededor, ya que hay píxeles del fondo pegados a nuestro personaje. Por desgracia, en un sprite de 8x8 como el nuestro apenas nos queda espacio para este reborde en la máscara, pero se podría haber aplicado si el sprite fuera de mayores dimensiones.
  
- Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando DS_NUMSPR=0, para imprimir el "primer" (y único sprite en nuestro caso) del spriteset. El Spriteset podría tener hasta 255 sprites dispuestos verticalmente (con sus máscaras intercaladas) y esta misma rutina, sin modificaciones, nos serviría para dibujar cualquiera de ellos variando DS_NUMSPR.+ Un apunte final sobre los ejemplos que hemos visto: nótese que estamos llamando a todas las rutinas asignando ''DS_NUMSPR = 0'', para imprimir el "primer" (y único sprite en nuestro caso) del spriteset. El Spriteset podría tener hasta 255 sprites dispuestos verticalmente (con sus máscaras intercaladas) y esta misma rutina, sin modificaciones, nos serviría para dibujar cualquiera de ellos variando ''DS_NUMSPR''.
  
  
-\\+\\ 
 ===== 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:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sevenup.png | Sprite de 16x16 pixeles (2x2 caracteres)}} {{ :cursos:ensamblador:gfx3_sevenup.png | Sprite de 16x16 pixeles (2x2 caracteres)}}
-\\ +\\  
-\\+\\  
 + 
 +   * 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), pero ahora tendremos que avanzar 8*2*2=32 bytes por cada sprite en los sprites sin máscara. El cálculo quedaría como BASE+(DS_NUMSPR*32). 
 + 
 +   * 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, ya que necesitamos incrementar HL (concretamente, L) para poder imprimir el segundo byte horizontal. 
 + 
 +   * 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), pero ahora tendremos que avanzar 8*2*2=32 bytes por cada sprite en los sprites sin máscara. El cálculo quedaría como BASE+(DS_NUMSPR*32). +   * 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, ya que necesitamos incrementar HL (concretamente, L) para poder imprimir el segundo byte horizontal. +
-  * El bucle vertical es de 16 iteraciones en lugar de 8, ya que el sprite tiene 2 caracteres verticales. +
-  * El cálculo de la dirección origen en los atributos cambia, ya que ahora tenemos 4 bytes de atributos por cada sprite. Así, la posición ya no se calcula como BASE+(DS_NUMSPR*1) sino como BASE+(DS_NUMSPR*4). +
-  * La impresión de los atributos también cambia, ya que ahora hay que imprimir 4 atributos (2x2 bloques) en lugar de sólo 1.+
  
  
Línea 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 +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da 
-   LD AB +    ld ab 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   ADD AC +    add ac 
-   LD EA+    ld ea
  
-   PUSH DE           ; Lo guardamos para luego, lo usaremos para +    push de           ; Lo guardamos para luego, lo usaremos para 
-                     ; calcular la direccion del atributo+                      ; calcular la direccion del atributo
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*32) +    ;;;     direccion = base_sprites + (NUM_SPRITE*32) 
-   ;;; Multiplicamos con desplazamientos, ver los comentarios. +    ;;; Multiplicamos con desplazamientos, ver los comentarios. 
-   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 +    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 +    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          ; HL = DS_NUMSPR*32 +    ld h          ; HL = DS_NUMSPR*32 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; Repetir 8 veces (primeros 2 bloques horizontales): +    ;;; 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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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+    djnz drawsp16x16_loop1
  
-   ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) +    ; 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+    ; 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 +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
-   ;;;LD A        ; No hay que hacer esta prueba, sabemos que +    ;;;ld a        ; No hay que hacer esta prueba, sabemos que 
-   ;;;AND 7           ; no hay salto (es un cambio de bloque) +    ;;;and 7           ; no hay salto (es un cambio de bloque) 
-   ;;;JR NZ, drawsp16_nofix_abajop +    ;;;jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
  
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
  
-   ;;; Repetir 8 veces (segundos 2 bloques horizontales): +    ;;; 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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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+    djnz drawsp16x16_loop2
  
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados.+    ;;; 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+    ;;; 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          ; A = 0 + H = H +    add a          ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR +    add hlbc         ; HL = HL+DS_NUMSPR 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*2 +    add hlbc         ; HL = HL+DS_NUMSPR*2 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*3 +    add hlbc         ; HL = HL+DS_NUMSPR*3 
-   ADD HLBC         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo+    add hlbc         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo
  
-   LDI +    ldi 
-   LDI                ; Imprimimos las 2 primeras filas de atributo+    ldi                ; Imprimimos las 2 primeras filas de atributo
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD A           ; A = L +    ld a           ; 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           ; Guardamos en L (L = L+30 + 2 por LDI=L+32) +    ld e           ; 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 
-   RET                ; porque no necesitamos incrementar HL y DE+    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
-\\ +\\  
- 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 HLHL** 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 ''DS_NUMSPR''. Una primera aproximación de multiplicación de HL = NUM_SPR * 32 podría ser aumentar el número de sumas ''add hlhl'' tal y como se realizan en las rutinas de 8x8:
  
 <code z80> <code z80>
-   ;;; Multiplicar DS_SPRITES por 32 con sumas +    ;;; 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          ; HL = DS_NUMSPR +    ld l          ; HL = DS_NUMSPR 
-   ADD HLHL        ; HL = HL * 2 +    add hlhl        ; HL = HL * 2 
-   ADD HLHL        ; HL = HL * 4 +    add hlhl        ; HL = HL * 4 
-   ADD HLHL        ; HL = HL * 8 +    add hlhl        ; HL = HL * 8 
-   ADD HLHL        ; HL = HL * 16 +    add hlhl        ; HL = HL * 16 
-   ADD HLHL        ; HL = HL * 32 +    add hlhl        ; HL = HL * 32 
-   ADD HLBC        ; HL = DS_SPRITES + (DS_NUMSPR * 32)+    add hlbc        ; HL = DS_SPRITES + (DS_NUMSPR * 32)
 </code> </code>
  
- 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 LA**, 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 ''ADD'' de 16 bits, más 7 t-estados de ''ld h, 0'', más 4 de ''ld la'', lo que da un total de 77 t-estados para realizar la multiplicación.
  
  La técnica empleada en el listado, proporcionada por **metalbrain**, implica cargar el valor de DS_NUMSPR en la parte alta de un registro de 16 bits (con lo que el registro tendría el valor de DS_NUMSPR*256, como si lo hubieramos desplazado 8 veces a la izquierda), y después realizar desplazamientos de 16 bits a la derecha, dividiendo este valor por 2 en cada desplazamiento. Con un desplazamiento, obtenemos en el registro el valor DS_NUMSPR * 128, con otro desplazamiento DS_NUMSPR * 64, y con otro más DS_NUMSPR * 32.  La técnica empleada en el listado, proporcionada por **metalbrain**, implica cargar el valor de DS_NUMSPR en la parte alta de un registro de 16 bits (con lo que el registro tendría el valor de DS_NUMSPR*256, como si lo hubieramos desplazado 8 veces a la izquierda), y después realizar desplazamientos de 16 bits a la derecha, dividiendo este valor por 2 en cada desplazamiento. Con un desplazamiento, obtenemos en el registro el valor DS_NUMSPR * 128, con otro desplazamiento DS_NUMSPR * 64, y con otro más DS_NUMSPR * 32.
Línea 1685: Línea 1727:
  
 <code z80> <code z80>
-   ;;; Multiplicar DS_SPRITES por 32 con desplazamientos >> +    ;;; 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 +    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 +    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          ; HL = DS_NUMSPR*32 +    ld h          ; HL = DS_NUMSPR*32 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
 </code> </code>
  
- 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 (''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''.
  
- 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, con un coste total de 55 t-estados.+ 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, con un coste total de 55 t-estados.
  
- 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 **INC H** (HL=HL+256).+ 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 ''inc h'' (HL=HL+256).
  
- 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"). Para ello se ha insertado el código de "//Siguiente_Scanline_HL//" dentro de la rutina (evitando el CALL y el RET). La instrucción inicial **INC H** de la rutina que vimos en el capítulo anterior no es necesaria porque la ejecuta la última iteración del bucle anterior.+ 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"). Para ello se ha insertado el código de ''Siguiente_Scanline_HL'' dentro de la rutina (evitando el ''call'' y el ''RET''). La instrucción inicial ''inc h'' de la rutina que vimos en el capítulo anterior no es necesaria porque la ejecuta la última iteración del bucle anterior.
  
  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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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) +    ; 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 +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
-   ;;;LD A        ; No hay que hacer esta prueba, sabemos que +    ;;;ld a        ; No hay que hacer esta prueba, sabemos que 
-   ;;;AND 7           ; no hay salto (es un cambio de bloque) +    ;;;and 7           ; no hay salto (es un cambio de bloque) 
-   ;;;JR NZ, drawsp16_nofix_abajop +    ;;;jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
  
-   DJNZ drawsp16x16_loop+    djnz drawsp16x16_loop
 </code> </code>
  
- 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, ya que sólo se debe chequear el caracter/tercio en el salto de un bloque de pantalla al siguiente, que sólo ocurre 1 vez en el caso de un sprite de 2x2  bloques impreso en posiciones de carácter. Además, en la última iteración es innecesario incrementar y ajustar HL, por lo que son ciclos de reloj que se malgastan.+ 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, ya que sólo se debe chequear el caracter/tercio en el salto de un bloque de pantalla al siguiente, que sólo ocurre 1 vez en el caso de un sprite de 2x2  bloques impreso en posiciones de carácter. Además, en la última iteración es innecesario incrementar y ajustar HL, por lo que son ciclos de reloj que se malgastan.
  
- Finalmente, a la hora de escribir los atributos cambia el cálculo de la posición origen (ahora es DS_NUMSPR*4, cuya multiplicación realizamos en base a 4 sumas), así como la copia de los 4 bytes, ya que hay que imprimir los 2 primeros (LDI + LDI), avanzar DE hasta la siguiente "fila de atributos", y copiar los 2 siguientes (con otras 2 instrucciones LDI).+ Finalmente, a la hora de escribir los atributos cambia el cálculo de la posición origen (ahora es DS_NUMSPR*4, cuya multiplicación realizamos en base a 4 sumas), así como la copia de los 4 bytes, ya que hay que imprimir los 2 primeros (''LDI'' ''LDI''), avanzar DE hasta la siguiente "fila de atributos", y copiar los 2 siguientes (con otras 2 instrucciones ldi).
  
  Veamos la ejecución de la rutina con un sencillo ejemplo... Primero dibujamos en SevenuP el pequeño personaje de 2x2 bloques con el que abríamos este apartado y lo exportamos a ASM:  Veamos la ejecución de la rutina con un sencillo ejemplo... Primero dibujamos en SevenuP el pequeño personaje de 2x2 bloques con el que abríamos este apartado y lo exportamos a ASM:
Línea 1761: Línea 1803:
  
 bicho_gfx: bicho_gfx:
-   DEFB    8,128,  4, 64,  0,  0,  7,224 +    DEFB    8,128,  4, 64,  0,  0,  7,224 
-   DEFB   15, 80, 15, 80, 15,240,  7,224 +    DEFB   15, 80, 15, 80, 15,240,  7,224 
-   DEFB    0,  0,  1, 64,  0, 32,  2,  0 +    DEFB    0,  0,  1, 64,  0, 32,  2,  0 
-   DEFB  104, 22,112, 14, 56, 28,  0,  0+    DEFB  104, 22,112, 14, 56, 28,  0,  0
  
 bicho_attrib: bicho_attrib:
-   DEFB   70, 71, 67,  3+    DEFB   70, 71, 67,  3
 </code> </code>
  
Línea 1773: Línea 1815:
  
 <code z80> <code z80>
-  LD HL, bicho_gfx +    ld hl, bicho_gfx 
-  LD (DS_SPRITES), HL +    ld (DS_SPRITES), hl 
-  LD HL, bicho_attrib +    ld hl, bicho_attrib 
-  LD (DS_ATTRIBS), HL +    ld (DS_ATTRIBS), hl 
-  LD A, 13 +    ld a, 13 
-  LD (DS_COORD_X), A +    ld (DS_COORD_X), a 
-  LD A, 8 +    ld a, 8 
-  LD (DS_COORD_Y), A +    ld (DS_COORD_Y), a 
-  XOR A +    xor a 
-  LD (DS_NUMSPR), A +    ld (DS_NUMSPR), a 
-  CALL DrawSprite_16x16_LD+    call DrawSprite_16x16_LD
 </code> </code>
  
  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:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite16x16_1.png?640 | Sprite 16x16 impreso en pantalla (sin máscara)}} {{ :cursos:ensamblador:gfx3_sprite16x16_1.png?640 | Sprite 16x16 impreso en pantalla (sin máscara)}}
-\\+\\ 
  
  Ampliando la zona de pantalla con el sprite:  Ampliando la zona de pantalla con el sprite:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sprite16x16_2.png | Ampliación del sprite 16x16 (sin máscara)}} {{ :cursos:ensamblador:gfx3_sprite16x16_2.png | Ampliación del sprite 16x16 (sin máscara)}}
-\\+\\ 
  
-\\ +\\  
-\\+\\ 
 ==== Impresión 16x16 usándo operaciones lógicas ==== ==== Impresión 16x16 usándo operaciones lógicas ====
  
- Podemos convertir la anterior rutina fácilmente en una rutina de impresión con operaciones lógicas añadiendo el **OR (HL)** (o el XOR) antes de la escritura del dato en pantalla.+ Podemos convertir la anterior rutina fácilmente en una rutina de impresión con operaciones lógicas añadiendo el ''or (hl)'' (o el ''XOR'') antes de la escritura del dato en pantalla.
  
  Simplemente, reemplazaríamos las diferentes operaciones de transferencia cambiando:  Simplemente, reemplazaríamos las diferentes operaciones de transferencia cambiando:
  
 <code z80> <code z80>
-   LD A, (DE)         ; Bloque 1: Leemos dato del sprite +    ld a, (de)         ; Bloque 1: Leemos dato del sprite 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; 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
 </code> </code>
  
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 +    or (hl)            ; Realizamos operación lógica 
-   LD (HL),         ; Copiamos dato a pantalla +    ld (hl),         ; 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
 </code> </code>
  
- 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 ''LD''.
  
  
-\\ +\\  
-\\+\\ 
 ==== 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), ahora tendremos que calcularla como BASE+(DS_NUMSPR*64). Esta circunstancia nos ahorra un desplazamiento del pseudoregistro "AL" hacia la derecha (evitamos un **RRA** **RR L**), lo que nos permite hacer la operación de multiplicación con sólo 46 t-estados:+  * 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), ahora tendremos que calcularla como BASE+(DS_NUMSPR*64). Esta circunstancia nos ahorra un desplazamiento del pseudoregistro "AL" hacia la derecha (evitamos un ''RRA'' ''rr l''), lo que nos permite hacer la operación de multiplicación con sólo 46 t-estados:
  
-\\+\\ 
  
  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 +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da 
-   LD AB +    ld ab 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   ADD AC +    add ac 
-   LD EA+    ld ea
  
-   PUSH DE           ; Lo guardamos para luego, lo usaremos para +    push de           ; Lo guardamos para luego, lo usaremos para 
-                     ; calcular la direccion del atributo+                      ; calcular la direccion del atributo
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*64) +    ;;;     direccion = base_sprites + (NUM_SPRITE*64) 
-   ;;; Multiplicamos con desplazamientos, ver los comentarios. +    ;;; Multiplicamos con desplazamientos, ver los comentarios. 
-   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 +    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          ; HL = DS_NUMSPR*64 +    ld h          ; HL = DS_NUMSPR*64 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 64) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   EX DEHL         ; Intercambiamos DE y HL para las OP LOGICAS+    ex dehl         ; Intercambiamos DE y HL para las OP LOGICAS
  
-   ;;; Dibujar 8 scanlines (DE) -> (HL) + bajar scanline y avanzar en SPR +    ;;; 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+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; 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),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; 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+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; 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),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; 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+    djnz drawspr16m_loop1
  
-   ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) +    ; 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+    ; 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 +    ;;;inc h           ; No hay que hacer inc h, lo hizo en el bucle 
-   ;;;LD A        ; No hay que hacer esta prueba, sabemos que +    ;;;ld a        ; No hay que hacer esta prueba, sabemos que 
-   ;;;AND 7           ; no hay salto (es un cambio de bloque) +    ;;;and 7           ; no hay salto (es un cambio de bloque) 
-   ;;;JR NZ, drawsp16_nofix_abajop +    ;;;jr nz, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   AND +    and 
-   JR NZ, drawsp16m_nofix_abajop +    jr nz, drawsp16m_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16m_nofix_abajop +    jr c, drawsp16m_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 drawsp16m_nofix_abajop: drawsp16m_nofix_abajop:
  
-   ;;; Repetir 8 veces (segundos 2 bloques horizontales): +    ;;; 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+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; 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),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; 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+    and (hl)         ; A = A and (hl
-   LD C         ; Nos guardamos el valor del AND +    ld c         ; 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),       ; Imprimimos el dato tras aplicar operaciones logicas +    ld (hl),       ; 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+    djnz drawspr16m_loop2
  
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados.+    ;;; 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+    ;;; 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          ; A = 0 + H = H +    add a          ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL+    ld e           ; DE tiene el offset del attr de HL
  
-   LD A, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar +    ld a, (DS_NUMSPR)  ; Cogemos el numero de sprite a dibujar 
-   LD CA +    ld ca 
-   LD B, 0 +    ld b, 0 
-   ADD HLBC         ; HL = HL+DS_NUMSPR +    add hlbc         ; HL = HL+DS_NUMSPR 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*2 +    add hlbc         ; HL = HL+DS_NUMSPR*2 
-   ADD HLBC         ; HL = HL+DS_NUMSPR*3 +    add hlbc         ; HL = HL+DS_NUMSPR*3 
-   ADD HLBC         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo+    add hlbc         ; HL = HL+HL=(DS_NUMSPR*4) = Origen de atributo
  
-   LDI +    ldi 
-   LDI                ; Imprimimos las 2 primeras filas de atributo+    ldi                ; Imprimimos las 2 primeras filas de atributo
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD A           ; A = L +    ld a           ; 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           ; Guardamos en L (L = L+30 + 2 por LDI=L+32) +    ld e           ; 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 
-   RET                ; porque no necesitamos incrementar HL y DE+    ret                ; porque no necesitamos incrementar HL y DE
 </code> </code>
  
  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, en el código hay 2 de ellas repetidas 8 veces, por lo que en desenrollamiento añadiría 14 veces el código de impresión). Esto puede suponer un problema cuando los programas son grandes y nos acercamos a los límites de la memoria del Spectrum.+  * 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, en el código hay 2 de ellas repetidas 8 veces, por lo que en desenrollamiento añadiría 14 veces el código de impresión). Esto puede suponer un problema cuando los programas son grandes y nos acercamos a los límites de la memoria del Spectrum.
  
  Probemos la rutina con un spriteset de múltiples sprites de 2x2 bloques con su máscara:  Probemos la rutina con un spriteset de múltiples sprites de 2x2 bloques con su máscara:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_sevenup_vertical.png | Sprites a exportar }} {{ :cursos:ensamblador:gfx3_sevenup_vertical.png | Sprites a exportar }}
-\\+\\ 
  
  Exportados con los parámetros adecuados, nos queda el siguiente resultado en forma de array:  Exportados con los parámetros adecuados, nos queda el siguiente resultado en forma de array:
Línea 2031: Línea 2073:
  
 bicho_gfx: bicho_gfx:
- DEFB 247,  8,127,128,251,  4,191, 64, 255,  0,255,  0,248,  7, 31,224 +    DEFB 247,  8,127,128,251,  4,191, 64, 255,  0,255,  0,248,  7, 31,224 
- DEFB 240, 15, 15, 80,240, 15, 15, 80, 240, 15, 15,240,248,  7, 31,224 +    DEFB 240, 15, 15, 80,240, 15, 15, 80, 240, 15, 15,240,248,  7, 31,224 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, 
- DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255, +    DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255, 
- DEFB 251,  4,191, 64,255,  0,255,  0, 248,  7, 31,224,240, 15, 15, 80 +    DEFB 251,  4,191, 64,255,  0,255,  0, 248,  7, 31,224,240, 15, 15, 80 
- DEFB 240, 15, 15, 80,240, 15, 15,240, 248,  7, 31,224,255,  0,255, +    DEFB 240, 15, 15, 80,240, 15, 15,240, 248,  7, 31,224,255,  0,255, 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,253,  2,223, 32 +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,253,  2,223, 32 
- DEFB 247,  8,255,  0,241, 14,143,112, 248,  7,135,120,255,  0,255, +    DEFB 247,  8,255,  0,241, 14,143,112, 248,  7,135,120,255,  0,255, 
- DEFB 253,  2,223, 32,255,  0,255,  0, 252,  3, 15,240,248,  7,  7,168 +    DEFB 253,  2,223, 32,255,  0,255,  0, 252,  3, 15,240,248,  7,  7,168 
- DEFB 248,  7,  7,168,248,  7,  7,248, 252,  3, 15,240,255,  0,255, +    DEFB 248,  7,  7,168,248,  7,  7,248, 252,  3, 15,240,255,  0,255, 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,254,  1,191, 64 +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,255,  0,254,  1,191, 64 
- DEFB 255,  0,255,  0,253,  2, 31,224, 250,  5, 15,240,255,  0,255, +    DEFB 255,  0,255,  0,253,  2, 31,224, 250,  5, 15,240,255,  0,255, 
- DEFB 254,  1,239, 16,253,  2,223, 32, 255,  0,255,  0,252,  3, 15,240 +    DEFB 254,  1,239, 16,253,  2,223, 32, 255,  0,255,  0,252,  3, 15,240 
- DEFB 248,  7,  7,168,248,  7,  7,168, 248,  7,  7,248,252,  3, 15,240 +    DEFB 248,  7,  7,168,248,  7,  7,168, 248,  7,  7,248,252,  3, 15,240 
- DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, +    DEFB 255,  0,255,  0,254,  1,191, 64, 255,  0,223, 32,253,  2,255, 
- DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255,  0+    DEFB 151,104,233, 22,143,112,241, 14, 199, 56,227, 28,255,  0,255,  0
  
 bicho_attrib: bicho_attrib:
- DEFB 70, 71, 67,  3, 70, 71, 67,  3, 70, 71,  3, 67, 70, 71,  3, 67+    DEFB 70, 71, 67,  3, 70, 71, 67,  3, 70, 71,  3, 67, 70, 71,  3, 67
 </code> </code>
  
  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:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_spr16x16_mask.png?640 | Sprite 16x16 con máscara sobre fondo alterno }} {{ :cursos:ensamblador:gfx3_spr16x16_mask.png?640 | Sprite 16x16 con máscara sobre fondo alterno }}
-\\+\\ 
  
  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**.
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_spr16x16_mask2.png | Zoom del sprite 16x16 con máscara sobre fondo alterno }} {{ :cursos:ensamblador:gfx3_spr16x16_mask2.png | Zoom del sprite 16x16 con máscara sobre fondo alterno }}
-\\+\\ 
  
  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:
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_16x16_mask3.png | Sprite 16x16 impreso sin atributos }} {{ :cursos:ensamblador:gfx3_16x16_mask3.png | Sprite 16x16 impreso sin atributos }}
-\\+\\ 
  
  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 "desfavorable" como es este patrón de píxeles alternos.  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 "desfavorable" como es este patrón de píxeles alternos.
Línea 2076: Línea 2118:
  Y es que, como vamos a ver a continuación, cada técnica de impresión tiene una situación de aplicación válida.  Y es que, como vamos a ver a continuación, cada técnica de impresión tiene una situación de aplicación válida.
  
-\\+\\ 
 ===== 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 "bloque a bloque" (en baja resolución), donde todos los sprites (y el mapa de juego) se mueven en la rejilla de 32x24 bloques del Spectrum (juegos de puzzle, rogue-likes, juegos basados en mapas de bloques, etc). La impresión por transferencia directa de datos (sprite -> pantalla) es adecuada para juegos con movimiento "bloque a bloque" (en baja resolución), donde todos los sprites (y el mapa de juego) se mueven en la rejilla de 32x24 bloques del Spectrum (juegos de puzzle, rogue-likes, juegos basados en mapas de bloques, etc).
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_impld.png | Juegos adecuados para impresión por transferencia }} {{ :cursos:ensamblador:gfx3_impld.png | Juegos adecuados para impresión por transferencia }}
 ;#; ;#;
 //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 ''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 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).
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_impor.png | Juegos adecuados para impresión por OR }} {{ :cursos:ensamblador:gfx3_impor.png | Juegos adecuados para impresión por OR }}
 ;#; ;#;
 //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 "inversa" del mismo sobre el sprite) y para poder ser borrados con otra operación 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 "inversa" del mismo sobre el sprite) y para poder ser borrados con otra operación ''XOR''.
  
-\\ +\\  
-\\+\\ 
 **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.
  
-\\+\\ 
 {{ :cursos:ensamblador:gfx3_impmask.png | Juegos adecuados para impresión por máscara }} {{ :cursos:ensamblador:gfx3_impmask.png | Juegos adecuados para impresión por máscara }}
 ;#; ;#;
 //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.
  
- Incluso una opción mucho más recomendable sería crear tantas rutinas específicas como tamaños de sprites tengamos en nuestro juego y hacer una rutina "maestra" (un //wrapper//) que llame a una u otra según el tamaño del Sprite que estemos solicitando imprimir. Un par de comprobaciones sobre el ancho y el alto del Sprite y saltos condicionales con un CALL final a la rutina adecuada resultará muchísimo más óptimo que la rutina que vamos a examinar:+ Incluso una opción mucho más recomendable sería crear tantas rutinas específicas como tamaños de sprites tengamos en nuestro juego y hacer una rutina "maestra" (un //wrapper//) que llame a una u otra según el tamaño del Sprite que estemos solicitando imprimir. Un par de comprobaciones sobre el ancho y el alto del Sprite y saltos condicionales con un call final a la rutina adecuada resultará muchísimo más óptimo que la rutina que vamos a examinar:
  
 <code z80> <code z80>
Línea 2169: Línea 2211:
 DrawSprite_MxN_LD: DrawSprite_MxN_LD:
  
-   ;;; Calcular posicion origen (array sprites) en HL como: +    ;;; Calcular posicion origen (array sprites) en HL como: 
-   ;;;     direccion = base_sprites + (NUM_SPRITE*ANCHO*ALTO)+    ;;;     direccion = base_sprites + (NUM_SPRITE*ANCHO*ALTO)
  
-   ;;;; Multiplicamos ancho por alto (en bloques) +    ;;;; Multiplicamos ancho por alto (en bloques) 
-   LD A, (DS_WIDTH) +    ld a, (DS_WIDTH) 
-   LD CA +    ld ca 
-   LD A, (DS_HEIGHT) +    ld a, (DS_HEIGHT) 
-   RLCA               ; Multiplicamos por 8, necesitamos +    rlca               ; Multiplicamos por 8, necesitamos 
-   RLCA               ; la altura en pixeles (FILAS*8) +    rlca               ; la altura en pixeles (FILAS*8) 
-   RLCA               ; Y la guardamos porque la necesitaremos: +    rlca               ; Y la guardamos porque la necesitaremos: 
-   LD (drawsp_height), A+    ld (drawsp_height), a
  
-   ;;; Multiplicamos Ancho_bloques * Alto_pixeles: +    ;;; Multiplicamos Ancho_bloques * Alto_pixeles: 
-   LD BA +    ld ba 
-   XOR A              ; A = 0+    xor a              ; A = 0
 drawsp_mul1: drawsp_mul1:
-   ADD A          ; A = A + C   (B veces)  = B*C +    add a          ; A = A + C   (B veces)  = B*C 
-   DJNZ drawsp_mul1   ; B veces -> A = A*C = Ancho * Alto +    djnz drawsp_mul1   ; B veces -> A = A*C = Ancho * Alto 
-                      ; Ahora A = Ancho*Alto (maximo 255!!!)+                        ; Ahora A = Ancho*Alto (maximo 255!!!)
  
-   ;;; Multiplicamos DS_NUMSPR por (Ancho_bloques*Alto_pixeles) +    ;;; Multiplicamos DS_NUMSPR por (Ancho_bloques*Alto_pixeles) 
-   LD B           ; Repetimos Ancho * Alto veces +    ld b           ; Repetimos Ancho * Alto veces 
-   LD HL, 0 +    ld hl, 0 
-   LD D           ; HL = 0 +    ld d           ; HL = 0 
-   LD A, (DS_NUMSPR) +    ld a, (DS_NUMSPR) 
-   LD E           ; DE = DS_NUMSPR+    ld e           ; DE = DS_NUMSPR
 drawsp_mul2: drawsp_mul2:
-   ADD HLDE         ; HL = HL+DS_NUMSPR +    add hlde         ; HL = HL+DS_NUMSPR 
-   DJNZ drawsp_mul2   ; Sumamos HL+DE B veces = DS_NUMSPR*B+    djnz drawsp_mul2   ; Sumamos HL+DE B veces = DS_NUMSPR*B
  
-                      ; guardamos el valor de ancho*alto_pixeles*NUMSPR +                        ; guardamos el valor de ancho*alto_pixeles*NUMSPR 
-   LD (drawsp_width_by_height), HL+    ld (drawsp_width_by_height), hl
  
-   ;;; Calculamos direccion origen copia en el sprite +    ;;; Calculamos direccion origen copia en el sprite 
-   LD BC, (DS_SPRITES) +    ld bc, (DS_SPRITES) 
-   ADD HLBC         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) +    add hlbc         ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR*ANCHO*ALTO) 
-                      ; HL contiene la direccion de inicio en el sprite+                       ; HL contiene la direccion de inicio en el sprite
  
-   ;;; Calculamos las coordenadas destino de pantalla en DE:+    ;;; Calculamos las coordenadas destino de pantalla en DE:
  
-   LD BC, (DS_COORD_X)     ; B = Y,  C = X +    ld bc, (DS_COORD_X)     ; B = Y,  C = X 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da 
-   LD AB +    ld ab 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   ADD AC +    add ac 
-   LD EA +    ld ea 
-   PUSH DE            ; Lo guardamos para luego, lo usaremos para +    push de            ; Lo guardamos para luego, lo usaremos para 
-                      ; calcular la direccion del atributo +                       ; calcular la direccion del atributo 
-   EX DEHL          ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl          ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
  
-   ;;; Bucle de impresión vertical +    ;;; 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           ; Contador del bucle exterior del bucle+    ld b           ; Contador del bucle exterior del bucle
  
 drawsp_yloop: drawsp_yloop:
-   LD C           ; Nos guardamos el contador+    ld c           ; Nos guardamos el contador
  
-   ;;; Bucle de impresion horizontal +    ;;; Bucle de impresion horizontal 
-   LD A, (DS_WIDTH) +    ld a, (DS_WIDTH) 
-   LD BA+    ld ba
  
-   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),         ; Copiamos dato a pantalla +    ld (hl),         ; 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 +    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 +    ;;; Avanzamos al siguiente scanline de pantalla 
-   INC H +    inc h 
-   LD AH +    ld ah 
-   AND +    and 
-   JR NZ, drawspNM_nofix +    jr nz, drawspNM_nofix 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawspNM_nofix +    jr c, drawspNM_nofix 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 drawspNM_nofix: drawspNM_nofix:
  
-   LD BC +    ld bc 
-   DJNZ drawsp_yloop  ; Repetimos "alto_en_pixeles" veces+    djnz drawsp_yloop  ; Repetimos "alto_en_pixeles" veces
  
-   ;;; Aqui hemos dibujado todo el sprite, vamos a los attributos+    ;;; 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+    ;;; Considerar el dibujado de atributos (Si DS_ATTRIBS=0 -> ret
-   LD  A,[DS_ATTRIBS+1]     ; para obtener la parte alta de la direccion +    ld  a,[DS_ATTRIBS+1]     ; para obtener la parte alta de la direccion 
-   OR                     ; para comprobar si es 0 +    or                     ; 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. +    ;;; Calcular posicion destino en area de atributos en DE. 
-   LD A           ; Codigo de Get_Attr_Offset_From_Image +    ld a           ; Codigo de Get_Attr_Offset_From_Image 
-   RRCA               ; Obtenemos dir de atributo a partir de +    rrca               ; Obtenemos dir de atributo a partir de 
-   RRCA               ; dir de zona de imagen. +    rrca               ; dir de zona de imagen. 
-   RRCA               ; Nos evita volver a obtener X e Y +    rrca               ; Nos evita volver a obtener X e Y 
-   AND 3              ; y hacer el calculo completo de la +    and 3              ; y hacer el calculo completo de la 
-   OR $58             ; direccion en zona de atributos +    or $58             ; direccion en zona de atributos 
-   LD DA +    ld da 
-   LD E           ; DE tiene el offset del attr de HL +    ld e           ; 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 +    ; Recuperamos el valor de ancho_caracteres * alto_en_pixeles * NUMSPR 
-   ; para ahorrarnos repetir otra vez dos multiplicaciones: +    ; 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 +    ;;; HL = ANCHO_BLOQUES*ALTO_PIXELES*NUMSPR 
-   ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> dividir /8 +    ;;; El Alto lo necesitamos en BLOQUES, no en píxeles-> dividir /8 
-   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 +    ;;;; HL = ANCHO_BLOQUES*ALTO_BLOQUES*NUMSPR 
-   LD CL +    ld cl 
-   LD BH +    ld bh 
-   LD HL, (DS_ATTRIBS) +    ld hl, (DS_ATTRIBS) 
-   ADD HLBC         ; HL = Base_Atributos + (DS_NUMSPR*ALTO*ANCHO)+    add hlbc         ; 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 BA+    ld ba
  
-   ;;; Bucle impresion vertical de atributos+    ;;; Bucle impresion vertical de atributos
 drawsp_attyloop: drawsp_attyloop:
-   LD CB+    ld cb
  
-   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 BA+    ld ba
  
-   ;;; Bucle impresion horizontal de atributos+    ;;; 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),         ; Escribir atributo +    ld (de),         ; Escribir atributo 
-   INC E +    inc e 
-   DJNZ  drawsp_attxloop+    djnz  drawsp_attxloop
  
-   POP DE             ; Recuperamos inicio de linea de atributos+    pop de             ; Recuperamos inicio de linea de atributos
  
-   ;;; Avance diferencial a la siguiente linea de atributos +    ;;; Avance diferencial a la siguiente linea de atributos 
-   LD AE +    ld ae 
-   ADD A, 32 +    add a, 32 
-   LD EA +    ld ea 
-   JR NC, drawsp_attrab_noinc +    jr nc, drawsp_attrab_noinc 
-   INC D+    inc d
 drawsp_attrab_noinc: drawsp_attrab_noinc:
  
-   LD BC +    ld bc 
-   DJNZ drawsp_attyloop+    djnz drawsp_attyloop
  
-   RET+    ret
  
 drawsp_height          DB 0 drawsp_height          DB 0
Línea 2349: Línea 2391:
  
 <code z80> <code z80>
-ORG 35000+    ORG 35000
  
-  LD HL, bicho_gfx +    ld hl, bicho_gfx 
-  LD (DS_SPRITES), HL +    ld (DS_SPRITES), hl 
-  LD HL, bicho_attrib +    ld hl, bicho_attrib 
-  LD (DS_ATTRIBS), HL +    ld (DS_ATTRIBS), hl 
-  LD A, 13 +    ld a, 13 
-  LD (DS_COORD_X), A +    ld (DS_COORD_X), a 
-  LD A, 8 +    ld a, 8 
-  LD (DS_COORD_Y), A +    ld (DS_COORD_Y), a 
-  LD A, 2 +    ld a, 2 
-  LD (DS_WIDTH), A +    ld (DS_WIDTH), a 
-  LD A, 2 +    ld a, 2 
-  LD (DS_HEIGHT), A +    ld (DS_HEIGHT), a 
-  XOR A +    xor a 
-  LD (DS_NUMSPR), A+    ld (DS_NUMSPR), 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:
 </code> </code>
  
- 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 ''OR'' ''XOR'' contra (HL).
  
  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 //Animaciones//, pero podemos distinguir inicialmente las 4 siguientes:  Hay diferentes técnicas de borrado de sprites, en las que profundizaremos cuando tratemos las //Animaciones//, pero podemos distinguir inicialmente las 4 siguientes:
  
-\\ +\\  
-\\+\\ 
 **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 "bloque vacío" de fondo que sirva de tile con el que sobreescribir con LDs una posición de sprite para borrarlo.  En juegos de fondo monocolor (típicamente fondo negro) se suele reservar el bloque 0 del SpriteSet para alojar un "bloque vacío" de fondo que sirva de tile con el que sobreescribir con LDs una posición de sprite para borrarlo.
  
-\\ +\\  
-\\+\\ 
 **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:
 </code> </code>
  
-\\ +\\  
-\\+\\ 
 **Borrado (e impresión) con XOR** **Borrado (e impresión) con XOR**
  
- Cuando un sprite ha sido dibujado con la operación XOR, podemos borrarlo realizando otra impresión en la misma posición mediante la misma operación. Esto es así porque la operación XOR es reversible.+ Cuando un sprite ha sido dibujado con la operación ''XOR'', podemos borrarlo realizando otra impresión en la misma posición mediante la misma operación. Esto es así porque la operación ''XOR'' es reversible.
  
- Veamos la tabla de verdad de la operación XOR:+ Veamos la tabla de verdad de la operación ''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 ''XOR'':
  
 <code> <code>
Línea 2476: Línea 2518:
 </code> </code>
  
- La impresión de un sprite con XOR produce una especie de "negativo" del Sprite en la pantalla allá donde haya píxeles de fondo activos, por lo que esto, unido a la facilidad de borrado, lo hace ideal para la impresión de cursores y punteros.+ La impresión de un sprite con ''XOR'' produce una especie de "negativo" del Sprite en la pantalla allá donde haya píxeles de fondo activos, por lo que esto, unido a la facilidad de borrado, lo hace ideal para la impresión de cursores y punteros.
  
  
-\\ +\\  
-\\+\\ 
 **Almacenamiento en buffer temporal** **Almacenamiento en buffer temporal**
  
Línea 2507: Línea 2549:
 ;------------------------------------------------------------- ;-------------------------------------------------------------
  
-   ;;; Recogida de datos y parametros +    ;;; Recogida de datos y parametros 
-   (...) +    (...) 
-   LD IX, (DS_TEMPBUF) +    ld ix, (DS_TEMPBUF) 
-   (...)+    (...)
  
-   ;;; Bucle de impresion del Sprite: +    ;;; 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),       ; NUEVO: Lo almacenamos en el array temporal +    ld (ix),       ; NUEVO: Lo almacenamos en el array temporal 
-   INC IX           ; NUEVO: Incrementamos IX (puntero array fondo)+    inc ix           ; NUEVO: Incrementamos IX (puntero array fondo)
  
-                    ; Ya podemos imprimir el sprite +                        ; Ya podemos imprimir el sprite 
-   LD A, (DE)       ; Tomamos el dato del sprite +    ld a, (de)       ; Tomamos el dato del sprite 
-   LD (HL),       ; Establecemos el valor en videomemoria +    ld (hl),       ; 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+    djnz drawsp8x8_loopLD
  
-   (...) +    (...) 
-   ;;; Impresion de atributos +    ;;; Impresion de atributos 
-   LD A, (DE)       ; NUEVO: Leemos el atributo actual +    ld a, (de)       ; NUEVO: Leemos el atributo actual 
-   LD (IX),       ; NUEVO: Lo guardamos en el array temporal+    ld (ix),       ; 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 +    ld (de), a 
-   RET+    ret
  
 DS_TEMPBUF   DEFW  0 DS_TEMPBUF   DEFW  0
 </code> </code>
  
- El registro IX no es especialmente rápido (10 t-estados el **INC IX** y 19 t-estados el **LD (IX), A** **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 ''inc ix'' y 19 t-estados el ''ld (ix), a'' ''ld (ix+0), a'', pero es más rápido utilizarlo que escribir y ejecutar una rutina adicional para almacenar el fondo.
  
  Cuando necesitemos borrar el sprite, bastará con recuperar el fondo que había antes de imprimirlo, es decir, volcar el contenido del buffer temporal en la posición del sprite a borrar. Para realizar este volcado podemos llamar a una de las funciones de volcado de Sprites basada en transferencias ya que el buffer del fondo en memoria se corresponde con un spriteset de 1 sólo elemento (DS_NUMSPR=0), sin máscara. Las rutinas impresión de 8x8, 16x16 o genéricas basadas en LD servirían para recuperar el contenido del fondo usando como origen el buffer temporal donde lo hemos almacenado.  Cuando necesitemos borrar el sprite, bastará con recuperar el fondo que había antes de imprimirlo, es decir, volcar el contenido del buffer temporal en la posición del sprite a borrar. Para realizar este volcado podemos llamar a una de las funciones de volcado de Sprites basada en transferencias ya que el buffer del fondo en memoria se corresponde con un spriteset de 1 sólo elemento (DS_NUMSPR=0), sin máscara. Las rutinas impresión de 8x8, 16x16 o genéricas basadas en LD servirían para recuperar el contenido del fondo usando como origen el buffer temporal donde lo hemos almacenado.
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 "restore" del fondo y antes del dibujado del nuevo sprite.  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 "restore" del fondo y antes del dibujado del nuevo sprite.
  
-\\+\\ 
 ===== 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 (''DS_SPRITES'' ''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:
  
 <code z80> <code z80>
-   LD BC, (DS_SPRITES)+    ld bc, (DS_SPRITES)
 </code> </code>
  
Línea 2574: Línea 2616:
  
 <code z80> <code z80>
-   LD BC, Tabla_Sprites+    ld bc, Tabla_Sprites
 </code> </code>
  
- Establecer BC a un valor inmediato (**LD BC, NN**) tiene un coste de 10 t-estados, frente a los 20 t-estados que cuesta establecerlo desde una posición de memoria (**LD BC, (NN)**). De esta forma evitamos cargar desde memoria las direcciones de los tilesets y también tener que establecerlos al inicio del programa.+ Establecer BC a un valor inmediato (''ld bc, NN'') tiene un coste de 10 t-estados, frente a los 20 t-estados que cuesta establecerlo desde una posición de memoria (''ld bc, (NN)''). De esta forma evitamos cargar desde memoria las direcciones de los tilesets y también tener que establecerlos al inicio del programa.
  
  
-\\+\\ 
 **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 ''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.
  
  Normalmente no deberíamos requerir de esta operación porque lo normal es estar sincronizado con las interrupciones y realizar el redibujado de los sprites en la ISR o al volver de la ISR de atención a la ULA.  Normalmente no deberíamos requerir de esta operación porque lo normal es estar sincronizado con las interrupciones y realizar el redibujado de los sprites en la ISR o al volver de la ISR de atención a la ULA.
  
- No obstante, si no estamos sincronizados con las interrupciones o si estamos usando IM1, es un dato interesante que nos puede evitar la ejecución de código ISR en medio de la ejecución de la rutina.+ No obstante, si no estamos sincronizados con las interrupciones o si estamos usando im1, es un dato interesante que nos puede evitar la ejecución de código ISR en medio de la ejecución de la rutina.
  
  Trataremos este tema con más detalle en el capítulo dedicado a //Animaciones//, en el apartado sobre //Evitar Flickering o Parpadeos//.  Trataremos este tema con más detalle en el capítulo dedicado a //Animaciones//, en el apartado sobre //Evitar Flickering o Parpadeos//.
  
  
-\\ +\\  
-\\+\\ 
 **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 (''POP'').
  
- Apuntando SP a nuestro sprite, un simple **POP DE** carga en E y D (en ese orden) los 2 bytes apuntados por SP e incrementa la dirección contenida en DE en 2 unidades, por lo que realizamos 2 operaciones de lectura y 2 incrementos (de 16 bits) en 1 byte de código y 10 t-estados, frente a los 7+7+6+6 = 26 t-estados que cuestan las operaciones LD+INC por separado. Eso implica leer en E y D dos bytes de datos del sprite o el byte de máscara y un byte de datos.+ Apuntando SP a nuestro sprite, un simple ''pop de'' carga en E y D (en ese orden) los 2 bytes apuntados por SP e incrementa la dirección contenida en DE en 2 unidades, por lo que realizamos 2 operaciones de lectura y 2 incrementos (de 16 bits) en 1 byte de código y 10 t-estados, frente a los 7+7+6+6 = 26 t-estados que cuestan las operaciones LD+INC por separado. Eso implica leer en E y D dos bytes de datos del sprite o el byte de máscara y un byte de datos.
  
- 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"CALL" (así como un PUSH) ejecutado con SP apuntando al Sprite provocaría la corrupción del mismo al introducirse en la pila la dirección de memoria del RET o RETI para la ISR.+ 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'' ''call'' (así como un ''PUSH'') ejecutado con SP apuntando al Sprite provocaría la corrupción del mismo al introducirse en la pila la dirección de memoria del ''RET'' ''RETI'' para la ISR.
  
  Veamos el pseudocódigo de una rutina que use la pila de esta forma:  Veamos el pseudocódigo de una rutina que use la pila de esta forma:
  
 <code> <code>
-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
 </code> </code>
  
- A continuación vamos a ver una versión "reducida" (sin impresión de atributos, y con un bucle de 16 iteraciones sin desenrollar) de DrawSprite_16x16 que utiliza la pila para recuperar los datos del sprite mediante **POP DE**. Esto implica la lectura de 2 bytes en una sóla instrucción y el doble incremento de DE para apuntar a los 2 siguientes elementos. + A continuación vamos a ver una versión "reducida" (sin impresión de atributos, y con un bucle de 16 iteraciones sin desenrollar) de ''DrawSprite_16x16'' que utiliza la pila para recuperar los datos del sprite mediante ''pop de''. Esto implica la lectura de 2 bytes en una sóla instrucción y el doble incremento de DE para apuntar a los 2 siguientes elementos.
- +
- En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, para poder recuperarlo antes del RET. Una vez calculada la dirección de origen del sprite a dibujar, realizaremos la copia de SP en DS_TEMP_SP (variable de memoria) y cargaremos el valor de HL en SP:+
  
 + En la rutina guardaremos el valor de SP en una variable temporal, antes de modificarlo, para poder recuperarlo antes del ret. Una vez calculada la dirección de origen del sprite a dibujar, realizaremos la copia de SP en ''DS_TEMP_SP'' (variable de memoria) y cargaremos el valor de HL en SP:
  
 <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). +    ;;; Deshabilitar interrupciones (vamos a cambiar SP). 
-   DI+    di
  
-   ; Guardamos en BC la pareja (x,y) -> B=COORD_Y y C=COORD_X +    ; 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: +    ;;; Calculamos las coordenadas destino de pantalla en DE: 
-   LD AB +    ld ab 
-   AND $18 +    and $18 
-   ADD A, $40 +    add a, $40 
-   LD DA +    ld da 
-   LD AB +    ld ab 
-   AND +    and 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   RRCA +    rrca 
-   ADD AC +    add ac 
-   LD EA+    ld ea
  
     ;;; Guardamos el valor actual y correcto de SP     ;;; Guardamos el valor actual y correcto de SP
-   LD (DS_TEMP_SP), SP+    ld (DS_TEMP_sp), sp
  
-   ;;; Calcular posicion origen (array sprites) en HL: +    ;;; 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 +    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 +    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          ; HL = DS_NUMSPR*32 +    ld h          ; HL = DS_NUMSPR*32 
-   ADD HLBC        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) +    add hlbc        ; HL = BC + HL = DS_SPRITES + (DS_NUMSPR * 32) 
-                     ; HL contiene la direccion de inicio en el sprite+                      ; HL contiene la direccion de inicio en el sprite
  
-   ;;; Establecemos el valor de SP apuntando al sprite +    ;;; Establecemos el valor de SP apuntando al sprite 
-   LD SPHL+    ld sphl
  
-   EX DEHL         ; Intercambiamos DE y HL (DE=origen, HL=destino)+    ex dehl         ; Intercambiamos DE y HL (DE=origen, HL=destino)
  
-   ;;; Repetir 8 veces (primeros 2 bloques horizontales): +    ;;; 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),         ; Ahora imprimimos los 2 bytes en pantalla +    ld (hl),         ; Ahora imprimimos los 2 bytes en pantalla 
-   INC L +    inc l 
-   LD (HL), D +    ld (hl), d 
-   DEC L+    dec l
  
-   ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) +    ; 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+    ; 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           ; Ajustar tercio/bloque si necesario +    ld a           ; Ajustar tercio/bloque si necesario 
-   AND +    and 
-   JR NZ, drawsp16_nofix_abajop +    jr nz, drawsp16_nofix_abajop 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
  
 drawsp16_nofix_abajop: drawsp16_nofix_abajop:
-   DJNZ drawsp16x16_stack_loop+    djnz drawsp16x16_stack_loop
  
-   ;;; En este punto, los 16 scanlines del sprite estan dibujados.+    ;;; En este punto, los 16 scanlines del sprite estan dibujados.
  
-   ;;; Recuperamos el valor de SP +    ;;; Recuperamos el valor de SP 
-   LD HL, (DS_TEMP_SP) +    ld hl, (DS_TEMP_SP) 
-   LD SPHL+    ld sphl
  
-   EI                  ; Habilitar de nuevo interrupciones +    ei                  ; Habilitar de nuevo interrupciones 
-   RET+    ret
 </code> </code>
  
- 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 //Scanline_Abajo_HL// entre ambos. El bucle convertido en dos quedaría de la siguiente forma:+ 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 //Scanline_Abajo_HL// entre ambos. El bucle convertido en dos quedaría de la siguiente forma:
  
 <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 +                       ; Y ademas no hace falta sumar 2 a DE 
-   LD (HL),         ; Ahora imprimimos los 2 bytes en pantalla +    ld (hl),         ; 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 +    ld (hl), d 
-   DEC L +    dec l 
-   INC H +    inc h 
-   DJNZ drawsp16x16_stack_loop1+    djnz drawsp16x16_stack_loop1
  
-   ; Avanzamos HL 1 scanline (codigo de incremento de HL en 1 scanline) +    ; 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 +    ; 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 +    ; No hay que comprobar si (H & $07) == 0 porque sabemos que lo es 
-   LD AL +    ld al 
-   ADD A, 32 +    add a, 32 
-   LD LA +    ld la 
-   JR C, drawsp16_nofix_abajop +    jr c, drawsp16_nofix_abajop 
-   LD AH +    ld ah 
-   SUB +    sub 
-   LD HA+    ld ha
 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 +                       ; Y ademas no hace falta sumar 2 a DE 
-   LD (HL),         ; Ahora imprimimos los 2 bytes en pantalla +    ld (hl),         ; 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 +    ld (hl), d 
-   DEC L +    dec l 
-   INC H +    inc h 
-   DJNZ drawsp16x16_stack_loop2+    djnz drawsp16x16_stack_loop2
 </code> </code>
  
-\\+La opción inversa sería utilizar ''PUSH'' para escribir en la VRAM (en vez de POP para leer el dato a escribir), pero tiene la pega de que sólo podríamos hacer escritura directa, y no por ejemplo un ''OR'' o ''XOR'' del dato en la pantalla. 
 + 
 +\\  
 +\\  
 +**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: 
 + 
 +<code> 
 +ABC 
 +DEF 
 +GHI 
 +</code> 
 + 
 +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: 
 + 
 +<code> 
 +Linea 0:  8_pixeles_linea_0_A    8_pixeles_linea_0_B     8_pixeles_linea_0_C 
 +Linea 1:  8_pixeles_linea_1_A    8_pixeles_linea_1_B     8_pixeles_linea_1_C 
 +... 
 +Linea 8:  8_pixeles_linea_0_D    8_pixeles_linea_0_E     8_pixeles_linea_0_F 
 +Linea 9:  8_pixeles_linea_1_D    8_pixeles_linea_1_E     8_pixeles_linea_1_F 
 +... 
 +Linea 22:  8_pixeles_linea_6_G    8_pixeles_linea_6_H     8_pixeles_linea_6_I 
 +Linea 23:  8_pixeles_linea_7_G    8_pixeles_linea_7_H     8_pixeles_linea_7_I 
 +</code> 
 + 
 +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 
 +</code> 
 + 
 +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:ensamblador:gfx3_sprite16x16_mask.asm|Impresión de Sprite 16x16 con máscara}}   * {{cursos:ensamblador:gfx3_sprite16x16_mask.asm|Impresión de Sprite 16x16 con máscara}}
   * {{cursos:ensamblador:gfx3_sprite16x16_mask.tap|Tap del ejemplo anterior}}   * {{cursos:ensamblador:gfx3_sprite16x16_mask.tap|Tap del ejemplo anterior}}
-\\+\\ 
 ===== Enlaces ===== ===== Enlaces =====
  
  • cursos/ensamblador/gfx3_sprites_lowres.1704558935.txt.gz
  • Última modificación: 06-01-2024 16:35
  • por sromero