Animación de sprites

La animación de sprites consiste en cambiar el diseño gráfico de un sprite, por ejemplo, que un sprite con forma humana mueva las piernas al caminar, o que una nube de humo se expanda o cambie de forma cuando estalla una bomba.

Para conseguir este efecto hay que definir cada una de las formas o “frames” del sprite (64 bytes) y almacenarlas en memoria conforme a lo ya explicado (empezando en posiciones múltiplo de 64 dentro del banco de 16 KB direccionado por el VIC). Posteriormente, hay que modificar el puntero del sprite para que vaya apuntando a cada una de las formas deseadas.

Es decir, un sprite no tiene por qué tener una única forma; puede tener varias y se puede cambiar entre ellas.

En este contexto, la temporización es fundamental. Es decir, normalmente no se podrá cambiar de una forma a otra sin controlar nada más. Habrá que controlar el ritmo de esos cambios para que la visualización sea buena.

Un primer intento para que el ritmo no sea demasiado rápido puede consistir en meter un bucle de N iteraciones que no haga nada especial, simplemente que pierda el tiempo. Enseguida se notará que son necesarias varias ejecuciones del bucle de espera, una después de configurar cada forma. Si el bucle sólo se ejecutara una vez, visualmente predominaría aquella forma/frame configurada justo antes del bucle de espera.

Pero incluso ejecutando el bucle de espera varias veces, después de cada forma, la visualización no será buena. Esto es así porque el microprocesador 6510 ejecuta el programa a la vez que el VIC refresca la pantalla (el VIC refresca la pantalla 60 veces por segundo) y, en función de cómo se entrelace la ejecución del programa y el refresco de pantalla, la visualización puede ser buena o no. En general, si no hay ningún tipo de sincronización entre ambas cosas la visualización será de mala calidad.

Por ello, para que la visualización sea buena es aconsejable hacer algún tipo de sincronización entre los refrescos del VIC y la ejecución del programa por parte del 6510. Por ejemplo, se puede detectar cuándo el VIC está actualizando la línea X de la pantalla y, en ese momento, o cada N actualizaciones de esa línea, cambiar de forma.

Para hacer esta detección (que el VIC está actualizando la línea X de la pantalla), se puede utilizar el registro $d012, también llamado “raster”. El “raster” es, precisamente, el “rayo” con el que el VIC va actualizando la pantalla línea por línea, de izquierda a derecha y de arriba abajo.

Otra forma de conseguir esto mismo es mediante interrupciones, puesto que es posible configurar el VIC para que determinados eventos gráficos, por ejemplo, el hecho de que el “raster” actualice la línea X, genere una interrupción, hecho que se puede aprovechar para, mediante una rutina de interrupción, cambiar de forma el sprite o actualizar un contador, de modo que el sprite cambie de forma cada N actualizaciones.

Animación 3.gif


Programa de ejemplo: Prog34
 

Expansión de sprites

Los sprites pueden tener el tamaño normal o se pueden expandir en dirección horizontal, vertical, o ambas. Cuando esto ocurre el tamaño se duplica en la dirección o direcciones elegidas.

Para conseguir esto hay que actuar sobre los registros del VIC:

  • Expansión horizontal: $d01d.
  • Expansión vertical: $d017.

Como todas las posiciones de memoria del C64, estos registros tienen 8 bits, uno por cada sprite. Por tanto, activando el bit X se consigue la expansión del sprite correspondiente, y desactivándolo se anula la expansión.

La expansión horizontal/vertical aplica tanto a sprites monocolor como a sprites multicolor.

Expansion


Programa de ejemplo: Prog33

Sprites multicolor

Los sprites, además de ser monocolor, también pueden ser multicolor.

En este último caso los bits/pixels se agrupan de dos en dos en sentido horizontal y, a cada una de las cuatro combinaciones posibles, le corresponde un color:

  • %00: Color transparente, es decir, el color del fondo de pantalla.
  • %01: Color multicolor número 1 (valor del registro $d025).
  • %10: Color del sprite (valor del registro $d027 – $d02e, según el número de sprite).
  • %11: Color multicolor número 2 (valor del registro $d026).

De lo anterior se deducen varias cosas:

  • Unos sprites pueden ser monocolor y otros multicolor.
  • Los sprites multicolor tienen menos resolución, ya que los pixels se agrupan de dos en dos en sentido horizontal. Y cada pareja tiene un color.
  • Los dos colores extra del sistema multicolor (valores de los registros $d025 y $d026) son compartidos entre todos los sprites multicolor. Aun así, los sprites multicolor conservan un color exclusivo (valores de los registros $d027 – $d02e).

