Usamos cookies y procesamos datos de tu navegación principalmente con fines técnicos, estadísticos y publicitarios; la utilización de nuestros servicios implica tu consentimiento. Más información en nuestra política de privacidad.

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. DevKitSMSTutorial Pic1.png

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:

DevKitSMSTutorial Pic2.png

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.

DevKitSMSTutorial Pic3.png

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.

DevKitSMSTutorial Pic4.png

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).

DevKitSMSTutorial Pic5.png

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í:

DevKitSMSTutorial Pic6.png

Es hora de probar que tal funciona la cosa con el emulador. Damos doble click a compile.bat y....

DevKitSMSTutorial Pic7.png

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í:

DevKitSMSTutorial Pic8.PNG

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:

DevKitSMSTutorial Pic9.PNG

Y ahora actualizaremos la posición del scroll a este valor inicial, justo antes del bucle:

DevKitSMSTutorial Pic10.PNG

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í:

DevKitSMSTutorial Pic11.PNG

¿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!

DevKitSMSTutorial Pic12.PNG

¡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.

DevKitSMSTutorial Pic13.PNG

Volvamos a darle a compilar, y vemos el resultado:

DevKitSMSTutorial Pic14.PNG

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:

DevKitSMSTutorial Pic15.PNG

Le damos a compilar, y ahora se puede ver el efecto de salto que se produce, de una forma más clara:

DevKitSMSTutorial Pic16.PNG

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 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.

DevKitSMSTutorial Pic17.png

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

DevKitSMSTutorial Pic18.PNG

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.

DevKitSMSTutorial Pic19.PNG

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.

DevKitSMSTutorial Pic20.PNG

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().

DevKitSMSTutorial Pic21.PNG

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:

DevKitSMSTutorial Pic22.PNG

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!

DevKitSMSTutorial Pic23.PNG

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:

DevKitSMSTutorial Pic24.PNG

¡¡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

DevKitSMSTutorial Pic25.PNG

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.

DevKitSMSTutorial Pic26.PNG

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.

DevKitSMSTutorial Pic27.PNG

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.

DevKitSMSTutorial Pic28.PNG

La implementación de esta nueva función de dibujado del protagonista es la siguiente.

DevKitSMSTutorial Pic29.PNG

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.

DevKitSMSTutorial Pic30.PNG

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í:

DevKitSMSTutorial Pic31.PNG

DevKitSMSTutorial Pic32.PNG

DevKitSMSTutorial Pic33.PNG

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.

DevKitSMSTutorial Pic34.PNG

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

DevKitSMSTutorial Pic35.PNG

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):

DevKitSMSTutorial Pic36.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):

DevKitSMSTutorial Pic37.PNG

Y si ahora ejecutamos, tenemos a nuestro amigo el espia perfectamente quieto, pero saliendo bien dibujado.

DevKitSMSTutorial Pic38.PNG

¿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.

DevKitSMSTutorial Pic39.PNG

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:

DevKitSMSTutorial Pic40.PNG

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í:

DevKitSMSTutorial Pic41.PNG DevKitSMSTutorial Pic42.PNG

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í:

DevKitSMSTutorial Pic43.PNG

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...

DevKitSMSTutorial Pic44.PNG

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:

DevKitSMSTutorial Pic45.PNG

Y tenemos justo a continuación una serie de definiciones de las diferentes teclas que podemos chequear:

DevKitSMSTutorial Pic46.PNG

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í:

DevKitSMSTutorial Pic47.PNG

Ahora le damos a compilar, y ya podemos cambiar la dirección a la que se mueve el muñeco!:

DevKitSMSTutorial Pic48.PNG

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í:

DevKitSMSTutorial Pic49.PNG

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):

DevKitSMSTutorial Pic50.PNG

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.

DevKitSMSTutorial Pic51.PNG

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í:

DevKitSMSTutorial Pic52.PNG

Vamos a probar que tal ha ido la cosa, compilamos y aparece nuesto simpático personaje quieto como un palo:

DevKitSMSTutorial Pic53.PNG

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.

DevKitSMSTutorial Pic54.PNG

¡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:

DevKitSMSTutorial Pic55.PNG

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 "&"

DevKitSMSTutorial Pic56.PNG

¿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:

DevKitSMSTutorial Pic57.PNG

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:

DevKitSMSTutorial Pic58.PNG

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!!

DevKitSMSTutorial Pic59.PNG

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.

DevKitSMSTutorial Pic60.PNG

Si ahora lanzamos una compilación e inmediatamente después abrimos el fichero bank1.h, podremos ver que ya han aparecido los recursos convertidos.

DevKitSMSTutorial Pic61.PNG

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:

DevKitSMSTutorial Pic62.PNG

Si le echáis un ojo, veréis las operaciones que nos permite dicha librería:

DevKitSMSTutorial Pic63.PNG

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í:

DevKitSMSTutorial Pic64.PNG

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.

DevKitSMSTutorial Pic65.PNG

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.

DevKitSMSTutorial Pic66.PNG

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:

DevKitSMSTutorial Pic67.PNG

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().

DevKitSMSTutorial Pic68.PNG

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:

DevKitSMSTutorial Pic69.PNG

DevKitSMSTutorial Pic70.PNG

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.

DevKitSMSTutorial Pic71.PNG

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.

DevKitSMSTutorial Pic72.PNG

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:

DevKitSMSTutorial Pic73.PNG

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:

DevKitSMSTutorial Pic74.PNG

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:

DevKitSMSTutorial Pic75.PNG

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:

http://deflemask.com/

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.

DevKitSMSTutorial Pic76.PNG

Ahora el fichero vgm lo convertimos igual que antes, a psg.

DevKitSMSTutorial Pic77.PNG

Le paso ahora la herramienta de compresión:

DevKitSMSTutorial Pic78.PNG

Y muevo el fichero resultante al directorio bank1, modifico el main.c tal que así:

DevKitSMSTutorial Pic79.PNG

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