sábado, 17 de marzo de 2012

Movimiento y colisiones

A lo largo de esta semana he estado haciendo pruebas con diferentes tipos de scroll y he llegado a la conclusión que con el cristal de 16MHz no hay mucho que hacer. No hay que olvidar que lo que pretendo hacer es el software de una consola con un microcontrolador de 8 bits, intentando ahorrar en materiales, y que las antiguas consolas de 8 bits tenían el procesador para el juego, el chip de gráficos, el de sonido, el de RAM, el de ROM... Así que si un sólo chip tiene que hacer todas las tareas como es el caso, creo que con lo que tengo por ahora puedo estar más que contento.

Antes de limpiar el código y decidirme por fin a hacer una librería en condiciones he probado mover un sprite animado por la pantalla, y ya de paso probar una colisión sencilla. Además he querido probar de cambiar la animación cada vez que colisionase. El ejemplo es bastante pedestre, de momento todos los tiles del TileMap son sólidos excepto el tile 0. El movimiento es a escala de tile (de 8 en 8 pixels), en el futuro será pixel a pixel.

Fondo

Sprites







Cada vez que el pájaro cambia de posición hay que restaurar el fondo de la posición anterior para no dejar una estela con el último grupo de tiles que conforman el objeto.
El código está en la pestaña de descargas. Espero poder hacer pronto la librería y poder alojarla en algún sitio como Google Code para no saturar de links de descarga la pestaña.


Monster Bird: http://www.spriters-resource.com/other_systems/alexkiddmw/sheet/35938

domingo, 11 de marzo de 2012

Scroll

Otra prueba que quería hacer antes de ponerme a ordenar el código y montar las librerías finales (o casi finales) es la del scroll de pantalla. De momento he probado con scroll horizontal repitiendo la misma pantalla de forma infinita.

Teoría del scroll


Para generar el scroll hay que crear dos buffers del tamaño de la pantalla y pintarlos uno detrás de otro, al llegar al final, reseteamos la posición de memoria inicial donde pintamos y volvemos al principio.

Arduino mega cuenta con un espacio reservado de 64K de memoria externa, de la posición 0x2200 a la 0xFFFF. El shield que estoy utilizando, MegaRAM, tiene bloques de 32K y pueden utilizarse 2 simultáneos consiguiendo un total de memoria de 56K aproximadamente. El buffer que estoy utilizando es de 29952B (208x144), si utilizo 2 buffers, necesito 59904B (58.5K). No cabe. Y si utilizo un bloque de memoria para cada buffer no puedo hacer scroll ya que los datos no serían contiguos en memoria.

Solución 1:

Dividir la pantalla en 2 de forma horizontal, colocar los datos de la parte superior en un bloque de memoria y los datos de la parte inferior en el otro bloque. Cuando empieza el render de lineas activas hay que activar el primer bloque de memoria y al llegar a la mitad de lineas hay que activar el segundo bloque.

División en 2 bloques

Solución 2:

Quitar dos columnas de tiles (16px) del buffer. Esto tiene 4 ventajas, la primera es el ahorro de memoria:

192 * 144 = 27648B = 27K
27 * 2 = 54K

54K caben en un bloque de 56K. Otra ventaja es que ahora tendré un aspecto de pantalla de 4:3:

144 * 4 / 3 = 192

La tercera es que al quitar 16px gano 64 ciclos de reloj o 4us para liberar CPU. Cada pixel tarda 4 ciclos:

16 * 4 = 64 ciclos
64 / 16 = 4us

Y la cuarta ventaja es que con esta resolución me acerco más a los márgenes de seguridad. Si el ancho máximo que podía conseguir era de 208 y el margen es del 90%:

208 * 90 / 100 = 187.2px

Sigo estando fuera de márgenes pero había que buscarle algo bueno a perder resolución.

De entre las 2 soluciones de momento me quedo con la segunda. Si quisiese más resolución tendría que cambiar el cristal de la placa Arduino Mega por uno más rápido, por ejemplo, con 20MHz tendría 260px, pero mi intención es la de no modificar la electrónica básica de Arduino. Además, necesitaría más RAM y estaría en las mismas.

52us * 20MHz = 1040ciclos
1040 / 4 = 260

El siguiente video muestra el scroll de la opción 2:





Esta vez he subido el código con los 2 ejemplos, está en la pestaña de descargas.


lunes, 5 de marzo de 2012

Sprites animados

Un sprite animado es una secuencia de imágenes que se repite para dar la sensación de movimiento. Para poder animar sprites con mi código primero debo liberar el procesador. Ahora estoy pintando un fotograma cada 20000us, cada fotograma son 312 líneas, y cada línea dura 64us.

 64 * 312 = 19968us
20000 - 19968 = 32us

 Dispongo de 32us para ejecutar la lógica de programa, o lo que es lo mismo de media línea. Una forma de liberar procesador sería utilizar un timer de 64us para pintar cada línea de forma separada, y quitar todos los delays finales de cada una de las líneas de sincronía y de las líneas no visibles. Pero si alguna línea tardase más de la cuenta en ejecutarse se perdería la sincronía.