Por último, queda especificar si un sprite es monocolor o multicolor. Esto se hace actuando sobre el registro $d01c, que tiene un bit para cada uno de los 8 sprites. Si el bit está a uno, el sprite es multicolor; si está a cero, el sprite es monocolor.

Las herramientas de diseño de sprites, como “Sprite Editor” de CBM prg Studio, permiten definir sprites monocolor y multicolor:

Multicolor


Programa de ejemplo: Prog32

Posicionamiento y movimiento de sprites

Este es el último paso para llegar a ver el sprite: ubicarlo en pantalla. Para este propósito hay dos registros por sprite (total 16 registros), uno para la coordenada X y otro para la coordenada Y.

Es importante tener en cuenta que las posiciones (X, Y) se refieren a la esquina superior izquierda de los sprites. Pero los sprites no son puntos; ocupan una cierta superficie.

Los registros del VIC para controlar las coordenadas de los sprites son:

$d000 – Coord. X del sprite 0 $d001 – Coord. Y del sprite 0
$d002 – Coord. X del sprite 1 $d003 – Coord. Y del sprite 1
$d004 – Coord. X del sprite 2 $d005 – Coord. Y del sprite 2
$d006 – Coord. X del sprite 3 $d007 – Coord. Y del sprite 3
$d008 – Coord. X del sprite 4 $d009 – Coord. Y del sprite 4
$d00a – Coord. X del sprite 5 $d00b – Coord. Y del sprite 5
$d00c – Coord. X del sprite 6 $d00d – Coord. Y del sprite 6
$d00e – Coord. X del sprite 7 $d00f – Coord. Y del sprite 7
$d010 – Bit más significativo coords.  X No aplica

Cada uno de estos registros, como todas las posiciones de memoria del C64, toma un valor de un byte. Por tanto, podríamos deducir que las posiciones de pantalla posibles para un sprite son desde la posición (0, 0) hasta la posición (255, 255).

En este sentido, hay que hacer dos comentarios importantes:

  • No todas las posiciones son visibles. Hay posiciones que quedan fuera de la zona visible de la pantalla. Por ejemplo, definiendo un sprite que consiste en un único pixel en la esquina superior izquierda de la matriz, he podido comprobar que las posiciones visibles son desde la (24, 50) hasta la (343, 249).
  • Del punto anterior se deriva que un byte no es suficiente para la coordenada X, ya que esta coordenada puede tomar el valor 343 (y otros superiores). En cambio, la coordenada Y no necesita más; con un byte es suficiente para la coordenada Y.

Para complementar lo que falta de la coordenada X, el VIC tiene un registro adicional: la posición $d010. Cada bit de esta posición (bit 0 … bit 7) se añade por la izquierda como bit más significativo a las coordenadas X de los sprites 0 … 7. Por tanto, las coordenadas X tienen 9 bits, los 8 bits menos significativos en las posiciones $d000, $d002, …, $d00e, y el bit más significativo en el bit correspondiente de la posición $d010. No es lo más cómodo, pero es así como funciona.

Por tanto, las coordenadas Y van desde 0 hasta 255 (1 byte), mientras que las coordenadas X van desde 0 hasta 511 (9 bits). Pero no todas esas posiciones (X, Y) son visibles; sólo son visibles desde la (24, 50) hasta la (343, 249). Y siempre teniendo en cuenta que estas posiciones se refieren a la esquina superior izquierda del sprite, y que estos límites pueden variar en función de que el C64 sea del modelo PAL (el modelo de TV analógica que triunfó en Europa) o del modelo NTSC (el modelo de TV analógica que triunfó en América).

Pulga

Por último, para mover sprites obviamente basta con cambiar su posición. El principal cuidado que hay que tener es cuando la coordenada X pase de 255 a 256 o viceversa. En tal caso, hay que acordarse de activar o desactivar el bit correspondiente al sprite en el registro $d010, así como inicializar a 0 o 255 los 8 bits menos significativos de la coordenada X.


Programa de ejemplo: Prog31

Sprites monocolor

Otro paso necesario para llegar a ver un sprite es definir su color. Aunque nos olvidemos de este paso, es probable que veamos el sprite, aunque no tendrá el color deseado; tendrá un color aleatorio en función del contenido de la posición de memoria del VIC que controla su color.

Los registros del VIC que controlan los colores de los sprites son:

$d027 – Color del sprite 0 $d028 – Color del sprite 1
$d029 – Color del sprite 2 $d02a – Color del sprite 3
$d02b – Color del sprite 4 $d02c – Color del sprite 5
$d02d – Color del sprite 6 $d02e – Color del sprite 7

