Koło informatyczne 2012

Na dzisiejszych zajęciach zaprogramujemy prostą aplikację z animacją. Utwórz nowy projekt okienkowy. Na formie umieść komponent Timer, który znajdziesz pod zakładką System na Palecie komponentów. Komponent ten generuje cyklicznie zdarzenie: onTimer. Dzięki temu nasza aplikacja będzie mogła wykonywać ciągłą animację. Najpierw zaprojektujemy interfejs, który w tym przypadku będzie bardzo prosty:

Form1

BorderIconsbiMaximize = false
Okno nie będzie można powiększać na cały ekran.

BorderStyle = bsSingle
Użytkownik nie będzie mógł zmieniać rozmiarów okna przez przeciąganie myszką jego krawędzi.

Caption = Animacja
Tytuł okna.

ClientHeight = 512
Wysokość obszaru graficznego okna. Taką wysokość będzie miała nasza powierzchnia rysunkowa Canvas.

ClientWidth = 512
Szerokość obszaru graficznego okna.

Name = frmAnimation
Pod tą nazwą będzie dostępna w programie zmienna klasy naszego okna.

Position = poScreenCenter
Okienko aplikacji będzie się pojawiało na środku ekranu.

Timer1

Interval = 20
Czas w ms pomiędzy kolejnymi zdarzeniami onTimer.

Name = tmrAnimate

 

Początkowy program będzie rysował ruchomy punkt, który "odbija" się od krawędzi okna. Punkt ma współrzędne x,y oraz dwa dodatkowe parametry dx i dy określające przesunięcie punktu w osi x i y po każdej klatce animacji. Zmienne te będą globalne,  Dlatego dodaj ich definicję na początku programu:

 

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

// Zmienne globalne
int x,y,dx,dy;

TfrmAnimation *frmAnimation;
//---------------------------------------------------------------------------

 

Zmienne zainicjujemy w zdarzeniu onCreate okna głównego aplikacji. Kliknij myszką okno lub wybierz frmAnimation z Object TreeView, następnie wybierz zakładkę Events w Object Inspector i kliknij dwukrotnie myszką obok zdarzenia onCreate, po czym wpisz do edytora treść funkcji obsługującej to zdarzenie:

 

void __fastcall TfrmAnimation::FormCreate(TObject *Sender)
{
  srand(time(NULL));  // inicjujemy generator pseudolosowy

  x = rand() % ClientWidth;  // losujemy pozycję x
  y = rand() % ClientHeight; // losujemy pozycję y
  dx = 1 + rand() % 5;       // losujemy przesunięcie w osi x
  dy = 1 + rand() % 5;       // losujemy przesunięcie w osi y
}

 

Rysowanie punktu umieścimy w obsłudze zdarzenia onPaint. Najpierw w Object Inspector na zakładce Events kliknij dwukrotnie obok zdarzenia onPaint i wpisz kod poniższej funkcji do edytora:

 

void __fastcall TfrmAnimation::FormPaint(TObject *Sender)
{
  Canvas->Brush->Color = clBlack;                       // kolor pędzla (tło)
  Canvas->FillRect(Rect(0,0,ClientHeight,ClientWidth)); // wymazujemy tło
  Canvas->Pixels[x][y] = clWhite;                       // stawiamy punkt
}

 

Rect(x1,y1,x2,y2) jest konstruktorem klasy TRect. Tworzy on tymczasowy obiekt typu TRect, który wykorzystujemy w funkcji FillRect() do wypełnienia okna kolorem czarnym.

Animację punktu wykonamy w procedurze obsługi Timera. W tym celu wybierz w Object TreeView komponent tmrAnimate, w Object Inspector wybierz zakładkę Events i kliknij dwukrotnie obok zdarzenia onTimer. Zostanie utworzona funkcja obsługująca to zdarzenie, która będzie cyklicznie wywoływana co 20 ms, czyli 50 razy na sekundę. Do edytora wpisz kod:

 

void __fastcall TfrmAnimation::tmrAnimateTimer(TObject *Sender)
{
  int t;

  t = x + dx;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientWidth)) dx = -dx;
  x += dx;     // modyfikujemy współrzędną x

  t = y + dy;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientHeight)) dy = -dy;
  y += dy;     // modyfikujemy współrzędną y

  Invalidate(); // wymuszamy rysowanie
}

 

