Processing math: 100%

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Испод хаубе: догађаји и њихова обрада

Наш програм чува тзв. ред догађаја у који се смештају информације о свим догађајима (рећи ћемо једноставније да се смештају догађаји). Ред можеш замислити као ред у самопослузи. У њега се смештају догађаји у оном редоследу у ком су се догодили и наш програм их обрађује једног по једног (као што касирка обрађује једног по једног купца). Дакле, програм може у неком тренутку анализирати догађаје који се налазе у том реду и на основу врсте догађаја који је регистрован може променити своје понашање и проузроковати неку реакцију на догађај. На пример, када се притисне тастер стрелице на горе, лик који се црта на екрану се може померити неколико пиксела ка врху екрана. Приликом анализе, догађаји се уклањају из тог реда. Анализу догађаја је пожељно вршити што пре након њиховог догађања из неколико разлога. Прво, ако је реакција на догађај закаснела, корисник добија програм који споро реагује на његове акције и такав програм може бити збуњујући и неудобан за употребу (замисли игрицу у којој управљаш ликовима помоћу тастатуре, али се ликови померају тек секунд након што притиснеш тастер). Друго, у реду се истовремено може чувати само ограничен број догађаја и ако се они не уклањају редовно из реда, неки догађаји неће моћи бити регистровани, па неће моћи бити ни обрађени.

Нагласимо још и то да се догађаји региструју тек када се иницијализује библиотека PyGame (помоћу pg.init) и када се иницијализује екран (помоћу pg.display.set_mode) - док се то не уради, догађаји се неће исправно регистровати.

Петља заснована на догађајима

Постоји неколико сценарија детекције и обраде догађаја у PyGame програмима. Један приступ се заснива на коришћењу функције pg.event.wait() којом се извршавање програма практично зауставља све док не наступи неки догађај. Помоћу ове функције веома једноставно можемо писати програме у којима се тело главне петље извршава по једном за сваки догађај који се региструје. За такве петље ћемо рећи да су вођене догађајима (енгл. event based loops).

Обрада искључивања прозора

Ову функцију и петљу вођену догађајима смо већ користили у свим програмима које смо до сада писали (и функција pygamebg.wait_loop функционише баш на овај начин). У њима смо занемаривали све догађаје осим догађаја pg.QUIT који наступа када корисник искључи прозор.

 
1
while pg.event.wait().type != pg.QUIT:
2
    pass
3

(obrada_dogadjaja_event_wait_quit)

Сваки догађај чува податак о типу догађаја коме можемо приступити помоћу поља type. У претходном коду се током израчунавања услова петље while позове функција pg.event.wait() која враћа први догађај из реда или чека да се неки догађај догоди ако је ред празан. Када у реду постоји неки необрађени догађај, функција pg.event.wait() га враћа. Након тога се одмах испитује његов тип и пореди са pg.QUIT. Ако догађај није тог типа (корисник није искључио прозор), тада је услов петље испуњен, извршава се тело петље у коме се налази наредба pass која нема никаквог ефекта и поново се враћа на проверу услова у којој се обрађује наредни догађај (ако је потребно, поново се чека на њега).

Монолитна имплементација

У ситуацији када су нам релевантни и други догађаји, структура петље је мало другачија. Посматрајмо наредни PyGame програм у коме се само исписују информације о догађајима.

14
 
1
import pygame as pg
2
3
pg.init()
4
pg.display.set_mode((200, 200))
5
6
kraj = False
7
while not(kraj):
8
    dogadjaj = pg.event.wait()       # čekamo na naredni događaj
9
    print(dogadjaj)                  # ispisujemo podatke o njemu
10
    if dogadjaj.type == pg.QUIT:     # proveravamo da li je to događaj isključivanja programa
11
        kraj = True
12
13
pg.quit()
14

(informacije_o_dogadjajima)

Покрени претходни програм на свом рачунару (на пример, из окружења IDLE) и видећеш како се током померања миша генерише велики број догађаја који описују мале помераје. Пробај да кликнеш мишем и приметићеш како се генеришу два догађаја - догађај притиска дугмета и догађај отпуштања дугмета. Слично је и са притиском на тастер тастатуре.

Структура претходне петље је таква да се у њој прихвата наредни догађај помоћу pg.event.wait. А онда се гранањем анализира тип догађаја који смо прихватили. Ако нам је релевантан само догађај искључивања прозора, тада се у гранању проверава само тип догађаја pg.QUIT. Када се тај догађај детектује, треба да прекинемо петљу (а тиме практично и програм). Један начин за то је коришћење логичке променљиве kraj, као у претходном програму. Уместо логичке променљиве, поново смо могли употребити наредбу break.

6
 
1
# petlja obrade događaja - čekamo dok korisnik ne isključi prozor
2
while True:
3
    dogadjaj = pg.event.wait()        # čekamo na naredni događaj
4
    if dogadjaj.type == pg.QUIT:      # proveravamo da li je to isklučivanje prozora
5
        break                         # ako jeste, prekidamo petlju
6

(pygame_skelet_obrada_dogadjaja_event_wait_break)

Обрада разних типова догађаја

Обе претходне варијанте остварују исти ефекат као и она коју смо користили у свим досадашњим програмима, међутим, иако су дуже и компликованије, имају предност да се лако уопштавају. Наиме, гранањем на основу типа можемо анализирати и друге типове догађаја и за сваки од типова можемо направити одговарајућу реакцију. Сваком типу догађаја тада можемо придружити једну грану у гранању који се реализује помоћу наредбе if-elif-else. Главна петља програма у којој се догађаји обрађују се тада може имплементирати на следећи начин (уместо ??? потребно је навести конкретне типове догађаја, попут KEYDOWN који се генерише притиском на неки тастер или MOUSEMOTION који се генерише када се помери миш, о којима ће више речи бити у наставку).

14
 
1
kraj = False
2
while not(kraj):
3
    ... # na ovom mestu se obično vrši crtanje
4
    pg.display.update()
5
6
    dogadjaj = pg.event.wait()            # čekamo na naredni događaj
7
8
    if dogadjaj.type = pg.QUIT:           # događaj isključivanja programa
9
        kraj = True
10
    elif dogadjaj.type = pg.???:          # događaj tipa ???
11
        ...
12
    elif dogadjaj.type = pg.???:          # događaj tipa ???
13
        ...
14

(obrada_dogadjaja_event_wait)

Цртање само када је то потребно

Реакција на догађај обично подразумева да се на екрану нешто промени, тј. да се сцена на екрану поново исцрта. Стога се кôд за цртање (или позив функције у којој се врши цртање) често смешта у тело главне петље (пре или после обраде догађаја), слично као што је случај и код анимација које не морају да реагују на догађаје, али такође веома често мењају слику на екрану. Приметимо да се у сваком кораку извршавања петље поново врши исцртавање, тј. црта се све изнова када год наступи било који догађај. То може имати смисла у анимацијама и играма у којима се сцена на екрану аутоматски често мења (на пример, противници се у играма често сами крећу по екрану, тј. игре садрже у себи и компоненту анимације кретања противника). Међутим, постоје и ситуације у којима се између два догађаја слика не мења и тада је непотребно стално вршити цртање (тиме само беспотребно оптерећујемо рачунар који исту слику црта више пута). Једноставан начин да се то оптимизује је да се уведе логичка променљива која означава да ли је потребно поново цртати.

20
 
1
treba_crtati = True
2
kraj = False
3
while not(kraj):
4
    if treba_crtati:
5
        # na ovom mestu se vrsi crtanje i osvežavanje prozora
6
        ...
7
        pg.display.update()
8
        treba_crtati = False   # ne treba crtati dok ne nastupi događaj koji menja nešto na ekranu
9
10
    # čekamo dok se ne dogodi naredni događaj
