Sprites
Al disseny del joc, el jugador comença per l’esquerra i els obstacles entren per la dreta. Podeu representar tots els obstacles amb objectes de la superfície per fer tot el dibuix més fàcil, però com sabeu on dibuixar-los? Com se sap si un obstacle ha xocat amb el jugador? Què passa quan l'obstacle ix de la pantalla? Què passa si voleu dibuixar imatges de fons que també es moguin? Què passa si voleu que les vostres imatges siguen animades? Podeu gestionar totes aquestes situacions i molt més amb els sprites.
En termes de programació, un sprite és una representació 2D d'alguna cosa a la pantalla. Essencialment, és una imatge. pygame proporciona una classe Sprite, que està dissenyada per contenir una o diverses representacions gràfiques de qualsevol objecte de joc que vulgueu mostrar a la pantalla. Per utilitzar-lo, creeu una nova classe que herede d'Sprite. Això us permet utilitzar els seus mètodes heredats.
Jugadors
A continuació s’explica com s’utilitzen els objectes Sprite amb el joc actual per definir el jugador.
Primer definiu Player ampliant pygame.sprite.Sprite. Després .__ init __()
utilitza .super()
per cridar al constructor de la classe pare.
A continuació, definim i inicialitzem .surf per mantindre la imatge que voleu mostrar, que actualment és un quadre blanc. També definim i inicialitzem .rect, que s'utilitzarà més endavant. Per utilitzar aquesta nova classe, heu de crear un objecte nou i canviar també el codi de dibuix. Amplieu el bloc de codi següent per veure-ho tot junt:
Entrada d’usuari
Fins ara, hem après a configurar Pygame i dibuixar objectes a la pantalla. Ara comença la diversió. Fareu que el joc es puga controlar mitjançant el teclat.
Abans, haviem vist que pygame.event.get() retorna la llista dels esdeveniments de la cua, que analitzem per trobar els seus tipus. Bé, aquesta no és l’única manera de llegir les tecles. pygame també proporciona pygame.event.get_pressed(), que retorna un diccionari que conté tots els esdeveniments KEYDOWN actuals a la cua.
Posar-ho al bucle del joc després del bucle de gestió d'esdeveniments torna un diccionari que conté les tecles apretades al començament de cada fotograma.
A continuació, escrivim un mètode a Player per analitzar aquest diccionari. Això definirà el comportament del sprite a partir de les tecles que es premen.
K_UP, K_DOWN, K_LEFT i K_RIGHT corresponen a les tecles de les fletxes del teclat. Utilitzarem .move_ip(), que significa moure des del punt actual, i reb per paràmetre el número de pixels a moure's en horitzontal i en vertical com si es tractara d'un eix de coordenades.
A continuació, cridem a .update() cada fotograma per moure el sprite del jugador en resposta a les pulsacions de tecles.
Amb això, ja veiem que el Player es mou tant en horitzontal com en vertical.
És possible que noteu dos problemes:
- El rectangle del jugador es mou molt ràpid. Ho solucionarem més endavant.
- El rectangle del jugador pot ixir-se'n de la pantalla. Solucionem-ho ara.
Per mantenir el jugador a la pantalla, cal afegir la lògica per detectar les colisions entre el jugador i els límits de la pantalla. Per fer-ho, comprovem si les coordenades del rectangle s’han desplaçat més enllà del límit de la pantalla. Si és així, indiquem al programa que el torne a la vora, quedant la funció update com a continuació s'indica:
Ací, en lloc d’utilitzar .move(), només heu de canviar les coordenades corresponents de .top, .bottom, .left o .right directament. Proveu-ho i veureu que el rectangle del reproductor ja no pot ixir-se'n de la pantalla.
Enemics
Què és un joc sense enemics? Utilitzarem les mateixes tècniques que ja hem après per crear una classe enemiga bàsica i, a continuació, crearem moltes instàncies d'aquesta per a que el jugador intente evitar-les. Primer, importeu la llibreria random. A continuació, creeu una nova classe de sprite anomenada Enemy, seguint el mateix patró que utilitzarem per a Player:
Hi ha quatre diferències notables entre Enemic i Jugador:
- Quan creem un enemic ho fem a una ubicació aleatòria al llarg de la vora dreta de la pantalla. Es troba en una posició entre 20 i 100 píxels de distància de la vora dreta i en algun lloc entre la vora superior i la inferior. De forma que al principi no serà visible i anirà apareixent per la vora dreta de la pantalla.
- Definim una velocitat speed com un número aleatori entre 5 i 20. Això especifica la velocitat amb què aquest enemic es mou cap al jugador.
- .update() no necessita arguments, ja que els enemics es mouen automàticament cap a l'esquerra a la velocitat aleatòria definida quan es va crear i que ja no canvia.
- Comprovem si l'enemic s'ha mogut fora de la pantalla al sobrepassar la vora esquerra. Per assegurar-nos que l’enemic estiga completament fora de la pantalla i que no desaparega mentre encara siga visible, comprovem que el costat dret de .rect haja sobrepassat el costat esquerre de la pantalla. Una vegada que l'enemic es troba fora de pantalla, cridem a .kill() per evitar anar consumint més recursos cada vegada.
Què fa .kill()? Per saber-ho, estudiem els Sprite Groups.
Sprite Groups
Una altra classe súper útil que proporciona Pygame són els Sprite Groups. Es tracta d'un objecte que conté un grup d'objectes Sprite. Aleshores, per què utilitzar-lo? No podem fer el seguiment dels nostres objectes Sprite en una llista? Bé, podem, però l’avantatge d’utilitzar un grup radica en els mètodes que exposa. Aquests mètodes ajuden a detectar si algun enemic ha xocat amb el jugador, cosa que facilita les actualitzacions.
Vegem com crear Sprite Group. Creem dos objectes de grup diferents:
- El primer grup tindrà tots els Sprite del joc.
- El segon grup tindrà només els objectes enemics.
A continuació, es mostra el codi:
Quan cridem el mètode .kill(), el Sprite s’elimina de tots els grups als quals pertany. Això també elimina les referències al Sprite, cosa que permet al garbage collector de Python recuperar la memòria quan siga necessari.
Ara que teniu un grup all_sprites, podeu canviar la manera com es dibuixen els objectes. En lloc de cridar a .blit() només amb Player, podem repintar tot sobre all_sprites:
Python | |
---|---|
Només hi ha un problema ... No tenim cap enemic. Podriem crear un munt d’enemics al principi del joc, però el joc es tornaria complicadíssim a l'apareixer tots junts. En el seu lloc, explorem com mantenir un subministrament constant d’enemics que arriben a mesura que avance el joc.
Esdeveniments personalitzats
El disseny demana que apareguen enemics a intervals regulars. Això significa que, a intervals establerts, hem de fer dues coses:
- Crea un enemic nou.
- Afegiu-lo a all_sprites i a enemics. (Sprite Groups)
Ja teniu codi que gestiona esdeveniments aleatoris. El bucle d'esdeveniments està dissenyat per buscar esdeveniments aleatoris que es produeixen a cada fotograma i tractar-los adequadament. Per sort, pygame no us limita a utilitzar només els tipus d’esdeveniments que té predefinits. Podeu definir els vostres propis esdeveniments per gestionar-los segons convinga.
Vegem com es crea un esdeveniment personalitzat que es genera cada pocs segons. Podeu crear un esdeveniment personalitzat com es mostra a continuació:
pygame defineix els esdeveniments internament com a enters, de manera que cal definir un nou esdeveniment amb un enter únic. L'últim esdeveniment reservat a Pygame es diu USEREVENT, de manera que definir ADDENEMY = pygame.USEREVENT + 1 garanteix que siga únic.
A continuació, heu d'insertar aquest nou esdeveniment a la cua d'esdeveniments a intervals regulars durant tot el joc. Necessitem d'alguna forma gestionar el temps, per això utilitzarem el mòdul de temps.
Disparem el nou esdeveniment ADDENEMY cada 250 mil·lisegons, o quatre vegades per segon. Per això farem una crida a .set_timer() fora del bucle del joc, ja que només necessitem un temporitzador, però es dispararà durant tot el joc cada 250 milisegons.
Afegim el codi per gestionar el nostre nou esdeveniment.
Sempre que el gestor d'esdeveniments veu el nou esdeveniment ADDENEMY, crea un enemic i l'afegeix a enemies i a all_sprites. Com que Enemy està en all_sprites, es dibuixarà a cada fotograma. També heu de cridar a enemies.update(), que actualitza totes les posicions dels enemics.
Detecció de col·lisions
El disseny del joc demana que finalitze el joc sempre que un enemic xoque amb el jugador. La comprovació de col·lisions és una tècnica bàsica de programació de jocs i, en general, requereix alguns càlculs matemàtics per determinar si dos sprites se superposaran.
Aquí és on resulta útil un framework com Pygame. Escriure un codi de detecció de col·lisions és tediós, però Pygame té MOLTS mètodes de detecció de col·lisions disponibles per utilitzar-los.
Per a aquest tutorial, utilitzeu un mètode anomenat .spritecollideany(), que detecta qualsevol col·lisió entre un sprite i els sprites d'un grup. Accepta un Sprite i un Grup com a paràmetres. Comprova les superposicions entre tots els .rect del grup i el .rect de l'sprite. Si és així, torna True, és a dir si detecta col·lisió. En cas contrari, torna False. Això s'ajusta perfectament a aquest joc, ja que hem de comprovar si un sol jugador xoca amb un grup d'enemics.
Vegem el codi:
Python | |
---|---|
Es comprova si el jugador ha xocat amb algun enemic. Si és així, es crida a player.kill() per eliminar-lo de tots els grups als quals pertany. Com que els únics objectes que es representen es troben en all_sprites, el jugador ja no es renderitzarà. Una vegada que el jugador haja perdur, també haurem d'eixir del joc, de manera que configureu running = False per ixir del bucle del joc.
Velocitat del joc
En provar el joc, potser vos haureu adonat que els enemics es mouen massa ràpid. Si no, tranquils, ja que el joc s'executarà a diferents velocitats segons el hardware subjacent, el sistema operatiu, etc.
La raó d'això és que el bucle del joc processa els fotogrames tan ràpidament com el processador i l'entorn ho permeten. Com que tots els sprites es mouen una vegada per fotograma, es poden moure centenars de vegades cada segon. El nombre de fotogrames que es manegen cada segon s’anomena velocitat de fotogrames (frame rate), un terme molt utilitzat pels gamers. Aconseguir-ne un adequat és la diferència entre un joc jugable i un altre que no ho és.
Normalment, volem una freqüència de fotogrames el més alta possible, per poder apreciar el major nombre de detalls, però, per a aquest joc, cal reduir-lo un poc perquè el joc es puga jugar. Afortunadament, el mòdul de temps de Python conté un rellotge dissenyat exactament per a aquest propòsit.
L’ús del rellotge per establir una velocitat de fotogrames reproduïble requereix només dues línies de codi. El primer crea un rellotge nou abans que comence el bucle del joc. Després utilitzem la funció .tick() per informar a pygame que el programa ha arribat al final del fotograma.
Python | |
---|---|
Si es passa una freqüència de fotogrames més xicoteta, transcorrerà més temps entre fotogrames, mentre que una freqüència de fotogrames més gran proporcionarà un joc més suau (i més ràpid).
En aquest moment, tenim un joc totalment funcional i jugable.