Час 11 - Анимације¶
Вероватно већ знаш да цртани филм настаје тако што се на екрану брзо смењују сличице, при чему је свака следећа слика веома слична претходној (ликови на слици су само мало померени у односу на претходни положај).
На пример, од наредних осам сличица лика у различитим положајима:
настаје следећа анимација трчања:
Анимације подразумевају брзу промену слике на екрану (на пример, 20 пута у секунди), обично у правилним временским интервалима (на пример, на сваких 50 милисекунди). Свака тако кратко приказана слика назива се оквир или фрејм анимације (енгл. frame). У овом делу приручника видећемо како можемо направити програме у којима се приказују неке анимације.
У програмима које смо до сада сретали слика се није мењала током
извршавања и цртање смо вршили само једном, пре главне петље програма
у којој смо чекали да корисник искључи прозор (у програмима заснованим
на библиотеци PyGameBg, та петља се остварује позивом функције
pygamebg.wait_loop
).
У програмима са анимацијом цртање ћемо вршити обично унутар тела те
главне петље или, још боље, у засебној функцији коју ћемо на том месту
позивати (у програмима заснованим на библиотеци PyGameBg, таква петља
се остварује позивом функције pygamebg.frame_loop
).
Програмирање анимација на сајту „Петља”¶
Све анимације које ћемо у наставку приказати разликоваће се само по томе који подаци одређују оно што се на слици налази, коду који извршава цртање и коду који мења податке када се прелази на наредни фрејм. Да бисмо ти олакшали сналажење са кодом који већ постаје дугачак и компликован, цртање и прелазак са тренутног на наредни фрејм ћемо издвојити у две помоћне функције које ти треба да напишеш, док ћемо главну петљу програма која те две функције позива ми писати уместо тебе (и она ће бити у „сивом делу кода”). Осим те две функције твој задатак ће бити и да дефинишеш променљиве које описују оно што се налази на екрану током анимације. То ће бити обично променљиве које описују положај (координате) објеката тј. ликова који се током анимације померају, њихову брзину, али и неки други подаци који се мењају током анимације.
Постоји неколико начина да се анимације реализују и у зависности од тога „сиви кôд” може бити реализован на различите начине. Ако програмираш анимације само на сајту „Петља”, тада не мораш уопште да читаш и анализираш сиви кôд (он ће увек бити унапред припремљен за тебе). Ако желиш да правиш програме са анимацијама и ван овог сајта, тада ипак треба да разумеш како сиви кôд функционише. За то ти онда препоручујемо да прочиташ овај текст.
Прикажимо сада кроз неколико примера технику коју ће вам олакшати прављење анимација у примерима који следе. Сви ће бити засновани на библиотеци PyGameBg, тако да ће и „сиви кôд” бити прилично једноставан.
Посебна функција за цртање у програму без анимације¶
За почетак прикажимо како се цртање може издвојити у посебну функцију (и то у прво у програму без анимација, а затим у програму са анимацијама). Кренимо од програма који смо раније већ срели, који црта три круга у разним бојама.
Цртање кругова је део главног програма. Исти ефекат можемо постићи ако
дефинишемо функцију crtaj
коју ћемо позвати из главног програма.
Насумично одређивање боје позадине током анимације¶
Наредни програм ће приказивати једноставну анимацију у којој ћемо четири пута у секунди на насумичан начин одређивати боју позадине. Одређивање насумичне боје вршићемо помоћу посебне функције nasumicna_boja. Дефинисаћемо функцију crtaj у којој ће се насумично одређивати боја и затим ће се позадина прозора бојити том бојом. Пошто ћемо користити библиотеку PyGameBg, анимацију ћемо на крају програма покретати позивом pygamebg.frame_loop(4, crtaj), чиме ћемо постићи да се функција crtaj аутоматски изнова позива 4 пута у секунди.
Подијум за игру¶
По истом принципу можемо прилагодити програм који је исцртавао подијум
за игру, тако да подијум стварно „оживи”. Пронађи кôд који исцртава
подијум и на основу тога допуни функцију crtaj
.
Промена боје позадине у круг¶
У многим анимацијама оно што се црта зависи од података који се мењају током анимације. Променимо програм у ком се мења боја позадине екрана тако да се боје не мењају насумично, него да се редом смењују црвена, зелена и плава.
Најједноставнији начин да се задатак реши је да боје држимо у листи (или торци) и да уз листу одржавамо и позицију текуће боје (њен индекс у листи). Након коришћења боје са те позиције, позицију ћемо увећавати за 1, при том проверавајући да се након последње боје поново вратимо на прву (да индекс постане 0). Најједноставнији начин да се то уради је да се након увећавања индекса за 1 израчуна његов остатак при дељењу са дужином листе (укупним бројем боја). Програм поново можемо реализовати издвајањем цртања у помоћну функцију, која ће се она током анимације аутоматски позивати два пута у секунди.
Приметимо да кôд који се налази у функцији користи променљиве boje
и broj_boje
које су дефинисане ван функције. Такве променљиве се
називају глобалне променљиве и њихова се вредност може без икаквих
проблема очитати из функције. Међутим, промена вредности глобалних у
функцији је компликованија. Наиме, ако желимо да глобалној променљивој
променимо вредност у функцији, на почетку те функције морамо нагласити
да је та променљива глобална (помоћу кључне речи global
иза које
следи листа глобалних променљивих, раздвојених запетама, којима ћемо у
тој функцији мењати вредност). Ако у функцији не бисмо навели реч
global
, добили бисмо поруку о грешци.
UnboundLocalError: local variable 'broj_boje' referenced before assignment
Нагласимо да глобалне променљиве нису најбољи начин за организовање сложенијих програма и постоје бољи начини да се подаци организују, међутим, у кратким програмима какве ћемо ми писати глобалне променљиве представљају најједноставније решење и стога ћемо их у наставку користити.
Функција crtaj
је у претходном програму извршавала два
задатка. Једно је цртање сцене на основу тренутних вредности глобалних
променљивих, а друго је промена вредности променљивих чиме се са
текућег прелази на наредни фрејм. И наредни програми са анимацијама ће
имати те две функционалности, па програм постаје лепши ако их
раздвојимо у две функције (иако овај корак можда делује сувишан код
овако кратких програма, код дужих програма ће бити веома корисно да
поступак цртања одвојимо од поступка измене стања програма).
Анализирајмо још једном претходни пример.
Стање програма је једнозначно одређено глобалном целобројном променљивом
broj_boje
која одређује редни број боје којом се прозор боји. Почетна вредност ове променљиве је нула, што значи да боје крећу да се приказују од почетка листе. Боје су смештене у глобалној листиboje
која се не мења током извршавања програма.Функција
crtaj
боји прозор бојом одређеном вредношћу променљивеbroj_boje
.Функција
novi_frejm
ажурира вредност стања тј. променљивеbroj_boje
тако што јој увећава вредност и враћа је на нулу када вредност достигне укупан број боја. Пошто се мења вредност променљивеbroj_boje
која је глобална, у функцијиnovi_frejm
морамо да променљивуbroj_boje
означимо помоћу кључне речиglobal
.
Сви програми са анимацијама које ћемо у наставку писати биће
организовани на овај начин и твој задатак ће бити да дефинишеш
променљиве које одређују стање програма и функције crtaj
и
novi_frejm
. На пример, програм који насумично мења боју позадине
можемо написати на следећи начин.
Анализирајмо још једном претходни пример.
Стање програма је једнозначно одређено глобалном целобројном променљивом
boja
која одређује тренутну боју позадине екрана. Почетна вредност ове променљиве се одређује насумично.Функција
crtaj
боји прозор бојом одређеном вредношћу променљивеboja
.Функција
novi_frejm
ажурира вредност стања тј. променљивеboja
тако што јој насумично додељује нову вредност. Пошто се мења вредност променљивеboja
која је глобална, у функцијиnovi_frejm
морамо да променљивуboja
означимо помоћу кључне речиglobal
. Ова се функција позива аутоматски у правилним временским итервалим и након ажурирања променљивих она позива функцијуcrtaj
да би се прозор поново исцртао.
Кретање лоптице¶
Прикажимо још један пример анимације организоване на начин који смо управо описали. Написаћемо програм који анимира лоптицу која се креће са леве ка десној ивици екрана.
Потребно је да дефинишемо променљиве које ће описивати стање објеката који се анимирају. У нашем примеру то је једна црвена лоптица и пошто се она креће хоризонтално, по средини екрана довољно је да памтимо само њену координату x (то може, на пример, бити координата њеног центра, а могла би бити и, на пример, координата горњег левог темена квадрата описаног око ње). Пошто лоптица своје кретање започиње на левом крају екрана, променљиву
x
ћемо иницијализовати на нулу.Функција
crtaj
се сада реализује веома једноставно. У њој бојимо позадину екрана у бело и затим исцртавамо лоптицу коришћењем вредности њеног положајаx
.При преласку на сваки нови фрејм потребно је да лоптицу померимо мало (на пример, за 1 пиксел) удесно. Дакле, у функцији
novi_frejm
потребно је само да увећамо вредност променљивеx
за 1. Пошто се мења вредност променљивеx
која је глобална, у функцијиnovi_frejm
морамо променљивуx
означимо помоћу кључне речиglobal
. Након померања лоптице позивамо функцијуcrtaj
.
Општи облик програма са анимацијама¶
Видели смо неколико примера и можеш приметити да смо у свима њима анимације остваривали по истом принципу, који ћеш ти примењивати и у наредним задацима.
1. Потребно је да дефинишеш глобалне променљиве којима се представљају подаци о ликовима и објектима на сцени (ти ће се подаци мењати током анимације).
2. Потребно је да дефинишеш функцију crtaj
која коришћењем тих
података црта сцену. У тој функцији нећемо вршити никакву промену
података.
3. Потребно је да дефинишеш функцију novi_frejm
која ажурира
податке о ликовима и објектима на сцени (при чему све променљиве
којима се у тој функцији мења вредност морају на њеном почетку бити
експлицитно означене као глобалне коришћењем кључне речи global
).
Након ажурирања променљивих, потребно је да позовеш функцију
crtaj
, да би се промењена сцена исцртала.
Неке једоставне анимације¶
Сада знаш општи принцип по ком ћемо програмирати анимације. Провежбај ово кроз наредних неколико задатака.
Насумично цртање кругова¶
Напиши програм који четири пута у секунди мења положај и боју круга који се приказује на прозору. Положај и боју одређивати насумично, тако да се круг у сваком тренутку налази унутар прозора.
Приступимо решавању овог задатка на начин који смо описали. Размисли прво који подаци описују стање наше анимације.
- Координата x центра лоптице.
- Тачно.
- Брзина лоптице.
- Покушај поново.
- Координата y центра лоптице.
- Тачно.
- Боја лоптице.
- Тачно.
- Број лоптица у прозору.
- Покушај поново.
Q-1: Које променљиве одређују стање анимације?
Дакле, тренутно стање програма одређено је координатама центра
x
иy
, полупречникомr
и бојом кругаboja
и њих ћемо представити глобалним променљивама (при чему се полупречник неће мењати).
Размисли сада шта је задатак функције crtaj
.
- Да насумично одреди положај лоптице тако што ће одредити вредности променљивих ``x`` и ``y``.
- Покушај поново.
- Да пре цртања обоји позадину у бело, да би се обрисао претходни цртеж.
- Тачно.
- Да нацрта круг са центром у тачки одређеној координатама ``x`` и ``y``.
- Тачно.
- Да обоји позадину прозора у боју ``boja``.
- Покушај поново.
- Да помери лоптицу један пиксел удесно увећавајући вредност променљиве ``x``.
- Покушај поново.
Q-2: Шта функција crtaj
треба да уради?
Дакле, функција
crtaj
ће бојити позадину у бело (како би се обрисао претходни круг) и цртаће круг на основу тренутних вредности глобалних променљивих.
Размисли сада шта је задатак функције novi_frejm
.
- Да помери лоптицу један пиксел удесно увећавајући вредност променљиве ``x``.
- Покушај поново.
- Да увећа полупречник лоптице ``r`` за 1.
- Покушај поново.
- Да нацрта круг са центром у тачки одређеној координатама ``x`` и ``y``.
- Покушај поново.
- Да на насумичан начин одреди нову вредност променљиве ``boja``.
- Tачно.
- Да насумично одреди положај лоптице тако што ће одредити вредности променљивих ``x`` и ``y``.
- Тачно.
Q-3: Шта функција novi_frejm
треба да уради?
Дакле, функција
novi_frejm
ће насумично одредити боју новог круга и одредиће x и y координате центра, тако да круг не испадне ван екрана. То ће важити ако x координата буде у интервалу \([r, sirina-r]\), а y координата буде у интервалу \([r, visina-r]\).
Покушај да самостално напишеш цео програм. Ако не успеш, не брини, јер тек почињеш да учиш како се реализују апликације. Потражи помоћ и затим допуни започети програмски кôд.
Срце које куца¶
Напиши програм који приказује анимацију срца које куца. Срце можеш
приказати коришћењем слике srce.png
.
Једини податак који се мења од фрејма до фрејма је то да ли треба или не треба приказати срце. Можемо увести логичку променљиву
treba_crtati
.У функцији
crtaj
слику ћемо приказивати само ако променљиваtreba_crtati
има вредност тачно (тј.True
).У функцији
novi_frejm
мењаћемо вредност променљивеtreba_crtati
. Ако јој је тренутно вредностTrue
, тада треба да се промени наFalse
, а ако јој је тренутно вредностFalse
, тада треба да се промени наTrue
. Најлакши начин да се то уради је да текућу вредност негирамо помоћу оператораnot
(наравно, можемо употребити и гранање).
Покушај да на основу овога допуниш започети програмски кôд.
Смајлић и тужић¶
Микица често мења расположење. Час је срећна, час је тужна. Напиши
програм који приказује слике смајлића и тужића које се наизменично
смењују. Можеш употребити слике smajlic.png
и tuzic.png
.
Овај задатак је поново веома сличан претходном.
Логичка променљива
smajlic
ће одређивати стање програма.Ако је у функцији
crtaj
вредност те променљивеTrue
, приказаћемо слику смајлића, а ако је вредностFalse
, приказаћемо слику тужића.У функцији
novi_frejm
негираћемо вредност променљивеsmajlic
.
Покушај да на основу овога потпуно самостално напишеш програм. Ако не успеш, онда потражи помоћ и допуни започети програмски кôд.
Домаћи задатак - сијалице¶
Напиши програм који приказује дисплеј на коме је поређано 10 сијалица, тако да се наизменично укључује једна по једна.
Стање програма ће у потпуности бити одређено вредношћу променљиве редним бројем сијалице која је укључена.
У функцији
crtaj
исцртаваћемо само укључену сијалицу у облику белог круга. Пречник сваке сијалице ћемо одредити тако што ћемо ширину екрана поделити редним бројем сијалице. Координата x центра те сијалице биће одређена редним бројем укључене сијалице (центар сијалице 0 налази се на растојањуr
од леве ивице екрана, центар сијалице 1 на растојањуr + 2r
, центар сијалице 2 на растојањуr+4r
и тако даље).У функцији
novi_frejm
увећаваћемо редни број укључене сијалице, при чему ћемо га враћати на нулу када достигне укупан број сијалица.
Покушај да на основу претходне дикусије потпуно самостално решиш задатак. Ако не успеш, онда потражи помоћ, па допуни започети кôд.
Домаћи задатак - цртани филм¶
Напиши програм који приказује цртани филм тако што наизменично
приказује пет слика на екрану на којима је приказан дечак који се
шета у различитим положајима. Слике носе називе setanje1.png
до
setanje5.png
.
Овај задатак личи на задатак у ком смо током анимације циклично
мењали боје позадине, једино што ћемо уместо листе која садржи називе
боја чувати листу која садржи слике које чине цртани филм. Слике је
пожељно учитати на почетку програма, пре почетка анимације (подсетимо
се, слику можемо учитати помоћу функције
pg.image.load
).
Стање анимације биће одређено искључиво редним бројем слике која се тренутно приказује. Променљива
slika
представљаће позицију слике у листи која се приказује у текућем фрејму.Функција
crtaj
има задатак само да обрише екран (бојећи позадину у бело) и да затим прикаже слику из листеslike
која се налази на позицији одређеној променљивомslika
(подсетимо се, слику приказујемо помоћу функцијеprozor.blit
).Функција
novi_frejm
ће имати задатак да увећа вредност променљивеslika
, тако да се вредност врати на нулу када превазиђе последњу вредност у листи.
На основу претходне дискусије допуни наредни програм.
Пројекат - дигитални сат¶
Напиши програм који приказује дигитални сат.
Приказ тренутног времена потребно је извршити сваке секунде. Задатак ћемо решити помоћу анимације која је подешена тако да се сваке прелази на нови фрејм и исцртава садржај прозора.
Ова анимација је необична јер нема потребе за представљањем стања
прозора. Дакле, нећемо имати нити глобалне променљиве нити функцију за
прелазак на нови фрејм. У функцији crtaj
очитаваћемо тренутно
време и приказивати га на средини екрана.
Језик Python нам пружа подршку да очитамо тренутно време системског
сата рачунара на ком се програм извршава. Функција datetime.now()
из модула datetime
(који увозимо на почетку програма помоћу
import datetime
) враћа тренутно време у облику структуре чије поље
hour
садржи број сати (од 0 до 23), поље minute
садржи број
минута (од 0 до 59) и second
садржи број секунди (од 0 до 59). На
основу тога поставићемо вредности променљивих sati
, minuti
,
sekunde
.
На основу броја сати, минута и секунди градимо текст који ћемо
приказати. Желимо да и сати и минути и секунде увек имају тачно две
цифре (ако су једноцифрени, желимо да се допуне водећим нулама). У
језику Python ниску str
можемо допунити водећим нулама до дужине
n
позивом str.rjust(n, "0")
. Подсетимо се да ниске
надовезујемо применом оператора +
.
Када смо направили ниску која садржи тренутно време приказујемо је на средини прозора. То радимо на потпуно исти начин као у примерима које смо приказали у поглављу о цртању (можемо да дефинишемо посебну функцију која исписује дати текст тако да му центар буде у датој тачки).
Допуни наредни програм на основу претходне дискусије.