11
    dogadjaj = pg.event.wait()
12
    # obrađujemo dogadjaj koji se dogodio
13
    if dogadjaj.type = pg.QUIT:      # isključivanjem prozora prekida se petlja
14
        kraj = True
15
    elif dogadjaj.type = pg.???:     # događaj tipa ???
16
        ...
17
        treba_crtati = True          # nešto se promenilo, pa treba ponovo crtati
18
    elif dogadjaj.type = pg.???:     # događaj tipa ???
19
        ...
20

(treba_crtati)

На почетку променљива treba_crtati добија вредност True како би се у првом проласку кроз тело петље слика нацртала. Одмах након првог цртања та променљива добија вредност False. У наредним проласцима кроз петљу цртање се не врши, све док се не региструје неки догађај који проузрокује промену сцене, након чега је потребно поново је исцртати. У склопу обраде таквог догађаја променљива treba_crtati добија вредност True, и на почетку наредног проласка кроз петљу сцена се поново црта, након чега се променљива treba_crtati поново поставља на False и наредно цртање се одлаже док се поново не деси одговарајући догађај.

Имплементација са помоћним функцијама

Прегледности ради обраду догађаја можемо издвојити у посебну функцију (при чему догађај искључивања прозора, због неизбежности његове анализе може остати у телу главне петље). Наравно, ако се у тој функцији врши измена глобалних променљивих, потребно је набројати их помоћу кључне речи global. Ако обраду догађаја издвојимо у посебну функцију, онда она може да кроз своју повратну, логичку вредност сигнализира да ли је потребно изнова нацртати сцену.

35
 
1
import pygame as pg
2
3
pg.init()
4
...
5
6
# crtamo scenu
7
def crtaj():
8
    ...
9
10
# obrađujemo dodgađaj
11
def obradi_dogadjaj(dogadjaj):
12
    if dogadjaj.type = pg.???:    # događaj tipa ???
13
        ...
14
        return True     # treba ponovo iscrtati scenu
15
    elif dogadjaj.type = pg.???:  # događaj tipa ???
16
        ...
17
        return True     # treba ponovo iscrtati scenu
18
    return False        # ne treba ponovo iscrtati scenu
19
20
treba_crtati = True
21
kraj = False
22
while not(kraj):
23
    if treba_crtati:
24
        crtaj()                # crtamo scenu i osvežavamo prikaz prozora
25
        pg.display.update()
26
        treba_crtati = False   # ne treba crtati dok ne nastupi događaj koji menja nešto na ekranu
27
28
    # čekamo dok se ne dogodi naredni događaj
29
    dogadjaj = pg.event.wait()
30
    # obrađujemo dogadjaj koji se dogodio
31
    if dogadjaj.type = pg.QUIT:    # isključivanje prozora
32
        kraj = True
33
    else:                          # ostali događaji
34
        treba_crtati = obradi_dogadjaj(dogadjaj)
35

(treba_crtati_funkcija)

Коришћење библиотеке PyGameBg

Када се користи библиотека PyGameBg онда главна петља програма може бити скривена иза позива функције pygamebg.event_loop. У том случају програм обично има наредну структуру.

22
 
1
import pygame as pg, pygamebg
2
3
# otvaramo prozor
4
pygamebg.open_window(...)
5
6
# crtamo scenu
7
def crtaj():
8
    ...
9
10
# obrađujemo događaj
11
def obradi_dogadjaj(dogadjaj):
12
    if dogadjaj.type = pg.???:
13
        ...
14
        return True     # treba ponovo iscrtati scenu
15
    elif dogadjaj.type = pg.???:
16
        ...
17
        return True     # treba ponovo iscrtati scenu
18
    return False        # ne treba ponovo iscrtati scenu
19
20
# započinjemo petlju zasnovanu na događajima
21
pygamebg.event_loop(crtaj, obradi_dogadjaj)
22

(treba_crtati_funkcija_pygamebg)

