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

RAM de color

En una entrada anterior hemos visto cómo pintar caracteres en pantalla, bien con las instrucciones “lda”/”sta”, bien con la rutina del Kernal “chrout”.

Pues bien, no sólo se pueden pintar caracteres. También se puede cambiar su color. Y para esto existe la RAM de color, que es una zona de 1000 posiciones RAM que determinan, una por una, el color de las 1000 posiciones correspondientes de la RAM de pantalla.

La RAM de color va desde la posición $d800 hasta la posición $dbe7. Por tanto, para fijar el color del carácter en la posición $0400, se debe fijar el color en $d800, y así sucesivamente.

Para fijar el color hay que almacenar en la posición elegida un byte con cualquiera de los 16 colores soportados por el C64 (en realidad sólo se utiliza el nibble menos significativo):

$00 – Negro $01 – Blanco $02 – Rojo $03 – Cyan (turquesa)
$04 – Morado $05 – Verde $06 – Azul $07 – Amarillo
$08 – Naranja $09 – Marrón $0a – Rojo claro $0b – Gris oscuro
$0c – Gris medio $0d – Verde claro $0e – Azul claro $0f – Gris claro

 
Colores


Programa de ejemplo: Prog29

Revisión de la situación

Hasta el momento hemos tratado los siguientes aspectos:

  • Introducción y objetivos del blog. Referencias.
  • El equipo de trabajo. CBM prg Studio y VICE.
  • Sistemas de numeración decimal, binario y hexadecimal.
  • El hardware del C64. La memoria y los registros del microprocesador.
  • Los modos de direccionamiento.
  • Las instrucciones del código máquina/ensamblador del 6510.
  • Subrutinas, macros e interrupciones.
  • Rutinas del Kernal.

En definitiva, nos hemos centrado en cómo es el código máquina/lenguaje ensamblador del C64 o, lo que es lo mismo, del microprocesador 6510.

A partir de ahora la temática cambia porque, una vez conocida la herramienta básica, se trata de aplicarla para hacer cosas: gráficos, sonido, entrada/salida, etc. Y estas capacidades las aportan los otros circuitos integrados del C64: el VIC (gráficos), el SID (sonido) y las CIAs (entrada/salida).

Como ya se comentó en su momento, a efectos del microprocesador, el resto de circuitos integrados no son más que posiciones de memoria. Por tanto, se manejan con instrucciones de lectura y escritura de datos (“lda”, “sta” y similares). Pero para poder usarlos, hay que conocer sus registros, qué hace cada uno de ellos, y en qué direcciones están.

Y esta será la temática principal a partir de ahora…

Códigos de pantalla vs caracteres PETSCII

A la hora de imprimir caracteres en pantalla tenemos dos opciones:

  • Hacerlo en una posición conocida, es decir, en cualquiera de las 1000 posiciones de la RAM de pantalla ($0400 – $07e7).
  • Hacerlo donde esté el cursor actualmente.

Para la primera opción usaremos directamente las instrucciones “lda” y “sta”, o sus variantes para los registros X e Y. Y para la segunda opción usaremos la rutina “chrout” del Kernal.

Pues bien, según la técnica que estemos usando debemos usar códigos de pantalla (“screen codes”) o la tabla de caracteres PETSCII. Como se podrá ver son tablas diferentes, porque en el primer caso el código correspondiente a la “A” es 1 y en el segundo caso es 65.

Códigos de pantalla

Cuando usamos “lda” y “sta” para poner un carácter en la pantalla (posiciones $0400 – $07e7) lo que estamos haciendo es exactamente lo mismo que cuando hacemos un “sta” con cualquier otra posición de memoria, es decir, simplemente almacenar ahí un byte.

Para los que sepan BASIC estamos hablando de hacer lo mismo que un “POKE 1024, 1”.

Lo que ocurre es que, si la posición utilizada pertenece a la RAM de pantalla, el VIC interpreta eso como que tiene que pintar determinado carácter en esa posición. Y el VIC pinta un carácter u otro en función de la tabla de códigos de pantalla.

Básicamente lo que hace el VIC es que lee el byte ahí almacenado (ej. 1), consulta su mapa de definición de caracteres, que está en ROM, y pinta el carácter (matriz de pixels) que corresponde a ese valor (ej. “A”).

El mapa de caracteres del C64 tiene 2 subconjuntos (a matizar en breve):

  • Las letras mayúsculas, los números y otros caracteres gráficos. Códigos 0 hasta 127.
  • Las versiones invertidas del punto anterior. Códigos 128 hasta 255.
Screen codes 1 Screen codes 2

Lo anterior (letras mayúsculas) es la opción por defecto al arrancar, pero se pueda cambiar pulsando SHIFT y la tecla Commodore (SHIFT y la tecla Windows en VICE). Eso sí, o las letras son todas mayúsculas, o todas minúsculas, pero no se pueden mezclar. Por eso es suficiente con un byte para codificar 512 caracteres (mayúsculas, mayúsculas invertidas, minúsculas y minúsculas invertidas).

