Sprites 101
En este artículo veremos cómo crear sprites desde cero, y darles vida en nuestro Next usando Boriel ZX Basic, NextLib y las herramientas de Remy Sharp.
Antes de empezar con este artículo, deberías haber preparado tu ambiente de programación con NextBuild: https://specnext.dev/es/2022/07/28/preparando-el-ambiente-para-programar-para-next-con-boriel-zx-basic-y-nextbuild/
El personaje
Para este tutorial vamos a mostrar un personaje de 16×16 pixels en pantalla, que haremos que camine alternando 4 frames. Como mis habilidades de dibujo son nulas, he comprado en Itch.io un pack de gráficos creado por Vryell, llamado “Tiny Village Pack”, y he seleccionado un personaje.
Creando los sprites
Vamos a crear 4 sprites a partir de la imagen original. Para ello utilizaremos las herramientas de Remmy Sharp: https://zx.remysharp.com/sprites/
Seleccionamos la pestaña “Import” y arrastramos el fichero con las imágenes sobre la pantalla del navegador.
En el área de sprites de la izquierda, seleccionamos el primer cuadro y pulsamos el botón “Copy in”. SI ahora seleccionamos la pestaña “Sprite editor, veremos que el Sprite se puede haber copia cortado o incompleto.
Para solucionar esto, volvemos a la pestaña “Import” y movemos la imagen con el ratón o con las teclas “Mayúsculas + Flecha” o “Control + Mayúsculas + Flecha”. Volvemos a pulsar “Copy in” y repetimos la operación hasta que tenemos el Sprite deseado.
En este ejemplo las imágenes ocupan 16×16, pero podemos crear sprites más grandes juntando varios, para ello podemos ir jugando con las opciones “Dimension” y “Auto repeat impot”.
Repetimos la operación con las tres imágenes restantes, eso sí, seleccionamos el segundo, tercer y cuarto cajón de la izquierda para ir definiendo cada uno de los 4 sprites.
Al tratarse de un personaje animado, podemos previsualizar la animación pulsando sobre el icono “Show animation options”.
Ahora, pulsamos sobre el botón “Download spritesheet” y le damos el nombre “Chica.spr”
Creando las carpetas del programa
Abriremos la carpeta “C:\ZXNext\NextBuildv7\Sources\” desde Visual Studio Code, tal como vimos en el post: https://specnext.dev/es/2022/07/28/preparando-el-ambiente-para-programar-para-next-con-boriel-zx-basic-y-nextbuild/
Vamos a crear una carpeta que llamaremos “Sprites101” dentro de “C:\ZXNext\NextBuildv7\Sources\”. Dentro de esta carpeta vamos a crear el fichero “SpriteTest1.bas”, que es con el que vamos a trabajar.
También vamos a crear una carpeta que llamaremos “data” dentro de “C:\ZXNext\NextBuildv7\Sources\Sprites101”, y copiaremos el fichero “Chica.spr” dentro.
El código
Abrimos el fichero “SpriteTest1.bas” y tecleamos el código siguiente (sugiero teclearlo en vez de copiarlo y pegarlo, con el fin de analizar que hace cada línea).
' -----------------------------------------------------------------------------
' - Sprites101 ----------------------------------------------------------------
' - Licencia MIT (Haz lo que quieras con el código) ---------------------------
' -----------------------------------------------------------------------------
' - Includes ------------------------------------------------------------------
#include <NextLib.bas>
' - Variables globales --------------------------------------------------------
dim x as uinteger ' Coordenada X del sprite
dim paso as ubyte ' Paso de la animación
dim animacion(5) as ubyte => { 0,1,2,3,2,1 } ' Secuencia de frames de la animación
' - Inicio --------------------------------------------------------------------
Main()
stop
' - Sub principal -------------------------------------------------------------
sub Main()
' Inicializamos el sistema
Inicializar()
' Inicializamos las variables del sprite
x=0
paso=0
' Bucle principal
while inkey$=""
' Mostramos el sprite en pantalla
UpdateSprite(x,50,0,animacion(paso),0,0)
' Mover la coordenada x
if x<320 then
x=x+1
else
x=0
end if
' Cambiamos el frame de la animación
if paso<5 then
paso=paso+1
else
paso=0
end if
' Pausa para ralentizar la animación
asm
halt
end asm
wend
end sub
' - Inicializa el sistema -----------------------------------------------------
sub Inicializar()
NextReg($07,3) ' Velocidad del procesador a 28MHz
' Inicializamos Layer2
NextReg($15,%00001001) ' Activamos Sprites y layers como SUL
ShowLayer2(1) ' Mostramos la layer 2
' Creamos los sprites
LoadSD("Chica.spr",$c000,$4000,0) ' Cargamos los sprites en $c000
InitSprites(4,$c000) ' Inicializamos 4 sprites
end sub
Includes
Lo primero que nos encontramos es un “include” para añadir la librería NextLib. Esta librería es necesaria para poder utilizar las características avanzadas del Next.
Las variables globales
Acto seguido definimos tres variables:
- x: Almacena la coordenada x del Sprite, que irá desde 0 a 320, por esa razón la definimos como uinteger.
- paso: Contiene el paso de la animación a mostrar.
- animacion: Guarda la secuencia de frames que se usará para animar el personaje. Esta secuencia es 0, 1, 2, 3, 2, 1 y repetimos, lo que hará que el personaje
Después se llama al SUB Main, que es la función principal del programa.
Justo después nos encontramos una sentencia “STOP”, que lo que hace es detener el programa. Es una buena práctica colocar este comando al final de nuestro programa para que este se detenga, ya que el emulador puede entrar en bucle al terminar.
Inicializando el sistema
El SUB Main, llama al método “Inicializar”, que inicializa el sistema.
Lo primero que hacemos es fijar la velocidad de la CPU a 28MHz:
NextReg($07,3)
El registro $07 permite controlar la velocidad de la CPU: 0=3.5Mhz (velocidad original), 1=7MHz, 2=14MHz, 3=28MHz.
Después definimos la visibilidad de los Sprites y la prioridad de las capas gráficas.
NextReg($15,%00001001)
El registro $15 permite configurar la visibilidad de los sprites: https://wiki.specnext.dev/Sprite_and_Layers_System_Register
En nuestro caso, activamos la visibilidad de los sprites (bit 0), ocultamos los sprites fuera de la pantalla convencional, es decir, en el borde (bit 5), y definimos la prioridad de capas como Sprites encima, después ULA (pantalla clásica) y Layer 2 al fondo (bits 4, 3 y 2).
El comando ShowLayer hace la Layer 2 visible.
Inicializando los Sprites
Para crear un sprite se deben “OUTear” los datos del sprite al puerto de control de sprites $303b. Para ello, cargamos los datos de los sprites, creados con Remy Sharp, en memoria:
LoadSD("Chica.spr",$c000,$4000,0)
Con este comando, cargamos el contenido del fichero “Chica.spr” en la dirección de memoria $c000. Se cargarán 16Kb desde el fichero ($4000 = 16384 bytes), desde el principio del fichero (0). Cada pixel de un Sprite ocupa un byte, por lo que un Sprite de 16×16 ocupa 256 bytes, así que en 16Kb podemos almacenar 64 definiciones de Sprites de 16×16.
Una vez cargados, los “OUTeamos” con ayuda del método “InitSprites” de NextLib:
InitSprites(4,$c000)
Esto inicializa 4 sprites ubicados a partir de la posición de memoria $c000
Cabe destacar que una vez hemos inicializado los sprites, ya se pueden borrar de la memoria o machacarlos con otros datos si lo necesitamos.
Bucle principal
Una vez inicializado el sistema y definidos los sprites, reseteamos las variables “x” y “paso”.
Definimos un bucle del tipo WHILE…WEND, que se repetirá hasta que pulsemos una tecla.
while inkey$=""
Dentro de este bucle, lo primero que hacemos es mostrar el Sprite en pantalla.
UpdateSprite(x,50,0,animacion(paso),0,0)
Coloca el Sprite en la coordenada horizontal definida por la variable global “x” y la coordenada vertical fija 50.
Usaremos el Sprite 0, y mostraremos la definición de Sprite indicada por la variable “animación(paso)”.
Los parámetros adicionales “0,0” indican que el Sprite se muestre sin modificadores, es decir, sin escalar, rotar ni aplicando espejo.
Lo siguiente es incrementar la coordenada “x” del Sprite hasta que tenga el valor de 320, y en ese caso la resetemos a 0.
La pantalla del Next en Layer 2 puede trabajar a 256×192, misma resolución que la ULA clásica del Spectrum) o a 320×256, en cuyo caso utiliza el borde de la pantalla, es decir, no se comprime la imagen, sino que se amplia al borde.
Los Sprites siempre utilizan la resolución de 320×256, por esa razón hemos definido la variable “x” como uinteger, y hemos definido que los sprites no se muestren en el borde con el NextReg $15. Así que el Sprite será visible entre las coordenadaas horizontales 32 a 288 y verticales 32 a 224.
Lo siguiente que hacemos es incrementar el número de frame a mostrar, incrementando la variable “paso” hasta que vale 5.
Por último, hacemos una pequeña pausa esperando a que suceda una interrución:
asm
halt
end asm
Para ello usamos el comando HALT de assembler, que detiene la ejecución hasta que se lanza la próxima interrupción. Esto permite sincronizar el programa e impedir que se produzcan “tirones” en la ejecución del mismo.
En un futuro artículo trataremos los temas que no se han podido tratar, como las propiedades avanzadas de los sprites: ampliación, rotación y espejo, ni el control de visibilidad.
Menciones y agradecimientos
Como siempre, a Boriel y a em00K por su labor.
Los gráficos utilizados en este tutoríal pertenecen al pack “Tiny Village Pack” de Vryell, que podéis encontrar en https://vryell.itch.io/tiny-village-pack a un precio ridículo para todo lo que ofrece.
https://zx.remysharp.com/sprites/ :
the sprite file is always 16384 bytes !!!
InitSprites(4,$c000) >>>> InitSprites(64,49152)
the 4 makes no sense.
this is ok :
https://gitlab.com/SpectrumNext/ZX_Spectrum_Next_FPGA/-/blob/master/cores/zxnext/nextreg.txt
InitSprites usa dos parámetros.
El primero, en este caso 4, indica el número de sprites que vamos a definir.
El segundo, $c000, que es igual a 49152 en decimal, es la dirección en la que empieza la definición de los esprites. Si te fijas, los sprites los cargamos en la dirección $c000, y en este ejemplo solo usamos 4 de los 64 posibles.
Un sprite de 16×16 ocupa 256 bytes. Si podemos tener 64 sprites de 16×16 nos dan un total de 16384.
Gracias @funkheld por tu interés!
——————————————————–
InitSprites uses two parameters.
The first, in this case 4, indicates the number of sprites to be defined.
The second one, $c000, which is equal to 49152 in decimal, is the address where the sprite definition starts. If you notice, we load the sprites in the address $c000, and in this example we only use 4 of the 64 possible ones.
A 16×16 sprite takes 256 bytes. If we can have 64 16×16 sprites that gives us a total of 16384.
Thanks @funkheld for your interest!
the sprite memory is almost always 16384 bytes for the next/n-go.
you can’t/shouldn’t use it for other things.
the sprite program always saves 16384 bytes.
so initsprite(64,….) is always ok.
nothing happens and you don’t save any memory with initsprite(4,….).
It is true that InitSprite(4,…) does not save memory, if at all it saves a little bit of time (not much), in this example I use it to explain how to use it.
Even if the program stores 16Kb, with LoadSD you can load only a part of those bytes, it is not necessary to load all 16Kb in memory.
Also note that once you have used InitSprite, the memory used by the sprites can be used for other things.
Another important aspect is that sprites can be redefined on the fly, and it is quite fast, but this was just an introductory article.