$$ \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.

Анимација по фазама

Семафор

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

Постоји неколико стања у којима семафор може да се нађе. На пример, може да светли црвено, да светли трепћуће жуто, да буде искључен итд. Период у току којег семафор не мења стање зваћемо фаза. При нормалном раду семафора фазе се циклично смењују и свака фаза има своје трајање. Узмимо као пример семафор на коме се смењују следеће четири фазе: 0 - црвено светло, 1 - црвено и жуто светло, 2 - зелено светло, и 3 - жуто светло. Распоред светала по фазама може да се зада матрицом, тако да сваки ред матрице представља једну фазу, а свака колона једно светло. Матрица има три колоне које редом одгвоарају црвеном, жутом и зеленом светлу. У матрици је елемент у реду r и колони k једнак 1 ако је у фази r укључено светло број k.

static int[,] SvetlaPoFazama = {
    { 1, 0, 0 }, // samo crveno
    { 1, 1, 0 }, // crveno i zuto
    { 0, 0, 1 }, // samo zeleno
    { 0, 1, 0 }, // samo zuto
};

Осим распореда светала, за сваку фазу треба задати и трајање, на пример у секундама:

static float[] TrajanjeFaze = { 2.5f, 1f, 2.5f, 1f }; // u sekundama

За опис стања довољне су две променљиве: редни број фазе (Faza) и број фрејмова до истека фазе (BrojFrejmovaDoPromene). Стање ажурирамо у функцији timer1_Tick тако што прво проверимо да ли је истекла претходна фаза. Ако је истекла, прелазимо на следећу фазу и на основу трајања фазе у секундама и интервала тајмера израчунавамо колико фрејмова ће трајати следећа фаза. На крају одбројимо један фрејм мање до истека фазе.

private void timer1_Tick(object sender, EventArgs e)
{
    if (BrojFrejmovaDoPromene == 0)
    {
        Faza = (Faza + 1) % BR_FAZA;
        BrojFrejmovaDoPromene = (int)(0.5f + TrajanjeFaze[Faza] * 1000f / timer1.Interval);
        Invalidate();
    }
    BrojFrejmovaDoPromene -= 1;
}

Захтев за новим исцртавањем (позив функције Invalidate()) је довољно прослеђивати само при промени фазе, мада се може поставити и на крај функције timer1_Tick.

Сви остали детаљи су технички и немају директне везе са анимацијом. На пример, дефинисали смо унапред 6 четки, за свако светло на семафору по једну за укључено и једну за искључено светло. Захваљујући томе, цртање светала у функцији Form1_Paint можемо да обавимо у петљи.

Цео програм би могао да изгледа овако:

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

namespace Semafor
{
    public partial class Form1 : Form
    {
        int BR_SVETALA = 3;
        int BR_FAZA;
        int R_SVETLA = 40;
        static int[,] SvetlaPoFazama = {
            { 1, 0, 0 }, // samo crveno
            { 1, 1, 0 }, // crveno i zuto
            { 0, 0, 1 }, // samo zeleno
            { 0, 1, 0 }, // samo zuto
        };

        static float[] TrajanjeFaze = { 2.5f, 1f, 2.5f, 1f }; // u sekundama
        Brush[,] Cetka = new SolidBrush[3, 2];
        Point[] CentarSvetla = new Point[3];
        int Faza;
        int BrojFrejmovaDoPromene;

        public Form1()
        {
            InitializeComponent();
            Text = "Semafor";
            BackColor = Color.DarkGray;
            int R1 = R_SVETLA + 20; // 20 je prostor okolo svetla
            ClientSize = new Size(2 * R1, 6 * R1);

            CentarSvetla[0] = new Point(R1, 1 * R1);
            CentarSvetla[1] = new Point(R1, 3 * R1);
            CentarSvetla[2] = new Point(R1, 5 * R1);

            int CRVENA = 0, ZUTA = 1, ZELENA = 2;
            int ISKLJUCENA = 0, UKLJUCENA = 1;
            Cetka[CRVENA, ISKLJUCENA] = new SolidBrush(Color.FromArgb(128, 0, 0));
            Cetka[CRVENA, UKLJUCENA] = new SolidBrush(Color.FromArgb(255, 0, 0));
            Cetka[ZUTA, ISKLJUCENA] = new SolidBrush(Color.FromArgb(128, 128, 0));
            Cetka[ZUTA, UKLJUCENA] = new SolidBrush(Color.FromArgb(255, 255, 0));
            Cetka[ZELENA, ISKLJUCENA] = new SolidBrush(Color.FromArgb(0, 128, 0));
            Cetka[ZELENA, UKLJUCENA] = new SolidBrush(Color.FromArgb(0, 255, 0));

            BR_FAZA = SvetlaPoFazama.GetLength(0);
            Faza = BR_FAZA - 1;
            BrojFrejmovaDoPromene = 0;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (BrojFrejmovaDoPromene == 0)
            {
                Faza = (Faza + 1) % BR_FAZA;
                BrojFrejmovaDoPromene = (int)(0.5f + TrajanjeFaze[Faza] * 1000f / timer1.Interval);
                Invalidate();
            }
            BrojFrejmovaDoPromene -= 1;
        }

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

            for (int i_svetlo = 0; i_svetlo < BR_SVETALA; i_svetlo++)
            {
                Point poz = CentarSvetla[i_svetlo];
                int ukljuceno = SvetlaPoFazama[Faza, i_svetlo];

                float R = R_SVETLA;
                g.FillEllipse(Cetka[i_svetlo, ukljuceno], poz.X - R, poz.Y - R, 2 * R, 2 * R);
            }
        }
    }
}

Напомена: анимацију рада семафора смо могли да постигнемо и мењањем вредности својства Interval тајмера у функцији timer1_Tick. Тиме бисмо направили фрејмове различитог трајања, тако да сваки фрејм траје колико и одговарајућа фаза. У том случају нам не би била потребна променљива BrojFrejmovaDoPromene, а функција timer1_Tick би била мало једноставнија.

private void timer1_Tick(object sender, EventArgs e)
{
    Faza = (Faza + 1) % BR_FAZA;
    timer1.Interval = (int)(0.5f + TrajanjeFaze[Faza] * 1000f);
    Invalidate();
}

Ипак, треба имати на уму да овакав поступак није увек могуће применити, а што можемо и увидети у примеру „Кртица” нешто ниже. Зато смо детаљно изложили поступак са бројањем фрејмова (једнаког трајања) као општији - такав поступак нам омогућава да чак и у току једне фазе сваки фрејм различито ажурирамо.

Задатак - трепћуће зелено

Копирајте претходни програм, па убаците трепћуће зелено светло после зеленог, а пре жутог светла.

Помоћ: Свако искључивање или укључивање зеленог светла сматраћемо новом фазом. Према томе, довољно је у матрици SvetlaPoFazama неколико пута додати редове { 0, 0, 0 } и { 0, 0, 1 }, а у низу TrajanjeFaze на исте позиције додати трајања убачених фаза (да би се добило трепћуће светло, нове убачене фазе треба да су знатно краће од већ дефинисаних). Остатак кода може да остане потпуно исти.

Пример - кртица

Напишите програм који приказује кртицу како извирује из рупе и враћа се у њу.

../_images/anim_mole.gif

На располагању је 10 слика на којима кртица редом све више вири из рупе.

../_images/anim_mole1.png ../_images/anim_mole2.png ../_images/anim_mole3.png ../_images/anim_mole4.png ../_images/anim_mole5.png ../_images/anim_mole6.png ../_images/anim_mole7.png ../_images/anim_mole8.png ../_images/anim_mole9.png ../_images/anim_mole10.png

Цео циклус има четири фазе, које заједно трају 28 фрејмова.

  • Прва фаза траје 10 фрејмова и током ње кртица излази из рупе (приказују се редом слике од прве до десете).

  • Друга фаза траје 5 фрејмова и током ње кртица је у највишем положају (приказује се десета слика).

  • Трећа фаза траје 10 фрејмова и током ње кртица улази у рупу (приказују се слике од десете до прве).

  • Четврта фаза траје 3 фрејма и током ње кртица је у рупи (приказује се прва слика).

Из поставке задатка знамо трајање сваке фазе у фрејмовима. Нумеришимо фрејмове једног пуног циклуса бројевима 0-27 (укупно 28 фрејмова). Тада су фрејмови прве фазе 0-9, друге фазе 10-14, треће 15-24 и последње, четврте фазе 25-27. Стање сцене ћемо описивати променљивом IndeksFrejma, која броји фрејмове у једном пуном циклусу. Ова променљива ће се повећавати за један по модулу 28. На основу вредности IndeksFrejma и управо одређених интервала за сваку фазу, можемо да одредимо којој фази припада текући фрејм, као и који је то фрејм по реду у текућој фази (ово је потребно за прву и трећу фазу, да бисмо знали коју слику треба приказати).

Следи комплетан код програма.

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

namespace Krtica
{
    public partial class Form1 : Form
    {
        Image[] Slika = {
            Properties.Resources.krtica1, Properties.Resources.krtica2,
            Properties.Resources.krtica3, Properties.Resources.krtica4,
            Properties.Resources.krtica5, Properties.Resources.krtica6,
            Properties.Resources.krtica7, Properties.Resources.krtica8,
            Properties.Resources.krtica9, Properties.Resources.krtica10
        };
        int IndeksFrejma;
        int IndeksSlike;

        public Form1()
        {
            InitializeComponent();
            Text = "Krtica";
            BackColor = Color.SkyBlue;
            ClientSize = new Size(150, 150);

            IndeksFrejma = 0;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            IndeksFrejma = (IndeksFrejma + 1) % 28; // ukupan broj frejmova je 28
            if (IndeksFrejma < 10)
                IndeksSlike = IndeksFrejma;         // prva faza - podizanje
            else if (IndeksFrejma < 15)
                IndeksSlike = 9;              // druga faza - krtica je gore
            else if (IndeksFrejma < 25)
                IndeksSlike = 24 - IndeksFrejma;   // treca faza - spustanje
            else
                IndeksSlike = 0;              // cetvrta faza - krtica je dole

            Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            int Y_HORIZONT = 125;
            // deo slike ispod horizonta
            Rectangle ZEMLJA = new Rectangle(0, Y_HORIZONT, ClientSize.Width, ClientSize.Height - Y_HORIZONT);
            Brush braonCetka = new SolidBrush(Color.FromArgb(60, 42, 3));

            Graphics g = e.Graphics;
            g.FillRectangle(braonCetka, ZEMLJA);
            g.DrawImage(Slika[IndeksSlike], 0, 0);
        }
    }
}

Задатак - авион

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

../_images/anim_airplane.png