Испод хаубе: догађаји и њихова обрада¶
Наш програм чува тзв. ред догађаја у који се смештају информације о свим догађајима (рећи ћемо једноставније да се смештају догађаји). Ред можеш замислити као ред у самопослузи. У њега се смештају догађаји у оном редоследу у ком су се догодили и наш програм их обрађује једног по једног (као што касирка обрађује једног по једног купца). Дакле, програм може у неком тренутку анализирати догађаје који се налазе у том реду и на основу врсте догађаја који је регистрован може променити своје понашање и проузроковати неку реакцију на догађај. На пример, када се притисне тастер стрелице на горе, лик који се црта на екрану се може померити неколико пиксела ка врху екрана. Приликом анализе, догађаји се уклањају из тог реда. Анализу догађаја је пожељно вршити што пре након њиховог догађања из неколико разлога. Прво, ако је реакција на догађај закаснела, корисник добија програм који споро реагује на његове акције и такав програм може бити збуњујући и неудобан за употребу (замисли игрицу у којој управљаш ликовима помоћу тастатуре, али се ликови померају тек секунд након што притиснеш тастер). Друго, у реду се истовремено може чувати само ограничен број догађаја и ако се они не уклањају редовно из реда, неки догађаји неће моћи бити регистровани, па неће моћи бити ни обрађени.
Нагласимо још и то да се догађаји региструју тек када се иницијализује
библиотека PyGame (помоћу pg.init
) и када се иницијализује екран
(помоћу pg.display.set_mode
) - док се то не уради, догађаји се
неће исправно регистровати.
Петља заснована на догађајима¶
Постоји неколико сценарија детекције и обраде догађаја у PyGame
програмима. Један приступ се заснива на коришћењу функције
pg.event.wait()
којом се извршавање програма практично зауставља
све док не наступи неки догађај. Помоћу ове функције веома једноставно
можемо писати програме у којима се тело главне петље извршава по
једном за сваки догађај који се региструје. За такве петље ћемо рећи
да су вођене догађајима (енгл. event based loops).
Обрада искључивања прозора¶
Ову функцију и петљу вођену догађајима смо већ користили у свим
програмима које смо до сада писали (и функција pygamebg.wait_loop
функционише баш на овај начин). У њима смо занемаривали све догађаје
осим догађаја pg.QUIT
који наступа када корисник искључи прозор.
Сваки догађај чува податак о типу догађаја коме можемо приступити
помоћу поља type
. У претходном коду се током израчунавања услова
петље while
позове функција pg.event.wait()
која враћа први
догађај из реда или чека да се неки догађај догоди ако је ред празан.
Када у реду постоји неки необрађени догађај, функција
pg.event.wait()
га враћа. Након тога се одмах испитује његов тип и
пореди са pg.QUIT
. Ако догађај није тог типа (корисник није
искључио прозор), тада је услов петље испуњен, извршава се тело петље
у коме се налази наредба pass
која нема никаквог ефекта и поново
се враћа на проверу услова у којој се обрађује наредни догађај (ако је
потребно, поново се чека на њега).
Монолитна имплементација¶
У ситуацији када су нам релевантни и други догађаји, структура петље је мало другачија. Посматрајмо наредни PyGame програм у коме се само исписују информације о догађајима.
Покрени претходни програм на свом рачунару (на пример, из окружења IDLE) и видећеш како се током померања миша генерише велики број догађаја који описују мале помераје. Пробај да кликнеш мишем и приметићеш како се генеришу два догађаја - догађај притиска дугмета и догађај отпуштања дугмета. Слично је и са притиском на тастер тастатуре.
Структура претходне петље је таква да се у њој прихвата наредни
догађај помоћу pg.event.wait
. А онда се гранањем анализира тип
догађаја који смо прихватили. Ако нам је релевантан само догађај
искључивања прозора, тада се у гранању проверава само тип догађаја
pg.QUIT
. Када се тај догађај детектује, треба да прекинемо петљу
(а тиме практично и програм). Један начин за то је коришћење логичке
променљиве kraj
, као у претходном програму. Уместо логичке
променљиве, поново смо могли употребити наредбу break
.
Обрада разних типова догађаја¶
Обе претходне варијанте остварују исти ефекат као и она коју смо
користили у свим досадашњим програмима, међутим, иако су дуже и
компликованије, имају предност да се лако уопштавају. Наиме, гранањем
на основу типа можемо анализирати и друге типове догађаја и за сваки
од типова можемо направити одговарајућу реакцију. Сваком типу догађаја
тада можемо придружити једну грану у гранању који се реализује помоћу
наредбе if-elif-else
. Главна петља програма у којој се догађаји
обрађују се тада може имплементирати на следећи начин (уместо ???
потребно је навести конкретне типове догађаја, попут KEYDOWN
који
се генерише притиском на неки тастер или MOUSEMOTION
који се
генерише када се помери миш, о којима ће више речи бити у наставку).
Цртање само када је то потребно¶
Реакција на догађај обично подразумева да се на екрану нешто промени, тј. да се сцена на екрану поново исцрта. Стога се кôд за цртање (или позив функције у којој се врши цртање) често смешта у тело главне петље (пре или после обраде догађаја), слично као што је случај и код анимација које не морају да реагују на догађаје, али такође веома често мењају слику на екрану. Приметимо да се у сваком кораку извршавања петље поново врши исцртавање, тј. црта се све изнова када год наступи било који догађај. То може имати смисла у анимацијама и играма у којима се сцена на екрану аутоматски често мења (на пример, противници се у играма често сами крећу по екрану, тј. игре садрже у себи и компоненту анимације кретања противника). Међутим, постоје и ситуације у којима се између два догађаја слика не мења и тада је непотребно стално вршити цртање (тиме само беспотребно оптерећујемо рачунар који исту слику црта више пута). Једноставан начин да се то оптимизује је да се уведе логичка променљива која означава да ли је потребно поново цртати.
На почетку променљива treba_crtati
добија вредност True
како
би се у првом проласку кроз тело петље слика нацртала. Одмах након
првог цртања та променљива добија вредност False
. У наредним
проласцима кроз петљу цртање се не врши, све док се не региструје неки
догађај који проузрокује промену сцене, након чега је потребно поново
је исцртати. У склопу обраде таквог догађаја променљива
treba_crtati
добија вредност True
, и на почетку наредног
проласка кроз петљу сцена се поново црта, након чега се променљива
treba_crtati
поново поставља на False
и наредно цртање се
одлаже док се поново не деси одговарајући догађај.
Имплементација са помоћним функцијама¶
Прегледности ради обраду догађаја можемо издвојити у посебну функцију
(при чему догађај искључивања прозора, због неизбежности његове
анализе може остати у телу главне петље). Наравно, ако се у тој
функцији врши измена глобалних променљивих, потребно је набројати их
помоћу кључне речи global
. Ако обраду догађаја издвојимо у посебну
функцију, онда она може да кроз своју повратну, логичку вредност
сигнализира да ли је потребно изнова нацртати сцену.
Коришћење библиотеке PyGameBg¶
Када се користи библиотека PyGameBg онда главна петља програма може
бити скривена иза позива функције pygamebg.event_loop
. У том
случају програм обично има наредну структуру.
Обрада догађаја у склопу петље засноване на фрејмовима¶
Обраду догађаја могуће је вршити и у склопу петље засноване на фрејмовима (једино што се тада она не врши одмах након догађаја, већ тек након преласка на наредни фрејм). Ово користимо када желимо да обрађујемо догађаје у склопу програма који врше анимације.
Монолитна имплементација¶
Коришћење библиотеке PyGameBg¶
Када се користи библиотека PyGameBg онда главна петља програма може
бити скривена иза позива функције pygamebg.frame_loop
. У том
случају програм обично има наредну структуру.