top of page

Movent-nos per pantalla de caselles




Objectiu

En aquest article expliquem com crear una pantalla a partir de caselles prefabricades i com moure un sprite dins d'aquest mapa detectant les col·lisions a les caselles que no siguin transitables.


Ho farem només amb dos tipus de casella, transitable o no, i partint dels patrons de l'Usas, un joc de plataformes de Konami per MSX2 i un dels meus preferits.


Els patrons els he extret carregant el joc amb l'openMSX, fent una pausa amb el debugger, obrint el visualitzador de la VRAM i volcant la imatge de la pàgina dels patrons a un fitxer. Aquest fitxer en format bmp s'ha passat a format MSX tal i com està explicat a l'article Carregar Imatges al MSX.


El programa només agafa dos tipus de casella per fer una introducció senzilla. Per poder crear un mapa més gros, jo recomano el programari Tiled que és multiplataforma i permet exportar el mapa a csv que és fàcilment copiable en un fitxer C com un array.


Què és un mapa de caselles?

Un mapa de caselles (en anglès tile map) és la creació d'un mapa a on cada casella té un codi en memòria que indica quin tipus de casella és i com s'ha de pintar. Nosaltres usarem caselles de 8x8 en screen 5, així tindrem que la nostra pantalla està formada per 32x26.5, perdrem els últims 4 pixels de l'última filera. Tenint en memòria el que és cada casella, podem saber si un sprite està en una posició de casella prohibida o no.


Per detectar si hi ha una col·lisió del sprite amb el mapa, utilitzarem la tècnica explicada en aquest article i que consisteix en trobar si la següent casella a on ha d'anar l'sprite és accessible o no.


Codi

En aquest programa carreguem una imatge de caselles i segons un mapa pintem aquestes caselles. Després creem un sprite i controlem que es mogui pel mapa sense que passi per sobre de les caselles que no són transitables.


Per fer el moviment de l'sprite utilitzem una rutina d'interrupcions més avançada que la que apareix al Fusion-C 1.3. L'antiga funció era InitVDPInterruptHandler (podeu veure el seu ús en l'exemple Interrupt-vdp.c) però tenia l'inconvenient que si la funció que s'executava durant la interrupció estava a la pàgina 2 de la memòria es quedava penjat. Amablement l'Oduvaldo Pavan va desenvolupar les funcions perquè funcionés la interrupció a qualsevol pàgina. Aquestes funcions són les que trobareu en aquest codi.


Això ho hem fet en el següent programa que es pot trobar en el gitlab de MoltSXalats:

La primera part del codi, crida els includes que s'utilitzaran en el programa, defeineix les variables per carregar les imatges (file, BUFFER_SIZE_SC5, LDbuffer, mypalette) i la variable map1 que contindrà la informació de si una casella és transitable o no. Aquest serà l'esquema que després transformarem a la pantalla pintant 8x8 pixels diferents segons si és un 0 o un 1 la casella de map1.

A la variable sprite definim un sprite senzillet que és el que utilitzarem per anar-nos movent per la pantalla.


Tot seguit venen les funcions per carregar fitxers des del MSX-DOS: FT_SetName, FT_errorHandler, FT_LoadSc5Image, FT_LoadPalette i FT_LoadPaletteMSXViewer que ja s'han explicat als posts de Carrega imatges al MSX i que utilitzarem per carregar les caselles de 8x8 a la memòria de vídeo del MSX.

Després de les funcions per carregar dades a la VRAM, comencem a definir les variables que usarem per controlar el hook (el ganxo) de les interrupcions de Vídeo. L'adreça 0xFD9F és la que llegeix el Z80 cada cop que hi ha una interrupció de vídeo i que nosaltres reescrivim per tal que executi la nostra funció.

La funció InterruptHandlerHelper és una petita funció en assemblador que s'utilitzarà per poder saltar a la nostra funció.


InitializeMyInterruptHandler s'encarrega de substituir l'adreça del ganxo de la interrupció per l'adreça de la nostra funció que passem com a primer paràmetre, myInterruptHandlerFunction. El segon paràmetre, isVdpInterrupt, li passem el valor 1 per indicar que és la interrupció del VDP.


Per què utilitzem una interrupció del VDP? La utilitzem per utilitzar un tempo en el joc, un ritme, a on a cada interrupció actualitzem totes les dades del joc: posició del jugador, posició dels enemics, la música, efectes sonors, etc. Utilitzant la interrupció del VDP, es perd l'opció de detectar la col·lisió de sprites. Però tens la ventatge de portar un tempo, per exemple, per portar el ritme de la música de fons.


En aquest cas utilitzarem la interrucpió per controlar el moviment de l'sprite.

La funció EndMyInterruptHandler serveix per retornar la interrupció del VDP a la seva rutina anterior abans que nosaltres hi poséssim la nostra.


La variable copsVsync és un comptador dels cops que entrem a la interrupció, serveix per portar diferents ritmes, potser els enemics s'actualitzen cada 2 cops, la música cada cop, etc.

La variable processar_moviment és una bandera per indicar que ja puc fer el moviment de l'sprite. Aquesta bandera em permet separar les tasques que es fan durant la interrupció, ja que indicarà a una altra funció del bucle principal que ja pot treballar processant el moviment de l'sprite. D'aquesta manera, si el càlcul del moviment fos molt pesat i durés més que el temps entre interrupcions, no molestaríem les altres tasques que també s'actualitzarien.


La funció main_loop és la que s'executa a cada interrupció del VDP, en aquests moments només tenim un comptador de cops d'interrupció i cada dues interrupcions actualitzem la bandera per poder processar la posició de l'sprite. Podeu provar amb altres valors, els valors més alts faran que no sigui tan manejable l'sprite i valors més petits farà que vagi molt ràpid.


