Lección 03 – Tiles y Planos

Aventuras en Megadrive: Tiles y Planos

 

DATOS TÉCNICOS

  • La MD maneja por HW 2 planos de fondo, planos A y B, y un plano de sprites.
  • Sólo los planos A y B pueden tener scroll.
  • Cada plano lo componen tiles de 8×8 píxels. Tile = Patrón = carácter.
  • La pantalla o plano visible son 40×28 tiles (horizontal*vertical). 1120 tiles visibles.
  • La pantalla o plano completa incluyen tiles fuera de pantalla, no visibles (necesarios para hacer scroll), por defecto 64×32 tiles. Puede tener otras dimensiones (32×32, 64×64 o 128×32), máximo 4096 tiles.
  • Cada tile puede ser usado tantas veces como queramos en cualquier plano.
  • Cada tile puede ser pintado con cualquiera de las 4 paletas de colores.
  • Cada tile puede ser pintado volteado/invertido sin necesidad de usar memoria adicional. Tanto horizontal como verticalmente.
  • Una cantidad importante de tiles pueden ser cargados en memoria usando DMA.
  • Los tiles se cargan en la memoria del VDP, llamada VRAM (Visual RAM) antes de usarse.
  • La MD tiene 64KB de VRAM.

 

Otros datos que debes saber

  • 1 tile = 32 bytes.
  • El background es un plano situado por detrás de todos los otros planos, por defecto el SGDK lo pinta con el primer color de la primera paleta.
  • El background no puede contener tiles, sólo colores.
  • El SGDK reserva espacio para 1310 tiles (+96 para la fuente).
  • Los tiles se mantienen en cada refresco de pantalla.
  • Cada pixel de un tile es un valor de 4 bits, que representa un índice color (en hexadecimal, de 0x0 a 0xF).
  • Existe un plano opcional llamado window, que no es más que una parte estática del plano A (dicha parte no tiene scroll). Suele usarse para el marcador.
  • Los planos A y B se pintan por filas(de izquierda a derecha y de arriba a abajo).
  • El plano de sprites se pinta por columnas (de arriba a abajo y de izquierda a derecha).

 

Creando tiles a mano

Podemos crear un tile de forma manual. Para cada píxel, le damos un valor hexadecimal que representará el índice de color de la paleta elegida. Es decir, ponemos un valor de 0 a 15 (F en hexadecimal). Esto es, 16 posibles valores.
Recordemos que una paleta de color puede tener precisamente 16 colores distintos.

#define MI_TILE 1
const u32 tile[8]=
{
		0x00111100,
		0x01144110,
		0x11244211,
		0x11244211,
		0x11222211,
		0x11222211,
		0x01122110,
		0x00111100
};

He creado una constante MI_TILE para referenciar el tile, cargaremos ese tile en la posición 1 de la VRAM. Esto no es necesario pero es didáctico.

¿Qué color es 0, 1, 2 o 4? Dependerá de la paleta aplicada a este tile. Si en la paleta asignada 0 es amarillo, esos píxels se pintarán en amarillo. Si asignamos otra paleta en la que el color 0 es negro, esos píxels se pintarán en negro.

Podemos cambiar esta paleta cuando queramos y por tanto cambiarían los colores del tile. Pero no nos adelantemos, vamos por partes. ¿Cómo mostrar este tile por pantalla?

// ...
	
	//carga el tile en la posición 1 de la VRAM
	VDP_loadTileData( (const u32 *)tile, MI_TILE , 1, 0); 
	
	//dibuja la tile 1 en el plano A en pos (5,5) con la paleta 0
	VDP_setTileMapXY(PLAN_A, MI_TILE, 5, 5);
	
	while(1)
	{
		VDP_waitVSync();
	}

VDP_loadTileData:   Carga el tile en memoria en la posición 1. No lo cargamos en la posición 0 (en C se comienza siempre en el cero), porque llenaríamos el background con el tile. El último parámetro indica que no vamos a usar DMA (es el último 0).

VDP_setTileMapXY: Dibuja el tile en el plano A en la posición (5,5). Recordemos que hablamos en tiles, no en píxels. En píxels sería la posición (40,40). Como no hemos asignado paleta previamente, el SGDK le asigna la paleta 0, que por defecto en el SGDK es una paleta de grises.

 

paletas por defecto del SGDK      pal0: grises,   pal1: rojos,   pal2: verdes,   pal3: azules

 