Screen codes 1 Screen codes 2

Caracteres PETSCII

En este caso la historia es distinta porque vamos a través de la rutina “chrout”. A esta rutina, por diseño, le deben llegar caracteres y no códigos de pantalla.

El equivalente en BASIC sería hacer un “PRINT ‘A’”. Lo que pasa es que en BASIC hay números y hay cadenas de caracteres (variables numéricas y alfanuméricas), pero en ensamblador todo vienen a ser bytes.

El C64 no sigue el estándar ASCII (caracteres de 7 bits), pero tiene un estándar equivalente llamado PETSCII. Además, la tabla PETSCII es compatible con ASCII en lo fundamental, es decir, usa los mismos valores que ASCII para identificar letras, números y algunos caracteres de control. A esto el C64 añade sus muy característicos caracteres gráficos.

PETSCII

No conozco los detalles internos de la rutina “chrout” (se podrían consultar desensamblando el código a partir de la posición $f1ca), pero me puedo imaginar que lo que hace es determinar la posición de pantalla en función de la posición del cursor, obtener el código de pantalla que corresponde al carácter PETSCII recibido como parámetro, almacenarlo en esa posición de memoria, y mover el cursor.

CBM prg Studio y text

En CBM prg Studio, y en cualquier ensamblador, es posible definir el contenido de determinadas posiciones o zonas de memoria con directivas como “text” (para textos), “byte” (para bytes), “word” (para palabras, es decir, dos bytes), etc.

En particular, “text” vale para definir textos que luego van a ser leídos e impresos en pantalla mediante un programa en ensamblador. Por ejemplo:

texto1 text “En un lugar de La Mancha…”
texto2 text ‘En otro lugar de La Mancha…’

Pues bien, si lo que desea el programador es que CBM prg Studio almacene ahí códigos de pantalla, entonces debe usar comillas simples. Por el contrario, si lo que quiere el programador es que CBM prg Studio almacene caracteres PETSCII, entonces debe usar comillas dobles.


Programa de ejemplo: Prog28

Rutinas del Kernal

El sistema operativo del C64 básicamente consta de dos piezas:

  • El intérprete de BASIC.
  • El Kernal.

El intérprete de BASIC es un programa en ROM que interpreta, es decir, traduce y ejecuta, los programas en BASIC. En este blog estamos más interesados en el ensamblador y el código máquina del C64, básicamente porque el BASIC es fácilmente accesible para casi todo el mundo. El BASIC nos interesa tangencialmente, principalmente porque desde BASIC se puede ejecutar (comando SYS) e interactuar con el código máquina.

El Kernal (hoy en día diríamos “Kernel”) es un conjunto de rutinas en código máquina, también ubicadas en ROM, que dan servicios básicos al programador. El listado de estas rutinas se puede consultar en la dirección http://sta.c64.org/cbm64krnfunc.html.

Como se puede observar en el listado anterior, cada rutina aparece descrita con:

  • Nombre.
  • Descripción de la función.
  • Parámetros de entrada.
  • Parámetros de salida.
  • Registros modificados (ya se explicó en su momento que, salvo que se tomen medidas para evitarlo, todas las rutinas en general pueden modificar los registros).
  • Dirección.
  • Dirección real.

Hay dos direcciones, una “dirección sin más” y una “dirección real”, porque el Kernal utiliza una tabla de saltos o “jump table”.

Así, para llamar la rutina “chrout”, por ejemplo, los programadores normalmente harán “jsr $ffd2”, o “jsr chrout” con la constante chrout = $ffd2, que es la dirección de entrada a la “jump table”. Pero si se analiza el contenido de la posición $ffd2 y siguientes se verá que es así:

$FFD2 6C 26 03 JMP ($0326)

Es decir, se hace un salto (“jmp”) a la dirección apuntada por el vector o puntero $0326 – $0327 que, mediante los comandos BASIC PRINT PEEK(806) y PRINT PEEK(807), se puede deducir que apunta a $f1ca (recuérdese el orden “Little endian”), que es precisamente la “dirección real” que aparece en la ficha de “chrout”.

Chrout

En resumen, podemos llamar a la rutina de forma directa (dirección $f1ca) o a través de la “jump table” (dirección $ffd2). Al hacerlo de esta segunda manera tenemos la ventaja (en realidad teníamos) de que, si un día Commodore cambiaba la ubicación de las rutinas en futuros modelos, los programas seguirían funcionando.

Por lo demás, el Kernal tiene muchas rutinas. Se anima al lector a revisarlas y probarlas. Dos de las más utilizadas son:

  • CHROUT. Sirve para imprimir un carácter en la posición del cursor.
  • GETIN. Sirve para leer un carácter del teclado.

Getin


Programa de ejemplo: Prog18