Generowanie gwiazd

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:

  1. Losowanie początkowych pozycji gwiazd
  2. Czyszczenie bitmapy (zamalowywanie na czarno)
  3. Rysowanie gwiazd na wylosowanych pozycjach (w bitmapie)
  4. Przesuwanie gwiazd w prawo
    • Jeżeli jakaś gwiazda wyleci poza bitmapę, przenieś ją na jej lewą krawędź
  5. Wyświetlenie bitmapy na ekranie
  6. 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.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *