Kirjoittaja: ZcMander
Kirjoitettu: 09.01.2008 – 28.10.2012
Tagit: grafiikka, kirjaston käyttö, kirjasto, koodi näytille, vinkki
Valikkoluokka käsittelemään valikon selausta ja asetuksien muuttamisia. Itse valikkoluokka on toteutettu MVC:llä (ei tosin täydellisellä, koska view ei saa read-only oikeutta modeliin ollenkaan) joten oman valikon piirturin teko pitäisi olla riittävän helppoa. Itse valikko sisältää tietenkin valikon kohtia (MenuItem(s)), joita moduulissa on luotu seuraavia:
- DummyMenuItem - Tulostaa kyseisen valikon kohdan nimen, kun valitaan (choice())
- ChoiceMenuItem - Mahdollisuus antaa lista joiden sisältä käyttäjä voi valita haluamansa (esim. resoluutiolistasta oma resoluutio)
- BackMenuItem - Mahdollisuus mennä valikossa edelliseen valikkoon, voidaan toteuttaa myös näppäimenpainalluksena
- SubMenu - Itse valikko, voidaan lisätä myös toisen valikon sisään
Tietenkin omia valikon kohtia on oikeastaan pakkokin tehdä, mutta valikko-luokka onkin suunniteltu sitä varten.
Esimerkki jäi hieman vähemmälle kommentoinnille, mutta tärkein koodi luokan käytön kanalta on create_menu-funktiossa ja näppäinpainalluksien tarkistuksessa.
Esimerkki ja moduuli vaatii toimiakseen pygame:n.
# -*- coding: utf-8 -*- import pygame UP = 1 DOWN = 2 CHOICE = 3 BACK = 4 class BackMenuItem: """Takaisin-kohta valikossa""" def __init__(self, name, menu): self.name = name self._menu = menu def __call__(self): global BACK self._menu.send_signal(BACK) class ChoiceMenuItem: """Valikon kohta, jossa pystyy valitsemaan tietyn kohdan joukon sisältä, esimerkiksi resoluutiolistasta sopiva resoluutio""" def __init__(self, menu, key, name, choices, default=0): """ menu = CMenu viittaus luokkaan, jotta voidaan lisätä asetuksien listaan key = string millä nimellä asetus löytyy asetuksista name = string millä nimellä valinta löytyy valikosata, huomaa että nimen jälkeen tulee vielä ": " ja valittu kohta choices = list lista mahdollisista valinnoista, tulee olla muotoa: ["avain", arvo, "avain2", arvo2, ...] """ self._base_name = name self._choices = choices self._choice = default self._set_name() menu.attach_to_key(key, self) def _set_name(self): """Asettaa kohdan nimen""" name = str(self._choices[self._choice*2]) self.name = self._base_name + ": " + name def __call__(self): """Vaihtaa valittua kohtaa""" self._choice += 1 if self._choice == len(self._choices)/2: self._choice = 0 self._set_name() def get_value(self): """Palauttaa kohdan arvon""" return self._choices[self._choice*2+1] class SubMenu: """Alivalikko""" def __init__(self, name): """ name = string valinnan nimi Luo alivalikon, jonka voi lisätä toiseen alivalikkoon """ self.name = name #Nimi joka näkyy valikossa self.title = name #Nimi joka näkyy otsikkona kun ollaan tässä valikossa self.tree = [] #Itse valikon sisältö self.choice = None #Mikä kohta on valittu def __call__(self): return self def add(self, item): """Lisää kohdan valikkoon""" if self.choice == None: self.choice = 0 self.tree.append(item) def choice(self): """Palauttaa valitun kohdan""" return self.tree[choice]() class MMenu: def __init__(self): """Alustaa luokan""" self._tree = [] self._depth = [] #Nykyinen syvyys self._curmenu = None self._settings = {} #Valikon asetuksia varten def attach_to_key(self, key, item): """ key = string asetuksen avain, jolla asetus tunnistetaan item = class luokka, josta arvo avaimelle haetaan Lisää aetuksen asetuksien listaan """ #Varmistetaan ettei korvata jo olemassa olevaa avainta if not self._settings.has_key(key): self._settings[key] = item.get_value return True else: return False def set_tree(self, tree): """Asettaa juuren valikolle""" self._tree = tree self._curmenu = self._tree def send_signal(self, signal): """ signal = int singaali Vastaanottaa signaalin ja toimii sen mukaan """ global UP, DOWN, CHOICE, BACK if signal == UP: if self._curmenu.choice == 0: self._curmenu.choice = len(self._curmenu.tree)-1 else: self._curmenu.choice -= 1 if signal == DOWN: if self._curmenu.choice == len(self._curmenu.tree)-1: self._curmenu.choice = 0 else: self._curmenu.choice += 1 if signal == CHOICE: #Jos kyseessä on alivalikko niin mennää sinne a = self._curmenu.tree[self._curmenu.choice]() if a != None: self._curmenu = a if signal == BACK: #Jos ei olla jo juuressa if self._tree != self._curmenu: #Haetaan valikko jonka sisällä nykyinen valikko on ja laitetaan se #nykyiseksi valikoksi a = self._tree while a.tree[a.choice] != self._curmenu: a = a.tree[a.choice] self._curmenu = a def get_current_menu(self): """Palauttaa nykyisen valikon""" return self._curmenu def get_settings(self): """Palauttaa asetukset""" r = {} for key in self._settings: r[key] = self._settings[key]() return r class VMenu: def __init__(self): self._font = pygame.font.Font(None, 50) self._header_font = pygame.font.Font(None, 300) def draw(self, menu): """Piirtää valikon""" screen = pygame.display.get_surface() #Piirretään otsikko surf = self._header_font.render(menu.title, True, [70,70,70]) x = screen.get_width()/2-surf.get_width()/2 screen.blit(surf, [x,10]) #Ja valikon kohdat for m in range(len(menu.tree)): #Haetaan nimi name = menu.tree[m].name #Vaihdetaan väriä jos on valittu kyseinen kohta color = [32,32,32] if menu.choice == m: color = [128,128,128] #Piirretään teksti surf = self._font.render(name, True, color) #Lasketaan paikka x = screen.get_width()/2-surf.get_width()/2 y = screen.get_height()/2-100 + 50*m #Ja piirretään se ruudulle screen.blit(surf, [x,y]) class CMenu: """"Nitoo" yhteen modelin ja viewin""" def __init__(self, tree, model=None, view=None): """ model, view = instance !HUOM! model ja view pitää olla instanseja Alustaa luokan """ #Jotta viewi voidaan korvata omalla if view != None: self._view = view else: self._view = VMenu() #Jotta modelli voidaan korvata omalla if model != None: self._model = model else: self._model = MMenu() #Asetetaan valikon juuri self._model.set_tree(tree) def draw(self): """Piirtää valikon""" self._view.draw(self._model.get_current_menu()) def send_signal(self, signal): """Lähettää signaalin model:n käsiteltäväksI""" self._model.send_signal(signal) def get_settings(self): """Palauttaa asetukset""" return self._model.get_settings() def attach_to_key(self, key, item): """Asettaa asetuksen asetuksien listaan""" self._model.attach_to_key(key, item)
# -*- coding: utf-8 -*- import pygame import menu class Renderer: def __init__(self, bgcolor=[128,64,255]): pygame.init() self._bgcolor = bgcolor def set_display(self, resolution, fullscreen=False, flags=0): """ resolution = list asetettava resoluutio fullscreen = bool onko kokoruudussa flags = int muita pygame:n flagejä Alustaa näytön lähimmälle sopivalle resoluutiolle """ flags = flags | pygame.DOUBLEBUF if fullscreen: flags = flags | pygame.FULLSCREEN pygame.display.set_mode(resolution, flags) def start(self): """Aloittaa piirtämisen""" pygame.display.get_surface().fill(self._bgcolor) def end(self): """Lopettaa piirtämisen""" pygame.display.flip() class StateMachine: def __init__(self): self._states = {"game": 1, "menu": 0, "quit": -1, } self._state = self._states["menu"] def set_state(self, state): if state in self._states.keys(): self._state = self._states[state] def get_state(self, name=""): if name == "": return self._state else: return self._states[name] class DummyMenuItem: def __init__(self, name): self.name = name def __call__(self): print "%s pressed" % self.name class StateMenuItem(DummyMenuItem): def __init__(self, name, state, statemachine): DummyMenuItem.__init__(self, name) self._state = state self._statemachine = statemachine def __call__(self): self._statemachine.set_state(self._state) def create_backitems(m, tree): for item in tree: try: item.add(menu.BackMenuItem("<- Takaisin", m)) create_backitems(m, item.tree) except: pass def create_menu(statemachine): mainmenu = menu.SubMenu("") m = menu.CMenu(mainmenu) #------------ Alkuvalikon kohdat ng = StateMenuItem("New game", "game", statemachine) options = menu.SubMenu("Options") quit = StateMenuItem("Quit", "quit", statemachine) mainmenu.add(ng) mainmenu.add(options) mainmenu.add(quit) #------------ Asetusvalikon kohdat d4 = DummyMenuItem("Sound") video = menu.SubMenu("Video") d6 = DummyMenuItem("Game") options.add(d4) options.add(video) options.add(d6) #------------ Näytönasetuksien kohdat reso = pygame.display.list_modes() r = [] for i in reso: #Suodatetaan liian pienet resoluutiot pois if i[0] > 600 and i[1] > 400: key = str(i[0]) + "x" + str(i[1]) r.append(key) r.append(i) default = str(reso[0][0]) + "x" + str(reso[0][0]) resolutions = menu.ChoiceMenuItem(m, "resolution", "Resolution",r) fs = menu.ChoiceMenuItem(m, "fullscreen", "Fullscreen", ["True", True, "False", False]) video.add(fs) video.add(resolutions) #Luodaan takaisin-kohdat create_backitems(m, mainmenu.tree) return m def main(): #Alustetaan renderöiä render = Renderer([64,64,64]) render.set_display((800,600)) #Alustetaan tilakone statemachine = StateMachine() #Luodaan valikko m = create_menu(statemachine) done = False while not done: #Käsitellään näppäimenpainallukset for e in pygame.event.get(): if e.type == pygame.QUIT or \ ( e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE ): done = True if e.type == pygame.KEYDOWN: if e.key == pygame.K_UP: m.send_signal(menu.UP) elif e.key == pygame.K_DOWN: m.send_signal(menu.DOWN) elif e.key == pygame.K_RETURN: m.send_signal(menu.CHOICE) elif e.key == pygame.K_BACKSPACE: m.send_signal(menu.BACK) #Jos valikon kautta joko mentiin pois tai aloitettiin uusi peli, niin #tulostetaan asetukset if statemachine.get_state() in [statemachine.get_state("game"), statemachine.get_state("quit")]: done = True print m.get_settings() #Piirretään valikko render.start() m.draw() render.end() if __name__ == "__main__": main()
Toiminnan perusteella tykkään. Tuossa voisi listauksissa ja ehkä jopa kuvauksessa mainita, että nuo menut sisältävä tiedosto on nimeltään menu.py, säästyypähän muut testailijat erheilmoitukselta :). Muutama typo, "renderöiä" ja "#Piirretänn". Tulee mieleen TextWidget , hiirellä käytettävä menuluokka.
"Piirretänn"-korjattu, mutta miten "renderöiä" pitäisi korjata? Yksvaihtoehto ois "renderi" tai ihan suomeksi "piirtäjä", mutta luulis selviän kaikista (kolmesta)
Äidinkielellisesti oikein lienee sanoa "renderöijä", mutta tuskinpa sillä on tajuamisen kannalta suurta merkitystä.
Kohdassa
global UP, DOWN, CHOICE
lienee jäänyt BACK pois, kun sitä kuitenkin testataan alla olevista if-lausekkeista viimeisessä.
Humm, ilmeisesti, kuitenkaan tulkki ei siitä mitään virhettä antanut, joten onko koko global-rivi turha? Noh, lisäänpä kyseisen BACK-ympäristömuuttujan tuonne.