$$ \newcommand{\floor}[1]{\left\lfloor{#1}\right\rfloor} \newcommand{\ceil}[1]{\left\lceil{#1}\right\rceil} \renewcommand{\mod}{\,\mathrm{mod}\,} \renewcommand{\div}{\,\mathrm{div}\,} \newcommand{\metar}{\,\mathrm{m}} \newcommand{\cm}{\,\mathrm{cm}} \newcommand{\dm}{\,\mathrm{dm}} \newcommand{\litar}{\,\mathrm{l}} \newcommand{\km}{\,\mathrm{km}} \newcommand{\s}{\,\mathrm{s}} \newcommand{\h}{\,\mathrm{h}} \newcommand{\minut}{\,\mathrm{min}} \newcommand{\kmh}{\,\mathrm{\frac{km}{h}}} \newcommand{\ms}{\,\mathrm{\frac{m}{s}}} \newcommand{\mss}{\,\mathrm{\frac{m}{s^2}}} \newcommand{\mmin}{\,\mathrm{\frac{m}{min}}} \newcommand{\smin}{\,\mathrm{\frac{s}{min}}} $$

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.

Кретање цртежа

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

Пре него што кренемо на примере, једна општа напомена, која се односи на све будуће програме у овом приручнику.

Напомена: Када приказујемо покретне слике, може доћи до треперења слика на екрану. До тога долази зато што освежавање екрана није синхронизовано са исцртавањем, па се дешава да се екран освежава док је слика полу-нацртана. Да бисмо избегли овај непријатан ефекат, потребно је међу својствима формулара наћи логичко својство DoubleBuffered и задати му вредност true.

../_images/anim_DoubleBuffered.png

Енглеска реч Buffer (бафер) у овом контексту означава простор у меморији рачунара који је резервисан за неку посебну намену. Додељивањем вредности true својству DoubleBuffered изабрали смо да користимо два бафера за смештање слике. У једном баферу се врши цртање, а други се користи за приказивање слике, а након завршеног исцртавања бафери мењају улоге. На тај начин програм неће бити „ухваћен у сред цртања”, што ће смањити поменути непожељан ефекат треперења.

Ауто

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

../_images/anim_car.png

Променљиве које описују сцену су:

  • Реалне променљиве AutoX и AutoY, које задају положај аута (тачније, горњег левог угла слике аута).

  • Реална променљива AutoVX, која представља брзину аута изражену у пикселима у секунди. Ова променљива ће при окретању аута узимати вредност супротну претходној, јо јест њена вредност ће мењати знак.

  • Целобројна променљива AutoSmer, која нам гвоори на коју страну се ауто креће. Ова променљива ће узимати вредности 0 и 1, а користиће се као индекс у низу од две слике аута (једна слика је ауто окренут на десно, а друга на лево).

Мењање вредности ових променљивих утиче на приказивање текуће или будућих слика. Осим њих, међу променљиве - чланице класе ћемо додати и реалну променљиву DT, коју нећемо мењати након постављања почетне вредности. Променљива DT ће представљати време између два фрејма изражено у секундама.

AutoX = 0;
AutoY = ClientSize.Height - slika[0].Height;
DT = timer1.Interval * 0.001f; // vreme jednog frejma u sekundama
AutoVX = 200;                  // brzina auta u pikselima u sekundi

Поменимо и један технички детаљ. Дата нам је само слика аута који се креће на десно. Другу слику (ауто окренут на лево) можемо да направимо помоћу неког од програма за обраду слике и да је додамо у ресурсе. То, међутим, није неопходно, јер слику можемо да обрнемо и у нашем C# програму. Користимо исти ресурс Properties.Resources.auto (слика аута који је окренут на десно) да слици дамо почетну вредност, а затим на слику применимо функцију RotateFlip, да бисмо добили слику другачије окренутог аута:

Image autoNalevo = (Image)Properties.Resources.auto;
autoNalevo.RotateFlip(RotateFlipType.RotateNoneFlipX);

Комплетан програм изгледа овако:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace AutoLevoDesno
{
    public partial class Form1 : Form
    {
        Image[] slika;
        float AutoX, AutoY, AutoVX, DT;
        int AutoSmer = 0; // koristi se kao indeks u nizu slika

        public Form1()
        {
            InitializeComponent();
            ClientSize = new Size(400, 300);
            Text = "Auto";
            BackColor = Color.SkyBlue;

            Image autoNalevo = (Image)Properties.Resources.auto;
            autoNalevo.RotateFlip(RotateFlipType.RotateNoneFlipX);
            slika = new Image[] { Properties.Resources.auto, autoNalevo };

            AutoX = 0;
            AutoY = ClientSize.Height - slika[0].Height;
            DT = timer1.Interval * 0.001f; // vreme jednog frejma u sekundama
            AutoVX = 200;                  // brzina auta u pikselima u sekundi
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            AutoX += AutoVX * DT;
            if (AutoX > ClientSize.Width || AutoX < -slika[0].Width)
            {
                AutoSmer = 1 - AutoSmer;
                AutoVX = -AutoVX;
            }
            Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawImage(slika[AutoSmer], AutoX, AutoY);
        }

    }
}

Као и раније, имамо функцију timer1_Tick, која ажурира стање сцене за сваки фрејм. Оно што је ново у овом примеру је да се положај слике мења из фрејма у фрејм.

Слику приказујемо тако да се њен горњи леви угао појави у тачки (AutoX, AutoY). Да би се ауто кретао, у сваком фрејму на x координату слике додајемо вредност AutoVX * DT. Када је вредност AutoVX позитивна, ауто се креће на десно, а када је негативна, ауто се креће на лево.

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

if (AutoX > ClientSize.Width || AutoX < -slika[0].Width)
{
    AutoSmer = 1 - AutoSmer;
    AutoVX = -AutoVX;
}

Обратите пажњу и на то да пролазак целог аута кроз леву ивицу прозора проверавамо условом \(AutoX < -slika[0].Width\), док је за десну ивицу прозора услов \(AutoX > ClientSize.Width\). Следећа слика вам може помоћи да разумете зашто је то тако. На слици је горњи леви угао слике аута у оба положаја означен црвеним кружићем.

../_images/anim_car_turn.png

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

Билијар

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

Погледајте како овде изгледа функција timer1_Tick, а како Form1_Paint.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Bilijar
{
    public partial class Form1 : Form
    {
        float CX, CY;
        float R = 15, DX = 5, DY = 3;

        private void timer1_Tick(object sender, EventArgs e)
        {
            CX += DX;
            CY += DY;
            if (CX - R < 0 || CX + R > ClientSize.Width) // ako je kugla "probila" levu ili desnu ivicu prozora
                DX = -DX; // menjamo joj smer po x osi (ako je isla nadesno, ici ce nalevo i obrnuto)

            if (CY - R < 0 || CY + R > ClientSize.Height) // ako je kugla "probila" gornju ili donju ivicu prozora
                DY = -DY; // menjamo joj smer po y osi (ako je isla nadole, ici ce nagore i obrnuto)

            Invalidate();
        }

        public Form1()
        {
            InitializeComponent();
            ClientSize = new Size(800, 400);
            Text = "Bilijar";
            BackColor = Color.DarkGreen;
            CX = ClientSize.Width / 2;
            CY = ClientSize.Height / 2;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.FillEllipse(new SolidBrush(Color.White), CX - R, CY - R, 2 * R, 2 * R);
        }
    }
}

Услове за проверу да ли је билијарска кугла дотакла неку ивицу прозора вреди погледати пажљивије. Крајња десна тачка кугле има x координату једнаку \(cx+r\). Ако би та вредност постала једнака ширини прозора, то би значило да кугла додирује десну ивицу прозора, а ако је \(cx + r > ClientSize.Width\), значи да је је кугла бар делом већ прошла десну ивицу прозора. У том случају наредбом \(dx = -dx\) постижемо да се од следећег фрејма x координати кугле додаје супротна вредност у односу на досадашњу, односно да се убудуће кугла помера за по 3 пиксела на лево. То ће изгледати као да се кугла одбила од десне ивице прозора.

Приметимо још један детаљ: уместо \(cx + r > ClientSize.Width\) могли смо да користимо и \(cx + r >= ClientSize.Width\) и програм би радио скоро исто. Међутим, пошто се кугла не помера за по један пиксел, не би ваљало да смо користили услов \(cx + r == ClientSize.Width\), јер би тада могло да се догоди да кугла прескочи положај који проверавамо и прође кроз ивицу прозора.

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

Проверите да ли сте ово разумели тако што ћете одговорити на следећа питања.

Кретање цртежа - питања

         Q-26: Изрази (cx - r < 0), (cx + r > ClientSize.Width), (cy - r < 0), (cy + r > ClientSize.Height) значе да је кугла из претходног примера у потпуности прошла неку од ивица прозора. Поређајте ивице тако да редом одговарају овим условима.

лева ивица
десна ивица
горња ивица
доња ивица
        

    Q-27: На коју страну се помера слика додавањем негативне вредности на њену x координату?

  • на десно
  • Покушајте поново
  • на горе
  • Покушајте поново
  • на лево
  • Тачно
  • на доле
  • Покушајте поново

    Q-28: Нека су димензије дате слике sl_sirina и sl_visina, а њен горњи леви угао (x, y). Како проверавамо да ли је слика у потпуности прошла кроз горњу ивицу прозора и више се не види ни један њен део?

  • if (x + sl_sirina < 0)
  • Покушајте поново
  • if (y + sl_visina < 0)
  • Тачно
  • if (x < 0)
  • Покушајте поново
  • if (y < 0)
  • Покушајте поново
         Q-29: Нека је w ширина прозора, im_w ширина слике, а (x, y) горњи леви угао слике. Поређајте логичке услове тако да редом имају наведена значења. А: "Слика је изашла кроз леву ивицу прозора", Б: "Слика је почела да излази кроз леву ивицу прозора", В: "Слика је изашла кроз десну ивицу прозора", Г: "Слика је почела да излази кроз десну ивицу прозора".

x+im_w<0
x<0
x>im_w
x+im_w>w
        

    Q-30: Нека је w ширина прозора, im_w ширина слике, (x, y) горњи леви угао слике, а dx величина за коју ће се касније мењати x координата слике. Помоћу којих наредби ће слика почети да се појављује улазећи у прозор кроз десну ивицу?

  • x = w; dx = -10;
  • Тачно
  • x = w + im_w; dx = -10;
  • Не, то је предалеко од десне ивице.
  • x = w - im_w; dx = -10;
  • Не, тако је цела слика већ у прозору.
  • x = w + im_w; dx = 10;
  • Не, слика је предалеко и још ће наставити да се удаљава.

Утисак покрета може да се постигне и на дисплејима врло ниске резолуције.

../_images/anim_ship.gif

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

Да бисмо на рачунару имитирали рад оваквог дисплеја, треба пре анимације да израчунамо положаје „светлећих диода”. Сцена се затим може описати осветљајима појединих диода. Кроз следећи пример можете добити ближу идеју како такве анимације функционишу.

Комета

Написати програм који приказује „трчеће светло” са трагом, као код комете. Бели круг се помера на десно, а за њим и остали, тамнији кругови. Сваки круг који изађе кроз десну ивицу прозора, појављује се поново на левој страни.

../_images/anim_LED.gif

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

../_images/anim_LED2.png

То значи да је у овом примеру сцена потпуно описана само једним бројем а то је позиција (редни број) диоде која најјаче светли. У програму је то променљива IndeksVodeceDiode. У функцији timer1_Tick је довољно да ову променљиву повећавамо за 1 по модулу 20.

Да бисмо имплементирали исцртавање, треба да одлучимо колики ће да буде „реп” ове комете. Ми смо изабрали реп дужине десет, што значи да ће у сваком фрејму бити исцртано 10 кругова различите осветљености.

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

using System;
using System.Drawing;
using System.Windows.Forms;

namespace DiodeKometa
{
    public partial class Form1 : Form
    {
        const int MAX_KASNJENJE = 10;  // broj dioda koje svetle (takodje broj frejmova tokom kojih dioda svetli)
        const int BROJ_DIODA = 20;     // ukupan broj dioda na displeju

        Brush[] Cetka = new Brush[MAX_KASNJENJE + 1]; // sivi tonovi od bele do crne
        int[] X = new int[BROJ_DIODA]; // x koordinate centara dioda
        int Y; // y koordinata centra svih dioda
        int R; // poluprečnik jedne diode
        int IndeksVodeceDiode; // redni broj poslednje uključene diode

        public Form1()
        {
            InitializeComponent();
            ClientSize = new Size(400, 100);
            Text = "Diode";
            BackColor = Color.Black;

            R = ClientSize.Width / (2 * BROJ_DIODA);
            for (int i = 0; i <= MAX_KASNJENJE; i++)
            {
                int nijansa = (int)(255.0f * (1.0f - (float)i / MAX_KASNJENJE));
                Cetka[i] = new SolidBrush(Color.FromArgb(nijansa, nijansa, nijansa));
            }

            for (int i = 0; i < BROJ_DIODA; i++)
                X[i] = R + i * 2 * R;

            Y = ClientSize.Height / 2;
            IndeksVodeceDiode = 0;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            // za svaku diodu koja trenutno svetli
            for (int kasnjenje = 0; kasnjenje <= MAX_KASNJENJE; kasnjenje++)
            {
                // odredimo redni broj diode koja je uključena pre 'kasnjenje' frejmova, a zatim je prikazimo
                int iDioda = (IndeksVodeceDiode + BROJ_DIODA - kasnjenje) % BROJ_DIODA;
                g.FillEllipse(Cetka[kasnjenje], X[iDioda] - R, Y - R, 2 * R, 2 * R);
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            IndeksVodeceDiode = (IndeksVodeceDiode + 1) % BROJ_DIODA; // prelazimo na narednu diodu
            Invalidate();
        }
    }
}

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

../_images/anim_running.gif ../_images/anim_running_moving.gif

Ако слике правите или преузимате са интернета (пазите на ауторска права), не заборавите да подесите транспарентност за боју позадине слика које користите.