di  -  martedì 23 Novembre 2010

Introduzione

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:

  1. screen1 : lo schermo dove andremo a renderizzare.
  2. altezza_s  : altezza dello schermo.
  3. larghezza_s : larghezza dello schermo.
  4. 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.

16 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    Cesare Di Mauro
     scrive: 

    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! :)

  • # 2
    homero
     scrive: 

    “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…

  • # 3
    Mirco Tracolli
     scrive: 

    @ 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.

  • # 4
    Emanuele Rampichini
     scrive: 

    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.

  • # 5
    Mirco Tracolli
     scrive: 

    @ 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

  • # 6
    Jacopo Cocchi
     scrive: 

    Pair programming: sviluppare un gioco in Python?

    :)

  • # 7
    Mirco Tracolli
     scrive: 

    @ Jacopo Cocchi

    In effetti… non sarebbe male come idea… :P

  • # 8
    Cesare Di Mauro
     scrive: 

    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. ;)

  • # 9
    Emanuele Rampichini
     scrive: 

    @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.

  • # 10
    Mirco Tracolli
     scrive: 

    @ 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

  • # 11
    Emanuele Rampichini
     scrive: 

    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.

  • # 12
    Cesare Di Mauro
     scrive: 

    Peccato non supporti le GIF animate.

    Per il resto m’è sembrata molto ben fatta.

  • # 13
    Mirco Tracolli
     scrive: 

    @ 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.

  • # 14
    Emanuele Rampichini
     scrive: 

    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. ;-)

  • # 15
    Cesare Di Mauro
     scrive: 

    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!).

  • # 16
    Mirco Tracolli
     scrive: 

    @ 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.

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.