
Mario PRO2 és una pràctica a on l'objectiu és programar noves funcionalitats en un joc ja començat.
La preparació de la pràctica (li direm la Part 0) és estudiar amb detall el codi donat, i experimentar amb ell per familiaritzar-s'hi al màxim. L'examen de la pràctica de PRO2 serà sobre aquest programa i és basarà en el codi inicial donat i en classes amb especificació que es desenvolupin posteriorment.
Després de la "Part 0", hi haurà una sèrie de parts a desenvolupar que després s'avaluaran amb l'examen de la pràctica. De moment (a 17 de Febrer), tenim la Part 1, i després de l'examen parcial sortiran dues parts més.
La "Part 0" va acompanyada de la secció següent que és una explicació del codi inicial i instruccions sobre com compilar i treballar amb el joc, i com està organitzat.
En el text hi ha exercicis intercalats que pretenen augmentar la comprensió sobre el projecte i fer les primeres modificacions per agafar confiança amb el codi.
L'arxiu del projecte inicial del Mario PRO2 es pot descarregar en el botó següent:
Per poder entendre el codi que es proporciona, cal llegir el programa i esbrinar com funciona tot. Com més clara estigui l'estructura del projecte i a on està implementada cada funcionalitat, més fàcil serà el desenvolupament.
Excepte la classe Window (fitxers window.cc i window.hh), i
el fitxer fenster.h que l'acompanya (que tenen codi complex de
gestió de finestres en Windows, Linux i MacOS), la resta del codi
no té gairebé res que no s'hagi estudiat abans, ja sigui a PRO1 o
a PRO2. Una petita part del que cal saber es farà durant la
segona part de PRO2, com és el treball amb punters.
Descomprimeix el projecte en una carpeta apart i obre VSCode en la carpeta. Tot hauria d'estar preparat perquè al prémer F5 el projecte es compili i s'executi. Ha de sortir la finestra del joc sola.
Per altra banda, des d'un terminal, tens aquestes altres possibilitats:
make clean, que esborra tots els fitxers intermitjos (*.o i
l'executable).
make tgz, que crea un tgz amb el projecte, per si vols
guardar la versió actual. El fitxer creat per tgz es numera
amb l'epoch del moment (un número de segons molt gran), de
tal manera que no pots sobreescriure un altre tgz que tinguis
i que sigui anterior, perquè tindrà un epoch diferent.
Recomanem fer make tgz amb certa freqüència i guardar els
fitxer en un lloc segur per no perdre versions de la pràctica i
poder recuperar versions anteriors en cas d'emergència.
make MODE=release, aquesta comanda construirà una versió
optimitzada del joc, que és possible que tingui més velocitat.
Abans de cridar make amb el mode "release" cal fer
make clean primer, per tal d'esborrar els fitxers objecte
(.o) sense optimitzar.
Per una banda hi ha el codi que no cal llegir ni estudiar:
fenster.h: el fitxer de capçalera que Window utilitza
internament. És una llibreria que permet obrir finestres i
pintar en els 3 sistemes operatius típics (Windows, Mac i
Linux).Després està window.hh, que está bé mirar pel seu interface,
o sigui la llista de mètodes que té i com s'utilitzen. Aquesta
classe està molt documentada, amb comentaris de C++ en format
Doxygen, i es pot entendre força bé
mirant el codi que l'utilitza. A VSCode, al posar el ratolí sobre
un mètode i esperar un parell de segons, surt la documentació en
una finestreta emergent, cosa que va molt bé per anar entenent en
context.
Finalment tenim el codi que cal llegir i entendre al 100%, perquè tot aquest codi fa servir conceptes que s'han treballat abans. Conté els mòduls següents:
geometry.hh: que només declara les dues tuples Pt (un punt
2D) i Rect (un rectangle 2D).utils.{hh,cc}: funcions útils ja implementades.Platform: l'objecte plataforma que apareix al joc.Mario: el personatge del Mario que apareix al joc.Game: la clase que gestiona el joc sencer.main.cc: el programa principal, amb el bucle principal del
joc.Els 3 primers mòduls estan a l'espai de noms (o namespace)
anomenat pro2. Per tant, per fer servir les classes i funcions
d'aquests mòduls cal: o bé prefixar el nom de la classe amb
pro2:: (en fitxers .hh); o bé posar using namespace pro2;
(en fitxers .cc).
El següent diagrama mostra les relacions de dependència entre
mòduls. Una fletxa des d'un mòdul A a un mòdul B indica que
A utilitza B.
El diagrama fa palès que els 3 mòduls de baix de tot són la base
sobre la que es construeix el joc (i alhora són els que pertanyen
a l'espai de noms pro2). Després hi ha Game, que implementa
la funcionalitat del joc. Game utilitza Mario i Platform
perquè té membres privats que són el personatge principal i un
vector de Platforms que són les plataformes del joc. El
diagrama mostra, també, quina posició ocuparia una classe nova
(de nom ????) que representi algun objecte del joc.
El programa sencer es basa en un bucle principal, molt típic dels jocs, en què es va creant una "pel·lícula interactiva" a base d'anar pintant fotogrames i simulant què passa entremig de dos fotogrames consecutius en funció de les tecles premudes, el ratolí, i les pròpies regles del joc.
El bucle és el següent:
window.next_frame()).update: S'actualitza l'estat del joc, és a dir, es fa la
simulació de moviments i tots aquells canvis que hi hagi hagut
durant el temps entre l'últim fotograma i l'actual.paint: Es pinta l'estat del joc a la pantalla.La classe Game té, precisament, dos mètodes anomenats update
i paint que es corresponen amb els passos 3 i 4, i també
is_finished que decideix parcialment si el joc ha finalitzat.
El codi del bucle és, exactament:
while (window.next_frame() && !game.is_finished()) {
game.update(window);
game.paint(window);
}
Un diagrama sobre els passos per pintar un sol fotograma pot ajudar a entendre-ho millor:
En el diagrama el temps és l'eix horitzontal i creix cap a la
dreta. En essència, Window::next_frame fa visible el fotograma
inmediatament, i tot seguit s'espera una quantitat de temps que
es calcula amb respecte a la última crida a Window::next_frame,
per tal de poder ajustar el temps entre fotogrames, que sol ser
de 16ms. Aquests 16 milisegons donen lloc a la freqüència
necessària per produir 60 fotogrames per segon (1000ms / 60
fotogrames). 60 FPS es considera l'estàndar perquè hi hagi
sensació de fluidesa suficient en un joc.
La seqüència de fotogrames, doncs, seria una repetició d'aquest patró:
Game::updateEl mètode Game::update té una estructura que ha de resultar
entenedora:
void Game::update(Window& window) {
process_keys(window);
update_objects(window);
update_camera(window);
}
Primer es processen les tecles (Game::process_keys) que s'han
premut entre el fotograma anterior i l'actual. Window té
mètodes per determinar quines són. La implementació de
Game::process_keys només detecta la tecla "Escape" i això és el
que Game fa servir per indicar al programa principal que el joc
s'ha acabat.
Exercici 0.1: Fes els canvis necessaris al codi del projecte que permeti pausar el joc amb la tecla 'P', i tornar-lo a engegar tornant a prémer 'P'. Pausar el joc vol dir que l'acció queda congelada en el mateix fotograma permanentment, fins que no es torni a posar en marxa. Això implica que no s'actualitzen els objectes ni la càmera, però sí que se segueix pintant el joc (i es processen les tecles!). Els canvis en aquest exercici són: a) afegir un camp nou a la classe
Game, i modificar un o més mètodes existents.
Després el mètode Game::update_objects crida el mètode update
de cada objecte que hi ha al joc. Això és una manera de delegar
la feina a cada objecte particular. Els objectes del joc que
necessiten update són aquells que no són estàtics i canvien de
posició o tenen animacions, com ara el Mario. En canvi, com que
les plataformes són fixes, no tenen mètode update i Game no
necessita cridar-lo.
Exercici 0.2: Fes els canvis necessaris (de nou afegir membres i canviar mètodes) per tenir un altre
Marioal joc. Abans de fer-ho, pensa bé si es tracta d'una nova classe o d'un nou objecte! Posa'l com a membre de la classeGameamb nommario2_. Inicialitza'l amb una coordenadaxuns 30 píxels a l'esquerra delmario_principal. Busca els llocs a on apareix el primerMarioi fes les mateixes crides als mateixos mètodes que els que es fan ambmario_. Abans de provar-ho, què penses que succeirà quan executis? Els dos Marios són independents? Ja tenim un joc per a dos jugadors?
En general, per afegir qualsevol objecte al joc, doncs, el mínim
que cal fer és posar-lo com a membre de la classe Game i afegir
les crides a les operacions update i paint corresponents dins
dels mètodes Game::update i Game::paint.
Exercici 0.3 (dificultat mitjana): Fes que
mario2_es pugui controlar amb tecles diferents quemario_. Caldria primer modificar la classeMariode la següent manera. Primer cal queMariopugui guardar les 3 tecles que el controlen en camps privats, que es poden dirjump_key_,left_key_, iright_key_. En el projecte inicial, les tecles són fixes i són l'espai i les fletxes d'esquerra i dreta. Pelmario2_cal que siguin "W" per saltar, "A" per l'esquerra i "D" per la dreta (això permet jugar a dues persones). Fixa't que pots passar un caràcter als mètodesWindows::is_key_pressediWindow::was_key_pressedperquè es fan servir els codis ASCII.Els valors d'aquestes 3 tecles s'haurien de passar al constructor de
Marioi allà guardar-les en els 3 camps privats. Després cal anar al codi que comprova les tecles que s'han premut i treure els valors fixos i posar-hi els camps privats, que tenen les tecles passades en el constructor. Finalment, en el constructor deGame, caldria construirmario_amb unes tecles imario2_amb unes altres. Fet això dues persones poden jugar al joc. Un moment... i la càmera?? Què passa amb la càmera??
Seguint amb Game::update, un cop actualitzats tots els objectes
al mètode Game::update_objects, hi ha una crida a
Game::update_camera. Aquí és important explicar amb més detall
què és la càmera i com funcionen les coordenades en el joc.
El joc fa servir un sistema de coordenades absolut, amb
coordenades enteres i amb l'eix x creixent cap a dreta (com en
matemàtiques), però l'eix y creixent cap a baix (al revés
que en matemàtiques). Malgrat aquest sistema de coordenades és
força confús per algú que ha estudiat matemàtiques primer, el món
dels videojocs i els sistemes de finestres han fet servir aquesta
convenció des del principi i s'ha anat consolidant amb el temps.
Així doncs, els objectes del joc poden tenir coordenades enteres
arbitràries, tant negatives com positives, i el que determina si
són visibles o no és el fet que Window té una "càmera", que no
és més que el subrectangle visible per la pantalla en un
moment donat.
És a dir, l'espai a on poden residir els objectes és il·limitat
(tot el pla bidimensional), però Window només pot mostrar una
petita part d'aquest espai, delimitada per un rectangle d'alçada
i amplada iguals que la finestra i amb una posició concreta en
l'espai bidimensional sencer. A aquest rectangle li diem la
"càmera", ja que per tal que el jugador vegi el Mario en tot
moment, la càmera ha de seguir la seva posició i fer els
moviments que calguin per tenir-lo sempre visible.
Per tant, Game::update_camera determina si Mario està massa
aprop dels límits de la pantalla (tant horitzonal com
verticalment) i desplaça la càmera cridant a
Window::move_camera, que no mou la "càmera" instantàniament,
sinó que distribueix el moviment en uns quants fotogrames, per
tal que sigui més suau.
Exercici 0.4: Simplifica el codi de
Game::update_cameraper tal que la càmera sempre tingui alMarioal centre de la pantalla. Es tracta de demanar la posició delMario, la posició central de la càmera, i cridarWindow::move_cameraindicant la distància entre la càmera i elMario. Executa el joc i prova a saltar a les plataformes. Et sembla que la càmera té un moviment bo per jugar?
Game::paintEl mètode Game::paint és el que fa servir les funcions de
pintat de Window per pintar tots els píxels del joc.
L'ordre en què es pinta cada objecte és molt important,
perquè si dos objectes A i B se solapen en l'espai (com ara el
Mario atravessant una plataforma de baix a dalt), pintar A i
després B implica que B es veurà en primer pla, i A es veurà al
seu darrere.
Exercici 0.5: En aquest exercici farem servir les utilitats del mòdul
utils.hhanomenadespaint_hlineipaint_vlineque pinten una línia horitzontal i una línia vertical, respectivament. Es tracta de d'afegir codi aGame::paintper dibuixar un requadre al marc de la pantalla, però en comptes de posar coordenades fixes i que el requadre sembli que està en el món delMario, cal que el requadre es mogui amb la càmera i sembli que realment és un requadre que es mou amb la pantalla. Pinta el requadre l'últim de tots o bé just després dewindow.clear(...)per veure la diferència. (En aquest exercici hauràs de buscar-te tu mateix els mètodes que et permetin fer el que es demana!)
Per exemple, en la següent imatge, en què Mario està
atravessant una plataforma desde sota, es veu com cal pintar el
Mario després de la plataforma perquè surti en primer pla.