Gdy uruchomisz program, animacja punktu będzie wykonywana, jednakże obszar okna mruga niemiłosiernie. Spowodowane jest to tym, iż zapis dokonywany jest bezpośrednio do karty graficznej, co powoduje zakłócenia. Aby się ich pozbyć, buforujemy powierzchnię graficzną okna – buforowanie zwiększa zapotrzebowanie na pamięć w aplikacji, jednakże dla nas nie będzie to żadną przeszkodą. Gdy okno jest buforowane, to zapisy są wykonywane do jego kopii w pamięci i nie pojawiają się bezpośrednio na ekranie. Dopiero gdy skończymy rysować, treść tego obszaru jest kopiowana do pamięci graficznej karty. Aby włączyć podwójne buforowanie grafiki, dodaj do procedury obsługi onCreate poniższe polecenie:

 

void __fastcall TfrmAnimation::FormCreate(TObject *Sender)
{
  srand(time(NULL));  // inicjujemy generator pseudolosowy

  x = rand() % ClientWidth;  // losujemy pozycję x
  y = rand() % ClientHeight; // losujemy pozycję y
  dx = 1 + rand() % 5;       // losujemy przesunięcie w osi x
  dy = 1 + rand() % 5;       // losujemy przesunięcie w osi y

  DoubleBuffered = true;
}

 


 

Kolejnym krokiem będzie animowanie odcinka. Różnica polega jedynie na tym, iż odcinek posiada dwa końce o współrzędnych x1,y1 i x2,y2. Dla każdych z tych współrzędnych określamy przesunięcia odpowiednio dx1, dy1, dx2 i dy2. Zmodyfikuj zmienne globalne:

 

// Zmienne globalne
int x1,y1,x2,y2,dx1,dy1,dx2,dy2;

 

Zmień funkcję obsługi zdarzenia onCreate:

 

void __fastcall TfrmAnimation::FormCreate(TObject *Sender)
{
  srand(time(NULL));  // inicjujemy generator pseudolosowy

  x1 = rand() % ClientWidth;  // losujemy pozycję x1
  y1 = rand() % ClientHeight; // losujemy pozycję y1
  dx1 = 1 + rand() % 5;       // losujemy przesunięcie w osi x
  dy1 = 1 + rand() % 5;       // losujemy przesunięcie w osi y

  x2 = rand() % ClientWidth;  // losujemy pozycję x2
  y2 = rand() % ClientHeight; // losujemy pozycję y2
  dx2 = 1 + rand() % 5;       // losujemy przesunięcie w osi x
  dy2 = 1 + rand() % 5;       // losujemy przesunięcie w osi y

  DoubleBuffered = true;
}

 

Zmień funkcję obsługi zdarzenia onPaint:

 

void __fastcall TfrmAnimation::FormPaint(TObject *Sender)
{
  Canvas->Brush->Color = clBlack;  // kolor pędzla (tło)
  Canvas->FillRect(Rect(0,0,ClientHeight,ClientWidth)); // wymazujemy tło
  Canvas->Pen->Color = clWhite;    // kolor linii
  Canvas->MoveTo(x1,y1);           // początek linii
  Canvas->LineTo(x2,y2);           // rysujemy linię do punktu końcowego
}

 

I na koniec zmieniamy funkcję obsługi zdarzenia onTimer:

 

void __fastcall TfrmAnimation::tmrAnimateTimer(TObject *Sender)
{
  int t;

  t = x1 + dx1;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientWidth)) dx1 = -dx1;
  x1 += dx1;     // modyfikujemy współrzędną x1

  t = y1 + dy1;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientHeight)) dy1 = -dy1;
  y1 += dy1;     // modyfikujemy współrzędną y

  t = x2 + dx2;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientWidth)) dx2 = -dx2;
  x2 += dx2;     // modyfikujemy współrzędną x2

  t = y2 + dy2;  // wykonujemy próbne dodawanie
  if((t < 0) || (t >= ClientHeight)) dy2 = -dy2;
  y2 += dy2;     // modyfikujemy współrzędną y2

  Invalidate(); // wymuszamy rysowanie
}

 

obrazek

 


 

