Dzisiaj zajmiemy się jednym z prostszych efektów graficznych – rysowaniem latających gwiazd. Punktem wyjścia będzie program, który napisaliśmy w tutorialu „Grafika w oknie – inicjalizacja”. Na dobry początek ogólny schemat według którego zakodujemy ten efekt:
- Losowanie początkowych pozycji gwiazd
- Czyszczenie bitmapy (zamalowywanie na czarno)
- Rysowanie gwiazd na wylosowanych pozycjach (w bitmapie)
- Przesuwanie gwiazd w prawo
- Jeżeli jakaś gwiazda wyleci poza bitmapę, przenieś ją na jej lewą krawędź
- Wyświetlenie bitmapy na ekranie
- Powrót do punktu 2
Aby zrealizować punkt pierwszy, potrzebujemy dobrego generatora liczb pseudolosowych. Algorytmów tego typu jest wiele. Ja skorzystałem z tzw. „Lehmer Generator”, którego implementację znajdziecie w archiwum dołączonym do tego artykułu, w pliku random.asm. Więcej informacji o generowaniu liczb pseudolosowych w assemblerze znajdziecie w zinie Natural Selection #1.
Plik random.asm zawiera dwie procedury: Randomize, która przygotowuje generator do działania oraz Random, która losuje liczbę z przedziału od zera do liczby podanej jako parametr. Wylosowane pozycje gwiazd będziemy zapisywać w dwóch tablicach: GwiazdyX i GwiazdyY. Zadeklarujmy również stałą ILOSC_GWIAZD.
.const ILOSC_GWIAZD equ 300 .data? GwiazdyX dw ILOSC_GWIAZD dup(?) GwiazdyY dw ILOSC_GWIAZD dup(?)
Losowanie pozycji gwiazd wykonamy w pętli, w części inicjalizacyjnej naszego programu:
invoke Randomize ;przygotuj generator liczb losowych mov ebx,ILOSC_GWIAZD-1 losuj: invoke Random,WND_WIDTH ;wylosuj współrzędną X mov word ptr [GwiazdyX+ebx*2],ax ;i zapisz ją w tablicy invoke Random,WND_HEIGHT ;wylosuj współrzędną Y mov word ptr [GwiazdyY+ebx*2],ax ;i zapisz ją w tablicy dec ebx ;zmniejsz licznik gwiazd jns losuj ;powtarzaj pętlę dla każdej gwiazdy
Dlaczego w drugiej linijce umieściłem w ebx wartość ILOSC_GWIAZD-1 a nie po prostu ILOSC_GWIAZD, a na końcu użyłem instrukcji jns zamiast jnz? Dlatego, że ebx ma zawierać numer aktualnie „obrabianej” gwiazdy. Jeżeliby ponumerować elementy tablic GwiazdyX i GwiazdyY, to pierwszy element miałby numer zero a ostatni ILOSC_GWIAZD-1. Ponieważ chcemy wylosować pozycje wszystkich gwiazd, zaczynamy od ostatniej, która ma numer ILOSC_GWIAZD-1, a kończymy na pierwszej, która ma numer zero (gwiazdy przetwarzamy od końca, ponieważ łatwiej jest to zakodować). Instrukcja jns (jump if not signed) powoduje, że skok wykona się jeśli sprawdzana liczba nie będzie ujemna. W tym przypadku pętla będzie chodzić tak długo, aż liczba w ebx będzie mniejsza od zera (jeżeli ebx<0, pętla zakończy się).
Mamy więc dwie tablice wypełnione współrzędnymi wszystkich gwiazd. Punkt drugi, czyszczenie bitmapy, wykonujemy jak zwykle:
mov ecx,WND_WIDTH*WND_HEIGHT mov edi,lpBackBitmap xor eax,eax rep stosd
Samo rysowanie gwiazd również nie sprawi nam problemu. Wystarczy w pętli odczytywać z tablicy GwiazdyX współrzędną X danej gwiazdy, a z tablicy GwiazdyY współrzędną Y, a następnie zamalować pixel o współrzędnych (X,Y) na biało. Oto odpowiedni kod:
mov edi,lpBackBitmap mov ebx,ILOSC_GWIAZD-1 xor eax,eax rysuj_gwiazdy: mov ax,word ptr [GwiazdyY+ebx*2] ;\ mov cx,WND_WIDTH ;| oblicz pozycje mul cx ;| gwiazdy w bitmapie add ax,word ptr [GwiazdyX+ebx*2] ;/ mov dword ptr [edi+eax*4],00FFFFFFh ;narysuj gwiazde
W tej samej pętli zaimplementujemy od razu punkt czwarty. Aby przesunąć gwiazdy w prawo, wystarczy zwiększyć współrzędną X każdej gwiazdy o jeden. Musimy jeszcze sprawdzić, czy gwiazda nie wyleciała poza bitmapę, a jeśli tak się stało, ustawić jej współrzędną X na zero.
inc word ptr [GwiazdyX+ebx*2] ;zwieksz wspolrzedna X gwiazdy o 1 cmp word ptr [GwiazdyX+ebx*2],WND_WIDTH ;czy gwiazda wyleciala poza okno? jna @F ;jezeli tak... mov word ptr [GwiazdyX+ebx*2],0 ;ustaw jej wspolrzedna X na zero @@: dec ebx ;zmniejsz licznik gwiazd jns rysuj_gwiazdy ;powtarzaj petle dla wszystkich gwiazd
Oczywiście możliwy jest również ruch w pozostałych kierunkach, a nawet po skosie. Na przykład aby gwiazdy leciały w górę, należy zmniejszać współrzędną Y o jeden.
To już właściwie wszystko jeśli chodzi o podstawowy sposób rysowania gwiazd. Pozostałe punkty (5 i 6) wykonujemy tak samo jak w pierwszym tutorialu. Skompiluj i uruchom teraz program stars1 (dołączony do tego tekstu). I co? Nie wygląda to zbyt pięknie. Gwiazdy w ogóle nie przypominają gwiazd. Brakuje im głębi. Aby poprawić wygląd naszych gwiazd zastosujemy technikę zwaną „parallax scrolling”. Polega ona na przesuwaniu różnych obiektów z różną prędkością, w zależności od tego jak daleko dany obiekt znajduje się od widza. Aby jeszcze bardziej polepszyć cały efekt, będziemy bardziej oddalone gwiazdy malować ciemniejszymi kolorami.
W naszej animacji zastosujemy trzy warstwy (oczywiście może ich być więcej, ale na razie zostańmy przy trzech). Jedną trzecią gwiazd będziemy rysować kolorem białym (00FFFFFFh) i przesuwać o trzy pixele w każdej klatce animacji, drugą jedną trzecią – kolorem szarym (00AAAAAAh) i przesuwać o dwa pixele, a pozostałe gwiazdy – kolorem ciemnoszarym (00555555h) i przesuwać o jeden pixel w każdej klatce. Aby dodać parallax scrolling do naszego programu, wystarczy zmienić pętlę rysuj_gwiazdy na ten kod:
rysuj_gwiazdy: mov ax,word ptr [GwiazdyY+ebx*2] ;\ mov cx,WND_WIDTH ;| oblicz pozycje mul cx ;| gwiazdy w bitmapie add ax,word ptr [GwiazdyX+ebx*2] ;/ .if ebx<ILOSC_GWIAZD*1/3 mov dword ptr [edi+eax*4],00FFFFFFh ;zamaluj pixel kolorem białym add word ptr [GwiazdyX+ebx*2],3 ;zwieksz wspolrzedna X o 3 .elseif ebx<ILOSC_GWIAZD*2/3 mov dword ptr [edi+eax*4],00AAAAAAh ;zamaluj pixel kolorem szarym add word ptr [GwiazdyX+ebx*2],2 ;zwieksz wspolrzedna X o 2 .else mov dword ptr [edi+eax*4],00555555h ;zamaluj kolorem ciemnoszarym inc word ptr [GwiazdyX+ebx*2] ;zwieksz wspolrzedna X o 1 .endif cmp word ptr [GwiazdyX+ebx*2],WND_WIDTH ;czy gwiazda wyleciala poza okno? jna @F ;jezeli tak... mov word ptr [GwiazdyX+ebx*2],0 ;ustaw jej wspolrzedna X na zero @@: dec ebx ;zmniejsz licznik gwiazd jns rysuj_gwiazdy ;powtarzaj petle dla wszystkich gwiazd
Gwiazdy wyglądają już ładnie, ale mają jeszcze jedną wadę – animacja się powtarza. Aby to wyeliminować, należy ponownie losować współrzędną Y gwiazdy, gdy wyleci ona poza ekran. Dzięki temu przy każdym obiegu gwiazda będzie poruszać się po innej linii. Kod jest prosty:
cmp word ptr [GwiazdyX+ebx*2],WND_WIDTH ;czy gwiazda wyleciala poza okno? jna @F ;jezeli tak... mov word ptr [GwiazdyX+ebx*2],0 ;ustaw jej wspolrzedna X na zero invoke Random,WND_HEIGHT ;wylosuj wspolrzedna Y mov word ptr [GwiazdyY+ebx*2],ax @@:
Mam nadzieję, że po tym krótkim tutorialu zakodowanie efektu latających gwiazd nie sprawi już ci problemu. Jak widzisz, kod jest bardzo prosty i opiera się jedynie na dobrej procedurze generującej liczby pseudolosowe. Jeżeli chcesz sobie to przećwiczyć, zakoduj program, w którym gwiazdy przesuwać się będą w jeden z pozostałych kierunków i/lub reagować na naciśnięcia klawiszy strzałek. Wykorzystaj wiadomości z poprzedniego artykułu i dodaj do programu scrollowany tekst. Możliwości jest wiele.
Załącznik:
czajnick_gdistars.rar
Pierwsza wersja tego tesktu zawierała błąd powodujący to, że programy w których zaimplementowano przedstawiony algorytm w niektórych przypadkach działały nieprawidłowo. Dzięki spostrzegawczości pana KATa, błąd został poprawiony 9 grudnia 2003.