Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

DirectX - DirectDraw

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest HNB. Jeżeli został importowany poprawnie, usuń ten szablon!

Część I

Skoro już wiemy, co chcemy możemy poczynić pierwsze kroku ku obranemu przez nas celowi-Stworzeniu profesjonalnej gry pod windowsa z wykorzystaniem Delphi, DirectX no i oczywiście WinApi(hehe:). Aby kontynuować musisz zdobyć przetłumaczone na Delphi nagłówki DirectX.

Możesz je z powodzeniem znale¥ć w Internecie. Gdy ów nagłówki zostaną przez ciebie ściągnięte możemy przystąpić do dalszej części kursu. Oki. Teraz tworzysz nowy projekt WinApi. I jak zwykle kasujesz całą zawartość (chamstwo, nie?:). Na początku piszemy coś takiego:

program ApiWin;

Tym sposobem informujemy kompilator, że będziemy pisać program(odkrywcze, nie?:), o nazwie wewnętrznej ApiWin. Po tym zabiegu dodajemy plik zasobów do naszej aplikacji. Dzięki temu zasobowi nasz program będzie miał ikonę. Tak tak teraz sami musimy o to zadbać. Najprościej taki plik z zasobami można zrobić w restoratorze, który znajdziesz w Komputer Świat Ekspert nr. 3/2003 (4). Który możesz zamówić archiwalnie pod numerem telefonu (nie będę go podawał, bo jeszcze mnie oskarżą o jakieś sztuczne promowanie tejże gazet, ale wierzcie mi, są tam naprawdę cool rzeczy). Aby wkompilować zasób do pliku *.exe używamy {$R jakiś tam plik zasobów}. Jeśli nie mamy ochoty zdobywać restoratora możemy zrobić to na dwa inne sposoby. Na pierwszy trzeba by poświęcić osobny artykuł a drugi to podstępna sztuczka. Może podebrać plik zasobów z "normalnego" projektu *.dpr(w katalogu ów projektu powinien być plik *.res). Dobra wróćmy do naszego kursu. W naszym przypadku piszemy:

{$R ikona.res}

Tym sposobem wkompilowaliśmy zasób. Następnie w sekcji uses używamy Messages(odpowiada za nazwy komunikatów), Windows i DirectDraw (chyba nie muszę tłumaczyć, do czego służą te dwa ostatnie:).

uses Messages, Windows, DirectDraw;

Deklarujemy stałe i zmienne:

const AppName = 'Mój pierwszy Dx w WinApi'; var lpDD : IDirectDraw; // Obiekt DirectDraw

A oto główny punkt programu. Inicjalizacja trybu graficznego (rozdzielczość). W tym arcie tylko tyle, ponieważ postanowiłem skupić się na WinApi. Oto jedyna procedura powiązana, z DirectX w tym arcie(zawsze coś:). Omówimy do końca WinApi a potem będziemy bawili się z narzędziami Bila(co ja pisze... Przecież windows to też jego produkt:).

procedure InitDirectDraw( h_Wnd : HWND );

h_Wnd to uchwyt do naszego okna. teraz kod naszej funkcji tradycyjnie zaczynający się od begin a kończący na end ;). Warto zwrócić uwagę, że w języku c++ mamy NULL a w Pascalu jego odpowiednikiem jest nil. Może to się bardzo przydać, jeśli będziesz coś przenosić z MSSDK microsoftu. Idąc dale dochodzimy do funkcji lpDD.SetDisplayMode, która ustawia rozdzielczość a DirectDrawCreate tworzy obiekt o jakiejś tam nazwie(konkretnie lpDD:). Pierwszy parametr to szerokość, drugi to wysokość a trzeci to ustawienie kolorów. Mamy do wyboru 8, 16, 24, 32 bitowy kolor. Im większy tym oczywiście lepszy:).

begin DirectDrawCreate(nil , lpDD, nil); lpDD.SetCooperativeLevel(h_Wnd, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN); lpDD.SetDisplayMode(320, 200, 32); end;

Nie jest to takie straszne, nie? Tak wygląda zainicjowanie trybu graficznego z wykorzystaniem DirectDrawa. Jest to równie proste jak w DelphiX czy PowerDrawie. A teraz to co programiści lubią najbardziej czyli WindowProc. Kierujemy tu wszystkimi zdarzeniami tak jak na zakładce Ewents w Vcl. Wystarczy że przeczytasz nazwy zdarzeń i wiesz o co chodzi:).

