Como hemos comentado varias veces, cc65, a través de su header file c64.h, tiene estructuras de datos que permiten manejar fácilmente el VIC, el SID, la RAM de color y las CIAs. Por ejemplo:

Y en el caso particular del VIC (ver header file cc65\include\_vic2.h):

Esto quiere decir que mediante una sintaxis de C tan sencilla como ésta (asignaciones):
VIC.spr0_x = 100
VIC.spr0_y = 100
es posible modificar la posición del sprite 0, por poner un ejemplo.
Veamos entonces cómo podemos hacer un ejemplo sencillo en C para manejar sprites. Si recordamos de alguna entrada antigua de este blog, para definir un sprite necesitamos:
- Diseñar el aspecto gráfico del sprite y guardar los 64 bytes que lo definen (en realidad 63) en alguna zona de memoria.
- Copiar esos 64 bytes a uno de los 256 “bloques” a los que tiene acceso el VIC.
- Poner el puntero del sprite (posiciones $07f8 –$07ff, según el número de sprite) apuntando al bloque elegido.
- Habilitar el sprite.
- Elegir el color del sprite.
- Posicionar el sprite.
Pues bien, veamos cada uno de estos pasos:
Diseñar el aspecto gráfico del sprite:
En este caso vamos a reutilizar algún sprite ya diseñado. Se trata de nuestra vieja amiga la pulga:

Guardar la definición del sprite en una zona de memoria:
Como sabemos, ese diseño de 24 x 21 pixels se traduce en (yendo de izquierda a derecha y de arriba abajo):
- 3 bytes por fila (24 pixels).
- 21 filas (21 pixels).
- En total, 63 bytes.
En ocasiones, en vez de 63 bytes se manejan 64, aprovechando el byte 64 para guardar la información del color (o colores, en el caso multicolor). También puede ser mero relleno o, simplemente, manejar 63 bytes.
En nuestro ejemplo en C esos 64 bytes se traducen en este array de 64 bytes (o char, en C vienen a ser lo mismo):

Usamos notación hexadecimal (0x…) porque resulta lo más directo en este caso.
Copiar la definición del sprite a uno de los 256 bloques:
Se podría intentar que al cargar el programa PRG en el C64, esos 64 bytes se cargaran ya directamente en el bloque en el que tienen que estar. Sin embargo, no es lo más práctico.
Por ello, lo que vamos a hacer es copiar esos 64 bytes desde la posición que les haya tocado “en suerte” al compilar, hasta el bloque en que queremos que se almacenen. Esto lo hacemos con la función copia_def_sprite(), de la que lógicamente necesitamos prototipo e implementación.
La implementación es así:

Como se puede ver, el código es sencillo:
- A partir del número de bloque determinamos la dirección destino multiplicando por 64.
- Utilizamos la función estándar memcpy() para copiar los 64 bytes del sprite desde el origen (el array de bytes) hasta el destino (el bloque elegido).
Ya tenemos la definición del sprite en su sitio.
Configurar el puntero del sprite:
Para configurar el puntero del sprite tenemos que guardar el número de bloque elegido en la posición correspondiente al sprite, que es la $07f8 en el caso del sprite 0 y así sucesivamente hasta la $07ff en el caso del sprite 7 (en total, 8 sprites).
Esto lo hacemos con la función conf_ptr_sprite() que, en una primera aproximación, tiene una implementación muy similar a la de copia_def_sprite(), es decir, basada en memcpy():

Posteriormente, veremos otras implementaciones mejores y más claras.
Habilitar el sprite:
Lo siguiente es habilitar el sprite, para lo que ni siquiera necesitamos una función, ya que se trata de asignar el valor 1 (o el valor previo OR 1) a la posición SPENA = $d015, que está accesible mediante VIC.spr_ena.
Esto es tan sencillo que no merece un pantallazo propio, así que aprovechamos para presentar el programa principal en su conjunto. Véase la línea 29 en particular:

Como se puede ver en las primeras líneas del programa principal main(), estamos usando el sprite 0 y el bloque 254. Pero cambiar esto se ha hecho fácilmente configurable mediante variables (sprite y bloque).
Elegir el color y posicionar del sprite:
Con estas dos últimas operaciones ocurre lo mismo que con la habilitación del sprite, son tan sencillas que no requieren de funciones propias. En el caso del color se trata de dar valor a la variable VIC.spr0_color y en el caso de la posición a las variables VIC.spr0_x y VIC.spr0_y.
Una cosa chula del header file _vic2.h es que hay dos formas de usar los registros, bien mediante un nombre específico para cada sprite, o bien mediante un array con un índice que indica el número de sprite. Por ejemplo, en el caso de las posiciones (ídem colores, etc.):

Es decir, la posición del sprite 0 se puede cambiar:
- Bien con VIC.spr0_x = X y VIC.spr0_y = Y.
- O con VIC.spr_pos[0].x = X y VIC.spr_pos[0].y = Y.
La segunda forma me parece mejor, ya que permite trabajar con una variable “sprite” o “num_sprite” que en ocasiones valdrá 0, en otras 1, en otras 2, … Es más general.
Resultado:
Bueno, pues el resultado de compilar y ejecutar el programa es el que cabría esperar:

En realidad, el resultado no es tan espectacular. El tema sprites lo tenemos controlado desde hace tiempo. Lo que me resulta más espectacular es lo sencillo y directo que resulta hacerlo en C.
Y más sencillo que se puede hacer, lo que me dará pie a futuras entradas…
Mejoras:
En el ZIP adjunto encontraréis la versión original de este programa (sprite1.c), así como varias mejoras:
- sprite2.c: En vez de usar el sprite 0 de forma fija usamos el sprite indicado por la variable “sprite”. Es decir, usamos las variables de _vic2.h en su variante por índice.
- sprite3.c: Definimos un header file (sprite3.h) con variables para tener acceso de forma fácil a los punteros de los sprites (posiciones $07f8 – $07ff), de forma que no tengamos que usar memcpy() para algo tan sencillo como darles valor.
- sprite4.c: Mejoramos el header file (ahora sprite4.h) para poder usar los punteros de los sprites mediante un índice, no con nombres fijos.
Mediante el uso de uniones de C se puede hacer un nuevo header file (sprite5.h) que permita tanto nombres fijos (SPR_PTR.spr0_ptr, SPR_PTR.spr1_ptr, …) como nombres con índice (SPR_PTR.spr_ptr[sprite]). Esto se deja como ejercicio para el lector.
Código de ejemplo: sprites