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.