function WindowProc(Window: HWnd; AMessage: UINT; WParam : WPARAM; LParam: LPARAM): LRESULT; stdcall; export; var dc : hdc; ps : paintstruct; r : TRect; begin WindowProc := 0; case AMessage of wm_paint: begin dc:=beginPaint(Window,ps); GetClientRect(Window,r); SetTextColor(dc, COLORREF($00FF0000)); //Ustawiamy kolor DrawText(dc,'Zabójcza rozdzielczość 320x200 : )',-1,r, DT_SINGLELINE or DT_CENTER or DT_VCENTER);//Wypisujemy tekst endPaint(Window,ps); Exit; end; wm_Destroy: begin PostQuitMessage(0); Exit; end; wm_create: begin initdirectdraw(window); //INICJUJEMY!!! exit; end; end; WindowProc := DefWindowProc(Window, AMessage, WParam, LParam); end;

taaa.... Teraz musimy zarejestrować nasze okienko. Nie ma sensu bym to dogłębnie omawiał skoro mamy zamiar programować gry. I tak nie będziemy z tego praktycznie nigdy korzystać. Ważne , że jest i działa :). Tylko jedynym wartym omówienia składnikiem jest WindowClass.hIcon := LoadIcon( hInstance, MakeIntResource( 102 ) ); Nadaje ikonkę z zasobów naszemu programikowi(tą w lewym górnym rogu).

{ Rejestracja klasy naszego okna } function WinRegister: Boolean; var WindowClass: WndClass; begin WindowClass.Style := cs_hRedraw or cs_vRedraw; WindowClass.lpfnWndProc := @WindowProc; WindowClass.cbClsExtra := 0; WindowClass.cbWndExtra := 0; WindowClass.hInstance := system.MainInstance; WindowClass.hIcon := LoadIcon( hInstance, MakeIntResource( 102 ) ); WindowClass.hCursor := LoadCursor(hInstance, MakeIntResource( 100 )); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := AppName; Result := RegisterClass(WindowClass) <> 0; end;

Teraz tworzymy/kreujemy nasze okno. Parametry dla fullscrena nieco się zmienią, omówię to w następnym artykule(jak dam radę go napisać;).

{ Robimy nasze okno!!! } function WinCreate: HWnd; var hWindow: HWnd; begin hWindow := CreateWindow(AppName, 'Mój pierwszy Dx w WinApi', ws_OverlappedWindow, cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault, 0, 0, system.MainInstance, nil); if hWindow <> 0 then begin ShowWindow(hWindow, CmdShow); ShowWindow(hWindow, SW_SHOW); UpdateWindow(hWindow); end; Result := hWindow; end;

Uwaga! Uwaga! Coś nowego dla ciebie jeśli ciągle programowałeś w Vcl!!! Akcja naszego programu dzieje się między begin a end a nie w żadnej procedurze/funkcji wywoływanej co jakiś czas. Dzięki głównej pętli programu uzyskujemy maxymalną wydajność.

var AMessage: Msg; hWindow: HWnd; done : boolean; begin if not WinRegister then begin MessageBox(0, 'Błąd rejestracji', nil, mb_Ok); Exit; end; hWindow := WinCreate; if longint(hWindow) = 0 then begin MessageBox(0, 'Błąd zainicjowania okna', nil, mb_Ok); Exit; end; done := false;

Doszliśmy do sedna sprawy. PeekMessage pobiera komunikaty wysyłane przez windows, TranslateMessage przetwarza je a DispatchMessage Wysyła. Tak wygląda prawdziwa aplikacja napisana dla windowsa. Nasza pierwsza 32-bitówka(aż łezka kręci się w oku:).

while (not done) do begin PeekMessage(AMessage, hWindow, 0, 0, PM_REMOVE); TranslateMessage(AMessage); DispatchMessage(AMessage); end; Halt(AMessage.wParam); end.

Część II

Pisząc ten artykuł zastanawiałem się czy przypadnie wam do gustu. Mam nadzieję, że tak:). Więc... Na dzisiejszej lekcji zajmiemy się wyświetlaniem bitmap za pomocą DirectDraw(jej:).

Podstawową zmianą będzie użycie DirectDraw4, aby nasz program działał już na komputerach z DirectX6(o ile ktoś takie jeszcze ma:). Wyświetlenie bitmapy nie jest trudne. Wystarczy zadeklarować odpowiednie zmienne. Ale zanim do tego przystąpimy nauczymy się obsługi klawiatury z WinApi. Zaczynamy od zadeklarowania odpowiedniej zmiennej :

var klawisz : array [1..256] of boolean;

Tablica ta będzie nas informować które klawisze są aktualnie wciśnięte a które nie:P. Korzystając z tej techniki nie potrzebujemy już DirectInput (hehe... : ] ). To powinno zaspokoić nasze potrzeby. Następnym etapem jest uwzględnienie odpowiednich zdarzeń w WndProc( w poprzednim artykule zwało się to WindowProc). Chodzi mi o zdarzenia już wcześniej wam znane z zakładki Events w Object Inspektorze - KeyUp i KeyDown. Ich odpowiedniki będą wyglądać tak(uważaj na mWParam w poprzednim trutorialu :) nazywał się on WParam) :

