0x02 Ensamblador ZX Spectrum Marciano – Pintando UDG
En este capítulo de Ensamblador ZX Spectrum Marciano, vamos empezar a dibujar usando UDG. El mapa de caracteres del ZX Spectrum está compuesto por doscientos cincuenta y seis valores, de los cuales podemos redefinir veintiuno, en concreto los que se encuentran entre el $90 (144) y el $A4 (164), ambos inclusive.
Antes de empezar, creamos una carpeta que se llame Paso02 y copiamos el arhivo Var.asm (dónde definimos los gráficos que vamos a usar) desde la carpeta Paso01.
Tabla de contenidos
- ¿Dónde están los UDG?
- Pintamos nuestros UDG
- Cargamos los UDG de los enemigos
- Ensamblador ZX Spectrum, conclusión
- Enlaces de interés
¿Dónde están los UDG?
El valor de la dirección de memoria $5C7B contiene la dirección de memoria donde están los gráficos definidos por el usuario, por lo que lo único que tenemos que hacer es cargar en esa dirección de memoria, la dirección dónde están definidos nuestros gráficos. Una vez que lo tengamos, al pintar con RST $10 cualquier carácter comprendido entre $90 y $A4, pintara los gráficos que hemos definido.
Vamos a crear un nuevo fichero llamado Const.asm y vamos a añadir la línea siguiente:
; Dirección de memoria donde se cargan los gráficos definidos por el usuario.
UDG: EQU $5c7b
En esta constante guardamos la dirección de memoria dónde cargaremos la dirección dónde están nuestros gráficos.
Pintamos nuestros UDG
Es el momento de hacer nuestra primera prueba, vamos a pintar los UDG. Creamos un fichero llamado Main.asm y vamos a añadir las líneas siguientes:
org $5dad
Main:
ld a, $90
ld b, $15
Loop:
push af
rst $10
pop af
inc a
djnz Loop
ret
end Main
Lo primero que hacemos es indicar dónde se va a cargar el programa, ORG $5DAD. El programa lo cargamos en la posición $5DAD (23981), ya que Batalla espacial va a ser un programa compatible con modelos 16K.
La siguiente línea es una etiqueta, Main, el punto de entrada del programa.
Lo siguiente que hacemos es cargar 144 en A, LD A, $90, y 21 en B, LD B, $15, para pintar desde el carácter 144 al 164, haciendo un total de veintiún caracteres.
El siguiente paso es hacer el bucle de veintiuna iteraciones, empezando con la etiqueta del mismo, Loop, y a continuación preservamos en la pila el valor del registro A, PUSH AF, lo cual es muy importante ya que la siguiente instrucción, RST $10, imprime en pantalla el carácter cuyo código esté cargado en el registro A, y luego modifica el valor de dicho registro. Acto seguido recuperamos de la pila el valor del registro A, POP AF.
A continuación, incrementamos A, INC A, para que apunte al siguiente carácter y decrementamos B y saltamos a Loop si no ha llegado a cero, DJNZ Loop. Por último volvemos al Basic, RET.
Con la última línea le indicamos a PASMO que debe incluir en el cargador Basic una llamada a la dirección de memoria donde se encuentra la etiqueta Main.
Es el momento de compilar y ver los resultados en el emulador.
pasmo --name Marciano --tapbas Main.asm Maciano.tap --public
Pero, ¿hemos pintado nuestros gráficos?
Como podemos ver, hemos pintado las letras mayúsculas de la A a la U, esto es debido a que en ningún momento hemos indicado dónde están nuestros gráficos.
Seguimos en el archivo Main.asm, justo debajo de la etiqueta Main añadimos las líneas siguientes:
ld hl, udgsCommon
ld (UDG), hl
Cargamos en HL la dirección de memoria dónde están nuestros gráficos, LD HL, udgsCommon, y luego cargamos ese valor en la dirección de memoria en la que hay que indicar dónde están nuestros gráficos, LD (UDG), HL.
Dado que tanto udgsCommon, como UDG no están definidas en el fichero Main.asm, justo detrás de la instrucción RET hay que añadir los includes para los ficheros Const.asm y Var.asm.
include "Const.asm"
include "Var.asm"
Ahora sí, podemos volver a compilar el programa, cargarlo en el emulador y ver nuestros gráficos en pantalla.
Mucho mejor, ¿verdad? Pero, hemos pintado veintiún gráficos: la nave, el disparo, la explosión, el marco, el carácter vacío, los cuatro gráficos del enemigo uno, y dos gráficos del enemigo dos. ¿Cómo vamos a pintar los otros dos gráficos del enemigo dos y los gráficos de los veintiocho enemigos restantes?
Cargamos los UDG de los enemigos
Si observáis la definición de los gráficos, la primera etiqueta se llama udgsCommon, y esto debería darnos una pista de como lo vamos a hacer. Como UDG comunes tenemos definidos quince gráficos (nave, disparo, explosión, marco y blanco), por lo que vamos a definir treinta y dos bytes para poder ir volcando en ellos los gráficos de los enemigos; lo vamos a hacer así porque los enemigos son uno por nivel, y la operación de volcado solo la tenemos que hacer una vez, justo con el cambio de nivel.
En el archivo Var.asm, justo por encima de la etiqueta udgsEnemiesLeve1 añadimos las siguientes líneas:
udgsExtension:
db $00, $00, $00, $00, $00, $00, $00, $00 ; $9f Left/Up
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a0 Rigth/Up
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a1 Left/Down
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a2 Rigth/Down
En este bloque de memoria es dónde vamos a ir volcando los gráficos de los enemigos, dependiendo del nivel en el que nos encontremos.
Probad a compilar ahora y observad que pinta. ¿Faltan los gráficos del enemigo uno verdad? Está pintando udgsExtension.
Creamos un nuevo archivo, Graph.asm, y vamos a implementar en él la rutina que carga en udgsExtension los gráficos de los enemigos de cada nivel, dato que recibe en A.
Para calcular la dirección de memoria donde se encuentran los gráficos, vamos a multiplicar el nivel por treinta y dos (bytes que ocupan los gráficos) y el resultado se lo vamos a sumar a la dirección de memoria donde se encuentran los gráficos del primer enemigo.
LoadUdgsEnemies:
dec a
ld h, $00
ld l, a
Dado que los niveles van de uno a treinta, decrementamos A, DEC A, para que no sume un nivel de más (si el nivel es cero, tiene que sumar cero veces a udgsEnemies, si es dos una vez, etc.).
Lo siguiente es cargar el nivel en HL, para lo cual cargamos cero en H, LD H, $00, y el nivel en L, LD L, A.
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
Multiplicamos el nivel por treinta y dos, sumando HL a si mismo cinco veces, ADD HL, HL. La primera suma es igual a multiplicar por dos, la segunda por cuatro y las siguientes por ocho, por dieciséis, y por treinta y dos.
ld de, udgsEnemiesLevel1
add hl, de
ld de, udgsExtension
ld bc, $20
ldir
ret
Por último, cargamos la dirección de los gráficos del enemigo uno en DE, LD DE, udgsEnemiesLevel1, y se lo sumamos a HL, ADD HL, DE, cargamos la dirección de la extensión de udgs en DE, LD DE, udgsExtension, cargamos en BC en número de bytes que vamos a cargar en udgsExtension, LD BC, $20, y cargamos los treinta y dos bytes de los gráficos del enemigo del nivel a udgsExtension, LDIR. Finalmente salimos, RET.
El aspecto final de la rutina es el siguiente:
; ----------------------------------------------------------------------------
; Carga los gráficos definidos por el usuario relativos a los enemigos
;
; Entrada: A -> Nivel de 1 a 30
;
; Altera el valor de los registros A, BC, DE y HL
; ----------------------------------------------------------------------------
LoadUdgsEnemies:
dec a ; Decrementa A para que no sume un nivel de más
ld h, $00
ld l, a ; Carga en HL el nivel
add hl, hl ; Multiplica por 2
add hl, hl ; por 4
add hl, hl ; por 8
add hl, hl ; por 16
add hl, hl ; por 32
ld de, udgsEnemiesLevel1 ; Carga la dirección de los gráficos del
; enemigo 1 en DE
add hl, de ; Lo suma a HL
ld de, udgsExtension ; Carga en DE la dirección de la extensión
ld bc, $20 ; Carga en BC el número de bytes a copiar, 32
ldir ; Copia los bytes del enemigo en los de extensión
ret
Y ahora vamos a probar la nueva rutina, para lo cual vamos a editar el archivo Main.asm, empezando por cambiar la instrucción LD B, $15, justo encima de la etiqueta Loop, y la dejamos como sigue, para imprimir los quince primeros UDG, los comunes:
ld b, $0f
El resto lo vamos a implementar entre la instrucción DJNZ Loop y la instrucción RET.
ld a, $01
ld b, $1e
Cargamos en A el nivel uno, LD A, $01, y en B el número de niveles totales (treinta), LD B, $1E. Implementamos un bucle para pintar los enemigos de los treinta niveles.
Loop2:
push af
push bc
call LoadUdgsEnemies
Preservamos los valores de AF, PUSH AF, y de BC, PUSH BC, ya que usamos A y B para controlar que enemigos pintamos y las iteraciones del bucle. A continuación, llamamos a la rutina que carga los gráficos del enemigo del nivel en udgsExtension, CALL LoadUdgsEnemies.
ld a, $9f
rst $10
ld a, $a0
rst $10
ld a, $a1
rst $10
ld a, $a2
rst $10
Los caracteres correspondientes a los gráficos de los enemigos son $9F, $A0, $A1 y $A2; los vamos cargando en A, LD A, $9F, y pintando, RST $10. Repetimos la operación con $A0, $A1 y $A2.
pop bc
pop af
inc a
djnz Loop2
Recuperamos el valor de BC, POP BC, de AF, POP AF, incrementamos A para pasar al siguiente nivel, INC A, y repetimos hasta que B sea 0, DJNZ Loop2.
Por último, a final del fichero y antes de END Main, incluimos el fichero Graph.asm.
include "Graph.asm"
El código final de Main.asm es el siguiente:
org $5dad
Main:
ld hl, udgsCommon
ld (UDG), hl
ld a, $90
ld b, $0f
Loop:
push af
rst $10
pop af
inc a
djnz Loop
ld a, $01
ld b, $1e
Loop2:
push af
push bc
call LoadUdgsEnemies
ld a, $9f
rst $10
ld a, $a0
rst $10
ld a, $a1
rst $10
ld a, $a2
rst $10
pop bc
pop af
inc a
djnz Loop2
ret
include "Const.asm"
include "Var.asm"
include "Graph.asm"
end Main
Compilamos y cargamos en el emulador; ya pintamos todos nuestros gráficos.
Ensamblador ZX Spectrum, conclusión
Llegados a este punto, ya tenemos definidos todos los gráficos y hemos aprendido como pintarlos.
En el próximo capítulo pintaremos el área de juego.
El código generado lo podéis descargar desde aquí.
Enlaces de interés
- Notepad++
- Visual Studio Code
- Sublime Text
- ZEsarUX
- PASMO
- Git
- Curso de ensamblador Z80 de Compiler Software
- Referencia Z80
- Ensamblador Z80 en Telegram
- Tutorial completo en formato PDF y EPUB
- Proyecto en itch.io
- Archivo .dsk con los juegos de los tutoriales
- Personalización y depuración con ZEsarUX
Ensamblador para ZX Spectrum Batalla espacial por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.
También puedes visitar el resto de tutoriales:
Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.