W następnym kroku stworzymy aplikację, która będzie animowała ciąg odcinków połączonych ze sobą w łamaną zamkniętą. Łamana jest figurą zbudowaną z odcinków w taki sposób, że koniec jednego odcinka staje się początkiem następnego. Łamana jest zamknięta, jeśli ostatni jej odcinek łączy się swoim końcem z początkiem pierwszego odcinka. Do przechowywania współrzędnych punktów będziemy potrzebowali tablicy. W Borland C++ Builder mamy do dyspozycji klasę TPoint, która w prosty sposób pozwala tworzyć punkty oraz je przechowywać. Zmień zmienne globalne:

 

// Zmienne globalne
const int MAXV = 30;    // liczba wierzchołków łamanej

TPoint V[MAXV+1];       // tablica punktów wierzchołkowych
int DX[MAXV], DY[MAXV]; // tablice przesunięć

 

Tworzymy najpierw stałą MAXV, która będzie określała liczbę wierzchołków łamanej. Następnie tworzymy trzy tablice:

 

V[]  - punkty wierzchołkowe. Jest ich o 1 więcej niż MAXV, ponieważ ostatni punkt musi być pierwszym, aby łamana była zamknięta.
DX[] - przesunięcia wzdłuż osi x
DY[] - przesunięcia wzdłuż osi y

 

Zmieniamy funkcję obsługi zdarzenia onCreate:

 

void __fastcall TfrmAnimation::FormCreate(TObject *Sender)
{
  srand(time(NULL));  // inicjujemy generator pseudolosowy

  for(int i = 0; i < MAXV; i++)
  {
    V[i]  = Point(rand() % ClientWidth, rand() % ClientHeight);
    DX[i] = 1 + rand() % 5;
    DY[i] = 1 + rand() % 5;
  }
  V[MAXV] = V[0];  // łamana zamknięta

  DoubleBuffered = true;
}

 

Point(x,y) jest konstruktorem klasy TPoint. Tworzy on w pamięci tymczasowy obiekt typu TPoint, który jest następnie wykorzystywany do przypisania tego punktu elementowi tablicy V[].

Zmieniamy funkcję obsługi zdarzenia onPaint:

 

void __fastcall TfrmAnimation::FormPaint(TObject *Sender)
{
  Canvas->Brush->Color = clBlack;                       // kolor pędzla (tło)
  Canvas->FillRect(Rect(0,0,ClientHeight,ClientWidth)); // wymazujemy tło
  Canvas->Pen->Color = clWhite;                         // kolor linii
  Canvas->Polyline(V,MAXV);
}

 

Zastosowaliśmy tutaj nową funkcję składową:

 

Canvas->Polyline(tablica_punktów, indeks_ostatniego_punktu);

 

Funkcja ta rysuje łamaną kolejno od punktu V[0] do V[MAXV]. Działa ona podobnie do serii wywołań funkcji MoveTo() i LineTo(), jednakże robi to o wiele szybciej. Pierwszy parametr określa kolejne wierzchołki łamanej i powinna to być tablica typu TPoint. Drugi parametr określa indeks ostatniego wierzchołka – nie jest to liczba wierzchołków!!!

Zmieniamy funkcję obsługi zdarzenia onTimer:

 

void __fastcall TfrmAnimation::tmrAnimateTimer(TObject *Sender)
{
  int t;

  for(int i = 0; i < MAXV; i++)
  {
    t = V[i].x + DX[i];
    if((t < 0) || (t >= ClientWidth)) DX[i] = -DX[i];
    V[i].x += DX[i];

    t = V[i].y + DY[i];
    if((t < 0) || (t >= ClientHeight)) DY[i] = -DY[i];
    V[i].y += DY[i];
  }
  V[MAXV] = V[0];  // dla łamanej zamkniętej

  Invalidate();
}

 

obrazek

 

Teraz zmieniamy funkcję obsługi zdarzenia onPaint:

 

void __fastcall TfrmAnimation::FormPaint(TObject *Sender)
{
  Canvas->Brush->Color = clBlack;                       // kolor pędzla (tło)
  Canvas->FillRect(Rect(0,0,ClientHeight,ClientWidth)); // wymazujemy tło
  Canvas->Brush->Color = clRed;                         // kolor wypełnienia łamanej
  Canvas->Pen->Color = clWhite;                         // kolor linii
  Canvas->Polygon(V,MAXV);                              // łamana z wypełnieniem - wielokąt
}

 