Kod: WM_KEYDOWN: // użytkownik nacisnął klawisz? begin klawisz[mWParam] := true; result := 0; exit; end; WM_KEYUP: // użytkownik odcisnął klawisz ;)? begin klawisz[mWParam] := false; result := 0; exit; end;

To wszystko. Teraz w głównej pętli możemy sobie kontrolować stany klawiszy(true i false). Numer komórki tablicy odpowiada kodowi klawisza który jest zgodny z kodem ASCII(tródno żeby nie był: )Jeśli jakiś klawisz został wciśnięty to informacja o tym trafi do naszej jakże wspaniałej tablicy. Pewnie zapytasz : Czemu opisuje tutaj WinApi?. Ja odpowiem : Ponieważ szanowny pan Nakiel(główny wodzu) nie przewidział działu dla WinApi : ]. Dobra. Teraz czas na zmiany. Nasze programiki będą(są) coraz bardziej obszerne więc należało by je podzelić(skawałkować : ). Stworzymy pewne Unity, których póki co liczba wyniesie 3 :
MDirectDraw.pas - Odpowiada za różne rzeczy związane z DirectDraw(od inicjalizacji po wyświetlanie).
MWinApi.pas - Różne procedury i funkcje powiązane z WinApi(np. WindowProc).
Zmienne.pas - Jak sama nazwa wskazuje będą tam głównie zmienne.
Możesz je sobie ściągnąć(z całą resztą:) u dołu tego artykułu(no bo niby gdzie indziej : ). Czy to koniec??? Na pewno nie!!! Przed nami jeszcze daleka kręta(heh:) droga. Teraz nastał czas na zadeklarowanie nowych zmiennych powiązanych z DirectDraw.

var //DirectX kontekst : HDC; // kontekst urządzenia dds_hdc : HDC; // HDC(kontekst) buforu hdcmem : HDC; // kolejny kontekst ;) hbmp : HBITMAP; // bitmapa lpDDSEkran : IDIRECTDRAWSURFACE4;// nasza główna powierzchnia lpDD : IDirectDraw; // obiekt DirectDraw lpDD4 : IDirectDraw4; // obiekt DirectDraw 4

Teraz czas na funkcję inicjującą DirectDraw. Tu dużo nam przybyło. Sami zobaczcie :

function InitDirectDraw( h_Wnd : HWND ) : HRESULT; var ddsd : TDDSURFACEDESC2; begin DirectDrawCreate(nil, lpDD, nil); //Tworzy obiekt DirectDraw który zwie się lpDD lpDD.QueryInterface(IID_IDirectDraw4,lpDD4); //Pobieramy interface DirectDraw4 lpDD4.SetCooperativeLevel(h_Wnd, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN); lpDD4.SetDisplayMode (width, height, bits, 0, 0); //rozdzielczość ZeroMemory(@ddsd,sizeof(ddsd)); //zerujemy pamięć dla ddsd ddsd.dwSize := sizeof(ddsd); ddsd.dwFlags := DDSD_CAPS; //takie tam bajery które muszą być ;) ddsd.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE; //Odnosi się do naszej głównej powierzchni //ok:) lpDD4.CreateSurface(ddsd,lpDDSEkran, nil); hdcmem := CreateCompatibleDC(0); //"robimy" drugi kontekst hbmp:=LoadImage(hInstance, 'tlo.bmp', IMAGE_BITMAP, 800, 600, LR_LOADFROMFILE); //wczytujemy bitmapę SelectObject(hdcmem, hbmp); //kopiowanie zawartości end;

A oto procedura wyświetlająca. Umieszczamy ją w głównej pętli.

procedure Render; begin lpDDSEkran.GetDC(kontekst);//Pobieramy kontekst urządzenia, bo zaraz go użyjemy BitBlt(kontekst,0,0,800,600,hdcmem,0,0,SRCCOPY); //Kopiujemy zawartość kontekstu 'hdcmem' do kontekstu 'kontekst' - czyli wyświetlmy obrazek lpDDSEkran.ReleaseDC(kontekst); //Zwalniamy główny kontekst end;

To wszystko. Wyświetlanie bitmap w DirectDraw nie jest trudne(jak widać:).

Na następnej lekcji omówię buforowanie(żeby nie mrugało : ).

Część III

W tym artykule zajmiemy się buforowaniem. Buforujemy po to żeby nam ekran nie mrugał. Nie będę się wdawać w szczegóły.

Ważne, że działa : P i tyle; ). Więc tradycyjnie zaczniemy od zdefiniowania nowych zmiennych.

var lpDDSBufor : IDIRECTDRAWSURFACE4;//bufor dds_hdc : HDC;//HDC buforu x, y : integer//położenie obrazka hgdi : HGDIOBJ;

Teraz zajmiemy się obsługą klawiatury. Kod ten powinniśmy umieścić w głównej pętli naszego programu. Odpowiada on za sterowanie naszym obrazkiem.

//klawisze if (klawisz[VK_ESCAPE] = true) then done := true; if (klawisz[VK_LEFT] = true) and (x > 1) then dec(x,2); if (klawisz[VK_RIGHT] = true) and (x < 799-100) then inc(x,2); if (klawisz[VK_UP] = true) and (y > 1) then dec(y,2); if (klawisz[VK_DOWN] = true) and (y < 599-100) then inc(y,2);

To tyle jeśli chodzi o sterowanie. Procedura inicjująca także różni się znacząco. Koło nowych rzeczy umieściłem niestosowny komentarz : ).

function InitDirectDraw( h_Wnd : HWND ) : HRESULT; var ddsd : TDDSURFACEDESC2; begin DirectDrawCreate(nil, lpDD, nil); lpDD.QueryInterface(IID_IDirectDraw4,lpDD4); lpDD4.SetCooperativeLevel(h_Wnd, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN); lpDD4.SetDisplayMode (width, height, bits, 0, 0); ZeroMemory(@ddsd,sizeof(ddsd)); ddsd.dwSize := sizeof(ddsd); //dodajemy do flagi bufor(informujemy o wykorzystaniu buforu) ddsd.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX; ddsd.dwBackBufferCount := 1; //powierzchnia posiada jeden bufor //ok:) lpDD4.CreateSurface(ddsd,lpDDSEkran, nil); //takie tam ;) ddsd.ddsCaps.dwCaps := DDSCAPS_BACKBUFFER; lpDDSEkran.GetAttachedSurface(ddsd.ddsCaps, lpDDSBufor); //to poniżej jest chyba zrozumiałe hdcmem := CreateCompatibleDC(0); hbmp:=LoadImage(hInstance, 'buzia.bmp', IMAGE_BITMAP, 100, 100, LR_LOADFROMFILE); hgdi:=SelectObject(hdcmem, hbmp); end;

Ufffffff...... W następnej lekcji wszystko będzie jeszcze bardziej pokręcone, więc się nie martw : ]. Inicjalizacje DirectDraw nie jest taka trudna. Te funkcje render są trochę oszukane ale dowiesz się o tym w następnej lekcji. Teraz zajmijmy się naszą jakże oszukaną :P funkcją Render(czy tam procedurą; ).

procedure Render; var r : TRect; begin hgdi:=SelectObject(hdcmem, hbmp); r.Left := 0; r.Top := 0; r.Right := 800; r.Bottom := 600; lpDDSBufor.GetDC(dds_hdc); FillRect(dds_hdc,r,2); //rysujemy kwadrat BitBlt(dds_hdc,x,y,100,100,hdcmem,0,0,SRCCOPY); lpDDSBufor.ReleaseDC(dds_hdc); SelectObject(hdcmem,hgdi); end;

Jak widać wyświetlenie kwadratu nie jest trudne, jak i samo buforowanie. Ów kwadrat rysujemy aby wyczyścić ekran. A.... Byłbym zapomniał po procedurze render która oczywiście znajduje się w głównej pętli umieszczamy coś takiego :

lpDDSEkran.Flip(nil,DDFLIP_WAIT);

Te procedura wyświetla nam na ekranie Scenę(zresztą sama nazwa Flip wskazuje na to). Bez tego istotnego elementu ujrzymy czarny ekran, i nici z naszej pracy, he, he ; ). Na następnej lekcji żeby narobić wam smaczku powiem, że zajmiemy się rysowaniem bitmap z parametrem transparent, czyli z przezroczystością.

Część IV - Kompendium

Podsumowanie dotychczasowych lekcji DirectDraw + nowe wiadomości.

Witam w kursie ciągnącym się niczym brazylijski serial ; ). Po dość długiej przerwie wreszcie piszę ten artykuł. Zniknąłem z rynku ; ) ponieważ wszedłem w ważny etap życia programisty. Odkrywałem OpenGL jedynie na modułach OpenGL i GL. Głowiłem się jak stworzyć własne formaty bitmap z kompresją RLE, stworzyć 2D w OpenGL, odgrywać muzykę w DirectMusic i DirectSound, odgrywać mp3 w DirectShow, pobierać stan klawiatury przy użyciu DirectInput, no i zajmowałem się Direct3D jak i DirectDraw, poznawałem jeszcze Windows Api itp. itd. Możecie mi wierzyć-na to wszystko idzie mnóstwo czasu. Teraz będę pisać troszkę artykułów co jakiś czas, abyście wy nie musieli tracić tyle czasu na szukanie tego co ja znalazłem i wymyśliłem. Mogę z całą pewnością stwierdzić, iż wszedłem w etap wyżej średniozaawansowany (będziecie mieli od kogo się uczyć : P). Tylko muszę uważać by nie popaść w samouwielbienie ; ).