Обрада догађаја у склопу петље засноване на фрејмовима

Обраду догађаја могуће је вршити и у склопу петље засноване на фрејмовима (једино што се тада она не врши одмах након догађаја, већ тек након преласка на наредни фрејм). Ово користимо када желимо да обрађујемо догађаје у склопу програма који врше анимације.

Монолитна имплементација

29
 
1
import pygame as pg
2
3
pg.init()
4
...
5
6
sat = pg.Clock()
7
kraj = False
8
while not(kraj):
9
    # obrađujemo sve događaje koji su nastupili između dva frejma
10
    for dogadjaj in pg.event.get():
11
        if dogadjaj.type == pg.QUIT:
12
            kraj = True
13
        elif dogadaj.type == ???:
14
            ...
15
        elif dogadjaj.type == ???:
16
            ...
17
18
    # ограничавамо извршавање тела петље на највише 25 пута у секунди
19
    sat.tick(25)
20
21
    # na ovom mestu vršimo promenu podataka koji opisuju
22
    # stanje objekata na slici
23
    ...
24
    # na ovom mestu ponovo vršimo crtanje
25
    ...
26
    pg.display.update()   # osvežavamo prikaz sadržaja prozora
27
28
pg.quit()
29

(frameloop_dogadjaji)

Имплементација са помоћним функцијама

Ако се користе помоћне функције, то онда изгледа овако.

42
 
1
import pygame as pg
2
3
pg.init()
4
...
5
6
# crtamo scenu
7
def crtaj():
8
    ...
9
10
# menjamo podatke koji opisuju stanje objekata na sceni
11
def novi_frejm():
12
    global ... # promenljive koje se mogu menjati tokom animacije
13
    ...
14
    crtaj()
15
16
# obradjujemo dogadjaj
17
def obradi_dogadjaj(dogadjaj):
18
    global ...   # promenljive koje se mogu promeniti na osnovu nekog dogadjaja
19
    if dogadjaj.type = pg.???:
20
        ...
21
    elif dogadjaj.type = pg.???:
22
        ...
23
24
sat = pg.Clock()
25
kraj = False
26
while not(kraj):
27
    # obrađujemo sve događaje nastale između dva frejma
28
    for dogadjaj in pg.event.get():
29
        if dogadjaj.type == pg.QUIT:
30
            kraj = True
31
        else:
32
            obradi_dogadjaj(dogadjaj)
33
34
    # prelazimo na naredni frejm
35
    novi_frejm()
36
    pg.display.update()   # osvežavamo prikaz sadržaja prozora
37
38
    # ограничавамо извршавање тела петље на дати број пута у секунди
39
    sat.tick(???)
40
41
pg.quit()
42

(frameloop_dogadjaji_funkcije)

Коришћење библиотеке PyGameBg

Када се користи библиотека PyGameBg онда главна петља програма може бити скривена иза позива функције pygamebg.frame_loop. У том случају програм обично има наредну структуру.

27
 
1
import pygame as pg, pygamebg
2
pygamebg.open_window(sirina, visina, "...")
3
4
# globalno stanje programa
5
...
6
7
# crtamo scenu
8
def crtaj():
9
    ...
10
11
# menjamo podatke koji opisuju stanje objekata na sceni
12
def novi_frejm():
13
    global ... # promenljive koje se mogu menjati tokom animacije
14
    ...
15
    crtaj()
16
17
# obradjujemo dogadjaj
18
def obradi_dogadjaj(dogadjaj):
19
    global ...   # promenljive koje se mogu promeniti na osnovu nekog dogadjaja
20
    if dogadjaj.type = pg.???:
21
        ...
22
    elif dogadjaj.type = pg.???:
23
        ...
24
25
# započinjemo animaciju sa mogućnošću reagovanja na događaje
26
pygamebg.frame_loop(???, novi_frejm, obradi_dogadjaj)
27

(frameloop_dogadjaji_pygamebg)