In questo articolo vedremo la parte più lunga e complessa di questa guida, ovvero la gestione del menù.
Il codice:
class main_menu(): def __init__(self, screen1, altezza_s, larghezza_s, ingame): #menu_opzioni, caricamento_gioco, salvataggio_gioco,inizio_gioco,screen) self.istanza = 0 self.screen = screen1 self.anim_push = 0 self.anim_push_bool = False self.ing = ingame self.bool_ing = False self.count_init = False try: stream = open("data/impostazioni.pkl", "r") pk = cPickle.Unpickler(stream) imp = pk.load() self._Schermo_larghezza = imp.larghezza_schermo self._Schermo_altezza = imp.altezza_schermo self._full = imp.full self._bool_buff = imp.bool_buff self._bool_hw = imp.bool_hw self._bool_opengl = imp.bool_opengl stream.close() except IOError: print ("Impossibile inizializzare Pygame") exit() ###### # Menu self.Sfondo = pygame.image.load("data/Sfondo.jpg").convert() self.Sfondo = pygame.transform.scale(self.Sfondo, (self._Schermo_larghezza, self._Schermo_altezza)) self.Continua = pygame.image.load("data/pulsanti/Continua.png").convert_alpha() self.Inizio = pygame.image.load("data/pulsanti/Inizio.png").convert_alpha() self.Carica = pygame.image.load("data/pulsanti/Carica.png").convert_alpha() self.Salva = pygame.image.load("data/pulsanti/Salva.png").convert_alpha() self.Opzioni = pygame.image.load("data/pulsanti/Opzioni.png").convert_alpha() self.Esci = pygame.image.load("data/pulsanti/Esci.png").convert_alpha() self.Continua_push = pygame.image.load("data/pulsanti/Continua_push.png").convert_alpha() self.Inizio_push = pygame.image.load("data/pulsanti/Inizio_push.png").convert_alpha() self.Carica_push = pygame.image.load("data/pulsanti/Carica_push.png").convert_alpha() self.Salva_push = pygame.image.load("data/pulsanti/Salva_push.png").convert_alpha() self.Opzioni_push = pygame.image.load("data/pulsanti/Opzioni_push.png").convert_alpha() self.Esci_push = pygame.image.load("data/pulsanti/Esci_push.png").convert_alpha() self.x = larghezza_s/2 self.y = altezza_s/2 self.Inizio_rect = pygame.Rect((self.x-48,self.y-24-48*2), (96,48)) self.Carica_rect = pygame.Rect((self.x-48,self.y-24-48), (96,48)) self.Salva_rect = pygame.Rect((self.x-48,self.y-24), (96,48)) self.Opzioni_rect = pygame.Rect((self.x-48,self.y-24+48), (96,48)) self.Esci_rect = pygame.Rect((self.x-48,self.y-24+48*2), (96,48)) self.Puntatore = pygame.image.load("data/puntatore.png").convert_alpha() self.anim_punt = False self.audio_click = pygame.mixer.Sound("data/sound/beep-3.wav") self.canale_mouse = pygame.mixer.Channel(1) self.Premuto = -1 self.Premuto_op = -1 ############## # Menu Opzioni self._640 = pygame.image.load("data/pulsanti/640.png").convert_alpha() self._800 = pygame.image.load("data/pulsanti/800.png").convert_alpha() self._1024 = pygame.image.load("data/pulsanti/1024.png").convert_alpha() self._1280 = pygame.image.load("data/pulsanti/1280.png").convert_alpha() self._AccHw = pygame.image.load("data/pulsanti/AccHw.png").convert_alpha() self._OpenGl = pygame.image.load("data/pulsanti/Opengl.png").convert_alpha() self._DoppioBuff = pygame.image.load("data/pulsanti/DoppioBuff.png").convert_alpha() self._Indietro = pygame.image.load("data/pulsanti/Indietro.png").convert_alpha() self._Applica = pygame.image.load("data/pulsanti/Applica.png").convert_alpha() self._FullScreen = pygame.image.load("data/pulsanti/FullScreen.png").convert_alpha() self._640_push = pygame.image.load("data/pulsanti/640_push.png").convert_alpha() self._800_push = pygame.image.load("data/pulsanti/800_push.png").convert_alpha() self._1024_push = pygame.image.load("data/pulsanti/1024_push.png").convert_alpha() self._1280_push = pygame.image.load("data/pulsanti/1280_push.png").convert_alpha() self._AccHw_push = pygame.image.load("data/pulsanti/AccHw_push.png").convert_alpha() self._OpenGl_push = pygame.image.load("data/pulsanti/Opengl_push.png").convert_alpha() self._DoppioBuff_push = pygame.image.load("data/pulsanti/DoppioBuff_push.png").convert_alpha() self._Indietro_push = pygame.image.load("data/pulsanti/Indietro_push.png").convert_alpha() self._Applica_push = pygame.image.load("data/pulsanti/Applica_push.png").convert_alpha() self._FullScreen_push = pygame.image.load("data/pulsanti/FullScreen_push.png").convert_alpha() self._640_a = pygame.image.load("data/pulsanti/640_attivo.png").convert_alpha() self._800_a = pygame.image.load("data/pulsanti/800_attivo.png").convert_alpha() self._1024_a = pygame.image.load("data/pulsanti/1024_attivo.png").convert_alpha() self._1280_a = pygame.image.load("data/pulsanti/1280_attivo.png").convert_alpha() self._AccHw_a = pygame.image.load("data/pulsanti/AccHw_attiva.png").convert_alpha() self._OpenGl_a = pygame.image.load("data/pulsanti/Opengl_attiva.png").convert_alpha() self._DoppioBuff_a = pygame.image.load("data/pulsanti/DoppioBuff_attivo.png").convert_alpha() self._FullScreen_a = pygame.image.load("data/pulsanti/FullScreen_a.png").convert_alpha() self._640_rect = pygame.Rect((self.x-150,self.y-200), (96,48)) self._800_rect = pygame.Rect((self.x+150-96,self.y-200), (96,48)) self._1024_rect = pygame.Rect((self.x-150,self.y-150), (96,48)) self._1280_rect = pygame.Rect((self.x+150-96,self.y-150), (96,48)) self._AccHw_rect = pygame.Rect((self.x-150,self.y-50), (96,48)) self._OpenGl_rect = pygame.Rect((self.x+150-96,self.y-50), (96,48)) self._DoppioBuff_rect = pygame.Rect((self.x-150,self.y+25), (96,48)) self._Indietro_rect = pygame.Rect((self.x-150,self.y+125), (96,48)) self._Applica_rect = pygame.Rect((self.x+150-96,self.y+125), (96,48)) self._FullScreen_rect = pygame.Rect((self.x+150-96,self.y+25), (96,48)) self.rect_premere = [self.Inizio_rect, self.Carica_rect,self.Salva_rect, self.Opzioni_rect,self.Esci_rect] self.rect_premere_opzioni = [self._640_rect,self._800_rect, self._1024_rect,self._1280_rect, self._AccHw_rect,self._OpenGl_rect, self._DoppioBuff_rect,self._Indietro_rect, self._Applica_rect,self._FullScreen_rect] ########################################## # Funzione che gestisce le azioni nel menu def action(self,coordinate_mouse): if self.bool_ing == False: if self.canale_mouse.get_busy() != True: self.canale_mouse = self.audio_click.play() if self.istanza == 0: for x in range(0,len(self.rect_premere)): if self.rect_premere[x].collidepoint(coordinate_mouse): self.Premuto = x self.anim_push_bool = True elif self.istanza == 1: for x in range(0,len(self.rect_premere_opzioni)): if self.rect_premere_opzioni[x].collidepoint(coordinate_mouse): self.Premuto_op = x self.anim_push_bool = True ############################################ # Funzione per muovere il personaggio ingame def action_p (self, stringa, num): if self.bool_ing == True: self.ing.giocatore.update(stringa,num) ######################################### # Funzione per chiamare il menu da ingame def action_esc(self): if self.bool_ing == True: self.bool_ing = False self.istanza = 0 else: self.bool_ing = True self.istanza = -1 #################################### # Funzione per il salvataggio ingame def save_this(self): try: stream = open("data/save.pkl", "w") pk = cPickle.Pickler(stream) pk.dump(self.ing.giocatore.coordinate) stream.close() pk.clear_memo() except IOError: print ("Impossibile salvare il gioco") exit() #################################### # Funzione per il caricamento ingame def carica_this(self): try: stream = open("data/save.pkl", "r") pk = cPickle.Unpickler(stream) coord = pk.load() base = self.ing.base stream.close() personaggio = giocatore("27382_1174921384",48,32, coord, None) Ingame = ingame(personaggio, self.screen, base) self.ing = Ingame except IOError: print ("Nessun salvataggio presente") ################################# # Funzione che gestisce il render def render(self,screen,temp,msxy): if self.bool_ing == True: self.ing.render() return screen.blit(self.Sfondo,(0,0)) ############# # Render Menu if self.istanza == 0: if self.Premuto == 0: if self.count_init == False: screen.blit(self.Inizio_push,(self.x-48,self.y-24-48*2-15)) else: screen.blit(self.Continua_push,(self.x-48,self.y-24-48*2-15)) else: if self.count_init == False: screen.blit(self.Inizio,(self.x-48,self.y-24-48*2-15)) else : screen.blit(self.Continua,(self.x-48,self.y-24-48*2-15)) if self.Premuto == 1: screen.blit(self.Carica_push,(self.x-48,self.y-24-48-10)) else: screen.blit(self.Carica,(self.x-48,self.y-24-48-10)) if self.Premuto == 2: screen.blit(self.Salva_push,(self.x-48,self.y-24-5)) else: screen.blit(self.Salva,(self.x-48,self.y-24-5)) if self.Premuto == 3: screen.blit(self.Opzioni_push,(self.x-48,self.y-24+48+5)) else: screen.blit(self.Opzioni,(self.x-48,self.y-24+48+5)) if self.Premuto == 4: screen.blit(self.Esci_push,(self.x-48,self.y-24+48*2+10)) else : screen.blit(self.Esci,(self.x-48,self.y-24+48*2+10)) if self.anim_push > 0.025 and self.Premuto == 4: # Esci exit() elif self.anim_push > 0.030 and self.Premuto == 3: # Opzioni self.Premuto = -1 self.anim_push = 0 self.anim_push_bool = False self.istanza = 1 try: stream = open("data/impostazioni.pkl", "r") pk = cPickle.Unpickler(stream) imp = pk.load() self._Schermo_larghezza = imp.larghezza_schermo self._Schermo_altezza = imp.altezza_schermo self._full = imp.full self._bool_buff = imp.bool_buff self._bool_hw = imp.bool_hw self._bool_opengl = imp.bool_opengl stream.close() except pygame.error,IOError: print ("Impossibile inizializzare Pygame") print (pygame.get_error()) exit() elif self.anim_push > 0.030 and self.Premuto == 2: # Salva self.Premuto = -1 self.anim_push = 0 self.anim_push_bool = False self.save_this() elif self.anim_push > 0.030 and self.Premuto == 1: # Carica self.Premuto = -1 self.anim_push = 0 self.anim_push_bool = False self.carica_this() elif self.anim_push > 0.030 and self.Premuto == 0: # Inizio self.Premuto = -1 self.anim_push = 0 self.anim_push_bool = False self.bool_ing = True self.istanza = -1 self.count_init = True else : if self.anim_push_bool == True: self.anim_push += temp if self.anim_punt == False: screen.blit(self.Puntatore,msxy) ##################### # Render Menu Opzioni if self.istanza == 1: if self.Premuto_op == 0: screen.blit(self._640_push,(self.x-150,self.y-200)) else: if self._Schermo_larghezza == 640 and self._Schermo_altezza == 480: screen.blit(self._640_a,(self.x-150,self.y-200)) else: screen.blit(self._640,(self.x-150,self.y-200)) if self.Premuto_op == 1: screen.blit(self._800_push,(self.x+150-96,self.y-200)) else: if self._Schermo_larghezza == 800 and self._Schermo_altezza == 600: screen.blit(self._800_a,(self.x+150-96,self.y-200)) else: screen.blit(self._800,(self.x+150-96,self.y-200)) if self.Premuto_op == 2: screen.blit(self._1024_push,(self.x-150,self.y-150)) else: if self._Schermo_larghezza == 1024 and self._Schermo_altezza == 768: screen.blit(self._1024_a,(self.x-150,self.y-150)) else: screen.blit(self._1024,(self.x-150,self.y-150)) if self.Premuto_op == 3: screen.blit(self._1280_push,(self.x+150-96,self.y-150)) else: if self._Schermo_larghezza == 1280 and self._Schermo_altezza == 1024: screen.blit(self._1280_a,(self.x+150-96,self.y-150)) else: screen.blit(self._1280,(self.x+150-96,self.y-150)) if self.Premuto_op == 4: screen.blit(self._AccHw_push,(self.x-150,self.y-50)) else: if self._bool_hw == True: screen.blit(self._AccHw_a,(self.x-150,self.y-50)) else: screen.blit(self._AccHw,(self.x-150,self.y-50)) if self.Premuto_op == 5: screen.blit(self._OpenGl_push,(self.x+150-96,self.y-50)) else: if self._bool_opengl == True: screen.blit(self._OpenGl_a,(self.x+150-96,self.y-50)) else: screen.blit(self._OpenGl,(self.x+150-96,self.y-50)) if self.Premuto_op == 6: screen.blit(self._DoppioBuff_push,(self.x-150,self.y+25)) else: if self._bool_buff == True: screen.blit(self._DoppioBuff_a,(self.x-150,self.y+25)) else: screen.blit(self._DoppioBuff,(self.x-150,self.y+25)) if self.Premuto_op == 7: screen.blit(self._Indietro_push,(self.x-150,self.y+125)) else: screen.blit(self._Indietro,(self.x-150,self.y+125)) if self.Premuto_op == 8: screen.blit(self._Applica_push,(self.x+150-96,self.y+125)) else: screen.blit(self._Applica,(self.x+150-96,self.y+125)) if self.Premuto_op == 9: screen.blit(self._FullScreen_push,(self.x+150-96,self.y+25)) else: if self._full == True: screen.blit(self._FullScreen_a,(self.x+150-96,self.y+25)) else: screen.blit(self._FullScreen,(self.x+150-96,self.y+25)) ######################################################### # Animazioni menu opzioni e cambiamenti impostazioni if self.anim_push > 0.030 and self.Premuto_op == 9: # FullScreen self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 if self._full == False: self._full = True else: self._full = False elif self.anim_push > 0.030 and self.Premuto_op == 8: # Applica aggiorna_imp( self._Schermo_larghezza, self._Schermo_altezza, self._full, self._bool_buff, self._bool_hw, self._bool_opengl) pygame.quit() run() elif self.anim_push > 0.030 and self.Premuto_op == 7: # Indietro self.istanza = 0 self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 6: # DoppioBuffer if (self._bool_hw == True or self._bool_opengl == True) and self._bool_buff == False: self._bool_buff = True else: self._bool_buff = False self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 5: # OpenGl if self._bool_opengl == False and self._bool_hw == False: self._bool_opengl = True else: self._bool_opengl = False self._bool_buff = False self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 4: # AccHw if self._bool_hw == False and self._bool_opengl == False: self._bool_hw = True else: self._bool_hw = False self._bool_buff = False self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 3: # 1280 self._Schermo_larghezza = 1280 self._Schermo_altezza = 1024 self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 2: # 1024 self._Schermo_larghezza = 1024 self._Schermo_altezza = 768 self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 1: # 800 self._Schermo_larghezza = 800 self._Schermo_altezza = 600 self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 elif self.anim_push > 0.030 and self.Premuto_op == 0: #640 self._Schermo_larghezza = 640 self._Schermo_altezza = 480 self.anim_push = 0 self.anim_push_bool = False self.Premuto_op = -1 else : if self.anim_push_bool == True: self.anim_push += temp if self.anim_punt == False: screen.blit(self.Puntatore,msxy)
Analisi:
Il codice ha qualche commento per non perdersi, prendeteli quindi come punti di riferimento. Per prima cosa analizziamo il costruttore; l’idea è quella di far gestire tutto al menù, come abbiamo già accennato in precedenza, per questo il costruttore necessita di quattro parametri:
- screen1 : lo schermo dove andremo a renderizzare.
- altezza_s : altezza dello schermo.
- larghezza_s : larghezza dello schermo.
- ingame : che potremo chiamare istanza di gioco.
Per questo esempio suddividerò l’analisi in righe, per rendere più facile la lettura.
- dalla 3 alla 9:
Istanza ci serve per tenere conto se siamo nel menù, oppure nel menù opzioni. Memorizziamo poi screen ed ingame. Le variabili anim_push ed anim_push_bool servono per gestire le animazioni dei pulsanti, invece bool_ing e count_init servono per tenere conto se siamo in gioco e se è la prima volta che inziamo a giocare.
- dalla 11 alla 25
Apriamo il file impostazioni utilizzando cPickle e teniamo conto delle attuali impostazioni per essere modificate memorizzandole in variabili omonime: _Schermo_larghezza, _Schermo_altezza, _full, _bool_buff, _bool_hw e _bool_opengl. Come potete notare gestiamo l’apertuare del file con le eccezioni, per ovviare a problemi di apertura o chiusura file. Infatti aprire un file analizzando poi lo stream può portare a degli errori se per esempio questo non esiste. Il modulo cPickle ci aiuta caricando le impostazioni salvata nell’oggetto imp, che altro non è che la classe impostazioni vista precedentemente. Vedremo in seguito come memorizzare i vari campi delle impostazioni.
Tenete conto che in questo caso, se avevate un file di configurazione scritto da voi, in queste righe dovevate fare il parsing del file per raccogliere le informazioni, mentre qui le carichiamo tutte con le prime 3 istruzioni e poi le associamo ad elementi della classe per tenere conto del loro cambiamento in gioco. Infatti cambiando le impostazioni dal menù opzioni, si dovrà cliccare su applica per rendere effettive le modifiche ed aggiornare il gioco (che nel nostro caso si riavvierà).
Vorrei ricorda inoltre che i file creati da cPickle non sono compatibili se sono stati creati su sistemi operativi differenti, quindi dovete cancellare il file con le attuali impostazioni se volete provare lo stesso esempio su un OS diverso, altrimenti non caricherà nulla e vi rilascerà un errore (gestito opportunatamente nell’eccezione).
- dalla 28 alla 59
Carichiamo sfondo di gioco e tutti i pulsanti del menù principale (comprese le loro animazioni, ovvero i tasti premuti). Dimezziamo le coordinate dello schermo, perché il nostro menù sarà sempre centrato, così da non portare complicazioni con risoluzioni differenti (ed ecco spiegato perché il gioco si riavvia una volta cambiate le impostazioni). Memorizziamo poi le zone dove dobbiamo gestire le collisioni, per creare le animazioni dei pulsanti e cliccare quindi su di essi. Carichiamo il puntatore del mouse e l’audio del click, prenotando un canale per lui. Teniamo conto del tasto premuto attualmente, in questo caso si parte da -1 perché il primo tasto, “Inizio”, sarà lo 0, mentre “Esci”, che è l’ultimo, sarà il 4.
- dalla 60 alla 110
Anche per il menù opzioni memorizziamo il tasto attualmente premuto e poi carichiamo tutti i pulsanti con le eventuali animazioni (per esempio il pulsante della risoluzione attuale sarà illuminato di un altro colore). Anche per il menù opzioni creiamo i vari rect per gestire le collisioni (con i click). Infine, creiamo due liste, rect_premere e rect_premere_opzioni dove memorizziamo tutti i rect relativi rispettivamente al menù ed al menù opzioni, perché dovremo controllare ad ogni click, in base all’istanza attuale, quale pulsante stiamo premendo.
- dalla 114 alla 127
Il metodo action gestisce le eventuali chiamate a seconda del pulsante premuto. Infatti ha bisogno delle coordinate del mouse per controllare su quale tasto si trova sopra il puntatore. A quel punto, restituisce il numero del tasto premuto, cambiando quindi il valore all’interno della classe, che reagirà in base a questo.
- dalla 131 alla 133
Questo metodo ha solo il compito di passare gli eventi al gioco vero e proprio se non stiamo nel menù. I movimenti sono poi controllati dalla classe giocatore, che è interna ad ingame e per questo possiamo accedere tranquillamente da qui, senza passare l’istanza di gioco perché già memorizzata nella classe menù.
- dalla 137 alla 143
Questo metodo fa passare al menù quando siamo in gioco e viceversa, semplicemente cambiano le variabili all’interno della classe.
- dalla 147 alla 156
Il salvataggio durante il gioco, per questo esempio, consiste solo nella memorizzazione delle coordinate attuali del giocatore, così che, caricandole, il giocatore torni nella posizione in cui era stato salvato. Per fare questo apriamo uno stream per il nostro file salvataggio, che sarà sempre sovrascritto e vi memorizziamo le coordinate prendendole dalla classe giocatore stesso (sempre all’interno di ingame). pk.clear_memo() è necessario se si salva più volte con lo stesso file, perché altrimenti il nostro salvataggio sarà aggiornato solo la prima volta, poiché cPickle si ricorderà di aver già scritto quel file e non lo modificherà più se non utilizziamo questo metodo (tecnicamente non servirebbe, perché cPickle non viene mai richiamato nello stesso ciclo di gioco, ma ho ritenuto opportuno fare chiarezza).
Un metodo analogo è presente per il salvataggio delle impostazioni, ma questo lo vedremo più avanti.
- dalla 160 alla 171
Il caricamento del nostro salvataggio è molto simile al caricamento delle impostazioni, l’unica differenza è che dobbiamo solo caricare il personaggio con le coordinate aggiornate.
- dalla 175 alla 255
Alla funzione reder passiamo lo schermo (screen), il tempo passato (temp) e le coordinate del mouse (msxy). Per prima cosa controlliamo se siamo in gioco oppure no, per chiamare così il rendering del gioco. Altrimenti renderizziamo lo sfondo e poi passiamo ai pulsanti. Ogni pulsante, in base allo stato in corso (premuto o no) renderizzeranno il tasto caricato in precedenza. Dalla linea 211 si gestisce l’animazione e le eventuali azioni da compiere, come per esempio il cambio di schermata se si clicca su opzioni, con il caricamento delle impostazioni attuali (nell’eventualità che, anche se le opzioni sono state modificate, rimangano le stesse se non si clicca su applica). E’ proprio questa parte che si occupa del tempo di animazione del pulsante e di rimettere “in sesto” le condizioni del menù, che sarà in attesa della pressione di un altro tasto. In poche parole, se il tempo di animazione non è concluso, il tasto rimane pigiato ed incrementiamo il tempo trascorso. Una volta concluso togliamo il tutto ed eseguiamo l’azione, un pò come si faceva per le animazioni delle sprites.
Per ultimo, renderizziamo il puntatore sullo schermo cone le coordinate msxy che avevamo passato prima.
- dalla 159 alla 410
La gestione del menù opzioni è la stessa, con la differenza che ora i pulsanti devono cambiare la configurazione delle impostazioni (caricata dal file all’interno della classe menù) e solo il tasto applica scrive il nuovo file una volta finita lascelta (riscrivendo la nuova classe impostazione sul file). Per questo carichiamo ogni volta il file impostazioni quando si accede al menù opzioni, perché se non si cambia nulla, le variabili che abbiamo comunque cambiato all’interno della classe non sono la configurazione attuale e non si capirebbe più quali scelte dei settaggi ha fatto il giocatore (infatti se per esempio scegliamo la risoluzione 800×600 questa si colorerà di azzuro, ma se non applichiamo nulla, quando rientriamo in opzioni questa non deve essere azzura, perché magari la risoluzione attuale è un’altra).
Come ho già accennato, il tasto opengl è solo messo per parcondicio, ma sicuramente la sua attivazione porterà a degli errori. Inoltre ho scelto delle risoluzioni standard, una cosa un pò più fine sarebbe riconoscere le varie risoluzioni possibili dello schermo per poi presentarle, ma come potete immaginare, poi ci sarebbe il problema del numero di pulsati da caricare e delle proporzioni degli oggetti sullo schermo. In questo esempio invece non ci sono problemi per come abbiamo impostato la cosa all’inizio, visto che i pulsanti, per esempio, hanno sempre la stessa grandezza e sono sempre visualizzati al centro dello schermo.
Conclusioni
Anche se il codice può sembrare poco chiaro al primo impatto, vedrete che, capito il meccanismo, sarà semplice implementare un vostro menù a seconda delle vostre esigenze. Naturalmente questa è solo una delle possibili soluzioni e con le ultime parti che saranno presentate nel prossimo articolo avrete un esempio concreto dove sperimentare le vostre idee.
Spero che per ora le cose siano abbastanza chiare e che vi torni molto utile questa parte, essenziale per la realizzazione di un videogioco.
Se mi posso permettere, credo che rifattorizzare il codice per renderelo più semplice e leggibile potrebbe essere materia di un prossimo articolo. :P
A mio avviso suddividere le varie parti funzionali in appositi metodi gioverebbe molto, come pure l’uso di identificatori più autoesplicativi (ad esempio qualcosa di più appropriato per “bool_buff”).
Inoltre una cosa carina (e una bella sfida :D) che potrebbe mostrare le potenzialità di Python come linguaggio dinamico potrebbe essere quella di sviluppare un sistema di caricamento e accesso gerarchico delle immagini.
Un metodo realizzato allo scopo potrebbe, dato il percorso base “data/”, eseguire la scansione ricorsiva delle cartelle e dei file, e caricare tutti quelli che hanno estensione “.png”. Il metodo restituirebbe, alla fine, un oggetto “navigabile”.
Ad esempio, supponendo che l’oggetto restituito si chiami Images, per accedere all’immagine “data/pulsanti/Continua.png” basterebbe scrivere nel codice Images.pulsanti.Continua.
Un meccanismo simile, ma non gerarchico, potrebbe essere utile per calcolare i vari rettangoli che poi utilizzi nella definizione di self.rect_premere e self.rect_premere_opzioni.
Per “intrappolare” le parti che possono sollevare eccezioni e causare la fine prematura dell’applicazione si potrebbe ricorrere a un apposito decoratore (da applicare alle funzioni / metodi).
E così via, per altre parti “ripetitive”, che potrebbero essere rifattorizzatore magari utilizzando delle classi, o oppure dizionari di funzioni (utili per “delegare” all’esterno certe azioni).
Sono idee buttate così, eh! :)
“Un metodo realizzato allo scopo potrebbe, dato il percorso base “data/”, eseguire la scansione ricorsiva delle cartelle e dei file, e caricare tutti quelli che hanno estensione “.png”. Il metodo restituirebbe, alla fine, un oggetto “navigabile”.”
x cdmauro
la prossima volta lo scrivete a quattro mani il codice e buona notte…
@ Cesare Di Mauro
A parte il fatto che non mi è neanche passato di mente l’uso dei decoratori, per mia sfortuna, perché mi avrebbe fatto risparmiare molte righe di codice, visto che le funzioni per i caricamenti dei file ecc… sono già belle che pronte…
Comunque sia, è buffo sentire dei suggerimenti che erano delle mie idee iniziali :D (almeno significa che con la testa ancora ci stò!!!). Purtroppo non ho avuto tempo materiale. Mi scuso se il codice è poco leggibile e spero che, come sto tentando ora, riesca sempre a migliorare la stesura del testo.
Don’t reapeat yourself… :D
http://en.wikipedia.org/wiki/Don't_repeat_yourself
Ogni volta che sento la tentazione copiare e incollare un pezzo del mio stesso codice da una parte all’altra ripeto il mantra: “DRY, DRY, DRY…” :D
Per il caricamento delle immagini ti consiglio di utilizzare un image manager (che mantiene al suo interno un dizionario con le varie immagini) passandogli magari in un for la lista dei path delle immagini contenute nella cartella (potresti utilizzare os.listdir).
Ho fatto una cosa del genere per una prova con le librerie SFML (a parte la scansione dei file che comunque è la parte più semplice):
http://pastebin.com/ikHQr0GS
È assolutamente basilare ma potrebbe esserti di aiuto per quanto riguarda l’implementazione dell’image manager.
@ Emanuele Rampichini
In effetti implementare un image manager non dovrebbe essere troppo difficile, terrò molto conto di questa cosa, sopratutto per il fatto che è quasi indispensabile negli esempi che faccio… :D
Pair programming: sviluppare un gioco in Python?
:)
@ Jacopo Cocchi
In effetti… non sarebbe male come idea… :P
Corsi e ricorsi: http://www.hwupgrade.it/forum/forumdisplay.php?f=112 ;)
@homero: il codice funziona, e questo penso sia l’obiettivo numero 1 per un programmatore.
Poi per rifinirlo ci si può pensare dopo, ma… serve tempo, appunto.
@Emanuele: l’idea più o meno è quella, ma personalmente preferirei non dover specificare i path di tutte le immagini, proprio per non ripetere sempre le stesse cose.
Le immagini stanno tutte in una precisa alberatura del filesystem: si dovrebbe effettuare automaticamente la scansione, e caricarle nell’image manager, in modo da renderle disponibili tutte in un colpo solo.
Codice più ridotto e pulito. ;)
@Cesare
Esatto. Io ho fatto in quel modo poichè dovevo caricarne giusto un paio. Il passo successivo è quello che dici tu.
Costruirsi una buona convenzione su come e dove mettere gli assets è il punto “focale”. Se lo fai in maniera intelligente anche il codice necessario si riduce di molto.
@ Emanuele
Scusate l’OT, ma vorrei chiedere ad Emanuele, qual’è la sua impressione riguardo le SFML, visto che mi stanno incuriosendo molto e non conosco nessun altro che le ha provate … :D
Beh… non ho fatto molto ma da quello che ho visto sono molto curate. A differenza delle sdl espongono una API completamente a oggetti e molto pulita, sono abbastanza modulari (ho dato una spulciata ai sorgenti e devo dire che sono ben organizzati e ben commentati).
Poi hanno il vantaggio di utilizzare in maniera trasparente opengl per il rendering a differenza di SDL.
Tra l’altro hanno anche un buon wiki, un buon forum e lo sviluppo è parecchio attivo.
Peccato non supporti le GIF animate.
Per il resto m’è sembrata molto ben fatta.
@ Cesare && Emanuele
Purtroppo ho qualche dubbio nell’utilizzo di opengl con pygame, perché vorrei utilizzare, per l’appunto, una libreria più “trasparente”.
Il mio intento sarebbe quello di presentare le opengl senza troppi fronzoli, per poi passare a cose più complesse che, con una buona base (“trasparente”), non saranno di difficile comprensione.
Credo che, facendo in questo modo, l’utilizzo di un game engine come panda3d e/o simili, non precluda le conoscenze di base di cui si ha bisogno in questo campo.
Spero che il mio pensiero sia comprensibile.
Rimarrò in attesa di chiunque abbia un suggerimento a riguardo; chiedo solo di giustificare la vostra risposta.
Scusate ancora l’OT.
SFML utilizza opengl per accelerare le trasformazioni ed è un api prettamente 2D. Naturalmente può essere utilizzata come semplice contenitore per una applicazione Opengl 3D ma in questo caso devi scriverti il motore grafico praticamente a mano. (trasformazioni, importazione modelli etc.. etc…). In pratica utilizzeresti 2 o 3 funzioni di SFML per mostrare la finestra e basta.
Se l’intento è quello di “imparare a scrivere giochi” direi che partire da un bel framework di alto livello come panda3d possa dare più soddisfazioni.
Considera che questi sono consigli “di massima” e le mie competenze in questo ambite sono estremamente limitate. ;-)
Arrivo soltanto ora, dopo una dormitona risanatoria.
Concordo con Emanuele.
Mirco, riguardo al 3D penso che tu non debba presentare le OpenGL, ma… il 3D appunto. Quel che serve è capire come gestirlo, mostrarne le problematiche, ed eventualmente le soluzioni.
Le OpenGL sono soltanto una libreria, sicuramente comune e utilizzata, ma non rappresenta certamente “il 3D”.
Tant’è che su Windows i programmatori, già da anni, hanno scelto di utilizzare le ben più comode e produttive DirectX (stesso giudizio espresso di recente da chi lavora a Gallium3D, in merito alla loro recente implementazione delle API DirectX 10+).
Altri preferiscono Ogre, Blender, o altre soluzioni. Ce n’è per tutti i gusti.
Panda3D, da quel che ho visto, sembra una soluzione semplice da utilizzare, e più utile didatticamente (ma non solo!).
@ Cesare && Emanuele
Vi ringrazio ad entrambi per le vostre risposte, ora ho più chiara la strada che voglio prendere :D.
Se c’è ancora qualcuno che vorrebbe dare dei suggerimenti o delle osservazioni o vuole proporre qualche guida, non si vergogni a commentare qui sotto. Io cercherò di soddisfare tutte le richieste possibili, tempo permettendo :P.