Przejdźmy jednak do rzeczy. W końcu piszę ten artykuł o DirectDraw a nie o jakichś tam innych interfejsach czy też o mnie. Poprzednie kursy pisałem w oparciu o kurs który był zamieszczony na warsztacie (konwertowałem z C++ na Delphi [no i zmieniałem treść :]). Teraz muszę stwierdzić, że był to mój ogromny błąd. DirectDraw zainicjowane w ten sposób "się krzaczy". Przykład??? Pisałem prezentację o hałasie (oczywiście ja robiłem wszystko a moi kumple się obijali : ( ) w Delphi z użyciem DirectDraw i FMODa. Wszystko pięknie działało... Do czasu. Nauczycielka puściła prezentację na jednym kąpie by pokazać innej klasie moje "dokonanie", gdy przy końcu wyłączyła się ale na dobre, komputer się popsuł, a obok mojej szóstki w dzienniku stanęła jedynka... Ehhhh... Raz na wozie raz pod wozem. Być może to prze FMOD. Są oczywiście inne sytuacje w których już wspomniane "krzaczenie" ujawnia się.

Zamiast wymieniać błędy poprzedniego rozwiązania zajmijmy się nowymi. Musimy napisać od nowa także obsługę windows.

Windows API (ale to już było... : )

Z reguły pisząc gry mamy zamiar wyświetlić je na pełnym ekranie a nie na jakimś tam przeklętym formularzu znanym nam doskonale z Vcl. Nowa funkcja WinInit wygląda teraz dużo lepiej : function WinInit( h_Inst : THANDLE; nCmdShow : Integer; var phWnd : HWND; var phAccel : HACCEL ) : HRESULT; var h_Wnd : HWND; wc : TWNDCLASS; h_Accel : HACCEL; begin // Rejestrowanie klasy okna wc.lpszClassName := 'nazwa'; wc.lpfnWndProc := @WindowProc; wc.style := CS_VREDRAW or CS_HREDRAW; wc.hInstance := h_Inst; wc.hIcon := 0;//LoadIcon( h_Inst, MakeIntResource( IDI_MAIN_ICON ) ); wc.hCursor := LoadCursor( 0, IDC_ARROW ); wc.hbrBackground := ( COLOR_WINDOW + 1 ); wc.lpszMenuName := nil; wc.cbClsExtra := 0; wc.cbWndExtra := 0; if ( RegisterClass( wc ) = 0 ) then begin result := E_FAIL; exit; end; // Wczytanie skrótów klawiatórowych h_Accel := LoadAccelerators( h_Inst, MakeIntResource( 0 ) ); // Stworzenie i pokazanie okna h_Wnd := CreateWindowEx( 0, 'nazwa', // Tu musi być tak samo jak w wc.lpszClassName 'tytul', WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, h_Inst, nil ); if ( h_Wnd = 0 ) then begin result := E_FAIL; exit; end; ShowWindow( h_Wnd, nCmdShow ); UpdateWindow( h_Wnd ); phWnd := h_Wnd; phAccel := h_Accel; result := DD_OK; end;

Jedyny problem może sprawić nowa funkcja LoadAccelerators. Wczytuje ona skróty klawiaturowe z zasobów. Bez tego zasobu (musimy go sami dołączyć) nie będzie działać Alt+F4 (co może być zaletą : ) jak i Escape. Jeśli olejemy ten zasób aplikacja nie będzie na nic reagować, po prostu nas "zablokuje". Jeśli wyjątkowo uprzemy się by go nie dawać musimy obsłużyć "ręcznie" klawiaturę (pamiętasz tablicę wciśnięć klawiszy???). Więcej na ten temat znajdziesz w poprzednich artykułach o DirectDraw. Ten zasób możesz ściągnąć wraz z przykładami do tego artykułu u dołu strony. Dla porządku wyjaśnię czym jest zwracana wartość przez tą funkcję. Otóż jest to HRESULT. W DirectDraw, jeśli wszystko idzie dobrze funkcje zwracają wartość DD_OK. Tak dla porządku ta funkcja choć lu¥no związana z DirectDraw powinna stosować się do reguły. Teraz czas pokazać wam naszą główną okienkową funkcję.

function WindowProc( h_Wnd : HWND; aMSG : Cardinal; wParam : Cardinal; lParam : Integer ) : Integer; stdcall; begin case aMSG of // Pause if minimized WM_COMMAND : begin case LOWORD( wParam ) of IDM_EXIT : begin // obsługa skrótów klawiaturowych PostMessage( h_Wnd, WM_CLOSE, 0, 0 ); result := 0; exit; end; end; end; WM_SETCURSOR : begin // Schowanie kursora SetCursor( 0 ); result := 1; exit; end; WM_SIZE : begin // Sprawdzenie czy okienko się skurczyło... if ( wParam = SIZE_MAXHIDE ) or ( wParam = SIZE_MINIMIZED ) then g_bActive := FALSE else g_bActive := TRUE; end; WM_EXITMENULOOP : begin // Ignoruje "zmarnowany" czas w menu g_dwLastTick := GetTickCount; end; WM_EXITSIZEMOVE : begin // Ignoruje "zmarnowany" czas podczas zmiany rozmiaru okna g_dwLastTick := GetTickCount; end; WM_SYSCOMMAND : begin // Zapobiega zmianie rozmaru okna i wyłączeniu monitora w trybie pełnoekranowym case wParam of SC_MOVE, SC_SIZE, SC_MAXIMIZE, SC_MONITORPOWER : begin result := 1; exit; end; end; end; WM_DESTROY : begin // Posprzątanie po aplikacji FreeDirectDraw; PostQuitMessage( 0 ); result := 0; exit; end; end; Result := DefWindowProc( h_Wnd, aMSG, wParam, lParam ); end;

Nieco nam się to skomplikowało. Większość da się zrozumieć (nazwy mówią same za siebie). Wyjaśnienia zapewne może wymagać zmienna g_dwLastTick. Zmienna ta jest niezbędna by działał zegar naszej aplikacji (coś w stylu timera tyle, że lepsze). Ale o tym za chwilkę...

Timer naszej aplikacji

To jest coś. Grafika nie będzie działać szybciej na szybszych komputerach (o to w drugą stronę nieco trudniej ; ). Wszystko będzie działać w należytym tempie. Jednak zanim przejdziemy do odmierzania czasu przyjrzyjmy się bliżej głównej pętli programu.

while ( true ) do begin // Pobierz komunikaty. if ( PeekMessage( aMSG, 0, 0, 0, PM_NOREMOVE ) ) then begin if not GetMessage( aMSG, 0, 0, 0 ) then // WM_QUIT - zamykamy... exit; // przetwórz i wyślij komunikaty if ( TranslateAccelerator( h_Wnd, h_Accel, aMsg ) = 0 ) then begin TranslateMessage( aMsg ); DispatchMessage( aMsg ); end; end else begin if ( g_bActive ) then begin // Narysowanie grafiki hr := ProcessNextFrame( h_Wnd ); if ( hr <> DD_OK ) then begin g_pDisplay := nil; if ( hr = E_NOTIMPL ) then begin MessageBox( h_Wnd, 'Masz pecha...' + #13#10 + 'Nastąpi wyjście z programu.', 'Błąd', MB_ICONERROR or MB_OK ); end else begin //DXTRACE_ERR( 'ProcessNextFrame', hr ); MessageBox( h_Wnd, 'Nie udało się narysować następnej klatki.' + #13#10 + 'Nastąpi wyjście z programu.', 'Błąd', MB_ICONERROR or MB_OK ); end; exit; end; end else begin // Id¥ spać jeśli nie masz nic do roboty ; ) WaitMessage; // Ignoruj czas g_dwLastTick := GetTickCount; end; end; end;

Oczywiście przed główną pętlą wykonujemy funkcję:

//Zainicjowanie okna windows if WinInit(hInstance, SW_SHOW, h_Wnd, H_Accel) <> DD_OK then TWOJA OBS£UGA B£ęDU; Nie można zapomnieć o zmiennych : var //////Zmienne Windows h_Wnd : HWND; // uchwyt okna h_Accel : HACCEL; //uchwyt klawiatury aMSG : MSG; g_bActive : Boolean = False; // Aplikacja jest aktywna? g_dwLastTick : DWord; hr : HRESULT;

Małymi kroczkami zbliżamy się ku końcowi. Teraz czas zaprezentować funkcję występującą w głównej pętli programu : ProcessNextFrame. To w niej właśnie rysujemy grafikę i bawimy się naszym stoperem.

function ProcessNextFrame : HRESULT; var dwCurrTick : DWord; dwTickDiff : DWord; begin // Jak dużo czasu upłunęło od ostatniego czasu dwCurrTick := GetTickCount; dwTickDiff := dwCurrTick - g_dwLastTick; // Nie uaktualnij jeśli nie powinieneś if ( dwTickDiff = 0 ) then begin result := DD_OK; exit end; g_dwLastTick := dwCurrTick; // Wyświetlenie sceny na ekranie hr := DisplayFrame; if ( hr <> DD_OK ) then begin if ( hr <> DDERR_SURFACELOST ) then begin //Zwolnienie zasobów w razie niepowodzenia FreeDirectDraw; //DXTRACE_ERR( 'DisplayFrame', hr ); result := hr; exit; end; // Jeśli surfaces został utracony należy go zresetować RestoreSurfaces; end; result := DD_OK; end;

Nasz regulator szybkości znajduje się na początku. Jeśli przyjrzysz mu się bliżej to na pewno go zrozumiesz. W dalszej części funkcji znajdują się dwie nowe funkcje : DisplayFrame i RestoreSurfaces. Są one ściśle powiązane z DirectDraw więc omówię je za "zakrętem".

DirectDraw - Inicjacja

Teraz zaczynam omawiać DirectDraw - "Wreszcie!!!" - pewnie powiesz. Po tylu nudnych rzeczach związanych z WinApi wreszcie coś ciekawego... Heh... Teraz będzie to bajecznie proste (w porównaniu do inicjacji z poprzednich lekcji). Czemu??? A to dzięki modułowi DDUtils (wszystko jest dołączone do przykładu który możesz ściągnąć u dołu strony). Jest on bajeczny (no prawie :). Zawiera dwie bardzo przyjazne klasy, które aż proszą się o użycie. Co prawda rozmiar naszego "programiku" wzrośnie do 90KB, to i tak można to przeboleć. Klasy te zwalniają nas od konieczności babrania się w tym całym bagnie nazwanym DirectDraw, za cenę kilku kilobajtów (Tylko nie kojarz tego z Vcl i DelphiX).

Pierwszą rzeczą którą robimy jest dołączenie modułu DDUtil w sekcji uses. Następnym krokiem jest zadeklarowanie zmiennej :

var g_pDisplay : TDisplay;

Jest to nasz swoisty "engin" DirectDraw. Teraz przejd¥my do zmiennych przechowujących grafikę (pojedynczą lub animację) :

var grafika : TSurface;

To nie będzie takie bolesne ; ). Do tworzenia w DirectDraw będziemy się posługiwać tylko dwiema zmiennymi !!! A to wszystko dzięki tym dwóm klasom zawartym w module DDUtil. Przejd¥my do inicjacji trybu graficznego.