La següent funció que trobem és la inicialitza la pantalla de joc. Entre les línies 283 i 286 ha carregat la imatge dels patrons en una pàgina oculta de la VRAM. Després fa dos bucles per recórrer la matriu del mapa de caselles, començant per les files i després les columnes. Per cada posició de la matriu mirem quin tipus de casella és. En aquest cas només en tenim 2, segons el número copiem la casella de 8x8 de la pàgina a on hem carregat la imatge i la posem a la posició corresponent. Quedant així dibuixada la pantalla. Les funcions HMMM tenen els primers dos paràmetres les coordenades origen de la casella 8x8 a copiar. Si canviem aquests dos paràmetres tindrem un altre dibuix per les caselles prohibides i permeses.


A les línies següents 299 a 303 creem l'sprite senzill per anar movent per la pantalla. Les següents línies són variables que utilitzarem com a coordenades de l'sprite i per passar a text aquestes coordenades per mostrar-les per pantalla.

Aquí tenim la funció moviment_sprite que és un bucle infinit fins que s'apreti la tecla ESC (línia 311). Dins aquest bucle, primer mirem si ens trobem en una interrupció que hem de calcular moviment (ens l'activa (indica) la funció de la interrupció VDP main_loop). En cas d'haver de trobar les posicions de l'sprite, mirem primer si s'ha apretat alguna fletxa (o joystick al primer port, ja que llegeix el mateix quan se li passa el paràmetre 0 a JoystickRead) i el guardem a la variable stick. Les línies 317 a 321 escriuen aquestes coordenades a la pantalla, primer passem el número a cadena de caràcters (sprintf) i els mostrem per pantalla amb PutText. D'aquesta manera ens serà més fàcil debugar ja que estem visualitzant les coordenades de l'sprite.


Després fem tot el control de les tecles apretades per moure l'sprite. Comencem amb el d'amunt (stick==1). Primer posem la posició que aniria l'sprite un pixel cap amunt i mirem en quina casella estaríem amb aquesta nova ubicació. Com que són blocs de 8x8, he de passar les coordenades a caselles, per això dividim entre 8, per fer-ho fàcil fem un desplaçament de 3 bits a la dreta (operació de bit >>) que aproxima a una divisió entre 8 agafant i arrodonint a la part entera. Ara hem de mirar aquesta casella a on estem de quin tipus és, per això busquem el valor a la nostra variable que conté el mapa traduint les caselles a array. Com que està fet per fileres, la posició de la casella X és tal com està i hem d'augmentar cada posició de les Y per 32 ja que el mapa té 32 caselles horitzontals. Això seria la línia 331.


A part, també hem de mirar la casella que està una posició a la dreta del mapa, que seria la línia 332. Aquesta segona línia l'he afegida de forma empírica, teòricament només faria falta mirar la futura casella a on anem si es pot accedir o no. Però si no la afegia accedia a una part de la casella prohibida. Ara bé, quan he estat fent aquest redactat, crec que el problema pot venir quan es fa l'arrodoniment a enter que sempre fa cap avall i per això s'ha d'agafar la següent casella. Bé, si hi ha algú que tingui una explicació millor, no dubteu en comunicar-nos-ho.


Com que és un cas senzill i només tenim 2 tipus de casella, mirem de si alguna de les dues caselles inspeccionades és prohibida (de tipus 1 en el nostre cas). Si és prohibida, tornem enrere la posició de l'sprite que havíem variat, en aquest cas y=y+1. Si hi haguessin més tipus de casella prohibida, hauríem d'afegir-les també en aquest if.


Finalment reubiquem l'sprite amb el nou valor de les coordenades.


La resta de casos tenen la mateixa estructura, canvien la posició de l'sprite i miren si la nova casella (i la següent) són transitables o no.

Finalment ja tenim la funció principal que comença configurant la pantalla (línies 378 i 379), després canvia el color 15 que a la paleta de la imatge carregada tenia un color fosc per posar-li el color blanc i es pugui llegir per pantalla.


Inicialitzem les variables i posem la funció main_loop en el ganxo de la interrupció del VDP (línia 385). Indiquem en quina posició començarà l'sprite i cridem la funció que té un bucle infinit fins que s'apreti la tecla ESC. Aquesta funció és moviment_sprite a la línia 389.


Quan sortim d'aquest bucle, restaurem el ganxo de la interrupció de VDP amb EndMyInterruptHandler i tornem a l'screen 0 amb els colors originals. Finalment acabem la funció i retornem al DOS amb Exit.


Conclusió

En aquest article hem construït un mapa senzill de caselles i hem mogut un sprite per aquesta pantalla sense que l'sprite passi per una casella prohibida. El mapa només té dos tipus de casella, si volem un mapa més acolorit i variat hem d'anar creant diferents tipus de casella. Aquests tipus haurem de controlar-los a l'hora de pintar afegint més condicionals a les línies 291 i sabent aquests tipus quines coordenades tenen a la VRAM a on està guardada per fer bé la còpia amb la funció HMMM.


El MSX2 té scroll vertical i el MSX2+ també té l'horitzontal. Què passa si tenim un mapa de caselles més gran que el que es visualitza per pantalla? Puc utilitzar les funcions d'scroll per moure tota la pantalla i continuar detectant les col·lisions? Aquests temes els tractarem en articles posteriors.


Ara que també hem vist la interrupció de vídeo, podríem provar com generar un replayer del MSX-Music (OPLL) i que a cada interrupció actualitzi les notes que estan sonant. Treballarem aquesta funcionalitat en articles posteriors.


Clica aquí per a veure l'exemple funcionant.




Comments


bottom of page