Pintando ÁREAS Y cambiando ATRIBUTOS

Pintar una región de la pantalla utilizando VDP_setTileMapXY() sería totalmente absurdo. Para rellenar un área de la pantalla utilizando un tile determinado, usaremos VDP_fillTileMapRect()

VDP_fillTileMapRect(plano, tile, x, y, ancho, alto) //1er parámetro: plano (A, B, WINDOW). //2do parámetro: tile a usar o bien TILE_ATTR_FULL() para indicar atributos //3er y 4º parámetro (x, y): posición a partir de la que se pinta el área //5º y 6º parámetro (ancho, largo):  área a pintar

Así por ejemplo:

VDP_fillTileMapRect(PLAN_A, MI_TILE, 2, 2, 10, 15);

Sirve para pintar un área de 10×15 tiles, a partir del tile 2,2, utilizando para ello el tile MI_TILE (que recordemos referencia a un tile en memoria del VDP).

 

Si queremos cambiar de paleta, indicar prioridades o voltear el tile hemos de indicarlo utilizando TILE_ATTR_FULL():

 TILE_ATTR_FULL()
 //primer parámetro:  paleta.  PAL2 = paleta de verdes
 //segundo parámetro: prioridad.  0 = baja prioridad
 //tercer parámetro:  volteo vertical.   1 = vflip
 //cuarto parámetro:  volteo horizon.   0 = no hflip
 //quinto parámetro:  tile referenciado.

Así por ejemplo:

VDP_fillTileMapRect(PLAN_A, TILE_ATTR_FULL(PAL2, 0, 0, 0, MI_TILE), 2, 2, 10, 15);

Sirve para pintar un área de 10×15 tiles, a partir del tile 2,2, utilizando para ello el tile MI_TILE (que recordemos referencia a un tile en memoria del VDP). Además le hemos asignado la tercera paleta (pal2) para pintarlo, dándole prioridad baja, y no volteando ni horizontal ni verticalmente.

 

PRIORIDAD

Hablemos brevemente de la prioridad.

GenesisPlanes

Detrás del todo el background, pintado con un color (ni tiles ni sprites aquí).
Por encima, con baja prioridad, plano B, por encima el plano A y luego Sprites.
Y por delante, con alta prioridad, plano B, por encima plano A y luego Sprites.

MD nos da la posibilidad de marcar un tile con alta o baja prioridad. Tanto para planos como para sprites. Podemos jugar con esto para que ciertos tiles aparezcan por encima o por debajo de otros aún estando en planos distintos.

A igual prioridad, un tile del plano superior machaca a uno del plano inferior.

Pero un tile con alta prioridad siempre aparecerá por encima de otro tile con baja prioridad, aunque el de baja prioridad esté en un plano superior.

De esta forma aunque el plano B está por debajo del plano A, podemos marcar ciertos tiles del plano B para que aparezcan «por encima» de los tiles del plano A (siempre que los del plano A sean de baja prioridad).  Por supuesto podemos marcar algunos del plano A para que aparezcan por encima de todos.

Podemos «jugar» tanto como queramos, marcando unos o todos los sprites de un plano, para conseguir interesantes efectos.

Lo mismo podemos aplicarlo para sprites, al fin y al cabo los sprites están formado por tiles. Lo veremos cuando hablemos de los sprites.

 

 

EJEMPLO Y CÓMO DEBUGEAR

He usado el emulador GensKmod. Cargar el ejemplo de más abajo y hacer clic en CPU>Debug>Genesis>VDP.  Este ejemplo está en mi github.

Vamos a poner varias tiles en pantalla, unas en plano A y otras en plano B, luego jugaremos con la prioridad y las que están en el plano B con alta prioridad saldrán por delante de las que están en plano A con baja prioridad.

Además invertiremos las tiles tanto horizontal como verticalmente.

#include <genesis.h>

#define MI_TILE 1

const u32 tile[8]=
{
0x44444421,
0x44442211,
0x44422110,
0x44221100,
0x42211000,
0x42110000,
0x21100000,
0x11000000
};