g_pDisplay := TDisplay.Create; hr := g_pDisplay.CreateFullScreenDisplay( h_Wnd, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP); //Jeśli hr jest różne od DD_OK to znaczy, że wystąpił //błąd. if ( hr <> DD_OK ) then begin MessageBox( h_Wnd, 'Twoja karta graficzna nie obsługuje takiej rozdzielczości!!!', 'Błąd', MB_ICONERROR or MB_OK ); exit; end;

To tylko tyle. W sumie wystarczyły by nam tylko trzy pierwsze linijki !!! Ale obsługa błędów musi być ; ). Wracając do linijek to... Pierwsza linijka tworzy (inicjuje) naszą zmienną/klasę. W trzeciej natomiast inicjujemy tryb graficzny. Wynik tej inicjalizacji nadajemy zmiennej hr. Pierwsza zmienna to uchwyt naszej aplikacji (np. form1.handle) druga, szerokość ekranu, trzecia to wysokość a czwarta to liczba bitów. Przejd¥my teraz do obsługi błędu. Powiem tu tylko jedno. Jeśli coś jest nie tak to zawsze zwracany wynik jest różny od DD_OK (tak jest ze wszystkim w DirectDraw). To wszystko na temat inicjowania DD.

Teraz po krótce omówię wczytywanie bitmap które jest po prostu banalną czynnością.