La mejor forma que he encontrado de hacerlo es cambiando los pins de sincronía por un pin PWM controlado por el Timer1 y que sea este el que se encargue de todo. Para remplazar los 2 pins de sincronía por uno sólo que haga por los dos he buscado el esquema interno del monitor 1084P y he encontrado esto:

Esquema del conector DIN6 en el monitor 1084P

 Así que las 2 sincronías van a parar al mismo sitio utilizando resistencias de 470. Cuando HSync es 1, VSync es 0 y viceversa. Así que conectando el pin que generará ahora la sincronía a cualquiera de las dos entradas de sincronía del monitor y la otra entrada a 0V tenemos sincronía con un sólo pin.


Esquema sincronía con PWM

No me he puesto a programar el código de sincronía PWM ya que existen un montón de ejemplos por la red, el mejor de todos es el que utiliza Mdmetzle (no se su nombre real) en su librería Arduino TVOut. He utilizado el código de inicialización del timer y las funciones de sincronía, he cambiado su función de render por la mía y he eliminado el sonido (por ahora).

Para hacer la prueba he utilizado el mismo TileMap que en el artículo anterior (quitando las 3 primeras plantas) y he incluido una animación de 4 fotogramas de Sonic girando.


Cuatro fotogramas = Sonic girando

En el siguiente código se puede ver que en el bucle principal de programa se pinta un fotograma diferente cada 100ms:

 
byte spr;
byte sprC = 0;
byte sprW = 3;

void loop()
{
// Game update code

spr = sprC * sprW; 

// Backbround
SetTile(2,11); 
SetTile(3,11); 
SetTile(4,11); 
SetTile(2,12); 
SetTile(3,12); 
SetTile(4,12); 
SetTile(2,13); 
SetTile(3,13); 
SetTile(4,13); 

// Character
SetSprite(2,11,spr); 
SetSprite(3,11,spr+1); 
SetSprite(4,11,spr+2); 
SetSprite(2,12,spr+12); 
SetSprite(3,12,spr+13); 
SetSprite(4,12,spr+14); 
SetSprite(2,13,spr+24); 
SetSprite(3,13,spr+25); 
SetSprite(4,13,spr+26); 

sprC = (sprC+1)%4; 
delay(100); 

}


El resultado final en un video con poca mala calidad, no tengo una cámara mejor:







El código está en la página de descargas.




Commodore Peripherial Documents: http://project64.c64.org/hw/peri.html
1084P Schematics: http://project64.c64.org/hw/1084p%20schematics.zip
Sprites Sonic: http://sdb.drshnaps.com/display.php?object=6468

sábado, 3 de marzo de 2012

TileSet & TileMap

Una vez conseguido el pintado desde SRAM toca hacer un motor de tiles para generar gráficos de forma que se consuman el mínimo de recursos posibles.

TileSet:
Es una colección de imágenes del mismo tamaño con las que podemos formar otras imágenes más grandes. Podemos compararlo con una paleta de colores, lo veremos más claro con la definición del tilemap.

TileMap:
Es una rejilla que contiene los índices de las imágenes del tileset. Imaginemos un mapa de bits, el mapa de bits es una cuadrícula en la que cada casilla tiene un color (un pixel). Pues a grandes rasgos, un tilemap es igual que mapa de bits pero cada una de las casillas es una imagen en vez de un color.

Un enlace donde se explica mejor la teoría de los tiles: Tilemapping - Juegos basados en tiles

Esta vez he utilizado para las pruebas unos tiles de Sonic the Hedgehog. He actualizado el software que convierte imágenes en código para poder generar el código del tileset. Lo podéis encontrar en la página de descargas.


TileSet

El siguiente código muesta como indexar los tiles en el TileMap:

 
...
byte TileMap[18][26] = {
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x02, 0x03, 0x0E, 0x0F, 0x00, 0x00, 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x08, 0x09, 0x0E, 0x0F, 0x00, 0x00, 0x0E, 0x0F, 0x10, 0x11, 0x0A, 0x0B, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, },
  {0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x06, 0x07, 0x0E, 0x0F, 0x0E, 0x0F, 0x10, 0x11, 0x06, 0x07, 0x0E, 0x0F, 0x06, 0x07, },
  {0x0C, 0x0D, 0x0C, 0x0D, 0x0C, 0x0D, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0C, 0x0D, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0C, 0x0D, 0x0E, 0x0F, 0x0C, 0x0D, },
  {0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, },
  {0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, },
  {0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, },
  {0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, },
};
...
 


El resultado de la mezcla es el siguiente:


Resultado al indexar los tiles del TileSet en el TileMap


Y así se ve por pantalla:

Resultado en el monitor


En la página de descargas podéis encontrar el código del ejemplo, se llama Tiles.
En la próxima entrega: Sprites animados.




Tiles: VGMaps
Sonic: http://www.vgmaps.com/Atlas/MasterSystem/index.htm#SonicTheHedgehog