Stosujemy tutaj nową funkcję:

 

Canvas->Polygon(tablica_punktów, indeks_ostatniego_punktu);

 

Funkcja ta rysuje łamaną  w kolorze określonym przez Canvas->Pen->Color, a następnie wypełnia ją kolorem określonym przez Canvas->Brush->Color. Wypełnienie jest realizowane zawsze pomiędzy dwoma sąsiednimi liniami, co daje dosyć ciekawy efekt graficzny:

 

obrazek

 

Zastanów się, jak przerobić ten program, aby kolory linii i wypełnienia łamanej były płynnie zmieniane w trakcie trwania animacji. Przyjrzyj się zmianie współrzędnych. Czy tej samej metody nie można zastosować do składowych kolorów?

 

Dodamy krótki komunikat tekstowy, który będzie wyświetlany u spodu okienka.

 

void __fastcall TfrmAnimation::FormPaint(TObject *Sender)
{
  Canvas->Brush->Color = clBlack;                       // kolor pędzla (tło)
  Canvas->FillRect(Rect(0,0,ClientHeight,ClientWidth)); // wymazujemy tło
  Canvas->Brush->Color = clRed;                         // kolor wypełnienia łamanej
  Canvas->Pen->Color = clWhite;                         // kolor linii
  Canvas->Polygon(V,MAXV);                              // łamana z wypełnieniem - wielokąt

  // wyświetlanie komunikatu tekstowego
  AnsiString s = "Aby zakończyć, naciśnij klawisz ESC";
  int tx,ty;
  Canvas->Font->Name="Times New Roman";          // krój czcionki
  Canvas->Font->Size = 14;                       // rozmiar czcionki w punktach
  Canvas->Font->Color = clYellow;                // kolor czcionki
  Canvas->Brush->Style=bsClear;                  // przezroczyste tło czcionek
  tx = (ClientWidth - Canvas->TextWidth(s)) / 2; // pozycja x tekstu - na środku w poziomie
  ty = ClientHeight - Canvas->TextHeight(s) - 2; // pozycja y tekstu - u dołu w pionie
  Canvas->TextOutA(tx,ty,s);                     // wypisujemy tekst
}

 

Do wyświetlania tekstu używamy składnika Canvas->Font, który pozwala zdefiniować różne parametry czcionki oraz odpowiednich funkcji. Pod spodem podajemy ich podsumowanie

 

Canvas->Font->Name  –  określa nazwę kroju. Tutaj wykorzystujemy typową czcionkę Times New Roman
Canvas->Font->Size

 – 

rozmiar czcionki w punktach
Canvas->Font->Color

 – 

kolor czcionki
Canvas->Brush->Style

 – 

określa sposób rysowania tła dla czcionek. Opcja bsClear oznacza, iż tło jest przezroczyste.
Canvas->TextWidth(tekst)

 – 

zwraca szerokość tekstu w pikselach
Canvas->TextHeight(tekst)

 – 

zwraca wysokość tekstu w pikselach
Canvas->TextOutA(x,y,tekst)

 – 

wyświetla tekst na podanych współrzędnych x,y, które odnoszą się do lewego górnego narożnika pierwszego znaku.

 

Ponieważ komunikat jest wyświetlany na końcu obsługi zdarzenia onPaint, po narysowaniu łamanej, literki zawsze będą na wierzchu.

 

obrazek

 

Aby komunikat miał sens, będziemy przechwytywali naciśnięcia klawiszy w funkcji obsługi zdarzenia onKeyPress dla głównego okna. Zdarzenie to powstaje, gdy użytkownik naciśnie klawisz będący znakiem drukowalnym (np. samo naciśnięcie Shift lub Ctrl nie generuje tego zdarzenia). Funkcja w parametrach otrzymuje kod ASCII naciśnietego klawisza. Zatem wybierz okno główne, po czym w Object Inspector przejdź do zakładki Events i kliknij dwukrotnie obok zdarzenia onKeyPress. Wpisz do edytora poniższy kod:

 

void __fastcall TfrmAnimation::FormKeyPress(TObject *Sender, char &Key)
{
  if(Key == 27) Close();
}

 

Funkcja sprawdza, czy naciśniety klawisz ma kod 27 (klawisz ESC). Jeśli tak, zamyka aplikację.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe