Prijavi problem


Obeleži sve katergorije 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.

Programiranje grafike pomoću Pygame, priručnik za sedmi razred

Анимације

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

На пример, од наредних осам сличица лика у различитим положајима:

../_images/liktrci1.png ../_images/liktrci2.png ../_images/liktrci3.png ../_images/liktrci4.png ../_images/liktrci5.png ../_images/liktrci6.png ../_images/liktrci7.png ../_images/liktrci8.png

настаје следећа анимација трчања:

../_images/liktrci.gif

Анимације подразумевају брзу промену слике на екрану (на пример, 20 пута у секунди), обично у правилним временским интервалима (на пример, на сваких 50 милисекунди). Свака тако кратко приказана слика назива се оквир или фрејм анимације (енгл. frame). У овом делу приручника видећемо како можемо направити програме у којима се приказују неке анимације.

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

Начини да се анимација оствари

Постоје два суштински различита начина да се анимација оствари. У једном је главна петља заснована на фрејмовима (енгл. frame-based loop) и њено тело се извршава одређени фиксиран број пута у секунди (обично од 20 до 50). У другом је главна петља заснована на догађајима (енгл. event-based loop) и њено тело се изврши по једном за сваки догађај који наступи. У овом поглављу ћемо се бавити само анимацијама заснованим на фрејмовима, док ћемо анимације засноване на догађајима описати у поглављу које се бави догађајима.

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

Чекање одређен број милисекунди између свака два фрејма

Најједноставнији начин да се анимација оствари је следећи.

У склопу главне петље прво вршимо цртање. Након тога обрађујемо све догађаје који су наступили током цртања и чекања, тј. који су наступили од претходне обраде догађаја (пре свега проверавамо да ли је корисник искључио прозор тј. да ли је наступио догађај pg.QUIT). Ако јесте, прекидамо петљу (било постављањем променљиве kraj на True, било наредбом break). Затим чекамо одређено време (на пример, 50 или 100 милисекунди). Чекање можемо остварити позивом функције pg.time.wait(???), чији је параметар дати број милисекунди (1000 милисекунди је једна секунда).

Док програм чека, сви догађаји који се у међувремену десе се смештају у једну листу (такозвани ред догађаја). Листа свих догађаја који су регистровани у реду од тренутка претходног читања тог реда се може добити функцијом pg.event.get(). Током обраде догађаја пролазимо кроз ту листу анализирајући један по један догађај (подсетимо се, пролазак кроз елементе листе остварује се наредбом облика for element in lista: ...). Дакле, у овом сценарију имамо петљу у петљи. У унутрашњој петљи for можемо вршити анализу свих догађаја и реаговати на њих о чему ће више бити речи у поглављу о догађајима.

Мана управо описаног приступа је то што је програм потпуно блокиран током чекања (не реагује на догађаје) и ако је тај период велики (ако се чека неколико стотина милисекунди), корисник може то и осетити.

Коришћење сата

Претходно решење не узима у обзир ни трајање исцртавања ни обраде догађаја (пауза је увек иста, без обзира на то колико су исцртавање и обрада догађаја трајали). То се може поправити ако се уместо pg.time.wait употреби сат који креирамо помоћу pg.Clock.

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

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

Коришћење помоћних функција

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

Посебна функција за цртање у програму без анимације

За почетак прикажимо како се цртање може издвојити у посебну функцију (и то у прво у програму без анимација, а затим у програму са анимацијама). Кренимо од програма који црта три круга у разним бојама.

Цртање кругова је део главног програма. Исти ефекат можемо постићи ако дефинишемо функцију crtaj коју ћемо позвати из главног програма.

Насумично одређивање боје позадине током анимације

Наредни програм ће приказивати једноставну анимацију у којој ћемо четири пута у секунди на насумичан начин одређивати боју позадине. Без помоћних функција тај програм се може написати на следећи начин.

Када кôд који врши цртање издвојимо у посебну функцију, можемо вам помоћи тако што ћемо сакрити главну петљу и пустити вам да се само фокусирате само на кôд за цртање.

Промена боје позадине у круг

У многим анимацијама оно што се црта зависи од података који се мењају током анимације. Променимо претходни програм тако да се боје не мењају насумично, него да се редом смењују црвена, зелена и плава. Кренимо поново од верзије без функција.

Најједноставнији начин да се задатак реши је да боје држимо у листи и да уз листу одржавамо и позицију текуће боје (њен индекс у листи). Након коришћења боје са те позиције, позицију ћемо увећавати за 1, при том проверавајући да се након последње боје поново вратимо на прву (да индекс постане 0). Најједноставнији начин да се то уради је да се након увећавања индекса за 1 израчуна његов остатак при дељењу са дужином листе (укупним бројем боја).

Програм поново можемо мало поједноставити издвајањем цртања у помоћну функцију и сакривањем главне петље.

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

UnboundLocalError: local variable 'broj_boje' referenced before assignment

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

Функција crtaj је у претходном програму извршавала два задатка. Једно је цртање сцене на основу тренутних вредности глобалних променљивих, а друго је промена вредности променљивих чиме се са текућег прелази на наредни фрејм. И наредни програми са анимацијама ће имати те две функционалности, па програм постаје лепши ако их раздвојимо у две функције.

Сви програми са анимацијама које ћемо у наставку писати биће организовани на овај начин и твој задатак ће бити да дефинишеш функције crtaj и novi_frejm. На пример, програм који насумично мења боју позадине можемо написати на следећи начин.

Кретање лоптице

Прикажимо још један пример анимације организоване на начин који смо управо описали. Написаћемо програм који анимира лоптицу која се креће са леве ка десној ивици екрана.

У главном програму корисимо сат којим одређујемо број фрејмова у секунди (приметимо да је главна петља заправо идентична као у претходном примеру). У петљи се позивају функција crtaj и функција novi_frejm и то обе по једном за сваки фрејм.

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

Функција crtaj се сада реализује веома једноставно. У њој бојимо позадину екрана у бело и затим исцртавамо лоптицу коришћењем вредности њеног положаја x.

При преласку на сваки нови фрејм потребно је да лоптицу померимо мало (на пример, за 1 пиксел) удесно. Дакле, у функцији novi_frejm потребно је само да увећамо вредност променљиве x за 1. Пошто се мења вредност променљиве x која је глобална, у функцији novi_frejm морамо променљиву x означимо помоћу кључне речи global.

Општи облик програма са анимацијама

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

1. Потребно је да дефинишеш глобалне променљиве којима се представљају подаци о ликовима и објектима на сцени (ти ће се подаци мењати током анимације).

2. Потребно је да дефинишеш функцију crtaj која коришћењем тих података црта сцену. У тој функцији нећемо вршити никакву промену података.

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