El codi de Game::paint efectivament pinta el Mario (cridant a
Mario::paint) en últim lloc.
Exercici 0.6: Afegeix una funció a
util.ccque es diguipaint_rect, que rebi un rectangle (de tipusRect) i un color i pinti tots els píxels del rectangle del color (mira primerpaint_hlineopaint_vlinecom a inspiració). Recorda declarar la funció autils.hh!
Exercici 0.7: Utilitzant
paint_rect, pinta un rectangle de color groc al mig de la pantalla a sobre de tots els altres objectes. Prova també de posar el rectangle al fons de tot, per sota inclús de les plataformes.
Exercici 0.8: Com faries perquè el rectangle groc fes pampallugues, és a dir, que aparegués i desaparegués ràpidament en seqüència?
Arribats aquí, cal que revisis la llista de preguntes d'aquí sota, i t'autoavaluïs, per veure si les pots contestar. Això et demostrarà si has entès l'estructura del joc i veus com poder-hi fer modificacions pel teu compte, un pre-requisit per poder començar la Part 1 (i fer l'examen de la pràctica en condicions!).
Pt i Rect i què representen.Game::update.Mario cau.Mario quan prems
les fletxes horitzontals.Mario miri cap a una banda o cap a
l'altra.Mario pugui saltar i com s'aconsegueix.Cada una d'aquestes preguntes no és només una qüestió conceptual, sinó que la millor resposta és saber fer modificacions al codi que demostren que teniu controlat aquell aspecte del joc.
Es tracta de fer un nou tipus d'objecte que el Mario pugui
recollir. El joc original tenia monedes, però pot ser qualsevol
altra cosa.
Els requeriments d'aquest objecte són:
Mario passa pel damunt els agafi implícitament i
per tant, desapareguin de la pantalla.