g_pDisplay.CreateSurfaceFromBitmap( grafika, PAnsiChar( 'm.bmp' ), 120, 120 );

Mój komentarz jest tu chyba zbędny...

DirectDraw - Wyświetlanie

Teraz przypomnij sobie pewną funkcję z funkcji ProcessNextFrame była tam taka jedna funkcja...

function DisplayFrame : HRESULT; begin if g_pDisplay = nil then begin result := DD_OK; exit; end; // Czyścimy ekran g_pDisplay.Clear( $FFFFFF ); // Wyświetlamy bitmapkę w buforze g_pDisplay.Blt( 0, 0, grafika, nil ); // Wyświetlamy zawartość buforu na ekranie hr := g_pDisplay.Flip; if ( hr <> DD_OK ) then begin result := hr; exit; end; result := DD_OK; end;

DirectDraw - Reset Surface

Czasami coś pójdzie nie tak i trzeba zresetować to badziewie (gdzie togo użyć pisałem wyżej [timer]). Oto i kod :

function RestoreSurfaces : HRESULT; begin hr := g_pDisplay.GetDirectDraw.RestoreAllSurfaces; if ( hr <> DD_OK ) then begin //DXTRACE_ERR( TEXT("RestoreAllSurfaces"), hr ); result := hr; exit; end; result := DD_OK; end;

