Animácia

Časovač

Časovač je nevizuálny komponent (počas behu programu ho nevidíme, ale prejavuje sa svojou činnosťou). Jeho úlohou je v pravidelných intervaloch generovať udalosť OnTimer. Niektoré zaujímavé vlastnosti časovača:

* Interval ... určuje čas v milesekundách medzi dvoma "tiknutiami" (udalosťami OnTimer)
* Enabled
... povolenie (true) alebo zakázanie (false) činnosti časovača


časovač na palete s komponentmi.

Do formulára položíme komponenty:

* TTimer (nachádza sa medzi komponentmi na záložke System);
* TLabel

Pri udalosti časovača OnTimer sa zavolá funkcia Timer1Timer:

int Cas=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Cas++;
Label1->Caption=Cas;
}

Môžeme používať aj viacero časovačov, pričom každý z nich "tiká" podľa svojich intervaloch.

int Cas=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Cas++;
Label1->Caption=IntToStr(Cas);
}

int Cas2=0;

void __fastcall TForm1::Timer2Timer(TObject *Sender)
{
Cas2++;
Label2->Caption=Cas2;
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
Timer1->Enabled=!Timer1->Enabled;
Timer2->Enabled=!Timer2->Enabled;
}

Na prvý pohľad bežia oba časovače paralelne, v skutočnosti sa veľmi rýchlo strieda vykonávanie funkcií Timer1Timer a Timer2Timer.


Animácia

Do formulára vložíme komponent obrázok a časovač. Na vykresľovanie animácie vyžijeme grafickú plochu obrázka:
TCanvas *g;
g=Image1->Canvas;
Časovaču treba nastaviť Interval na hodnotu 50. Čím je tento interval kratší, tým sa za sekundu nakreslí viac obrázkov, a preto bude animácie plynulejšia (nebude trhaná).

int y=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->Brush->Color=clWhite;
g->FillRect(Image1->ClientRect);
y=y+5;
if (y>=Image1->Height) y=0;
g->Brush->Color=clRed;
g->Ellipse(100-10, y-10, 100+10, y+10);
}

Rovnomerne zrýchlený pohyb:

float y=0, dy=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->Brush->Color=clWhite;
g->FillRect(Image1->ClientRect);
dy=dy+0.5;
y=y+dy;
if (y>Image1->Height-10) {
y=Image1->Height-10;
dy=-dy;
}
g->Brush->Color=clRed;
g->Ellipse(100-10, y-10, 100+10, y+10);
}


Ovládanie pomocou klávesnice

Šípkami na klávesnici budeme určovať smer pohybu štvorčeka. Využijeme udalosti formulára:

* OnKeyDown ... vzniká pri stlačení klávesu
* OnKeyUp ... vzniká pri pustení klávesu

Ukážka udalosti OnKeyDown (OnKeyUp funguje analogicky):
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
Caption=Key;
}

V parametri Key je uložený kód práve stlačeného klávesu - celé 16-bitové číslo (typ WORD). Pri stláčaní klávesov mení nadpis formulára. Tak sa dajú zistiť kódy rôznych klávesov, napríklad, kláves šípka vľavo má kód 37 - namiesto čísel sa však ľahšie pamätá konštanta VK_LEFT. Analogicky existujú konštanty VK_RIGHT, VK_UP, VK_DOWN.

Udalosti klávesnice vznikajú iba vtedy, keď je okno našej aplikácie aktívne. Pozor: ak v aplikácii použijeme tlačidlá, editovacie políčka a podobné komponenty, ktoré reagujú na vstup z klávesnice, treba formuláru nastaviť vlastnosť KeyPreview na true a funkcie k udalostiam OnKeyDown a OnKeyUp programovať veľmi obozretne.

Ovládanie pohybu šípkami na klávesnici:
int x=0, y=0, dX=0, dY=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->Brush->Color=clWhite;
g->FillRect(Image1->ClientRect);
x=x+dX;
y=y+dY;
g->Brush->Color=clRed;
g->Rectangle(x-10, y-10, x+10, y+10);
}

void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
if (Key==VK_LEFT) {
dX=-5;
Key=0;
} else
if (Key==VK_RIGHT) {
dX=+5;
Key=0;
} else
if (Key==VK_UP) {
dY=-5;
Key=0;
} else
if (Key==VK_DOWN) {
dY=+5;
Key=0;
}
}

void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key,
TShiftState Shift)
{
if (Key==VK_LEFT || Key==VK_RIGHT) {
dX=0;
Key=0;
} else
if (Key==VK_UP || Key==VK_DOWN) {
dY=0;
Key=0;
}
}

Všimnime si, že do Key priraďujeme hodnotu 0. Tým povieme, že iné komponenty, ktoré s klávesnicou pracujú, už nemajú reagovať na stlačenie tohto klávesu (pozn.: parameter Key nie je vďaka symbolu & iba obyčajný parameter - bude o tom podrobnejšia prednáška).

Predchádzajúci program vyzerá na prvý pohľad komplikovane. Spôsobuje to viacnásobné vnáranie príkazu if ... else. Pritom chceme podľa hodnoty v premennej Key zvoliť iba jednu zo štyroch možných alternatív a vykonať pár príkazov na nastavenie smeru pohybu. Pre takéto situácie existuje v C++ príkaz switch - pomocou neho zapíšeme predchádzajúci program elegantne.

Najčastejšie používaná syntax príkazu:

switch (výraz) {
case hodnota1:
príkaz;
...
príkaz;
break;
case hodnota2:
príkaz;
...
príkaz;
break;
...
default:
príkaz;
...
príkaz;
}

Ako príkaz funguje:

* vyhodnotí sa výraz a podľa jeho hodnoty sa rozhodne, ktorá vetva case sa vykoná:
o ak výraz==hodnota1, začnú sa vykonávať príkazy za vetvou case hodnota1: ...
o ak výraz==hodnota2, začnú sa vykonávať príkazy za vetvou case hodnota2: ...
o ...
* ak sa hodnota výrazu nezhoduje so žiadnou hodnotou uvedenou za case, vykonajú sa príkazy za vyhradeným slovom default: (táto vetva je nepovinná - nemusíme ju písať)
* kľúčové slovo break ukončí vykonávanie príkazu switch. Keby sme break zabudli napísať, prípadne ho naschvál nenapísali, pokračovalo by sa vo vykonávaní príkazov aj z nasledujúcej vetvy case (prípadne default).

void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
switch (Key) {
case VK_LEFT:
dX=-5;
Key=0;
break;
case VK_RIGHT:
dX=+5;
Key=0;
break;
case VK_UP:
dY=-5;
Key=0;
break;
case VK_DOWN:
dY=+5;
Key=0;
}
}

void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key,
TShiftState Shift)
{
switch (Key) {
case VK_LEFT:
case VK_RIGHT:
dX=0;
Key=0;
break;
case VK_UP:
case VK_DOWN:
dY=0;
Key=0;
}
}


Animácia viacerých objektov

V prípade animovania viacerých objektov nepoužijeme pre každý objekt zvlášť časovať. Bolo veľmi neefektívne a objekty by sme ťažko synchronizovali. Preto používame iba jediný časovač a funkciu, ktorá obsluhuje udalosť OnTimer iba mierne modifikujeme:

* zmažeme grafickú plochu
* pohneme všetky objekty
* nakreslíme všetky objekty

class TVlocka {
private:
int X, Y;
public:
void Nastav(int nX, int nY);
void Pohni();
void Kresli();
};

void TVlocka::Nastav(int nX, int nY) {
X=nX;
Y=nY;
}

void TVlocka::Pohni() {
if (random(4)==0) X=X+random(3)-1;
Y=Y+2+random(2);
if (Y>Form1->Image1->Height) Y=0;
}

void TVlocka::Kresli() {
g->Pixels[X][Y]=clWhite;
}

const Max=1000;
TVlocka v[Max];
int poc=0;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
g->Brush->Color=clBlack;
g->FillRect(Image1->ClientRect);
g->Brush->Color=clRed;
for (int i=0; i<poc; i++) {
v[i].Pohni();
v[i].Kresli();
}
}

//-------------------------------------- -------------------------------------

void __fastcall TForm1::Image1MouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
if (poc>=Max) return;
v[poc].Nastav(X, Y);
poc++;
}

void __fastcall TForm1::Image1MouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{
if (!Shift.Contains(ssLeft) || poc>=Max) return;
v[poc].Nastav(X, Y);
poc++;
}