Испод хаубе: догађаји и њихова обрада¶
Наш програм чува тзв. ред догађаја у који се смештају информације о свим догађајима (рећи ћемо једноставније да се смештају догађаји). Ред можеш замислити као ред у самопослузи. У њега се смештају догађаји у оном редоследу у ком су се догодили и наш програм их обрађује једног по једног (као што касирка обрађује једног по једног купца). Дакле, програм може у неком тренутку анализирати догађаје који се налазе у том реду и на основу врсте догађаја који је регистрован може променити своје понашање и проузроковати неку реакцију на догађај. На пример, када се притисне тастер стрелице на горе, лик који се црта на екрану се може померити неколико пиксела ка врху екрана. Приликом анализе, догађаји се уклањају из тог реда. Анализу догађаја је пожељно вршити што пре након њиховог догађања из неколико разлога. Прво, ако је реакција на догађај закаснела, корисник добија програм који споро реагује на његове акције и такав програм може бити збуњујући и неудобан за употребу (замисли игрицу у којој управљаш ликовима помоћу тастатуре, али се ликови померају тек секунд након што притиснеш тастер). Друго, у реду се истовремено може чувати само ограничен број догађаја и ако се они не уклањају редовно из реда, неки догађаји неће моћи бити регистровани, па неће моћи бити ни обрађени.
Нагласимо још и то да се догађаји региструју тек када се иницијализује
библиотека PyGame (помоћу pg.init
) и када се иницијализује екран
(помоћу pg.display.set_mode
) - док се то не уради, догађаји се
неће исправно регистровати.
Петља заснована на догађајима¶
Постоји неколико сценарија детекције и обраде догађаја у PyGame
програмима. Један приступ се заснива на коришћењу функције
pg.event.wait()
којом се извршавање програма практично зауставља
све док не наступи неки догађај. Помоћу ове функције веома једноставно
можемо писати програме у којима се тело главне петље извршава по
једном за сваки догађај који се региструје. За такве петље ћемо рећи
да су вођене догађајима (енгл. event based loops).
Обрада искључивања прозора¶
Ову функцију и петљу вођену догађајима смо већ користили у свим
програмима које смо до сада писали (и функција pygamebg.wait_loop
функционише баш на овај начин). У њима смо занемаривали све догађаје
осим догађаја pg.QUIT
који наступа када корисник искључи прозор.
while pg.event.wait().type != pg.QUIT:
pass
(obrada_dogadjaja_event_wait_quit)
Сваки догађај чува податак о типу догађаја коме можемо приступити
помоћу поља type
. У претходном коду се током израчунавања услова
петље while
позове функција pg.event.wait()
која враћа први
догађај из реда или чека да се неки догађај догоди ако је ред празан.
Када у реду постоји неки необрађени догађај, функција
pg.event.wait()
га враћа. Након тога се одмах испитује његов тип и
пореди са pg.QUIT
. Ако догађај није тог типа (корисник није
искључио прозор), тада је услов петље испуњен, извршава се тело петље
у коме се налази наредба pass
која нема никаквог ефекта и поново
се враћа на проверу услова у којој се обрађује наредни догађај (ако је
потребно, поново се чека на њега).
Монолитна имплементација¶
У ситуацији када су нам релевантни и други догађаји, структура петље је мало другачија. Посматрајмо наредни PyGame програм у коме се само исписују информације о догађајима.
import pygame as pg
pg.init()
pg.display.set_mode((200, 200))
kraj = False
while not(kraj):
dogadjaj = pg.event.wait() # čekamo na naredni događaj
print(dogadjaj) # ispisujemo podatke o njemu
if dogadjaj.type == pg.QUIT: # proveravamo da li je to događaj isključivanja programa
kraj = True
pg.quit()
(informacije_o_dogadjajima)
Покрени претходни програм на свом рачунару (на пример, из окружења IDLE) и видећеш како се током померања миша генерише велики број догађаја који описују мале помераје. Пробај да кликнеш мишем и приметићеш како се генеришу два догађаја - догађај притиска дугмета и догађај отпуштања дугмета. Слично је и са притиском на тастер тастатуре.
Структура претходне петље је таква да се у њој прихвата наредни
догађај помоћу pg.event.wait
. А онда се гранањем анализира тип
догађаја који смо прихватили. Ако нам је релевантан само догађај
искључивања прозора, тада се у гранању проверава само тип догађаја
pg.QUIT
. Када се тај догађај детектује, треба да прекинемо петљу
(а тиме практично и програм). Један начин за то је коришћење логичке
променљиве kraj
, као у претходном програму. Уместо логичке
променљиве, поново смо могли употребити наредбу break
.
# petlja obrade događaja - čekamo dok korisnik ne isključi prozor
while True:
dogadjaj = pg.event.wait() # čekamo na naredni događaj
if dogadjaj.type == pg.QUIT: # proveravamo da li je to isklučivanje prozora
break # ako jeste, prekidamo petlju
(pygame_skelet_obrada_dogadjaja_event_wait_break)
Обрада разних типова догађаја¶
Обе претходне варијанте остварују исти ефекат као и она коју смо
користили у свим досадашњим програмима, међутим, иако су дуже и
компликованије, имају предност да се лако уопштавају. Наиме, гранањем
на основу типа можемо анализирати и друге типове догађаја и за сваки
од типова можемо направити одговарајућу реакцију. Сваком типу догађаја
тада можемо придружити једну грану у гранању који се реализује помоћу
наредбе if-elif-else
. Главна петља програма у којој се догађаји
обрађују се тада може имплементирати на следећи начин (уместо ???
потребно је навести конкретне типове догађаја, попут KEYDOWN
који
се генерише притиском на неки тастер или MOUSEMOTION
који се
генерише када се помери миш, о којима ће више речи бити у наставку).
kraj = False
while not(kraj):
... # na ovom mestu se obično vrši crtanje
pg.display.update()
dogadjaj = pg.event.wait() # čekamo na naredni događaj
if dogadjaj.type = pg.QUIT: # događaj isključivanja programa
kraj = True
elif dogadjaj.type = pg.???: # događaj tipa ???
...
elif dogadjaj.type = pg.???: # događaj tipa ???
...
(obrada_dogadjaja_event_wait)
Цртање само када је то потребно¶
Реакција на догађај обично подразумева да се на екрану нешто промени, тј. да се сцена на екрану поново исцрта. Стога се кôд за цртање (или позив функције у којој се врши цртање) често смешта у тело главне петље (пре или после обраде догађаја), слично као што је случај и код анимација које не морају да реагују на догађаје, али такође веома често мењају слику на екрану. Приметимо да се у сваком кораку извршавања петље поново врши исцртавање, тј. црта се све изнова када год наступи било који догађај. То може имати смисла у анимацијама и играма у којима се сцена на екрану аутоматски често мења (на пример, противници се у играма често сами крећу по екрану, тј. игре садрже у себи и компоненту анимације кретања противника). Међутим, постоје и ситуације у којима се између два догађаја слика не мења и тада је непотребно стално вршити цртање (тиме само беспотребно оптерећујемо рачунар који исту слику црта више пута). Једноставан начин да се то оптимизује је да се уведе логичка променљива која означава да ли је потребно поново цртати.
treba_crtati = True
kraj = False
while not(kraj):
if treba_crtati:
# na ovom mestu se vrsi crtanje i osvežavanje prozora
...
pg.display.update()
treba_crtati = False # ne treba crtati dok ne nastupi događaj koji menja nešto na ekranu
# čekamo dok se ne dogodi naredni događaj
dogadjaj = pg.event.wait()
# obrađujemo dogadjaj koji se dogodio
if dogadjaj.type = pg.QUIT: # isključivanjem prozora prekida se petlja
kraj = True
elif dogadjaj.type = pg.???: # događaj tipa ???
...
treba_crtati = True # nešto se promenilo, pa treba ponovo crtati
elif dogadjaj.type = pg.???: # događaj tipa ???
...
(treba_crtati)
На почетку променљива treba_crtati
добија вредност True
како
би се у првом проласку кроз тело петље слика нацртала. Одмах након
првог цртања та променљива добија вредност False
. У наредним
проласцима кроз петљу цртање се не врши, све док се не региструје неки
догађај који проузрокује промену сцене, након чега је потребно поново
је исцртати. У склопу обраде таквог догађаја променљива
treba_crtati
добија вредност True
, и на почетку наредног
проласка кроз петљу сцена се поново црта, након чега се променљива
treba_crtati
поново поставља на False
и наредно цртање се
одлаже док се поново не деси одговарајући догађај.
Имплементација са помоћним функцијама¶
Прегледности ради обраду догађаја можемо издвојити у посебну функцију
(при чему догађај искључивања прозора, због неизбежности његове
анализе може остати у телу главне петље). Наравно, ако се у тој
функцији врши измена глобалних променљивих, потребно је набројати их
помоћу кључне речи global
. Ако обраду догађаја издвојимо у посебну
функцију, онда она може да кроз своју повратну, логичку вредност
сигнализира да ли је потребно изнова нацртати сцену.
import pygame as pg
pg.init()
...
# crtamo scenu
def crtaj():
...
# obrađujemo dodgađaj
def obradi_dogadjaj(dogadjaj):
if dogadjaj.type = pg.???: # događaj tipa ???
...
return True # treba ponovo iscrtati scenu
elif dogadjaj.type = pg.???: # događaj tipa ???
...
return True # treba ponovo iscrtati scenu
return False # ne treba ponovo iscrtati scenu
treba_crtati = True
kraj = False
while not(kraj):
if treba_crtati:
crtaj() # crtamo scenu i osvežavamo prikaz prozora
pg.display.update()
treba_crtati = False # ne treba crtati dok ne nastupi događaj koji menja nešto na ekranu
# čekamo dok se ne dogodi naredni događaj
dogadjaj = pg.event.wait()
# obrađujemo dogadjaj koji se dogodio
if dogadjaj.type = pg.QUIT: # isključivanje prozora
kraj = True
else: # ostali događaji
treba_crtati = obradi_dogadjaj(dogadjaj)
(treba_crtati_funkcija)
Коришћење библиотеке PyGameBg¶
Када се користи библиотека PyGameBg онда главна петља програма може
бити скривена иза позива функције pygamebg.event_loop
. У том
случају програм обично има наредну структуру.
import pygame as pg, pygamebg
# otvaramo prozor
pygamebg.open_window(...)
# crtamo scenu
def crtaj():
...
# obrađujemo događaj
def obradi_dogadjaj(dogadjaj):
if dogadjaj.type = pg.???:
...
return True # treba ponovo iscrtati scenu
elif dogadjaj.type = pg.???:
...
return True # treba ponovo iscrtati scenu
return False # ne treba ponovo iscrtati scenu
# započinjemo petlju zasnovanu na događajima
pygamebg.event_loop(crtaj, obradi_dogadjaj)
(treba_crtati_funkcija_pygamebg)
Обрада догађаја у склопу петље засноване на фрејмовима¶
Обраду догађаја могуће је вршити и у склопу петље засноване на фрејмовима (једино што се тада она не врши одмах након догађаја, већ тек након преласка на наредни фрејм). Ово користимо када желимо да обрађујемо догађаје у склопу програма који врше анимације.
Монолитна имплементација¶
import pygame as pg
pg.init()
...
sat = pg.Clock()
kraj = False
while not(kraj):
# obrađujemo sve događaje koji su nastupili između dva frejma
for dogadjaj in pg.event.get():
if dogadjaj.type == pg.QUIT:
kraj = True
elif dogadaj.type == ???:
...
elif dogadjaj.type == ???:
...
# ограничавамо извршавање тела петље на највише 25 пута у секунди
sat.tick(25)
# na ovom mestu vršimo promenu podataka koji opisuju
# stanje objekata na slici
...
# na ovom mestu ponovo vršimo crtanje
...
pg.display.update() # osvežavamo prikaz sadržaja prozora
pg.quit()
(frameloop_dogadjaji)
Имплементација са помоћним функцијама¶
Ако се користе помоћне функције, то онда изгледа овако.
import pygame as pg
pg.init()
...
# crtamo scenu
def crtaj():
...
# menjamo podatke koji opisuju stanje objekata na sceni
def novi_frejm():
global ... # promenljive koje se mogu menjati tokom animacije
...
crtaj()
# obradjujemo dogadjaj
def obradi_dogadjaj(dogadjaj):
global ... # promenljive koje se mogu promeniti na osnovu nekog dogadjaja
if dogadjaj.type = pg.???:
...
elif dogadjaj.type = pg.???:
...
sat = pg.Clock()
kraj = False
while not(kraj):
# obrađujemo sve događaje nastale između dva frejma
for dogadjaj in pg.event.get():
if dogadjaj.type == pg.QUIT:
kraj = True
else:
obradi_dogadjaj(dogadjaj)
# prelazimo na naredni frejm
novi_frejm()
pg.display.update() # osvežavamo prikaz sadržaja prozora
# ограничавамо извршавање тела петље на дати број пута у секунди
sat.tick(???)
pg.quit()
(frameloop_dogadjaji_funkcije)
Коришћење библиотеке PyGameBg¶
Када се користи библиотека PyGameBg онда главна петља програма може
бити скривена иза позива функције pygamebg.frame_loop
. У том
случају програм обично има наредну структуру.
import pygame as pg, pygamebg
pygamebg.open_window(sirina, visina, "...")
# globalno stanje programa
...
# crtamo scenu
def crtaj():
...
# menjamo podatke koji opisuju stanje objekata na sceni
def novi_frejm():
global ... # promenljive koje se mogu menjati tokom animacije
...
crtaj()
# obradjujemo dogadjaj
def obradi_dogadjaj(dogadjaj):
global ... # promenljive koje se mogu promeniti na osnovu nekog dogadjaja
if dogadjaj.type = pg.???:
...
elif dogadjaj.type = pg.???:
...
# započinjemo animaciju sa mogućnošću reagovanja na događaje
pygamebg.frame_loop(???, novi_frejm, obradi_dogadjaj)
(frameloop_dogadjaji_pygamebg)