DirectDraw - Kluczowanie koloru

Zapewne rysowane przez nas grafiki nie są wyłącznie kształtu kwadratu. Mogą być takie i owakie ; ). Rysujemy sobie powiedzmy ludzika na białym tle. Nie chcemy tego białego tła. I właśnie tu z pomącą przychodzi nam kluczowanie koloru. Podajemy naszej zmiennej "graficznej" by ignorowała ten kolor. Oto przykładowy kod ( pomija kolor czarny ) :

// Pomijaj kolor czarny hr := grafika.SetColorKey( 0 ); if ( hr <> DD_OK ) then begin //Twoja obsługa błędu end;

Najlepiej "kluczować" tuż po wczytaniu bitmapy z pliku.

DirectDraw - Animacja

Hoho... Bez tego chyba nikt nie chciał by grać w gry! Chyba nie muszę tłumaczyć co to animacja ; )? Kłopot zaczyna się już przy wczytywaniu :). Kod będzie wyglądać mniej więcej tak...

g_pDisplay.CreateSurfaceFromBitmap( grafika, PAnsiChar( 'm.bmp' ), szerokość klatki * liczba kolumn, wysokość klatki * liczba wierszy );

Rozumiesz? Może dam ci obraz dla takiego kodu :

g_pDisplay.CreateSurfaceFromBitmap( grafika, PAnsiChar( 'animate.bmp' ), 32 * 5, 32 * 6 );

A to jest plik animate.bmp :

Przejdźmy do wyświetlenia takiego potworka...

g_pDisplay.Blt( 0, 0, grafika, @myrect);

Pierwsze dwa parametry to pozycja (x,y). Drugi to zmienna z naszą grafiką. Natomiast trzeci to "kwadrat" wycięty z tej grafiki i wyświetlony na ekranie. myrect to zmienna typu TRect;. Dla uproszczenia możemy napisać swoją procedurę wyświetlającą bitmapkę. Na początku musimy stworzyć pewien rekord.

type TAnimacja = record SzeokoscAnim : integer; WysokoscAnim : integer; LKolumn : integer; LWierszy : integer; grafika : TSurface; end;

Następnie deklarujemy stałe z definicją rozdzielczości w której pracujemy :

const rWidth : integer = 640; rHeight : integer = 480; rBits : integer = 32;

oczywiście wartości te możesz modyfikować :). Zadeklarujmy nową zmienną :

var animacja : TAnimacja;

Teraz musimy napisać nową procedurę ładującą grafikę (praca od korzeni : D ).

procedure LoadAnimation(dir : string; SzerokoscKlatki, WysokoscKlatki, LiczbaKolumn, LiczbaWierszy : integer; var animacja : TAnimacja); begin animacja.SzerokoscAnim := SzerokoscKlatki; animacja.WysokoscAnim := WysokoscKlatki; animacja.LKolumn := LiczbaKolumn; animacja.LWierszy := LiczbaWierszy; g_pDisplay.CreateSurfaceFromBitmap( animacja.grafika, PAnsiChar(dir), SzerokoscKlatki * LiczbaKolumn, WysokoscKlatki * LiczbaWierszy ); end;

A teraz przejdźmy do sedna sprawy-wyświetlenia klatki animacji :

procedure DrawFrame(x, y : integer; animacja : TAnimacja; Klatka : integer); var myrect : TRect; begin if (x+animacja.SzerokoscAnim < 0) then exit; if (x > rWidth) then exit; if (y+animacja.WysokoscAnim < 0) then exit; if (y > rHeight) then exit; myrect.left := (klatka mod animacja.LKolumn)*animacja.SzerokoscAnim; myrect.top := (klatka div animacja.LKolumn)*animacja.WysokoscAnim; myrect.right := myrect.left + animacja.SzerokoscAnim; myrect.bottom := myrect.top + animacja.WysokoscAnim; g_pDisplay.Blt( x, y, animacja.grafika, @myrect); end;

DirectDraw - Zwalnianie zasobów

Nikt nie lubi sprzątać, ale niestety trzeba. Oto i kod...

procedure FreeDirectDraw; begin animacja.grafika.Free; animacja.grafika := nil; grafika.Free; grafika := nil; g_pDisplay.Free; g_pDisplay := nil; end;

DirectDraw - Podsumowanie

Uffffffff........ To chyba wszystko co trzeba wiedzieć na temat wyświetlania grafiki by napisać swoją fajną gierkę 2D. Trochę długaśny ten artykuł...

Tekst dodał:
Adam Sawicki
31.03.2006 22:18

Ostatnia edycja:
Adam Sawicki
31.03.2006 22:18

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#1 edytuj 31.03.2006 22:18 Adam Sawicki 33.91 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)