DevkitSMSTutorial
Contenido
|
Tutorial Programación en C para Master System
Lección 0: Instalando el entorno de desarrollo
Antes de empezar nada, necesitaremos instalar un entorno de desarrollo completo. Vamos a ir por partes con ello:
Compilador C
El compilador usado para programar con el DevKitSMS es el SDCC, un compilador para microprocesadores empotrados, entre ellos el Z80. Vamos a instalarlo primero
- Se necesita bajar un build igual o superior a 3.5.5 (build #9487). Lo podéis conseguir aquí: [1]
- Una vez instalado, entra en una consola de comandos (por ejemplo, En windows la command line o el powershell). Escribe "sdcc --version" y comprueba que se muestra el número de versión de SDCC. Si no lo hace, es probable que tengas que añadir SDCC al PATH.
DevKitSMS
Ahora toca descargar el DevKitSMS. Está alojado en [2]. Si sabéis usar el Git, clonad el repositorio. Sino, podéis bajarlo todo como un zip en el botón verde que pone "Clone or Download".
- Abre el directorio donde has bajado el devkitSMS. Entra en el directorio ihx2sms, coge el archivo ihx2sms.exe y copialo en el directorio /bin de tu instalación de SDCC.
- Haz lo mismo con los directorios folder2c y assets2banks. Coge sus ejecutables y cópialos en el directorio /bin de tu instalación de SDCC
- devKitSMS permite programar tanto para MasterSystem como para GameGear y SG-1000. Como de momento nos interesa la Master, coge del directorio SMSlib el fichero SMSlib.lib, y colócalo en el directorio /lib/z80 de tu instalación de SDCC.
Emulador
Para ayudarte en tu proceso de desarrollo, necesitarás un emulador con funciones de desarrollo, que te permita ver el estado de la máquina en todo momento. Hay dos buenas opciones para ello:
Meka
El típico emulador de master desde hace casi 20 años, tiene muchas features, pero está bastante viejuno. Lo puedes bajar de aquí: [3]
Emulicious
Un emulador bastante moderno y con muchas features, está programado en Java y es totalmente multiplataforma. Lo puedes bajar de aquí: [4]
Una vez bajado el emulador, podéis asociarlo a los archivos terminados en .sms
BM2TILE
Una herramienta que permite convertir los gráficos desde formatos modernos a binarios entendibles por la Master. La podéis bajar de aquí. [5]. Aseguraros de guardarlo en algún directorio que tengáis en el PATH (o lo copiáis en el directorio de vuestro proyecto, como queráis).
Una vez instalados todos estos programas, podemos avanzar a la lección 1 !!.
Lección 1: Creando el proyecto
Vamos a empezar creando un directorio en nuestra máquina donde albergaremos el codigo de nuestro proyecto. Para el tutorial voy a crear un directorio llamado elotroladogame en la carpeta documentos, pero lo podéis crear donde os plazca.
Añadir Ficheros del devKitSMS
Ahora debemos copiar una serie de archivos al directorio del proyecto. El primero de todos es el crt0_sms.rel del directorio de devKitSMS al directorio de nuestro proyecto. A continuación copiamos el fichero SMSlib.h al mismo directorio del proyecto. El siguiente fichero es el peep-rules.txt del mismo directorio que el anterior, lo copiamos también al directorio del proyecto. Por último, copiamos los archivos PSGlib.h y PSGlib.rel al mismo directorio. Ahora deberíamos tener el directorio de nuestro proyecto de la siguiente manera:
- crt0_sms.rel
- SMSlib.h
- peep-rules.txt
- PSGlib.h
- PSGlib.rel
Creamos el fichero del juego
Como nuestro siguiente paso, crearemos el fichero c principal para el juego, lo llamaremos game.c. Podéis crearlo con el editor de texto que os guste. Yo suelo usar Sublime Text, pero Notepad++ o Edit Plus, etc, son también buenas opciones. Para un IDE más currado, yo recomiendo CLion, pero vale pastuja.
Una vez creado el fichero, empezaremos añadiendo la libreria de smslib, usando la directiva include de C. Seguidamente crearemos la función main. Para los que no conozcan C, todo programa que creemos empezará ejecutándose automáticamente en la función main del fichero principal. Justo antes definiremos una función llamada init_console(), que se encargará de inicializar la consola, y luego dejaremos la consola en un bucle infinito. Veamos primero el código.
Buenooo, la cosa se ha puesto algo más complicada, ¿verdad?. Voy a explicar el codigo poco a poco. init_console es una función que hemos definido nosotros y que vamos a usar para inicializar la consola. Contiene lo siguiente:
- La llamada a SMS_init inicializa la consola (vectores de interrupción, etc)
- La llamada a SMS_setSpriteMode(SPRITEMODE_NORMAL) setea el modo de los sprites a normal (más información en la siguiente lección)
- La llamada a SMS_displayOn() sirve para encender el renderizado en el VDP.
Una vez llamada esta función, llegamos a un bucle while(1), que es un bucle infinito (1 se evalua como cierto siempre). De momento lo único que hacemos en este bucle es llamar a:
- SMS_waitForVBlank(): Esta función se queda esperando la interrupción de dibujado vertical. Esta interrupción salta cuando el haz de electrones ha acabado de dibujar la pantalla completa. Es muy útil para sincronizar la velocidad del juego, ya que esta función sincronizará la ejecución a 50 vueltas de bucle (en PAL) o a 60 (en NTSC).
Vamos a probar de compilar este pequeño código, para eso necesitamos un script de compilación.
Bat de Compilación
En el directorio del proyecto (elotroladogame en nuestro caso), crea un fichero que se llame compile.bat. Y le añadimos este contenido:
La primera linea se encarga de compilar el fichero del juego (game.c), la segunda línea linka el resultado con la librería, la tercera transforma el binario en una rom con extensión .sms, y la última línea ejecuta Emulicious y abre la rom recién creada.
Vemos lo que obtenemos...es una magnífica...¡pantalla negra!. Ojo, la rom está funcionando perfectamente, simplemente no hemos puesto nada que se mostrase en la pantalla. Eso lo haremos en la próxima lección!.
Lección 2: Pintar un Fondo
Tener una pantalla en negro no es algo que nos emocione mucho, así que vamos a intentar poner algo en la pantalla. Para eso primero tendré que explicar un poco como funciona el sistema gráfico de la Master System.
Los graficos de la Master
Las consolas de 8 bits y 16 bits tenían que lidiar con una gran limitación: la poca memória (RAM y VRAM) de la que disponian. Una manera de poder sobrepasar esta limitación era usando un sistema gráfico particular, los gráficos compuestos de baldosas (tiles). En la mayoría de estas consolas estos tiles eran piezas de 8x8 pixeles. Lo que se hacía era reservar una parte de la memoria para estas piezas, y luego construir los fondos y sprites colocando estas piezas como un puzzle, lo que permitía ahorrar memoría frente a guardar todos los pixeles de la memoría.
En el caso concreto de la master system, esta tenía una resolución típica (hay otros modos) de 256x192(32x24 tiles), y era capaz usar dos paletas de 16 colores simultaneos, a escoger entre 64 colores. Una de las paletas quedaba restringida a los fondos, y la otra podia ser usada por fondos y sprites, con lo que se podían llegar a usar fondos de 32 colores simultaneos. La master disponia de una capa dedicada exclusivamente al fondo (Background Layer), que tenía un tamaño de 32x28 tiles, en la que podíamos componer una imagen usando los tiles disponibles en la VRAM (usando además su capacidad de espejarlos horizontal y verticalmente), y sobre la que podíamos desplazar la "ventana virtual" del scroll.
Usualmente pues, una imagen de un fondo de MasterSystem tendría tres conjuntos diferentes de información: el tileset (conjunto de todos los diferentes tiles de la imagen), el tilemap (o mapa con el tile a colocar en cada posición) y la paleta a usar (los 16 colores de la imagen)
Una primera prueba con un fondo
Vamos a hacer una primera prueba con un fondo, para que se vea algo en la pantalla. Primero de todo, bajaros este fichero:
https://www.dropbox.com/s/zbdgfrq8b2wmqk1/SegaMasterSystemTitleScreen.png?dl=0
Creamos una carpeta nueva dentro de nuestro proyecto, llamada gfx, y colocamos el fichero ahí. Luego creamos una carpeta llamada bank1 en la raiz del proyecto.
Ahora vamos a nuestro fichero bat, y añadimos el siguiente paso en la primera linea:
bmp2tile.exe .\gfx\SegaMasterSystemTitleScreen.png -savetiles .\bank1\backgroundtiles.psgcompr -mirror -removedupes -savepalette .\bank1\backgroundpalette.bin -savetilemap .\bank1\backgroundtilemap.bin -exit
Esta linea ejecutará la utilidad bmp2tile para convertir la imagen que hemos descargado a tres ficheros diferentes:
- backgroundtiles.psgcompr
- backgroundpalette.bin
- backgroundtilemap.bin
Estos ficheros se habrán guardado en la carpeta bank1, y contendrán, respectivamente, los tiles, la paleta y el tilemap. La extensión que hemos puesto a los ficheros define el tipo de fichero. La extensión .bin indica que son binarios directamente, mientras que la extensión psgcompr, aplicable a los tiles, indica que estos están comprimidos usando la compresión "Phantasy Star Gaiden" (una compresión usada en ese juego de GameGear y replicada por los desarrolladores homebrew). Los tres ficheros, independientemente de si están comprimidos o no, están en formato binario, así que los convertiremos a ficheros c planos. Para eso usaremos la utilidad folder2c. añadiremos la siguiente línea debajo de la que hemos añadido justo antes
folder2c bank1 bank1
En esta línea le estamos diciendo que coja el directorio bank1, y convierta su contenido en ficheros c. El segundo parámetro es como queremos que se llamen los ficheros de salida. Si miramos ahora en el directorio raiz de nuestro proyecto, hay dos ficheros nuevos.
- bank1.h
- bank1.c
Dichos ficheros contienen los datos del fondo que acabamos de exportar, en formato C. En el fichero de cabecera .h, podemos ver las referencias al tileset, tilemap y paleta.
Ahora añadiremos un paso más para compilar, en que compilaremos el fichero bank1.c, y lo añadiremos como parámetro en el paso de linkado. La línea a añadir es esta: sdcc -c -mz80 --peep-file peep-rules.txt bank1.c
Y la linea de linkado queda tal que así: sdcc -o elotroladogame.ihx -mz80 --no-std-crt0 --data-loc 0xC000 crt0_sms.rel game.rel bank1.rel SMSlib.lib
Por si acaso, os pongo como queda mi fichero .bat después de estos pasos (yo tengo la linea del bmp2tile descompuesta en tres, pero habría de dar lo mismo).
Volvamos ahora a nuestro fichero game.c. Añadiremos una función nueva llamada load_graphics2vram(). Dentro de esta haremos estos tres pasos:
- Cargar la paleta de fondos con SMS_loadBGPalette(backgroundpalette_bin)
- Cargar los tiles comprimidos con formato PSGaiden con SMS_loadPSGaidencompressedTiles (backgroundtiles_psgcompr,0) , donde 0 es el primer tile donde empezamos a volcar el contenido.
- Cargamos el tilemap con SMS_loadTileMap(0,0,backgroundtilemap_bin,backgroundtilemap_bin_size), donde los dos primeros parámetros es la posición donde empezamos a cargar el Tilemap, y el último es el tamaño del mismo (está en bank1.h).
Añadimos arriba en los includes la referencia a bank1.h, y ponemos la llamada a nuestra nueva función justo antes del bucle. El código quedaría así:
Es hora de probar que tal funciona la cosa con el emulador. Damos doble click a compile.bat y....
Tachán! ya tenemos un fondo en nuestra pantalla! No ha sido tan complicado, ¿verdad?
Em la próxima lección, ¡le vamos a dar más vida con un poco de scroll!
Lección 3: Scroll
Ahora que ya tenemos un bonito fondo con el logo de Sega, es hora de que le demos una vuelta de tuerca para mejorar-lo. Aquí aprenderemos como usar el scroll por hardware que nos brinda el VDP de la Master System.
Scroll por Hardware en Master System
Una de las características que definieron a la 3ª generación de consolas (Nes, Master system, Atari 7800....) es el hecho de que sus chips gráficos permitían la realización de scroll por hardware con una precisión de 1 pixel. En cambio, por ejemplo, la mayoría de microordenadores de 8 bit no soportaban de forma nativa la realización de scroll. En esas máquinas, el scroll debía realizar-se por software, lo que en la práctica suponía repintar todo el fondo con un cierto desplazamiento cada vez que movieramos un poco el scroll. Por esta razón muchos de los juegos de estas máquinas cuentan con scrolles lentos, o con desplazamientos de 8px.
En la master system, como hemos comentado, tenemos una metapantalla definida por el tilemap de 32x30 tiles, dentro de esta pantalla podemos mover libremente una ventana de 32x24 tiles(en el modo normal), que es lo se mostrará en la pantalla del televisor.
Seteo de la posición del scroll
Vamos a definir primero un par de variables que contendrán los valores actuales de la posición del scroll. Para los que no sepáis programación, las variables son como pequeños cajones donde podemos guardar un valor que vamos a usar más adelante. Dichas variables se pueden definir dentro de una función (y solo serán accesibles dentro de esa función) o de forma global. En sistemas de 8 bits como la Master, es mejor usar variables globales y reutilizarlas en lo posible, dado que pasar variables de una función a otra resulta costoso en terminos de procesamiento.
Cada variable puede tener diferentes tipos. Por ejemplo, si definimos una variable como char, ocupará un byte (8 bits). Dicho byte servirá para representar valores de -128 a 127. Si la variable es unsigned char, servirá para representar valores de 0 a 255. Si definimos una variable como int, en cambio, esta ocupará 2 bytes (16 bits), y permitirá representar valores de -32,768 a 32,768, y si es un unsigned int, de 0 a 65535.
Definiremos dos variables globales para guardar la coordenada inicial de la ventana virtual de scroll, y lo haremos como unsigned char (ya que de 0 a 255 nos basta para guardar las posiciones de la pantalla virtual de 256x240). Sería algo así:
Ahora les daremos un valor inicial a dichar variables, con lo que en el main, justo antes de la llamada a init_console(), las seteamos a cero:
Y ahora actualizaremos la posición del scroll a este valor inicial, justo antes del bucle:
Este paso es innecesario en este caso concreto, ya que el scroll siempre se inicia por defecto en la posición 0,0, pero lo dejo por claridad. Lo siguiente que haremos es actualizar la variable de la posición del scroll en cada vuelta del bucle, y posteriormente actualizamos la posición del scroll. El código quedaría así:
¿Por que lo hemos separado por la instrucción SMS_waitForVBlank()? Esto lo hemos hecho por que como comenté en la lección pasada, esta función paraliza la ejecución hasta que se lanza la interrupción de barrido vertical, que es justo cuando la pantalla ha acabado de dibujar la línea inferior. Poniendo el seteo del scroll justo después, nos aseguramos que actualizamos el scroll mientras la pantalla no ha empezado a redibujarse, con lo que se evita un cambio de scroll a mitad del recorrido de la misma. Vayamos a probar que tal se ve esto, ¡compilemos otra vez!
¡Ya tenemos un logo de Master System moviéndose suavemente hácia un lado!. Si os fijais en la ventana del tilemap de Emulicious, podréis ver como la "ventana virtual" se desplaza a través del tilemap. Cuando esta ventana sale por la derecha, vuelve a entrar por la izquierda, ya que actua como si estuviera en un rollo sin fín. Adicionalmente, estamos sacando ventaja del hecho de que la pantalla tiene exactamente 256 pixeles de ancho, justo el rango de nuestra variable que guarda la posición del scroll. Cuando la variable llega a un valor de 256, pasa a valer 0, pero al coincidir con la posición inicial del fondo, no nos damos cuenta de ese "reinicio" de su valor.
Vamos a añadir ahora un incremento en la posición y del scroll. Añadimos estas lineas para incrementar la variable de la posición y, y setear la posición y del scroll.
Volvamos a darle a compilar, y vemos el resultado:
Ahora el fondo scrollea tanto en horizontal como en vertical, produciendo un efecto de scroll diagonal. En este caso, dado que el tilemap tiene 240 pixeles de altura en lugar de 256, se puede observar un pequeño salto en un determinado momento del scroll. Esto se produce por que cuando sobrepasamos el valor 240 del scroll vertical, vemos la misma posición de scroll que si estuviésemos en la posición cero. Para cuando llegamos al valor 255, tenemos el scroll justo en la misma posición que si tuvieramos el scroll en la posición 15. En el siguiente frame, desbordamos el valor máximo de la variable, y esta pasa a valer 0, con lo que el scroll da un salto de 15 pixeles "para atrás". Si lo queremos ver mejor, podemos quitar el seteo horizontal, y dejar que solo se actualice el scroll verticalmente:
Le damos a compilar, y ahora se puede ver el efecto de salto que se produce, de una forma más clara:
Y esto ha sido de momento todo por esta lección. ¡¡La próxima, empezaremos a jugar con sprites!!
Edit: Añado fichero con el código hasta la lección 3
Lección 4: Sprites
La Master System, como la NES y a diferencia de algunos ordenadores de 8 bits como el Amstrad o el Spectrum, permite la gestión de sprites directamente por medio del hardware. Esto tiene una gran ventaja, ya que el propio sistema es el que se encarga de gestionar el repintado de sprites sobre los fondos, la superposición de los sprites entre ellos gracias a la prioridad, etc. Juntamente con el scroll por hardware que ya hemos visto en la anterior lección, los sprites por hardware ayudaron a definir los videojuegos de esta generación, con scrolles rápidos con multitud de sprites moviéndose. Y también hizo míticos algunos de sus problemas, como los famosos parpadeos de sprites.
Sprites en la Master System
En la master hay dos tipos de sprites, los de 8x8 pixeles, y los "tall sprites", que son de 8x16 (dichos modos son excuyentes, así que no se pueden tener sprites de 8x8 y de 8x16 mezclados). En total permite la gestión de 64 sprites simultáneos, pero con una importante limitación, no puede haber más de 8 sprites en la misma línea horizontal de dibujado (El VDP, cuando va a empezar a dibujar un scanline, solo cachea los primeros 8 sprites que encuentra). En la Master todos los sprites comparten una misma paleta de 16 colores (15 colores + transparente), lo que puede parecer una limitación importante (La nes permite escoger entre 4 paletas de 3 + transparente, lo que en mi opinión resulta más limitante aún). Este color transparente es siempre el primer color, color zero, de la paleta.
Obviamente, el hecho de que los sprites sean de 8x8 o 8x16 obliga a tener que usar varios de ellos para ser capaces de animar figuras que tengan un tamaño acceptable para el juego. Eso es lo que provoca que se agoten tan rápidamente los sprites disponibles, especialmente por línea. Dado que en la misma línea siempre se dibujaban los 8 sprites con la mayor prioridad, si teníamos un juego en el que abundaban los momentos con más de 8 sprites en línea, habría varios de estos sprites que nunca se llegarian a dibujar. Una manera de solucionar esto era ir alternando las prioridades de los sprites cada frame, haciendo así que se fueran pintando alternativamente, y provocando el famoso parpadeo.
Añadiendo un sprite
Vamos a coger un sprite para usar en nuestra primera prueba. He cogido un sprite del juego SpyvsSpy, al que he recoloreado un poco para darle algo más de vidilla. Bajaros el fichero que he colgado aquí, y lo colocamos en el directorio gfx
https://www.dropbox.com/s/yn34al6y2y5ggyq/spyvsspysprite.png?dl=0
Si os fijais en el tamaño que tiene, podemos ver que es de 16x24 pixeles, por lo que necesitaremos 2x3 sprites para representarlo. La librería no incluye ningun tipo de soporte para "metasprites", peor esto se puede solucionar de forma bastante fácil, aunque de momento lo haremos a mano. Vamos a añadir unas líneas para exportar los gráficos del sprite.
Si os fijais, para ahorrar unas líneas he combinado la exportación de tiles, tilemap y paleta en una sola línea, y lo he cambiado a si mismo en la exportación del fondo que ya teníamos. Es exactamente lo mismo, solo que en la lección anterior había preferido ponerlo en líneas diferentes para que se entendiera mejor. Si examinais el fichero bank1.h, vereis que ahora ya tenemos las referencias a los tiles y paleta del sprite. Vamos a empezar por un primer paso, vamos a cargar la paleta de los sprites justo después de cargar la de los fondos, y luego añadiremos la carga de los tiles justo después de la carga de los tiles de fondo.
He definido un par de constantes para indicar las posiciones de la memoria de vídeo donde vamos a cargar los tiles, dado que cargaremos los sprites de tiles al principio de la memória, y los de sprites a partir de la posición 256. Si ejecutamos el juego con el emulicious, podremos ver como los tiles del sprite han sido cargados. Ahora tocará definir los sprites y empezar a usarlos. Vamos a crear un player y a colocarlo sobre el fondo.
Vamos a crear un par de variables que guardaran la posición del player, las llamamos player_x y player_y, y ahora añadiremos unos pasos justo antes y después de la instrucción SMS_waitForVBlank().
La función SMS_initSprites() sirve para inicializar y resetear el sistema de sprites, cosa que hacemos cada frame. Seguidamente llamamos a draw_main_character(), una función definida por nosotros (y que os explicaré más adelante) que se encarga de crear todo los sprites del player. Seguidamente llamamos a SMS_finalizeSprites(), lo que indica que ya hemos definido todos los sprites que hay en este frame. Después de VBlank, que nos marca que la pantalla acaba de dibujarse, llamamos a SMS_copySpritestoSAT(), que copia todos los sprites a la SAT (Sprite Attribute Table). Ahora queda la mandanga buena, que es definir la función draw_main_character:
No os asustéis por esta función, es la primera cosa que tiene pinta de complicada de todo el tutorial. En esta función estamos usando dos bucles For anidados. Un bucle For ejecuta un cierto número de veces el código contenido en su interior, dependiendo de la condición definida en él. En este caso, tenemos un bucle exterior que da 3 vueltas y un bucle interior que da 2. En cada vuelta del bucle interior hacemos una llamada a SMS_addSprite(), que coge tres parámetros: posicion x, posicion y y tile a pintar. Nuestra idea es hacer que se añadan los 6 sprites necesarios para dibujar el player, actualizando las posiciones y los tiles que son necesarios dibujar. Una apunte importante, la expresión i << 3 indica que hagamos 3 desplazamientos de bit a la izquierda (si, lo sé, suena a chino), y es una manera más eficiente de hacer i*8. O sea, cuando veais una expresion tipo x << 3, pensadla como x*8, una x << 2 es x*4, etc.
La posicion x con la que hacemos la llamada se calcula como la posición x original del player, sumando el número de vuelta del For interior multiplicado por 8, la posición y se calcula como la posición y original del player más el número de vuelta del For exterior multiplicado por 8. Por último, el tile se calcula como la posición inicial de los tiles de sprite sumando el numéro de vueltas exteriores multiplicado por dos, y sumando el número de vueltas interiores.
Vale, ahora que creo que me he quedado solo, voy a poner una pequeña tabla para que se entienda:
Sprite número | posicion x | posicion y | número de Tile |
---|---|---|---|
1 | player_x | player_y | SPRITE_TILES_POSITION |
2 | player_x+8 | player_y | SPRITE_TILES_POSITION + 1 |
3 | player_x | player_y+8 | SPRITE_TILES_POSITION + 2 |
4 | player_x+8 | player_y+8 | SPRITE_TILES_POSITION + 3 |
5 | player_x | player_y+16 | SPRITE_TILES_POSITION + 4 |
6 | player_x+8 | player_y+16 | SPRITE_TILES_POSITION + 5 |
Como véis, lo que se consigue con estos dos bucles es dibujar los 6 sprites, empezando por la esquina superior izquierda, y acabando por la inferior derecha, que coincide con el orden en el que exporta los tiles la utilidad Bmp2Tile. Vamos a ver como se va a ver nuestro nuevo protagonista. Compilemos!
Podés ver que tenemos ahora un pequeño protagonista quietecito en la pantalla. Si quisieramos, podemos comentar la actualización del scroll en el main para que podamos ver más claramente a nuestro pequeño protagonista. Vamos a darle algo de movimiento. Añadid este cambio en el bucle del main:
¡¡Ya tenemos nuestro protagonista dándose un paseo por la pantalla!! Lástima que de momento vaya un poco tieso y quieto. Vamos a arreglar eso en la próxima lección.
PD: El link a los materiales de la lección 4: https://www.dropbox.com/s/bnbakqfxihf7tna/elotroladotutolesson4.zip?dl=0
Lección 5: Animando los Sprites
En la última lección dejamos a nuestro amigo Spy deslizándose como si estuviera en una cinta transportadora lo qual como un primer acercamiento no está mal, pero no quedaría muy bien en un juego de verdad. Vamos a animar su sprite para darlo un poco de vidilla.
Preparando el spritesheet
Primero de todo, bajaros la imagen que os adjunto a continuación, y colocadla en el mismo directorio gfx. Esta imagen es lo que llamamos un spritesheet, que contiene diversas animaciones del personaje principal. La master, a diferéncia de la NES, no tiene capacidad de espejar los sprites de los personajes (aunque si de los fondos), por lo que hemos de colocar el personaje dos veces en el spritesheet, uno para cada sentido.
https://www.dropbox.com/s/9wjmpmxg89icp23/spyvsspyspritesheet.png?dl=0
Vamos a exportar el spritesheet entero, para ello simplemente habéis de cambiar el nombre del fichero de la cuarta línea del compile.bat, y aseguraros que están puestas las opciones de noremovedupes y nomirror.
Si ahora intentáis ejecutar el juego, veréis como nuestro muñeco ha quedado totalmente deformado. Una ojeada a la ventana donde vemos el contenido de la VRAM nos da la clave: Al haber exportado los nuevos frames, la posición de las partes del cuerpo de nuestro muñeco han cambiado.
Animación sin recarga
Vamos a hacer un pequeño cambio en la manera de calcular los tiles que muestran nuestro muñeco, lo haremos en una función nueva a la que llamaremos draw_main_character2 (nos interesa mantener la función vieja por un tiempo). Vamos a añadir unas cuantas constantes y hacer la función más generica:
Las cuatro constantes de arriba definen el número de frames y tiles que componen cada frame, y el número de frames contando ambas direcciones, lo que nos resulta muy util para saber cuantos tiles saltar para buscar las partes inferiores del cuerpo.
La implementación de esta nueva función de dibujado del protagonista es la siguiente.
Dicha implementación no es la mejor por que se puede hacer por que incluye multiplicaciones, pero lo he dejado así de momento para que quede más claro. Como véis, he definido adicionalmente una variable llamada current_frame, que indicará el número de frame de la animación del protagonista en el que nos encontramos actualmente, por lo que tomará como valor 0, 1 o 2, para referirse a cada uno de los tres frames de los que disponemos. Lo inicializaremos a cero en el mismo sitio donde inicializamos las variables referidas al scroll, y lo incrementamos en cada llamada de draw_character_2. Vayamos a compilar y ejecutar, a ver que tal.
¡Uf!, parece que a nuestro espía le ha cogido el baile de San Vito. El problema con esta implementación es que cambiamos el frame de animación por cada barrido de la pantalla, con lo que nuestro espía da 20 pasos completos por cada segundo. Esto es demasiado, así que vamos a hacer estos pequeños cambios que hay a continuación.
Hemos definido una nueva variable más, que es como un contador general de frames ejecutados. La he definido de tipo unsigned char (valores de 0 a 255) por que de momento ya me está bien que se vuelva a setear a cero cuando llegue a 255, pero puede ser que en un juego de verdad nos interese más que el tipo de la variable sea un unsigned int. En todo caso, la condición estraña del if se lee como: Cuando el resto de dividir la variable entre 16, sea igual a cero. Esto quiere decir que la condición se cumplirá cada 16 frames, y será entonces cuando se cambie el frame del personaje. Vayamos a probar ahora a ver que tal. ¡A compilar!
¡Mucho mejor! Ahora la animación tiene mucho más sentido con el movimiento del muñeco. Vamos a hacer un par más de cambios, y el efecto va a mejorar aún más!. ¡Ojo que vienen curvas!
Mejorando la apariencia
Vamos a intentar ahora que se mueva tambien el fondo a la vez, y aparte, que cambie la dirección a la que anda el muñeco cada cierto tiempo. Para poder hacer esto, crearemos una nueva variable que contiene la dirección actual, y la llamamos direction. Definimos dos constantes, DIRECTION_LEFT i DIRECTION_RIGHT y les damos como valor 0 y 1. Esta variable la inicializamos a DIRECTION_RIGHT al inicio, y la iremos cambiando de valor en el bucle principal cada vez que el framecounter llegue a 255. En ese mismo bucle principal actualizaremos las posiciones del scroll y del player, dependiendo de la dirección a la que estemos mirando. Por otro lado hemos de cambiar también la función que dibuja el player, para tener en cuenta la dirección a la que estamos mirando. El codigo final va a ser así:
Le damos a compilar y ... ¡voilá! Ahora nuestro personaje es capaz de andar en las dos direcciones! Aunque aún no parece un juego, nuestro experimento ya empieza a tener un aspecto más trabajado.
En la siguiente lección veremos de que forma podemos optimizar más la gestión de la animación, pero de momento os dejo aquí un link a los materiales de esta lección:
https://www.dropbox.com/s/5fqmi62i3vommh0/elotroladotutolesson5.zip?dl=0
Lección 6: Animando más eficientemente
En la pasada lección, para animar a nuestro protagonista cargamos todo su spritesheet en memoria simultaneamente. Esta aproximación tiene un problema cuando se tratan de animaciones en las que cada frame es muy grande y hay muchos frames. ¡Recordad que solo tenemos unos 200 y poco tiles para nuestros sprites!. Para mejorar esto, ¡vamos a hacer animaciones con recarga!
Animación con recarga
Vamos a usar una variación de los recursos que usamos en la lección anterior, van a estar ordenados de una manera diferente para que podamos sacar ventaja de la carga en la VRAM. Hechemos un ojo a la imagen que vamos a usar:
https://www.dropbox.com/s/grsg5s4vgdyjnl7/spyvsspyspritesheet2.png?dl=0
Como veis, aquí los tiles están colocados en grupos de dos, y para cada frame colocamos primero los dos superiores, luego los dos inferiores, etc. Si ahora substituimos la imagen de la anterior lección por esta, veremos que pasa si ejecutamos (he cambiado el nombre de la imagen del compile.bat por spritesheet2.png):
De una forma parecida a como pasaba en la anterior lección, el cambiar el orden de los tiles provoca que los gráficos salgan de forma incorrecta. En este caso, hemos de cambiar como pintamos los tiles, debido a que ahora siempre pintaremos los mismo tiles. Dejaremos la funcion draw_main_character de la siguiente manera (he guardado la vieja llamandola draw_main_character2):
Y si ahora ejecutamos, tenemos a nuestro amigo el espia perfectamente quieto, pero saliendo bien dibujado.
¿Por que estamos haciendo esto de esta manera? El objetivo ahora es dibujar nuestro protagonista siempre con los seis primeros tiles de la parte de la VRAM dedicada a los sprites. E iremos actualiando el contenido de esa parte de la VRAM en cada frame. ¿Que ganamos con eso? que en lugar de ocupar 36 tiles (6 x 6) con todas las animaciones del protagonista, solo gastamos 6. La desventaja es que vamos a gastar tiempo de proceso en cargar esos tiles, con lo que no podemos hacer esto con, por ejemplo, 5 enemigos a la vez.
Vayamos a ver que cambios vamos a hacer. Primero de todo, necesitamos que los tiles de los sprites no esten comprimidos, dado que nos interesa rapidez y que no se pierda tiempo con la descompresión. Vamos a ir al directorio bank1, borraremos el fichero spritetiles.psgcompr, y cambiaremos el compile.bat para grabar los sprites con el nombre spritetiles.bin.
Si ahora probamos de compilar, tendremos un error de compilacion, dado que ha cambiado el nombre del recurso que estábamos usando. No os preocupeis, esto lo arreglamos rápidamente cambiando la función de carga de tiles:
Ahora vamos a proceder a cambiar el sistema de animación. Ahora solo necesitamos cargar los 6 tiles de la aniimación a la vez, así que la función de carga la volvemos a modificar para cargar solo 6 tiles. Dado que cada tile ocupa 32 bytes, y queremos cargar 6 tiles, el tamaño es 192. Para no poner el número directamente, vamos a hacer un par de defines al principio. Los defines y la función quedan así:
Y vamos a cambiar otra vez como funciona la función draw_main_character. Si os fijáis, ahora se va a volver a parecer bastante a la función que teníamos definida antes, y que hemos guardado como draw_main_character2. Primero de todo vamos a calcular qual es el primer tile para un determinado frame de la animación, y a partir de ahí cargar los seis tiles subsiguientes. Para calcular ese primer tile tenemos que tener en cuenta tanto el total de tiles que contiene la animación hacia una dirección, como el número de tiles que hay en cada frame. La función quedaría así:
Aquí uno de los puntos clave es esta expresión: &spritetiles_bin[frame_offset]. Aquí estamos cogiendo un punto inicial del array más adelante que la posición 0 (el símbolo & sirve para coger el puntero a esa posición). Vamos a probar que tal se ve la cosa...
Perfecto! Como podéis ver, ahora solo estamos gastando seis tiles por cada frame de la animación, así que tenemos muchos tiles disponibles para usar en los demás sprites del juego!. La desventaja, como he comentado antes, es que no te puedes permitir recargar muchos tiles en un mismo frame, por que es un proceso intensivo que consume bastante proceso.
En la siguiente lección añadiremos la capacidad de controlar a nuestro personaje!. De momento os dejo con este link con el material de esta lección:
https://www.dropbox.com/s/oxll48j6tyybov8/elotroladotutolesson6.zip?dl=0
Lección 7: Añadiendo Control
De momento todo lo que hemos hecho solo nos permite tener una bonita animación corriendo por la pantalla, pero eso se parece poco a un juego real. Para hacer que se parezca más, añadiremos la posibilidad de poder mover al muñeco usando el pad, y que tanto el sprite como sus animaciones, como el fondo, reaccionen a nuestro input, moviéndose de la forma que toca.
Añadiendo control por pad
Tenemos varias funciones de lectura de Pad, que permiten saber que direcciones se han pulsado en un determinado momento, cuales se han soltado, cuales es el status de todas ellas, etc. Sus prototipos, que podemos consultar en SMSlib.h, son los siguientes:
Y tenemos justo a continuación una serie de definiciones de las diferentes teclas que podemos chequear:
Vamos a añadir un cambio en el bucle principal, para que podamos cambiar la dirección en la que movemos el muñeco. Vayamos al main y cambiemos el punto donde cambiabamos la dirección cada ciertos frames. La dejamos así:
Ahora le damos a compilar, y ya podemos cambiar la dirección a la que se mueve el muñeco!:
Añadiendo fisicas
Lo malo del estado en que hemos dejado esto, es que en el fondo no tenemos un verdadero control del muñeco, solo estamos cambiando su dirección de forma un tando aleatoria. Vamos a cambiar esto para tener un control más preciso. Uno de los primeros pasos es cambiar como funciona el movimiento del fondo. Vamos a hacer que el fondo solo avance cuando el muñeco se encuentra cerca de una de las esquinas, he cogido unos valores a ojo, de manera que cuando el player está más a la derecha que la coordenada 200, o más a la izquierda de la coordenada 56, es el fondo el que se desplaza, y sinó, es el muñeco el que lo hace. La función main la dejaríamos así:
Ahora le damos a compilar, y....¡funciona perfectamente! Ahora el movimiento tiene un poco más de sentido. Vamos a arreglarlo un poco más para que nuestro protagonista no está andando todo el rato, y se pare cuando no estamos pulsando una dirección concreta. Definiremos una variable llamada player_vx que indica la velocidad horizontal de nuestro protagonista. La definiremos como signed char por que puede ser positiva o negativa (nos mueve a la derecha o a la izquierda):
Y cambiamos la lógica de como reaccionamos a las pulsaciones del teclado, en vez de usar SMS_getKeysPressed, usaremos SMS_getKeysStatus, que nos permite conocer las direcciones que "siguen" pulsadas.
Y por último vamos a cambiar también como actualizamos los frames. A partir de ahora, el hecho de haya velocidad horizontal y no sea zero, es lo que utilizaremos para determinar que debemos ir cambiando de frame, y si la velocidad es zero, nos setearemos en el frame 0 de la animación. El código queda así:
Vamos a probar que tal ha ido la cosa, compilamos y aparece nuesto simpático personaje quieto como un palo:
Si ahora empezamos a dar a derecha o izquierda, veremos como nuestro protagonista empieza a andar en la dirección que sea sin ningún tipo de problema.
¡Y funciona perfectamente! Sería una lástima dejarlo así, ¿no?
Aceleración y Desaceleración
Vamos a darle un toque adicional a las físicas añadiendo la posibilidad del salto. Para ello deberemos definir una nueva variable que nos indique la velocidad vertical, la llamamos player_vy. Dicha variable la definimos justo al lado de la velocidad horizontal, y la inicializamos a 0. He añadido también dos definiciones para las posiciones iniciales del player, que voy a usar de momento para controlar la caída durante el salto:
También he cambiado como gestiono el input de las teclas. Ahora uso las funciones SMS_getKeysHeld() y SMS_getKeysPressed(), por que voy a diferenciar el "mantener" una tecla apretada, del realizar una sola pulsación. Aparte, dejo de usar el operador de igualdad para las comparaciones, ya que me interesa que pueda haber más de una tecla pulsada a la vez. Así que usaremos el operador de AND binario "&"
¿Que estamos diciendo aquí? que si la velocidad vertical es 0 (estamos en el suelo) y pulsamos el boton 1, le asignaremos al player una velocidad vertical de -2. Vamos a tratar ahora esa velocidad vertical:
Lo que estamos haciendo aquí es, si la velocidad es distinta de zero, y nuestro personaje está en su posición inicial, o más arriba, aplicamos la velocidad a la posición actual. En caso de que nos hayamos pasado de su posición inical, recolocamos el player en su posición inicial, y seteamos la velocidad vertical a cero. Ya solo nos queda un paso. Para que un salto sea mas o menos realista, necesitamos que esta velocidad vertical vaya disminuyendo al subir y vuelva a incrementarse al bajar. Esto lo haremos en la función draw_main_character:
Vale, ahora le damos a compilar, y tendremos a nuestro muñeco que será capaz tanto de saltar hacia arriba como hacia adelante. Un gran progreso!!
Os dejo aquí un zip con todo el material de esta lección, y para la próxima le vamos a añadir un poco de sonido a nuestro mejunje.
https://www.dropbox.com/s/d8qvdv6v4n4fdeb/elotroladotutolesson7.zip?dl=0
Lección 8: Sonido
Vamos a dar un buen cambio de tercio y nos vamos a dedicar a añadir algo de sonido a nuestro juego de prueba. La Master System cuenta con un chip SN76489 para generar el sonido, con tres canales de onda cuadrática programables y un canal de ruido programable. devKitSMS proporciona el sonido usando la libreria PSGLib del mismo autor, sverx, y que nos viene de perlas para nuestros experimentos.
Añadiendo dependencias y recursos
El formato en que se encuentran grabados se conoce como psg, y podéis generaros vuestras própias músicas usando un tracker que los soporte, o soporte vgm y luego los pasais por un conversor.Para nuestra prueba, bajar los dos ficheros psg que os indico a continuación. Uno de ellos es una música y el otro un efecto de sonido, obra de mi compi David Sánchez "Murciano".
https://www.dropbox.com/s/lmcy8hhraszbw1n/music.psg?dl=0 https://www.dropbox.com/s/4dioq0jy930r0li/fx.psg?dl=0
Ahora cogéis estos dos ficheros y los ponéis el directorio bank1. La utilidad folder2c se encargará de convertir los ficheros en arrays de c usables por nosotros.
Si ahora lanzamos una compilación e inmediatamente después abrimos el fichero bank1.h, podremos ver que ya han aparecido los recursos convertidos.
Vale, vamos a meternos en harina. Primero de todo, añadimos a nuestro fichero la cabecera de la PSGlib. Si no la tenemos descargada, está en un subdirectorio dentro del mismo repositorio del devkitSMS. Asegurémonos que tanto PSGlib.h como PSGlib.rel están en el directorio base de nuestra prueba. Añadimos la cabecera:
Si le echáis un ojo, veréis las operaciones que nos permite dicha librería:
Modificamos nuestro fichero .bat para añadir la libreria de sonido en las dependencias a la hora de compilar el main, justo después de cuando añadimos la SMSLib, la llamada a compilar queda tal que así:
Ahora, justo antes del bucle principal del main, añadimos una llamada a PSGPlay(), que es la función que sirve para iniciar la reproducción de una canción.
Y añadimos, justo después del VBlank, una llamada a PSGFrame(). Dicha llamada, lo que realiza, es la ejecución de un frame de la canción. Como queremos que la ejecución de la canción vaya al ritmo que toca, ponemos la llamada justo después del VBLANK, por lo que va sincronizada con la velocidad del juego.
Le damos a compilar y...¡ya tenemos una magnífica música de David "Murciano" sonando en la master!
Efectos fx y opciones adicionales
Vamos a incluir también el efecto de sonido que os he pasada antes. Hemos de realizar solo dos cambios muy tontos. Primero de todo, incluiremos la llamada PSGSFXPlay() cuando nuestro protagonista vaya a dar un salto:
En el segundo paramétro estamos pasando el canal que vamos a usar para la reproducción del efecto de sonido, hay dos disponibles SFX_CHANNEL2 y SFX_CHANNEL3, y la opción de hacerlo sonar en dos a la vez SFX_CHANNELS2AND3. Recordad que cuando sacamos un efecto de sonido por un canal, ese canal queda "ocupado" durante el tiempo que dura el efecto, con lo que perturba a la música que esté sonando en ese momento. Bueno, ahora solo nos queda añadir la llamada a PSGSFXFrame() justo al lado de donde llamamos el PSGFrame().
Vale, le damos a compilar y...¡ya tenemos un efecto sencillo de sonido sonando!. Es ideal poner la llamada a reproducir un SFX en algún lugar del código que no se ejecute muchas veces seguidas, ya que eso produciría un sonido de repetición bastante desagradable. Vamos a añadir un efecto mas. Existe una función adicional que se llama PSGSetMusicVolumeAttenuation, que sirve para modificar el volumen de la música. Dicha función accepta valores desde 0 (ninguna atenuación) a 15 (silencio). Vamos a añadir una variable al inicio y la inicializaremos a cero:
Y luego añadimos en el main un poco de código para que vaya cambiando la atenuación cada 64 frames. Lo hago cada tantos frames por que en las pruebas que he hecho, si cambias la atenuación cada frame o cada dos o tres, distorsiona bastante el sonido.
Y ahora le damos candela, y ya está! ya tenemos un efecto nuevo para el sonido!.
Como crear PSGs para la master system
Voy a irme un poco por la tangente ahora con un pequeño tutorial sobre como exportar canciones desde un tracker. Hay varias maneras de generar esas canciones, pero todas incluyen en generar un fichero en formato VGM (Video Game Music) y posteriormente convertir dicha canción al formato PSG, que es el que usa la librería.
MOD2PSG
En mi primera prueba, he usado la aplicación mod2psg. Dicha herramienta os permite abrir los antiguamente conocidos como "modulos" hechos con algún tracker, y convertirlos a vgm. Podéis encontrar la herramienta en: http://www.smspower.org/Music/Mod2PSG
Luego he cogido un mod bajado de internet. El formato acceptado es el .MOD de 4 canales, el típico de los primeros tiempos del amiga:
https://www.dropbox.com/s/bgopfit49cco7hw/fourma.mod?dl=0
Abrimos dicho mod con la aplicación MOD2PSG, y le damos a exportar a vgm.
https://www.dropbox.com/s/ub22kbqfmh6enjn/fourma.vgm?dl=0
Ahora voy a usar las herramientas que se encuentran en el repositorio de la libreria PSGlib para convertir este vgm en un PSG. Para vuestra comodidad, he puesto las tres herramientas en un directorio llamado tools en el proyecto. Primero uso vgm2psg:
Ahora vamos a usar la herramienta llamada psgcomp para comprimir la información contenida en este psg, de manera que podamos rascar unos cuantos kbs (que van muy preciados en un cartucho). Si miramos, el psg que hemos sacado ocupa 11 kb. Le pasamos la herramienta de compresión:
Y nos queda un fichero justo por debajo de los 5 kbs! Vamos a probar que tal si lo incluímos en el juego. Copiamos el psg comprimido al directorio bank1, y cambiamos el código del main de la siguente forma:
Compilamos, y ya podemos escuchar la nueva música sacada desde un mod!
Deflemask
Otra alternativa de la que disponemos es la de usar un tracker, como Deflemask, para componer nuestras propias canciones, y luego crear el PSG para usarlo en nuestros juegos. El citado deflemask es uno de los trackers más populares actualmente, y permite generar tracks para multitud de consolas. Está disponible en descarga gratuita en:
Vamos a coger una de las canciones de ejemplo que están disponibles allí, y la exportaremos para usarla en nuestro juego. He escogido una llamada animeopening, y la exporto como fichero vgm.
Ahora el fichero vgm lo convertimos igual que antes, a psg.
Le paso ahora la herramienta de compresión:
Y muevo el fichero resultante al directorio bank1, modifico el main.c tal que así:
Compilamos y .... ¡ya tenemos la música funcionando perfectamente en nuestra pequeña demo! Para escucharla mejor podéis comentar las líneas referentes a la atenuación que hemos implementado antes.
Os dejo aquí el zip con el código y las utilidades de esta lección: https://www.dropbox.com/s/n0g9ijnvw1jku46/elotroladotutolesson8.zip?dl=0
Lecciones avanzadas
Lección 9: Scroll
Introducción
Después de un buen tiempo parado, es hora de retomar este tutorial con una lección que seguro que estabais esperando, ¡¡la de como hacer scroll!!.
Para ello usaremos una librería creada por el usuario Psidum de la web http://www.smspower.org . Se trata de la librería GSLib. Dicha librería permite scroll en las 8 direcciones, y funciona a través del uso de metatiles de 16x16 (o 2x2 tiles, vamos). Una de las características más interesantes que tiene es que Psidum ha creado adicionalmente una herramienta llamada UGT (nada que ver con el sindicato, aparentemente) que permite importar una imagen o un archivo de Tiled, saca los tiles y los metatiles y es capaz de exportarlo todo listo para ser compilado en nuestro juego.
Podéis acceder a su repositorio aquí: https://bitbucket.org/Psidum/gslib/src/master/
Empezamos
A partir de ahora, y para simplificar, iré poniendo el tutorial en Github, e iré anotando cada lección con un tag, de manera que os podáis ir bajando las distintas lecciones solo con clonar el repositorio. El link del repositorio será este:
https://github.com/kusfo/shootertutorial
Entonces, considerando que ya teneis SDCC configurado como hicimos en la primera lección (aunque no estaría de más actualizar la versión que tengaís), podéis clonar el repositorio en el Tag lesson 0. En este caso tendremos un esqueleto vacío pero ya preparado con los directorios con los assets que vamos a usar.
Y un game.c vacío con solo los includes de las librerías que vamos a usar.
Vamos a empezar creando una función llamada init_console, y llamamos allí al código necesario para inicializar la consola, de manera que quede como a continuación:
Y añadimos en el main la inicialización, el bucle principal, y la llamada a VBLANK, nos queda el código así:
Si vamos ahora al compile.bat que os he adjuntado, veréis que aún no estamos exportando ningún gráfico ni nada, solo compilamos el juego y lo linkamos. Vamos a darle caña. Ejecutamos compile.bat y se lanzará el emulador con una bonito rom en negro. ¡No está mal!. Es hora de empezar a ponerle mandanga..
Escenario
Para esta lección voy a usar un tileset del usuario surt que fue colgada originalmente en: https://opengameart.org/content/void-blaster
En el directorio material he incluido un fichero tmx de Tiled con el mapeado creado para la prueba. Si lo abriis con Tiled podréis ver que he añadido solo la capa de mapeado, pero nada en las demás. Las demás se usan para prioridades, colisiones o casillas especiales, las veremos más adelante.
Vamos a usar UGT para exportar del TMX a los ficheros en .c que nos interesan. Añadimos esta línea al compile.bat:
Esto nos generará los archivos necesarios, y los guardará en el directorio assets. Ahora necesitamos convertir estos assets a ficheros usables como bancos. En las primeras lecciones usamos folder2c, pero ahora vamos a usar la herramienta assets2banks, más moderna y con más posibilidades. Podéis leer la documentación de la herramienta en, pero para lo que vamos a hacer es bastante fácil. En el directorio assets crearemos un fichero assets2banks.cfg, y definiremos un nodo con los assets generados por el UGT.
Y ahora añadimos una línea al compile.bat para que se llame al assets2banks.exe, con la opción --singleheader, y otra linea a continuación donde compilaremos la libreria GSLib.
Si ahora ejecutamos una vez el compile.bat, tendremos ya generado un fichero assets2banks.h, y aparte se nos habrá generado un bank2.rel y un GSLib.rel, que añadiremos a la linea de linkage (recordad de añadir también el -Wl-b_BANK2=0x8000):
Ahora vamos a atacar el game.c. Primero de todo, incluimos el bank2.h, y GSLib.h. Ahora toca llamar a las funciones específicas para cargar los recursos y las tablas de scroll. Para cargamos la paleta y los tiles usando las mismas funciones que estabamos usando en otras lecciones, y luego llamamos a las funciones de GSLLib para inicializar el mapa, posicionar la ventana en la coordenada (0,0), y llamar al refresco del VDP después de esta primera carga. Así que nos quedaría lo siguiente fuera del bucle:
Y dentro del bucle habremos de llamar a la función GSL_VBlank justo después de nuestra llamada a VBlank:
Si ahora llamamos a nuestro compile.bat, se ejecutará nuestra rom y ¡el escenario aparecerá! Sin embargo, dicho escenario está totalmente estático...
Pues se tratará de que hemos actualizar la posición del scroll cada frame. De momento para no liarla, haremos que se avance una cantidad entera, en este caso 1 px. Definimos dos variables, a las que llamamos deltax y deltay:
Y ahora inicializamos deltax a 1 justo antes del bucle:
Si ahora le damos a ejecutar...¡Ya tenemos el scroll funcionando!.
Mejoras
Todo esto está muy bien, pero si os fijáis, el escenario empieza a corromperse una vez llegamos a su final. Esto ocurre por que no estamos controlando que estamos llegando al final del escenario. Y también tenemos un pequeño problema al empezar el scroll (por el tiempo necesario en cargarlo).Para ello en el bucle principal, haremos una pequeña trampa para que el scroll tarde un poco en empezar a correr (para ver mejor el inicio del nivel, esto con un fundido luego quedaría estupendo). Y aplicamos los deltas al scroll hasta que lleguemos al final del nivel (cosa que comprobamos con GSL_getMapWidthInPixels() y GSL_getCurrentX(), más 256px que es el ancho de la pantalla). El código del bucle quedaría tal que así:
Si ahora le damos candela, veremos nuestro escenario moverse justo hasta llegar al final, ¡donde nos espera la malvada base enemiga! Podéis bajar el código completo en el tag "lesson 1" del repositiorio. ¡Y ahora a esperar a mejorar todo esto con la próxima lección!
Lección 10: Sprites y Scroll
Bueno, ahora que ya hemos conseguido tener un fondo scrolleando sin problema, vamos a usar los conocimientos que teníamos de añadir sprites de pasadas lecciones para añadir una nave protagonista, y la acompñaremos de algún sprite malote por ahí :-).
Primero de todo, voy a coger la nave protagonista que estaba en el mismo material que gráfico que compartió surt. La voy a recortar para quedarme solo la nave aislada.
Una vez bien recortada, puedo comprobar como la nave se cuadra en los tiles de 8px, por que cada parte de la nave la tendremos que construir con un sprite de 8x8px. Si os fijáis tendremos que usar 10 sprites, 1 en la primera linea, 4 y 4 en las dos siguientes, y 1 más en la última linea.
La grabo dentro del directorio material, como nave.png. Ahora debo añadir una linea en el compile.bat para exportar la nave llamando al BMP2tile, que además incluyo en el directorio ./tools/bmp2tile por comodidad. Deberemos exportar con el quitar duplicados para evitar que nos ponga más de 1 tile vacío, pero si que desactivamos el quitar espejados ya que los sprites no se espejan en la Master. La línea a incluir es tal que así:
Añadiremos los tiles y la paleta de la nave dentro del fichero assets2banks.cfg para que vayan junto a los assets del fondo.
Y ahora cargaremos la paleta de la nave como la paleta de los sprites, y los tiles de la misma a partir de la posición 256 de la VRAM, ya que hemos definido en el init_console que los sprites usan la segunda mitad de la VRAM.
Para dibujar la nave, dada la forma irregular de la misma, es un poco más complicado que el muñeco que usamos anteriormente. Deberemos dibujar 1 sprite en una linea, 4 y 4 en las dos siguientes y 1 en la última. Para hacerlo más fácil, voy a definir un struct que contendrá la posición de la nave:
Y le inicializo los valores x e y para la nave justo antes del bucle principal, tal que así:
Y ahora voy a definir una función, llamada draw_player, donde dibujo los 10 sprites, posicionándolos todos a partir de las coordenadas del player (que asumimos como las coordenadas de la esquina superior izquierda). Los números de tile para los sprites os pueden parecer un poco confusos, así que he añadido los números de tile a los que se convierten al quitar los duplicados:
Y la función quedará así:
Y ahora tengo que añadir las llamadas a inicializar sprites, añadirlos y luego copiar el SAT justo después del VBLANK, tal como hacíamos anteriormente:
Si ahora le damos candela a compilar, ¿que tendremos?
¡Una estupenda nave recorriendo el espacio junto a nosotros! No está nada mal para las pocas líneas que le hemos añadido al código.
Tomar el control
Lo que hemos hecho ahora no está mal, pero es un poco aburrido como juego, ¿no? Vamos a añadir control de forma parecida a como hicimos en las lecciones anteriores. La noveda va a ser que vamos a trabajar con deltas, para tratar de mejor forma los dos ejes, ya que ahora podemos en las dos dimensiones a la vez. Añadimos primero los deltas en la struct del player:
Ponemos los deltas como signed char por que pueden ser negativos si no estamos moviendo hacia arriba o hacia la izquierda. Añadimos los deltas a la función de dibujado del player que hemos creado justo antes:
E inicializamos los deltas antes del bucle principal:
Ahora solo queda crear una función donde gestionaremos todo el control de la nave. En esta determinaremos el delta de cada eje dependiendo de que teclas se encuentran pulsadas en cada momento, tratando ambos ejes de forma independiente. Así nos quedaría algo del estilo:
Y simplemente hemos de añadir la llamada justo antes de dibujar los sprites:
Si ahora le damos a compilar, podremos admirar la gran maniobralidad de nuestra nave. ¡Ojo como se desliza por la pantalla!
Un poco de animación
Lo que hemos conseguido hasta ahora no está mal, pero la nave resulta demasiado estática. Vamos a darle un poco más de vida, vamos a cambiar uno de los dos fuegos de motor para que sean diferentes. Así que he cogido el editor, y he sacado esto:
Como ahora los dos motores son diferentes, la numeración de los tiles va a cambiar, ya que no se considerarán duplicados. La numeración quedará así:
Y ahora vamos a añadir una lógica para que se vayan dibujando de forma alternativa un motor o otro a la hora de dibujar el jugador:
¿Que hace esta función? "frame_cnt & 15" es otra manera de calcular el resto de dividir por 16, con lo que es equivalente a frame_cnt % 16. En la condición estamos decidiendo dibujar un frame o otro dependiendo de si el resto del contador de frames dividido por 16 es menor o mayor que 7, con lo que tenemos 8 frames con un tile de motor y otros 8 frames con el otro, y así succesivamente.
Le damos a compilar, y....
¡Ya tenemos la nave con animación moviéndose por el escenario libremente! No está mal, no está mal, para el poco código que hemos tenido que echar. Podéis encontrar todo el código de esta lección en el repositorio con el tag "lesson2". En próximas lecciones le iremos dando un poco más de alegría a la fase. ¡Nos vemos pronto!