Los valores a almacenar en estas posiciones y los colores correspondientes son los que ya se vieron en la entrada dedicada a la RAM de color.

Activación y desactivación de sprites

Todos los pasos anteriores (diseñar el sprite, obtener sus 64 bytes, ubicarlos en una zona de memoria adecuada, y dar valor al puntero del sprite) son pasos necesarios, pero no son suficientes para ver el sprite.

Además de todo lo anterior, hay que activar el sprite, lo cual se consigue activando el bit correspondiente (desde el bit 0 hasta el bit 7) de la posición $d015. En esta posición hay un registro del VIC que lleva control de qué sprites están activos (bit a 1) y cuáles no (bit a 0).

Este es un ejemplo típico en el que interesa utilizar números en binario y/u operaciones lógicas. Por ejemplo, si cargamos el valor %00000001 en la posición $d015 activaremos el sprite 0. Igualmente, haciendo un “and” del acumulador y la posición $d015 podremos desactivar sprites (aquellos cuyo bit en el acumulador esté a 0), igual que podremos activarlos si lo que hacemos es un “ora” (aquellos cuyo bit en el acumulador esté a 1), o cambiarlos de estado si lo que hacemos es un “eor” (aquellos cuyo bit en el acumulador esté a 1).

Punteros de sprites

En la entrada anterior hemos visto que un sprite se define con 64 bytes. La forma de obtener esos bytes es agrupando de 8 en 8, de izquierda a derecha y de arriba abajo, los bits/pixels de la matriz de 504 pixels, más un byte extra para alinear a 64.

¿Y ahora qué hacemos con esos 64 bytes? ¿Dónde los ubicamos en la memoria? ¿Cómo le indicamos al VIC dónde encontrarlos? La respuesta a estas preguntas es mediante los “punteros de sprites”.

Los “punteros de sprites” son 8 posiciones de memoria, una por sprite, que le indican al VIC dónde encontrar la definición de los sprites (sus 64 bytes). Pero, si una posición de memoria en el C64 se especifica mediante 16 bits (o 2 bytes), ¿cómo es posible usar sólo un byte para indicar dónde empieza la definición de un sprite?

La respuesta es que tiene truco. El VIC es capaz de direccionar 16 KB de memoria (no 64 KB, que es lo que puede direccionar el 6510). Y si dividimos 16 KB (16.384 bytes) en bloques de 64 bytes, salen 256 bloques. Y cada uno de esos 256 bloques se puede identificar con un solo byte, porque con un byte se pueden codificar 256 valores.

Por tanto:

  • La definición de los sprites la tenemos que ubicar en el banco de 16 KB direccionados por el VIC, que en el mapa de memoria estándar del C64 es el banco que va desde la posición $0000 hasta la $3fff (el banco más bajo de los 4 bancos de 16 KB).
  • La definición de los sprites no la podemos ubicar en cualquier posición dentro de esos 16 KB. Cada sprite tiene que empezar en una posición que sea múltiplo de 64.
  • El puntero al sprite sale de tomar la primera posición de la definición y dividirla por 64. Esta división tiene que dar un resultado entre 0 y 255 y, por tanto, puede almacenarse en un byte (el puntero al sprite).

Estos conceptos se describen gráficamente en la siguiente imagen:

Punteros sprites

Por tanto, si los 64 bytes de nuestra pulga los colocamos en $3f80, que es múltiplo de 64, su puntero tendría que valer 254.

Y nos queda una última cuestión: ¿dónde se almacena el puntero? Es decir, ¿dónde se almacena ese valor 254? La respuesta es que los punteros de los sprites se almacenan en los últimos 8 bytes (hay 8 sprites) del 1 KB correspondiente a la RAM de pantalla. Es decir, en el mapa de memoria estándar, en el que la RAM de pantalla ocupa las páginas 4, 5, 6 y 7, los punteros a los sprites se almacenan en las posiciones $07f8 – $07ff.

Por tanto, si queremos que el primer sprite (sprite 0) sea la pulga, en la posición $07f8 deberemos almacenar el valor 254, y en el bloque $3f80 – $3fbf deberemos almacenar los 64 bytes que definen la pulga.

Definición de sprites

Cada sprite se define mediante una matriz de 24 x 21 puntos o pixels, 24 en sentido horizontal y 21 en sentido vertical. Es decir, cada sprite es una matriz de 504 pixels, y cada pixel se codifica con un bit. El bit será 1 si el pixel está activado, y será 0 si el pixel está desactivado. Y así es como se define el aspecto gráfico del sprite.

