Aventuras en Megadrive:
Shadow & Highlight. Mapa de Prioridades.
MAPA DE PRIORIDADES
Este ejemplo está inspirado en la demo que hizo RealBrucest para ilustrar
S&H en el SGDK. Se encuentra en este hilo de Spriteminds.
Hasta ahora hemos usado la forma más sencilla de S&H: a pantalla completa.
Sin embargo hay formas más sofisticadas de usarlo. Y es que podemos trabajar a nivel de tile para determinar que tiles tienen o no tiene prioridad (y por tanto se les aplica o no el sombreado).
En esta entrada, vamos a utilizar un mapa de prioridades: Vamos a utilizar un bitmap donde la parte pintada con el color 0 de la paleta se entenderá como «sin prioridad» y, por tanto, sombreada, y cualquier parte pintada con cualquier otro color se entenderá «con prioridad» y, por tanto, no sombreada. De esta forma es muy sencillo y visual diferenciar zonas de prioridad simplemente pintando en la imagen.
Esta es la imagen que he utilizado:
El programa de ejemplo carga este bitmap y lo recorre tile a tile, para cada tile se apunta si tiene prioridad (color morado) o no tiene prioridad (color negro=color 0 paleta) en un vector (que tiene el tamaño de un fondo, 40×28 tiles = 1120 tiles).
Una vez hecho, creamos en el plano A un fondo con las prioridades del vector anterior, para establecer la prioridad de cada tile.
- El plano A va a ser un plano vacío, con color 0 (transparente).
- El plano B será el plano «real» que queremos que se vea.
De esta forma, cuando el VDP muestre el resultado final, veremos sólo el plano B con las zonas sombreadas y no sombreadas según el plano A y las prioridades.
La mejor forma de verlo, es ver el resultado final:
… y a continuación estudiar los mapas=planos=fondos utilizados:
- bga_priority es el plano de prioridades. En negro la parte sombreada (color 0) y el foco la parte no sombreada (cualquier otro color). He pintado una especie de foco para ilustrar un problema del que luego hablaremos.
- bgb es un dibujo normal que si pintará en el plano B.
Este es el proceso que he seguido:
- Creo un vector con 40*28 posiciones. Lo inicializo a cero.
- Bucle: Recorro la imagen bga_priority, para cada tile de la imagen miro si , si tiene o no prioridad y lo apunto en el vector anterior, utilizando para ello la máscara TILE_ATTR_PRIORITY_MASK.
- Crea un fondo en el plano A con dichas prioridades. Literalmente un fondo de prioridades, sin pintar nada más.
- Crea un fondo en el plano B utilizando la imagen de fondo «normal».
Podríamos cambiar estas prioridades en cualquier momento, modificando el plan A.
Este ejemplo contempla una pantalla estática (40×28), sin scroll, pero podría hacerse.
TILE_ATTR_PRIORITY_MASK es una máscara, una palabra reservada por el SGDK, esta palabra equivale al número 0x8000, esto pasado a binario tiene un ‘1’ justo en el bit de prioridad del tile. Es decir, justo el bit que sirve para indicar prioridad.
Lo que hacemos en el bucle es mirar si cada tile tiene como color el 0 o no.
- Si tiene el 0, ignoraremos el tile. El vector para ese tile seguirá con un cero en dicha posición.
- Si tiene cualquier otro valor (colores del 1 al 15), para ese tile le sumamos al vector TILE_ATTR_PRIORITY_MASK, lo cual deja un ‘1’ en el bit de prioridad.
Parece complicado pero en realidad es no lo es tanto, mejor ver el código comentado. No obstante dejo aquí parte del código para que lo veáis:
// DATOS _________________________________________ //crea una vector con tantas posiciones como tiles tiene la pantalla (1120) u16 tilemap_buffer[SCENARIO_NUM_TILES]; //puntero a la primera posición del vector u16 *aplan_tilemap = &tilemap_buffer[0]; //inicializa el vector todo a cero (por defecto hay 'basura') for(int j=0; j<SCENARIO_NUM_TILES;j++) tilemap_buffer[j]=0; //puntero A LOS TILES de la imagen "mapa de prioridades", ojo, a sus tiles no a una imagen u16 *shadowmap_tilemap = bg_prioridad.map->tilemap; //para contar, valor inicial tiles totales de una pantalla u16 numtiles = SCENARIO_NUM_TILES; // PROCESO ____________________________________________ // BUCLE: Recorremos desde el ultimo al primer tile, para cada uno comprobamos el valor de ese tile // * si es 0 (color negro, indice 0 de paleta), no hacemos nada // * si NO es cero (cualquier otro color, indice distinto 0 de paleta) marcamos ese tile // con prioridad (TILE_ATTR_PRIORITY_MASK) while(numtiles--) { //comprueba el valor del color del tile (0..15) if(*shadowmap_tilemap) *aplan_tilemap |= TILE_ATTR_PRIORITY_MASK; //equivale a *aplan_tilemap = *aplan_tilemap | TILE_ATTR_PRIORITY_MASK; //TILE_ATTR_PRIORITY_MASK = 0x8000h. Al vector aplan_tilemap (fue inicializado a CERO) // le sumamos '1' cuando el valor del color del tile es distinto de cero aplan_tilemap++; //avanza en el vector shadowmap_tilemap++; //avanza en el otro vector } //EN ESTE PUNTO tenemos el vector del mapa de prioridades, en cada posicion tiles configuradas con la prioridad // Carga en el plano A en posiciones 0,0 con dimensiones 40x28 tiles el contenido que hay en &tilemap_buffer[0] VDP_setTileMapDataRectEx(PLAN_A, &tilemap_buffer[0], 0/*u16 basetile*/, SCENARIO_POS_X, SCENARIO_POS_Y, SCENARIO_WIDTH_TILES, SCENARIO_HEIGHT_TILES, SCENARIO_WIDTH_TILES); // Carga el planoB (el escenario "real" tal cual es, nada raro) VDP_drawImage(PLAN_B, &bg_B_real, SCENARIO_POS_X, SCENARIO_POS_Y);
Esquinas cantosas y limitaciones
Hagamos un poco de zoom, zona de las rodillas es donde mejor se ve:
El foco del mapa de prioridades es una diagonal, pero el sombreado sale a cuadraditos. Sucede que estamos trabajando tile a tile, y un tile son 8×8 píxels, de ahí que no se dibuje una diagonal perfecta.
Es una limitación de la técnica utilizada. Podríamos usar sprites para disimularlo, pero perderíamos sprites útiles.
Por supuesto otra limitación evidente de esta técnica es que usamos el plano A para las prioridades y sólo podemos usar el plano B para dibujar un fondo. Realmente podemos dibujar en el plano A sin problemas, pero ya es complicarse un poco la vida porque podríamos machacar la info de prioridad.
GITHUB
El código de esta lección, y de todas las demás, lo podrás encontrar en mi github: