Per rendere a tutti gli effetti il nostro operato un videogioco, per quanto piccolo possa essere, bisogna donargli un’ interfaccia grafica che abbia il compito di gestire le impostazioni video, come risoluzione, accelerazione hardware ecc… e sopratutto che consenta di salvare le impostazioni correnti, compresa la partita attualmente in atto.
Per fare questo ho deciso di chiedere supporto al modulo python cPickle, che si occupa della serializzazione (salvataggio) di oggetti creati dall’utente in stringhe di dati. L’utilità sta nel fatto che, queste stringhe, possono essere scritte su un qualsiasi file e successivamente lette.
Il vantaggio che ci offre questo modulo non è del tutto indifferente. Pensate infatti di memorizzare le vostre impostazioni video o di gioco in uno o più file creati da voi a piacimento che, ogni volta, devono essere letti in un certo modo per far si che quei dati memorizzati possano essere utili.
Con cPickle invece possiamo benissimo ricreare gli oggetti che stavamo utilizzando senza preoccuparci di come dovranno essere letti o scritti i file. Anche questo ha però alcune limitazioni, per esempio nella memorizzazione di sottoclassi ecc… (consultare la guida ufficiale di python per altri dettagli) però è un ottima soluzione se non si volgio utilizzare file xml e/o file creati dall’utente stesso, dove è necessario occuparsi del parsing (cioè di come deve essere letto il file).
Il codice
Non voglio tenervi sulle spine e quindi passo direttamente alla presentazione delle funzion e delle classi che andremo ad analizzare:
def tps(orologio, fps): def carica_imm_sprite(nome,h,w,num): class giocatore(pygame.sprite.Sprite): class ingame(): class impostazioni(): class main_menu(): def Eventi(event, Main): def caricamento_imp(): def salvataggio_imp(): def aggiorna_imp(w,h,full,buff,hw,opengl): def run(): if __name__ == "__main__": run()
Come avrete sicuramente notato, ci sono alcune funzioni che abbiamo già visto nei precedenti esempi, quindi non saranno analizzata se non nei cambiamenti (riporterò comunque il codice completo per una migliore consultazione).
- Le funzioni tps e carica_imm_sprite non sono cambiate e hanno sempre lo stesso scopo (caricamento delle immagini e gestione del tempo di gioco).
- La classe giocatore è la stessa classe sprite che abbiamo utilizzato nella guida precedente. L’unica differenza è che non gestiamo le collisioni per semplificare un pò il codice di questo esempio, visto che lo scopo principale è quello di salvare lo stato di gioco attuale e di poterlo caricare correttamente.
- La classe ingame contiene tutti gli elementi di gioco ed è questa che si occupa di renderizzare il gioco avviato.
- La classe impostazioni serve per tener conto dello stato di gioco, in particolare della risoluzione video e dei flag utilizzati (ACCHW, DOUBLEBUFF, OPENGL, FULLSCREEN).
- La classe main_menu, come potete immaginare, si occupa del menù di gioco e di tutte le funzionalità che ci interessano in questo esempio: caricamento e salvataggio di gioco, caricamento e salvataggio delle impostazioni di gioco.
- La funzione Eventi coordina gli eventi di sistema per gestire il gioco, vedremo poi in dettaglio come.
- La funzione caricamento_imp si occupa di caricare dal file impostazioni.pkl da noi creato, le impostazioni memorizzate.
- La funzione salvataggio_imp ha il compito di creare un file di configurazione nell’eventualità che questo non esista.
- La funzione aggiorna_imp cambierà il file di configurazione da noi creato con le impostazioni aggiornate.
Per rendere chiaro il funzionamento globale devo spiegarvi come ho deciso di organizzare le cose:
- Main_menu contiene ingame, che a sua volta contiene il giocatore.
- Eventi richiama solo funzioni di main_menu.
In questo modo gestire gli eventi consiste solo nel passare ad una funzione di main_menu a seconda dell’evento analizzato. Main_menu, che al suo interno contiene anche l’istanza di gioco, si occuperà di passare dal gioco al menù testuale e della gestione degli input dell’utente (a seconda se siamo ingame o nel menù). Main_menu avrà quindi al suo interno anche una propria gestione del caricamento delle impostazioni ed una parte per il caricamento dei salvataggi di gioco. Il videogame vero è proprio però è ingame. In quest’ultimo viene gestita tutta la dinamica relativa alle animazioni del giocatore, delle regole di gioco, del mondo di gioco (non presenti completamente in questo esempio) ecc…
Conclusioni
In questa serie di articoli analizzeremo quindi questi oggetti che daranno vita al menù del nostro gioco, aggiungendo quelle feature di base che caratterizzano qualsiasi videogame. Con ciò sarete in grado di realizzare un piccolo videogioco completo, l’unica vostra limitazione sarà la fantasia. Infatti le nozioni che cercherò di introdurre non sono di difficile comprensione e una volta appreso il meccanismo, è facile modificarlo secondo le vostre esigenze.
Voglio solo ricordare che questa impostazione non è sbagliata ma nessuno vieta di cambiare le cose: se per esempio preferite gestire gli eventi con una classe a parte che coordina i vari stati di gioco (ingame e menu), oppure inserire gli eventi all’interno delle classi esistenti, potete benissimo farlo. Ricordate che durante la progettazione del vostro videogioco dovete sempre tenere conto delle cose essenziali e di quelle superflue. Avere una base solida (concettualmente parlando) vi porterà a fare determinate scelte che vi faciliteranno la vita, quindi non perdete mai l’obbiettivo che volete raggiungere creando il vostro oggetto videoludico.
Sperando di aver stuzzicato ancora il vostro interesse, vi do appuntamento al prossimo articolo.
interessante la parte relativa alla GUI..anche il solo uso dei pulsanti può arricchire notevolmente il gioco..
per quanto riguarda il salvataggio su file invece non so se mi affiderò a cPickle o farò da me..aspetterò di vedere come lavora ^^
@ Francesco
Per questi piccoli esempi mi è stato molto utile l’utilizzo di questa libreria che, per piccole cose o comunque per cose non troppo complesse, adempie perfettamente al suo dovere.
Tempo permettendo, ho intenzione di riscrivere lo stesso codice implementato però con sqllite, più ad ampio spettro ed adatta anche a giochi più complessi.
Spero comunque che questo progetto sia lo stesso utile ed interessante… :D
sqlite mi sembra un “overkill” per serializzare lo stato del gioco. Io utilizzerei un formato plain text come YAML (xml non mi piace :D) con la librerie pyYAML: http://pyyaml.org/wiki/PyYAML.
Utilizzare un DBMS per me in questo caso ti da più grane che vantaggi. ;-)
Certamente l’utilizzo di un DBMS sarebbe opportuno con giochi che richiedono molte informazioni da caricare. Non conoscevo però questa soluzione (YAML) e, non essendo amante come te dell’xml, credo che utilizzerò anche questa libreria.
Riscriverò quindi l’esempio per funzionare anche con YAML, appena ho tempo. Grazie del suggerimento!!!
Ritratto… l’ora avanzata mi ha fatto scrivere una cavolata immensa. Il formato YAML (o xml) va bene per i settings, livelli e altre cose di questo tipo (la possibilità di cambiare i settings modificando un file di testo è molto comoda). Per lo stato del gioco il pickling è la scelta più logica.
Rimane comunque il consiglio di base di non utilizzerei un DBMS. :D
@ Emanuele
Da quello che ho visto mi tornerà molto utile… comunque sia, spero di imparare il suo utilizzo :D
(c)Pickle è la soluzione migliore per serializzare dati “consumabili” soltanto dall’applicazione.
Per i salvataggi si potrebbe sperimentare JSON, che è abbastanza leggero e leggibile. Da Python 2.6 è stata integrato un modulo per gestirlo nella libreria standard.
P.S. XML fa schifo pure a me. :D
@ Cesare di Mauro
Non conoscevo neanche JSON :D . Ho letto la guida online (più che altro ho dato uno sguardo, per ora), e mi intriga molto, sopratutto perché si scosta poco da cPickle ed è molto versatile. Ma sopratutto è integrato in python, motivo molto più importante degli altri (a mio avviso).
Se posso, cercherò di adattare il codice anche a questa libreria, spero solo di avere un pò di tempo dopo questa serie di articoli!!! :D
JSON è la lingua franca del web, come e più di XML.
Per questo ti consiglio (anzi, lo consiglio a tutti) di prenderlo seriamente in considerazione.
P.S. Tra l’altro l’output generato è estremamente simile alla sintassi di Python (JavaScript come linguaggio somiglia molto al nostro a livello di dichiarazione di costanti e strutture più complesse). ;)
Ok, ora JSON ha preso la mia priorità… sto cercando guide (oltre quella ufficiale) per capire meglio il suo funzionamento, se qualcuno ha qualcosa da consigliarmi lo dica pure… :D
Francamente conosco soltanto la guida ufficiale, e mi trovo già bene così.
Comunque al momento puoi usarlo al posto di c/Pickle, visto che la stessa interfaccia (metodi dump/dumps e load/loads). Poi se hai cose particolari da serializzare in JSON puoi proseguire coi meccanismi più avanzati (che sono spiegati molto bene nella guida).
Già che ci siamo… qualcuno ha provato a utilizzare i protocol buffers di Google?
http://code.google.com/p/protobuf/
Mi sembra che abbiano idee interessanti ma non ho mai avuto tempo di approfondire.
No, non li ho mai provati anche se sono interessanti.
Ma il motivo è che da un po’ di anni utilizzo un framework che, a mio avviso, è decisamente migliore: http://www.zeroc.com/
Tra l’altro giusto poco dopo che Google ha rilasciato Protocol Buffers, quelli di ZeroC ne hanno aggiunto il supporto ad Ice: http://www.zeroc.com/labs/protobuf/index.html :D
P.S. Alla scorsa PyCon ho presentato una sorta di NFS realizzato interamente in Python con Ice: http://www.pycon.it/conference/talks/ice-a-framework-for-middlewares e nell’archivio trovate sia le slide che il codice. :P
Peccato che ancora non lavoro in un progetto di gruppo, altrimenti potrei subito mettere in pratica queste soluzioni. Magari un giorno… :D
leggo i commenti e sorrido…. ;-D
penso a tutti i santi che ho buttato giù quando programmavo il sistema di salvataggio di un gioco per Nintendo DS… 8192 byte per salvare 4 partite, 1 set completo di impostazioni ridondate (perchè Nintendo scassa le P!”£$% con i suoi Lot Check), svariati checksum sparsi in giro per il bufferino e algoritmi vari ed eventuali per “tentare” di ricostruire i dati persi in caso di corruzione inferiore ad una certa percentuale…
E sono fortunato! Se il publisher fosse stato più tirchio, ci avrebbero dato una cartuccia con 512 byte per il salvataggio! :D
E voi che parlate di serializzazioni automatizzate, database, json… è proprio vero che il benessere ( o troppa potenza di calcolo/storage nel nostro caso specifico ) porta naturalmente a sprecare le risorse!
E’ normale che sia così. Oggi su PC avrebbe poco senso perdere tempo con routine di de/serializzazione mirate alla massima efficienza.
Su Commodore 64 e Amiga, tra l’altro, nemmeno si poneva il problema: generalmente non c’erano i salvataggi, e il gioco doveva esser completato una volta iniziato. Ore e ore di interminabili godurie. :D
Ovviamente su C64 e Amiga la testa la si sbatteva contro il muro per altri tipi di ottimizzazione. -_-
Ogni piattaforma ha le sue peculiarità (o rogne? :D).
@Cesare:
eh già! Io sinceramente mi diverto molto di più quando ho davanti dei limiti stringenti, piuttosto che lavorare su PC. Mi viene la sindrome del foglio bianco… troppe risorse mi intorpidiscono il cervello, mentre lavorando su DS e altre console, o anche sugli smartphone, mi si accende la lampadina per cercare di trarne il massimo possibile :-)
Chiaramente parlo così perchè faccio giochi, se facessi programmi di produttività d’ufficio, o gestionali, o altri programmi professionali, non mi porrei il problema dell’ottimizzazione al bit e, anzi, rimarrei largo nello sfruttare le risorse privilegiando la velocità di sviluppo, il riutilizzo e la robustezza del codice.
Il fatto è che programmando molto ad alto livello (lasciatemi passare il termine, per quanto riguarda il python), l’ottimizzazione è limitata alle “specifiche” degli algoritmi che dobbiamo utilizzare. Magari con altri linguaggi (cito il c++), ci sono più possibilità di spremere il codice per avere il massimo.
Spero che la mia limitata esperienza non mi abbia portato a fare affermazioni errate :D
PS : comunque sia è sempre buona cosa cercare il massimo in tutto quello che facciamo. (@ TheKaneB) il dilemma del foglio bianco è passabile se abbiamo preso delle decisioni forti all’inizio del progetto. Scegliere una strada infatti limiterà per forza di cose le nostre possibilità, spingendoci quindi a creare un qualcosa in un campo ristretto, ma dove siamo padroni! Non so se questo che ho scritto sia un consiglio, però spero sia interpretato nel modo giusto!!! :P
@TheKaneB: come diceva Mirco, l’importante è dare il meglio di noi qualunque sia il progetto.
Io a lavoro programmo quasi esclusivamente in Python e non mi passa per la mente di ottimizzare a manetta tutto (magari qualcosa la penso al volo, mentre sto scrivendo), perché non ne vale assolutamente la pena: funziona bene e le risorse a disposizione sono in eccesso, come giustamente dicevi anche tu.
In questi casi la variabile da ottimizzare è il tempo: più si è produttivi, e meglio è, perché si tratta di realizzare velocemente servizi (o server a cui essi si appoggeranno).
Viceversa, a casa (la notte, se ho qualche briciolo di tempo ed energie) smanetto col C per migliorare la virtual machine di Python, anche se da un po’ mi frulla per la testa di usare l’assembly x86 per alcune cose. :D
Qui sono fondamentali le prestazioni, con un occhio di riguardo anche allo spazio (in particolare la memoria occupata).
Mi diverto in entrambi i casi. Anche perché con Python cerco di sfruttarne il più possibile le caratteristiche peculiari per rendere il codice “bello” ai miei occhi (in ufficio mi chiamano “l’esteta del codice” :D). Mi piace il codice bello a vedersi, e leggibile: mi appaga molto.
Ciò non toglie che anche una routine o pezzo di codice ottimizzati in assembly, che tengano anche conto della pipeline in-order di CPU come gli Atom sfruttando al meglio i (pochi) registri disponibili, mi lascia particolarmente soddisfatto. :P
Finché godo, va bene tutto, insomma. L’importante è non arrivare all’alienazione o alla frustrazione; programmare non è sempre bello, anche se abbiamo sposato il nostro hobby col lavoro. :-/
@Mirco e @Cesare: sostanzialmente la pensiamo allo stesso modo: se ci sono risorse a iosa, curo meglio l’aspetto della produttività e del riutilizzo del codice; mentre se ho risorse limitatissime cerco di spendere più tempo nell’ottimizzazione a basso livello. In entrambi i casi, ci sono cose divertenti e cose “pallose” da morire.
PS: :-) mi piace leggere delle vostre esperienze personali, anchè perchè è difficile sentire parlare un programmatore. Molti miei colleghi, nel campo dei VG, o sono primedonne (il codice mio è il migliore di tutti, io sono il più intelligente, io l’avrei fatto meglio di Tizio e Caio, ecc…) oppure sono introversi da morire.
Quelli diciamo “normali”, con un sano equilibrio psichico, siamo veramente una sparuta minoranza!
@ TheKaneB
Naturalmente ognuno ha i suoi difetti: io per esempio non cambio mai una cosa se prima non ho capito dove ho sbagliato e perché, anche se sò perfettamente che l’altra scelta è migliore.
A parte questo, se si pensa che c’è sempre modo di migliorarsi (e sottolineo sempre!!!) e che l’unione fa la forza (è bene imparare a confrontarsi con la gente!!!), non si dovrebbero mai avere problemi a superare certi ostacoli (e non solo nelle programmazione).
Purtroppo, queste piccole frasi che ho scritto sopra a volte sono soltato assimilate senza essere comprese e questo, come hai notato, porta ad alcune situazioni imbarazzanti (ex: “primedonne”).
E’ paradossale che nel mondo del digitale, dove si cerca di condividere tutto (compresa la propria vita (nessun riferimento particolare !!! :D )), non si riesca a condividere delle esperienze che possono far crescere non uno, ma un gruppo…
Con questo noioso assolo, concludo e mi scuso per l’OT :D.
Io invece faccio il contrario. Quando programmo sono una specie di maniaco del refactoring. Scrivo riscrivo disfo riscrivo e riscrivo. :D
E quando leggo codice del “me del passato” mi prenderei a schiaffi. :P
Poi ho l’altro piccolo difetto di appassionarmi praticamente a tutto. Cosa che spesso non mi permette di andare pienamente a fondo su nessun argomento. Pensa che attualmente sto dividendo le mie giornate tra VHDL, studio di python stackless e disegno digitale…
Lo so che dovrei concentrarmi su una sola cosa ma non ce la faccio proprio. :D
@ Emanuele
A chi lo dici!!! Io vorrei riprendere in mano qualche progetto con il c++ e sotto questo punto di vista mi tornerà molto utile panda3d. Infatti, oltre ad essere completo come game-engine, può essere utilizzato sia in python che in c++.
PS: ecco un altro impegno che si aggiunge :D .