A continuación, se muestra un ejemplo de diseño de un sprite, concretamente una pulga. En el ejemplo se han omitido los 0’s para facilitar la identificación de la figura:

1 1
1 1
1 1
1 1
1 1 1 1
1 1 1 1
1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1
1 1
1 1
1 1
1 1 1 1 1 1 1 1 1 1

Lógicamente, la definición del sprite tendrá que acabar en memoria bajo la forma de bytes de información. Esto se consigue de la siguiente manera: los bits o pixels se agrupan 8 en 8 de izquierda a derecha y de arriba abajo. Y cada grupo de 8 bits/pixels da lugar a un byte.

Por tanto, 504 bits equivalen a 63 bytes. Otra forma de obtener el mismo resultado es multiplicando los 3 bytes por fila por las 21 filas: 63 bytes.

En el ejemplo de la pulga, los 63 bytes que definen el sprite son los siguientes:

BYTE $00,$00,$00

BYTE $00,$00,$00

BYTE $00,$00,$00

BYTE $01,$00,$80

BYTE $00,$81,$00

BYTE $00,$42,$00

BYTE $00,$42,$00

BYTE $00,$5A,$00

BYTE $00,$3C,$00

BYTE $00,$7E,$00

BYTE $00,$5A,$00

BYTE $06,$7E,$60

BYTE $05,$7E,$A0

BYTE $04,$FF,$20

BYTE $02,$7E,$40

BYTE $02,$3C,$40

BYTE $02,$18,$40

BYTE $01,$00,$80

BYTE $01,$00,$80

BYTE $01,$00,$80

BYTE $1F,$00,$F8

En resumen, un sprite se codifica o define con 63 bytes. Pero 63 bytes es un número un poco extraño, porque no es potencia de dos, y ya sabemos que en informática casi siempre se utilizan potencias de dos. La potencia de dos más cercana a 63 es 64, porque 64 es 2^6.

De hecho, a la hora de almacenar en memoria los 63 bytes de un sprite no se puede hacer en una posición arbitraria, hay que hacerlo siempre empezando en una posición de memoria que sea múltiplo de 64. De otro modo, el VIC no sería capaz de localizar y tratar correctamente la información.

En definitiva, un sprite ocupa 63 bytes, pero en la práctica podemos actuar como si ocupara 64 bytes. Al último byte se le suele llamar “padding” o relleno, y a veces se utiliza para guardar la información del color.

Hay aplicaciones que permiten la definición y tratamiento de sprites, como es el caso del “Editor de Sprites” de CBM prg Studio. Merece la pena darse una vuelta por alguna de estas herramientas, porque facilitan mucho el trabajo con sprites. Por ejemplo, “Editor de Sprites” permite hacer cosas como:

  • Diseñar y visualizar sprites.
  • Obtener los bytes equivalentes en diferentes formatos (ficheros binarios, directivas “byte” del ensamblador, etc.).
  • Importar sprites desde diferentes formatos.
  • Definir el color (caso monocolor) o colores (caso multicolor).
  • Rotar sprites.
  • Voltear sprites.
  • Invertir sprites.
  • Expandir sprites.
  • Animar sprites.
  • Etc.

Sprite Editor

Sprites

Una de las capacidades más características del VIC son los “sprites”. Los sprites son objetos gráficos que se pueden mover por la pantalla.

El VIC soporta hasta 8 sprites a la vez, aunque con técnicas avanzadas como la “multiplexación de sprites” se pueden conseguir más sprites.

Con los sprites se puede hacer todo lo que sigue:

  • Definirlos.
  • Activarlos y desactivarlos.
  • Fijar su color. Pueden ser monocolor o multicolor.
  • Expandirlos.
  • Posicionarlos en pantalla y moverlos.
  • Animarlos.
  • Fijar sus prioridades.
  • Detectar colisiones.

Y todo esto es lo que vamos a ver en las entradas que siguen, empezando por la definición.

Color del fondo y del borde de la pantalla

Igual que es posible modificar el color de los caracteres en pantalla, también es posible modificar el color del borde y del fondo de la pantalla. Esto se hace con las posiciones:

  • Borde de pantalla: $d020.
  • Fondo de pantalla: $d021.

Por tanto, para cambiar el color del borde, llega con hacer un “sta”/”stx”/”sty” con el color deseado (ver tabla de colores de la entrada anterior) en la posición $d020. Y análogamente con el color de fondo y la posición $d021.

Mainframe


Programa de ejemplo: Prog30