int main( )
{

//carga el tile en VRAM
VDP_loadTileData( (const u32 *)tile, MI_TILE, 1, 0);

//dibuja el tile en el plano A en pos (1,5) con la paleta por defecto (pal0)
VDP_setTileMapXY(PLAN_A, MI_TILE, 1, 5);

//Dibuja el tile usando TILE_ATTR_FULL()
//primer parámetro: paleta. PAL2 = paleta de verdes
//segund parámetro: prioridad. 0 = baja prioridad
//tercer parámetro: volteo vertical. 1 = vflip
//cuarto parámetro: volteo horizon. 0 = no hflip
//quinto parámetro: tile referenciado.
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL2, 0, 0, 0, MI_TILE), 3, 5);

//Dibuja 2 veces el tile, la primera vez en el plano B con pal 0 (grises)
//la segunda en el plano A con pal1 (rojos),
VDP_setTileMapXY(PLAN_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, MI_TILE), 5, 5);
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL1, 0, 0, 0, MI_TILE), 5, 5);

//Dibuja 2 veces el tile, igual que antes pero el tile del plano B con alta prioridad
VDP_setTileMapXY(PLAN_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, MI_TILE), 7, 5);
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL1, 0, 0, 0, MI_TILE), 7, 5);

//dibuja la tile volteada horizontalmente, verticalmente y luego ambas a la vez
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL0, 1, 0, 1, MI_TILE), 3, 8);
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL0, 1, 1, 0, MI_TILE), 5, 8);
VDP_setTileMapXY(PLAN_A, TILE_ATTR_FULL(PAL0, 1, 1, 1, MI_TILE), 7, 8);

while(1)
{
VDP_waitVSync();
}

return 0;
}

 

En este ejemplo utilizamos tan sólo un tile.   El programa:

  1. Dibuja el tile en la posición (1,5) sin especificar paleta, el SGDK asigna la pal0 (grises) y la dibuja tal cual, no hemos indicado nada más.
  2. Dibuja de nuevo el tile en la posición (3,5) usando la pal2 (verdes).
  3. Dibuja 2 veces el tile en la misma posición (5,5). Primero en el plano B con pal0 (grises) y luego en el plano A con pal1(verdes). Veremos el tile verde, el del plano A.
    A igual de prioridad, el tile del plano A machaca al del plano B que está por debajo.
  4. Volvemos a hacer lo mismo en la posición (7,5) pero esta vez dando prioridad al tile del plano B. Al tener más prioridad, es este tile el que prevalece.
  5. Finalmente dibuja más abajo el tile volteándolo, primero verticalmente, luego horizontalmente y por último tanto vertical como horizontalmente.

Un tile dibujado varias veces, en varias posiciones, con distintas paletas, prioridades… ¿en qué afecta esto a la memoria? Veamos el debugger:

Captura

  • A la izq los distintos ejemplos. Lo que se vería en la TV.
  • A la derecha las 4 paletas cargadas en memoria. Por defecto aparece seleccionada la primera paleta (pal0, grises), nos lo indica la flecha roja.
  • Debajo de las paletas la VRAM del VDP. Por defecto el SGDK carga 16 tiles en memoria (que se colorean en la paleta seleccionada, por eso salen en gris), esto se puede cambiar por supuesto. Haciendo clic en las otras paletas veremos las tiles pintadas en la VRAM con dichas paletas (esto se refleja sólo en el debugger, no en el propio juego). La flechita cambiará para indicar qué paleta estamos seleccionando.

La tile 1, que es la que hemos definido, se carga en la posición 1 de memoria (tal y como hemos indicado en el programa). En nuestro ejemplo la tile 1 es como un triangulo.

Si pulsamos en una tile de la VRAM, aparece más abajo en grande. 

GensKmod nos permite ver en tiempo real todos estos datos, por lo cual es muy útil para ver qué está pasando y si nuestro programa hace lo que queremos.

El tile del ejemplo sólo está una vez en memoria, aunque en pantalla hemos pintado varias. Usar muchas veces una misma tile no necesita memoria adicional, esa es la gracia del VDP.

Nótese que debajo de las 4 paletas «reales» GensKmod tiene otra paleta «virtual» con una selección de colores propia, de momento la ignoraremos, que no nos confunda.

 

 

GITHUB

El código de esta lección, y de todas las demás, lo podrás encontrar en mi github:

https://github.com/danibusvlc/aventuras-en-megadrive

2 comentarios en “Lección 03 – Tiles y Planos

  1. Hello!

    I like your lessons, they are informative!
    Can you tell me how you can remove a painted tile?

    Thanks for the answer, and good luck!

Los comentarios están cerrados.