DirectDraw

  Niemalże każdy kto miał lub ma do czynienia z komputerem, wcześniej lub puźniej zetknie się z grami i prawie każdemu zamarzy się by stworzyć własną grę. Tworzenie gier jest znacznie trudniejsze od tworzenia programów użytkowych, ale nie jest niemożliwe. W zasadzie osoby programujące w C++ są w znacznie lepszej sytuacji od programujących w innych językach, ponieważ (jak gdzieś przeczytałem) jeśli chcemy żeby coś działało naprawdę szybko, trzeba to napisać w C++ lub w asambleże. Tak więc skoro już potraficie programować w C++ to nie musicie się uczyć wszystkiego od początku. Prawie wszystkie gry z którymi się stykamy korzystają z bibliotek DirectX, dlatego ten dział jest poświęcony programowaniu z wykorzystaniem DirectDraw, który wchodzi w skład biblioteki DirectX.
DirectDraw jest wykorzystywany w grach 2D. Jednakże ten dział nie jest poświęcony tworzeniu gier, lecz moim zamiarem jest przybliżenie osobom początkującym specyfikacji DirectDraw. Dział jest prezentowany w formie lekcji, i w każdej z nich stopniowo będziemy tworzyć coraz bardziej zaawansowane programy wykorzystujące DirectDraw.

UWAGA!!! Jeżeli podczas kompilowania projektu wyskakuje komunikat: Unresolved external "DirectDrawCreate", lub podobny, to należy dołączyć do projektu plik ddraw.lib.

pobierz bibliotekę ddraw.lib

Menu



Lekcja 1. Wyświetlanie grafiki w trybie DirectDraw.

  W tej lekcji pokażę jak za pomocą DirectDraw załadować z pliku grafikę w formacie bmp i wyświetlić ją na formularzu. Żeby w C++ Builder skorzystać z biblioteki DirectDraw należy dołączyć do projektu plik ddraw.h. W tym celu w pliku nagłówkowym (np. Unit1.h) w sekcji include dodajemy wpis: #include <ddraw.h>:

#ifndef Unit1H
#define Unit1H
//--------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ddraw.h> // <--


Następnie w sekcji private pliku nagłówkowego deklarujemy DirectDraw o nazwie lpDD (nazwa dowolna):

private:
        LPDIRECTDRAW            lpDD; // <--


Potem deklarujemy powierzchnię podstawową o nazwie lpDDEkran (nazwa dowolna). Powierzchnia jest to miejsce w pamięci komputera w którym przechowywany jest obraz:

private:
        LPDIRECTDRAW            lpDD;
        LPDIRECTDRAWSURFACE     lpDDSEkran; // <--


Teraz deklarujemy kontekst urządzenia o nazwie kontekst (nazwa dowolna):

private:
        LPDIRECTDRAW            lpDD;
        LPDIRECTDRAWSURFACE     lpDDSEkran;
        HDC                     kontekst; // <--


Następnie tworzymy deklarację funkcji Start która posłuży nam do uruchomienia DirectDraw (nazw funkcji jest dowolna):

private:
        LPDIRECTDRAW            lpDD;
        LPDIRECTDRAWSURFACE     lpDDSEkran;
        HDC                     kontekst;
        void __fastcall Start(void); // <--




  • Plik nagłówkowy Unit1.h: (BCB4)

    //--------------------------------------------------------
    #ifndef Unit1H
    #define Unit1H

    //--------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include <ddraw.h>

    //--------------------------------------------------------
    class TForm1 : public TForm
    {
    __published:
            void __fastcall FormPaint(TObject *Sender);
            void __fastcall FormKeyDown(TObject *Sender, WORD &Key,
              TShiftState Shift);
            void __fastcall FormDestroy(TObject *Sender);
    private:
            void __fastcall Start(void);

            LPDIRECTDRAW            lpDD;
            LPDIRECTDRAWSURFACE     lpDDSEkran;
            HDC                     kontekst;
    public:
            __fastcall TForm1(TComponent* Owner);
    };
    //--------------------------------------------------------
    extern PACKAGE TForm1 *Form1;
    //--------------------------------------------------------
    #endif




      Przechodzimy do pliku pliku żródłowego (np. Unit1.cpp) i tworzymy definicję prototypu funkcji Start (prototyp tej funckcji umieściliśmy w pliku nagłówkowym Uni1.h):

    void __fastcall TForm1::Start(void)
    {

    }


    Na razie funkcja jest pusta i nic nie robi, ale zaraz to zmienimy. Na początku deklarujemy wewnątrz funkcji kontekst urządzenia o nazwie hdcmem (nazwa dowolna). Wcześniej w pliku nagłowkowym zadeklarowaliśmy już kontekst urządzenia o nazwie kontekst. Obydwa obiekty czyli hdcmem i kontekst są tego samego typu, lecz mają różne przeznaczenie. Obiekt kontekst to uchwyt do powierzchni lpDDSEkran, natomiast hdcmem jest uchwytem do bitmapy hbmp, którą również deklarujemy wewnątrz funkcji:

    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem; // <--
     HBITMAP hbmp; // <--
    }


    Następnie tworzymy obiekt DirectDraw o nazwie lpDD, po czym informujemy go jak ma współpracować z naszym programem, potem ustawiamy rozdzielczość ekranu i głębię kolorów. W podanym przykładzie wybrałem rozdzielczość 1024x768 z 16 bitową głębią kolorów. Wybrana rozdzielczość ekranu i głębia kolorów nie muszą odpowiadać rzeczywistym rozmiarom wczytywanej bitmapy. W dalszej części kodu dostosujemy rozmiary bitmapy do rozmiarów ekranu, ale o tym potem:

    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL); // <--

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN); // <--
     lpDD->SetDisplayMode(1024, 768, 16); // <--
    }


    Teraz deklarujemy opis powierzchni ddsd (nazwa dowolna), na której będziemy wyświetalć grafikę, po czym zerujemy zakres pamięci wykorzystywany przez ten opis. Następnie ustawiamy właściwość dwSize tak, żeby miała rozmiar obiektu ddsd, potem włączamy flagę informującą o tym, że wykorzystujemy struktury DDSD_CAPS oraz DDSD_BACKBUFFERCOUNT. Potem informujemy o tym, że opis powierzchni ddsd odnosi się do powierzchni podstawowej, a następnie ustawiamy liczbę tylnych buforów:

    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd; // <--
     ZeroMemory(&ddsd, sizeof(ddsd)); // <--
     ddsd.dwSize = sizeof(ddsd); // <--
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; // <--
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX; // <--
     ddsd.dwBackBufferCount = 1; // <--
    }


    Teraz tworzymy powierzchnię podstawową lpDDSEkran z wykorzystaniem opisu powierzchni ddsd. Następnie tworzymy drugi kontekst hdcmem o takiej strukturze jak kontekst podstawowy - kontekst. Potem ładujemy do obiektu hbmp plik graficzny w formacie bmp, po czym kopiujemy zawartość hbmp do kontekstu hdcmem. Następnie pobieramy kontekst urządzenia kontekst i umieszczamy go na powierzchni lpDDSEkran po czym kopiujemy zawartość kontekstu hdcmem i umieszczamy go w kontekście - kontekst i tutej następuje wyświetlenie grafiki. Po tym usuwamy obiekty hbmp i hdcmem i zwalniamy główny kontekst:

    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd;
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL); // <--

     hdcmem = CreateCompatibleDC(NULL); // <--
     hbmp = (HBITMAP)LoadImage(Handle, "test.bmp", IMAGE_BITMAP, 1024, 768, LR_LOADFROMFILE); // <--
     SelectObject(hdcmem, hbmp); // <--

     lpDDSEkran->GetDC(&kontekst); // <--
     BitBlt(kontekst, 0, 0, 1024, 768, hdcmem, 0, 0, SRCCOPY); // <--

     DeleteObject(hbmp); // <--
     DeleteDC(hdcmem); // <--

     lpDDSEkran->ReleaseDC(kontekst); // <--
    }


      Funkcja Start jest już gotowa do wykorzystania, trzeba ją tylko zainicjować. Dokonamy tego w zdarzeniu OnPaint formularza Form1. Nie próbujcie inicjować funkcji wewnątrz innych zdarzeń bo prawdopodobnie nie zadziała prawidłowo:

    void __fastcall TForm1::FormPaint(TObject *Sender)
    {
     Start();
    }


    Po uruchomieniu programu ekran wypełni wybrana przez nas grafika i nie będzie widać żadnych przycisków umożliwiających zamknięcie programu. Dlatego w zdarzeniu OnKeyDown formularza Form1 umieścimy kod kończący działanie programu po wciśnięciu przycisku Escape:

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     if(Key == VK_ESCAPE)
        Close();
    }


    Na zakończenie w zdarzeniu OnDestroy formularza Form1 umieszczamy kod przywracający taką rozdzielczość ekranu, jaka była przed uruchomieniem programu oraz usuwamy obiekt lpDD:

    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
     lpDD->RestoreDisplayMode();
     lpDD->Release();
    }




  • Plik źródłowy Unit1.cpp: (BCB4)

    //--------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop

    #include "Unit1.h"
    //--------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //--------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
     lpDD = NULL;
    }
    //--------------------------------------------------------
    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd;
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL);

     hdcmem = CreateCompatibleDC(NULL);
     hbmp = (HBITMAP)LoadImage(Handle, "test.bmp", IMAGE_BITMAP, 1024, 768, LR_LOADFROMFILE);
     SelectObject(hdcmem, hbmp);

     lpDDSEkran->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 1024, 768, hdcmem, 0, 0, SRCCOPY);

     DeleteObject(hbmp);
     DeleteDC(hdcmem);

     lpDDSEkran->ReleaseDC(kontekst);
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormPaint(TObject *Sender)
    {
     Start();
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     if(Key == VK_ESCAPE)
        Close();
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
     lpDD->RestoreDisplayMode();
     lpDD->Release();
    }
    //--------------------------------------------------------



    ...i to w zasadzie koniec pierwszej lekcji. Jeśli nie wszystko zrozumiałeś(aś) to nie przejmuj się tym, to normalne. Zrozumienie przyjdzie z czasem.

    ...powrót do menu.



    Lekcja 2. Wyświetlanie grafiki w trybie DirectDraw z wykorzystaniem dwóch powierzchni - bufora.

      Na pierwszej lekcji nauczyliśmy się wyświetlać grafikę w trybie DirectDraw w zdarzeniu OnPaint formularza Form1, ale jeśli byśmy chcieli wywołać funkcję np. w zdarzeniu OnShow forumlarza, lub po kliknięciu na przycisku to funkcja Start nie zadziała prawidłowo za pierwszym razem, żeby ją uruchomić będzie konieczne np. dwukrotne kliknięcie na przycisku. Problem ten można usunąć stosując dwie powierzchnie, na których będzie umieszczana grafika. Rozwiązanie podane w tej lekcji jest dużo lepsze od prezentowanego wcześniej, oraz stanowi wstęp do lekcji trzeciej, w której pokażę jak wyświetlać pliki graficzne wczytywane z dysku, w formie slajdów.
    Nie będę prezentował wszystkich kroków jakie trzeba wykonać, żeby utworzyć kod, ponieważ kod z tej lekcji nie różni się zasadniczo od poprzedniego, dlatego opiszę tylko elementy, które trzeba dodać a na końcu pokaże cały kod dla plików: nagłówkowego i źródłowego.
      Zaczynamy od przejścia do pliku nagłówkowego (np. Unit1.h) i definiujemy w sekcji private dodatkową powierzchnię o nazwie lpDDSBuffer (nazwa dowolna):

    private:
            void __fastcall Start(void);

            LPDIRECTDRAW            lpDD;
            LPDIRECTDRAWSURFACE     lpDDSEkran;
            LPDIRECTDRAWSURFACE     lpDDSBuffer; // <--
            HDC                     kontekst;



    ...i to są wszystkie zmiany w pliku nagłówkowym.


  • Plik nagłówkowy Unit1.h: (BCB4)

    //--------------------------------------------------------
    #ifndef Unit1H
    #define Unit1H

    //--------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include <ddraw.h>

    //--------------------------------------------------------
    class TForm1 : public TForm
    {
    __published:
            void __fastcall FormKeyDown(TObject *Sender, WORD &Key,
              TShiftState Shift);
            void __fastcall FormDestroy(TObject *Sender);
    private:
            void __fastcall Start(void);

            LPDIRECTDRAW            lpDD;
            LPDIRECTDRAWSURFACE     lpDDSEkran;
            LPDIRECTDRAWSURFACE     lpDDSBuffer;
            HDC                     kontekst;
    public:
            __fastcall TForm1(TComponent* Owner);
    };
    //--------------------------------------------------------
    extern PACKAGE TForm1 *Form1;
    //--------------------------------------------------------
    #endif




    Teraz przechodzimy do pliku źródłowego (np. Unit1.cpp) i wewnątrz funkcji Start dołączamy drugą powierzchnię:

    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd;
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL);

     ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; // <--
     lpDDSEkran->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSBuffer); // <--
    }


    Kolejna zmiana w stosunku do lekcji pierwszej jest taka, że tam kontekst urządzenia kontekst był umieszczany na powierzchni lpDDSEkran, natomiast teraz zostanie umieszczony w buforze, czyli na powierzchni lpDDSBack:

     lpDDSBuffer->GetDC(&kontekst); // <--


    Następne zmiany występują w sposobie uwalniania głownego kontekstu, oraz została dodana procedura przełączania powierzchni i na koniec zostaje zwolniona powierzchnia lpDDSBuffer:

     lpDDSBuffer->ReleaseDC(kontekst); // <--
     lpDDSEkran->Flip(NULL, 0); // <--

     DeleteObject(hbmp);
     DeleteDC(hdcmem);

     lpDDSBuffer->Release(); // <--


    ...i to już prawie wszystkie zmiany, może jeszcze tylko dodamy w zdarzeniu OnKeyDown formularza Form1 procedurę wywołania funkcji Start po naciśnięciu klawisza Enter (kod klawisza: 13):

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     if(Key == VK_ESCAPE)
        Close();
     if(Key == 13) Start(); // <--
    }


    To już wszystkie zmiany.


  • Plik źródłowy Unit1.cpp: (BCB4)

    //--------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop

    #include "Unit1.h"
    //--------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //--------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
     lpDD = NULL;
    }
    //--------------------------------------------------------
    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;
     HBITMAP hbmp;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd;
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL);

     ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
     lpDDSEkran->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSBuffer);

     hdcmem = CreateCompatibleDC(NULL);
     hbmp = (HBITMAP)LoadImage(Handle, "test.bmp", IMAGE_BITMAP, 1024, 768, LR_LOADFROMFILE);
     SelectObject(hdcmem, hbmp);

     lpDDSBuffer->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 1024, 768, hdcmem, 0, 0, SRCCOPY);

     lpDDSBuffer->ReleaseDC(kontekst);
     lpDDSEkran->Flip(NULL, 0);

     DeleteObject(hbmp);
     DeleteDC(hdcmem);
     lpDDSBuffer->Release();
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     if(Key == VK_ESCAPE)
        Close();
     if(Key == 13)
        Start();
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
     lpDDSEkran->Release();
     lpDD->RestoreDisplayMode();
     lpDD->Release();
    }
    //--------------------------------------------------------



    ...powrót do menu.


    Lekcja 3. Wyświetlanie grafiki w trybie DirectDraw, w formie slajdów.

      Tak jak to wynika z tematu lekcji, spróbujemy wczytać z dysku pliki graficzne i wyświetlać je kolejno z wykorzystaniem biblioteki DirectDraw. Skorzystamy z kodu, który został już stworzony w lekcji 2, nie będziemy tutej dodawać żadnych nowych elementów DirectDraw, istniejące elementy zostaną poprzenoszone. Tak więc z pliku żródłowego (np. Unit1.cpp), z funkcji Start przenosimy fragment kodu: HBITMAP hbmp; do pliku nagłówkowego (np. Unit1.h), do sekcji private, również w tym pliku deklarujemy zmienną typu int - 'x'. Zmienna 'x' posłuży do przełączania kolejnych plików graficznych. Obiekt hbmp został przeniesiony do pliku nagłówkowego po to, żeby był dostępny globalnie, w każdym miejscu projektu:


  • Plik nagłówkowy Unit1.h: (BCB4)

    //--------------------------------------------------------
    #ifndef Unit1H
    #define Unit1H

    //--------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include <ddraw.h>

    //--------------------------------------------------------
    class TForm1 : public TForm
    {
    __published:
            void __fastcall FormKeyDown(TObject *Sender, WORD &Key,
              TShiftState Shift);
            void __fastcall FormDestroy(TObject *Sender);
    private:
            void __fastcall Start(void);

            LPDIRECTDRAW            lpDD;
            LPDIRECTDRAWSURFACE     lpDDSEkran;
            LPDIRECTDRAWSURFACE     lpDDSBuffer;
            HDC                     kontekst;
            HBITMAP                 hbmp;
            int x;
    public:
            __fastcall TForm1(TComponent* Owner);
    };
    //--------------------------------------------------------
    extern PACKAGE TForm1 *Form1;
    //--------------------------------------------------------
    #endif




    ...i to są wszystkie zmiany w pliku nagłówkowym.
      Teraz umieszczamy na dysku w wybranym (dowolnym) katalogu pliki graficzne, które chcemy wyświetlać w formie slajdów. Pliki mogą mieć różne rozmiary, ale muszą to być bitmapy. Nadajemy plikom takie same nazwy, zmieniając tylko kolejne numery, np.: Grafika001.bmp, Grafika002.bmp, Grafika003.bmp, itd... Chodzi o to, żeby przy pobieraniu nazw i ścieżek dostępu do plików nie komplikować wszystkiego. Oczywiście metoda którą tutej pokażę jest bardzo prosta, ale każdy może opracować własny sposób pobierania plików.
    W kązdym bądź razie teraz przechodzimy do pliku źródłowego i przenosimy fragmenty kodu z funkcji Start do funkcji FormKeyDown. Wewnątrz funkcji FormKeyDown w zdarzeniu odpowiadającym kliknięciu przycisku Enter definiujemy zmienną typu String - 'path' i jako wartość podajemy jej ścieżkę dostępu do plików graficznych, dodatkowo formatujemy ją w taki sposób żeby po każdorazowym wciśnięciu przycisku Enter nazwa pliku została zmieniona o wartość zmiennej x, która to zmienna będzie również zmieniała swoją wartość przy każdym kliknięciu przycisku. Może to wszystko wydawać się trochę niezrozumiałe, dlatego proponuję przyjżeć się plikowi źródłowemu:


  • Plik źródłowy Unit1.cpp: (BCB4)

    //--------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop

    #include "Unit1.h"
    //--------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //--------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
     lpDD = NULL;
     x = 0;
    }
    //--------------------------------------------------------
    void __fastcall TForm1::Start(void)
    {
     HDC hdcmem;

     DirectDrawCreate(NULL, &lpDD, NULL);

     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     DDSURFACEDESC ddsd;
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL);

     ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
     lpDDSEkran->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSBuffer);

     hdcmem = CreateCompatibleDC(NULL);
     SelectObject(hdcmem, hbmp);

     lpDDSBuffer->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 1024, 768, hdcmem, 0, 0, SRCCOPY);

     DeleteDC(hdcmem);
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     x++;

     if(Key == VK_ESCAPE)
        Close();
     if(Key == 13)
        {
        String path = "C:\\Nazwa_Katalogu\\Nazwa_Pliku" + FormatCurr("00#", x) + ".bmp";
        hbmp = (HBITMAP)LoadImage(Handle, path.c_str(), IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);
        Start();
        lpDDSBuffer->ReleaseDC(kontekst);
        lpDDSEkran->Flip(NULL, 0);
        if(x >= 3)x = 0;
        }
    }
    //--------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
     DeleteObject(hbmp);
     lpDDSBuffer->Release();
     lpDDSEkran->Release();
     lpDD->RestoreDisplayMode();
     lpDD->Release();
    }
    //--------------------------------------------------------



    Jak widać w swoim przykładzie ograniczyłem (if(x >= 3)x = 0;) liczbę wczytywanych plików do trzech, lecz można stosować dowolną liczbę plików. Proszę zwrócić również uwagę na metodę LoadImage, w poprzednich lekcjach podawaliśmy jako parametry żądaną rozdzielczość ekranu, np.: 1024, 768:

     hbmp = (HBITMAP)LoadImage(Handle, path.c_str(), IMAGE_BITMAP, 1024, 768, LR_LOADFROMFILE);


    ...teraz natomias przekazujemu jako parametry - NULL:

     hbmp = (HBITMAP)LoadImage(Handle, path.c_str(), IMAGE_BITMAP, NULL, NULL, LR_LOADFROMFILE);


    W ten sposób wczytywane pliki zachowają swoje naturalne wymiary i jeśli są mniejsze lub większe od zdefiniowanej rozdzielczości to nie zostaną ściśnięte lub rozciągnięte. Sugeruję jednak, żebyście sobie poeksperymentowali podając rózne wymiary. Na tym kończy się ta lekcja.

    ...powrót do menu.


    Lekcja 4. Tworzenie obiektu którym można sterować za pomocą klawiatury.

      Wszystko co dotychczas tworzyliśmy, było raczej dalekie od problematyki gier. Na tej lekcji, spróbujemy to wszystko uporządkować, zebrać w całość. Dodakowo zrobimy coś nowego, a mianowicie oprócz tła, które tworzyliśmy na poprzednich lekcjach, umieścimy obiekt np. statek kosmiczny, którym będziemy sterować za pomocą klawiszy strzałek z klawiatury. Nowością będzie również stworzenie wielu funkcji, z których każda będzie odpowiadała za pojedyńczą czynność, np. inicjowanie DirectDraw, inicjowanie tła, inicjowanie obiektu, wyświetlanie tła ...itp.

      Zaczynamy od zadeklarowania w pliku nagłówkowym (np. Unit.h), w sekcji private obiektu DirectDraw, czterech powierzchni, opisu powierzchni, kontekstu, bitmapy oraz dwóch zmiennych typu int. Oprócz tego tworzymy również prptotypy funkcji:

    // Plik nagłówkowy np. Unit1.h.
    //--------------------------------
    private:
      LPDIRECTDRAW            lpDD;
      LPDIRECTDRAWSURFACE     lpDDSEkran;
      LPDIRECTDRAWSURFACE     lpDDSBufor;
      LPDIRECTDRAWSURFACE     lpDDSBack;
      LPDIRECTDRAWSURFACE     lpDDSObiekt;
      DDSURFACEDESC           ddsd;
      HDC                     hdcmem;
      HBITMAP                 hbmp;

      int x, y;

      void __fastcall Start(void);
      void __fastcall InitBackground(void);
      void __fastcall Background(void);
      void __fastcall InitObject(void);
      void __fastcall MyObject(void);
      void __fastcall Transparent(void);
      void __fastcall Ruchy(WORD &Key);

    plik tekstowy


    Do pliku nagłówkowego nie będziemy już w tej lekcji wracać, dlatego teraz opiszę skrótowo do czego służą obiekty i funkcji, które zadeklarowaliśmy.
    Tak więc obiekt 'lpDD' będzie potrzebny do zainicjowania DirectDraw, powierzchnia 'lpDDSEkran' posłuży nam do wyświetlania tła i obiektu (obiekt - statek kosmiczny) na ekranie monitora, na powierzchni 'lpDDSBufor' tło i obiekt będą "rysowane" zanim zostaną umieszczone na powierzchni lpDDSEkran, pozwoli to uniknąć migotania obrazu przy zmianie np. położenia statku kosmicznego, powierzchnia 'lpDDSBack' będzie zawierała tło, natomiast powierzchnia 'lpDDSObiekt' będzie zawierała obiekt - statek kosmiczny, 'ddsd' jest to opis powierzchni na której będziemy wyświetlać grafikę, 'hdcmem' to kontekst urządzenie, który będzie "przenosił" grafikę do odpowiednich powierzchni, 'hbmp' jest to bitmapa którą posłużymy się do wczytywania grafiki. Zmienne 'x' i 'y' posłużą nam do poruszania statkiem kosmicznym.
    Funkcja 'Start' zainicjuje DirectDraw oraz ustawi parametry pracy powierzchni lpDDSEkran i lpDDSBufor.
    Funkcja 'InitBackground' zainicjuje i ustawi parametry wyświetlania powierzchni lpDDSBack.
    Funkcja 'Background' będzie odpowiedzialna za wyświetlanie tła.
    Funkcja 'InitObject' zainicjuje i ustawi parametry wyświetlania powierzchni lpDDSObiekt.
    Funkcja 'MyObject' będzie wyświetlała obiekt - statek kosmiczny.
    Funkcja 'Transparent' będzie usuwała ze statku kosmicznego tło, czyniąc kolor czarny bezbarwnym.
    Funkcja 'Ruchy' posłuży do poruszania statkiem kosmicznym.

      Przechodzimy do pliku źródłowego (np. Unit1.cpp) i tworzymy definicje funkcji zadeklarowanych w pliku nagłówkowym. Zaczynamy od funkcji Start, która zawiera tylko elementy prezentowane na poprzednich lekcjach, więc nie będę jej opisywał, dodam tylko, że wewnątrz funkcji zostały umieszczone wywołania funkcji InitBackground(), InitObject() i Transparent(). Definicje tych funkcji jeszcze nie istnieją, lecz wkrótce je dodamy, na razie definiujemy funkcję Start:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
            : TForm(Owner)
    {
     lpDD = NULL;
     lpDDSEkran = NULL;
     lpDDSBufor = NULL;
     lpDDSObiekt = NULL;
     x = 0;
     y = 0;
    }
    //--------------------------------
    void __fastcall TForm1::Start(void)
    {
     DirectDrawCreate(NULL, &lpDD, NULL);
     lpDD->SetCooperativeLevel(Handle, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
     lpDD->SetDisplayMode(1024, 768, 16);

     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
     ddsd.dwBackBufferCount = 1;

     lpDD->CreateSurface(&ddsd, &lpDDSEkran, NULL);
     ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;

     lpDDSEkran->GetAttachedSurface(&ddsd.ddsCaps, &lpDDSBufor);

     InitBackground();
     InitObject();

     Transparent();

     DDBLTFX ddbltfx;
     ddbltfx.dwSize = sizeof(ddbltfx);
     ddbltfx.dwFillColor = 0;
     lpDDSBufor->Blt(NULL, NULL, NULL,DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);
    }
    //--------------------------------


    W następnej kolejności definiujemy funkcję InitBackground, oprócz wcześniej poznanych metod wewnątrz funkcji, w opisie powierzchni ddsd znajdują się takie elementy jak: DDSD_HEIGHT, DDSD_WIDTH, ddsd.dwWidth i ddsd.dwHeight, nie trudno jest się chyba domyśleć, że służą one do podanie rozmiaru wczytywanego pliku graficznego, przy czym nie muszą to być rzeczywiste rozmiary grafiki, ponieważ te parametry określają rozmiar powierzchni na której zostanie umieszczona grafika. Trudno jest mi to wytłumaczyć dlatego proponuję po zdefiniowaniu już wszystkich funkcji podać jako parametry wartości większe i mniejsze od zdefiniowanej rozdzelczości ekranu i zobaczyć co się będzie działo. Funkcja umieszcza grafikę tylko na powierzchni lpDDSBack i nie spowoduje to jeszcze wyświetlenia tła na ekranie, żeby wyświetlić tło wewnątrz funkcji Background przekopiujemy powierzchnię lpDDSBack do bufora lpDDSBufor, a następnie przeniesiemy ją na powierzchnię lpDDSEkran, tak więc dopiero wywołanie funkcji Background() spowoduje wyświetlenie grafiki na ekranie monitora:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::InitBackground(void)
    {
     HDC kontekst;

     hdcmem = CreateCompatibleDC(NULL);
     hbmp = (HBITMAP)LoadImage(NULL, "tło.bmp", IMAGE_BITMAP, 1024, 768, LR_LOADFROMFILE);
     SelectObject(hdcmem, hbmp);

     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
     ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
     ddsd.dwWidth = 1024;
     ddsd.dwHeight = 768;
     lpDD->CreateSurface(&ddsd, &lpDDSBack, NULL);

     lpDDSBack->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 1024, 762, hdcmem, 0, 0, SRCCOPY);
     lpDDSBack->ReleaseDC(kontekst);
    lpDDSBack->Flip(NULL, DDFLIP_WAIT);
    }
    //--------------------------------
    void __fastcall TForm1::Background(void)
    {
     HDC kontekst;
     lpDDSBufor->GetDC(&kontekst);
     lpDDSBufor->ReleaseDC(kontekst);
     RECT rect = {0, 0, 1024, 768};

     lpDDSBufor->Blt(&rect, lpDDSBack, NULL, DDBLT_WAIT, NULL);
     lpDDSEkran->Flip(NULL, DDFLIP_WAIT);
    }
    //--------------------------------


    Następnie definiujemy funkcję InitObject, która inicjuje wyświetlanie obiektu - statku kosmicznego i tutej trzeba sobie przygotować plik graficzny w formacie bmp ze statkiem kosmicznym, ja posłużyłem się grafiką o wymiarach 60x60 pikseli i takie parametry są podawane wewnątrz funkcji, możecie sobie skopiować plik lub stworzyć własny. Jak łatwo zauważyć funkcja InitObject jest bardzo podobna do funkcji InitBackground. To dlatego, że obie funkcję działają tak samo i w zasadzie mogłyby różnić się tylko grafiką i jej rozmiarami, zdecydowałem się jednak na umieszczenie wewnątrz funkcji InitObject dodatkowego bufora - 'ddsd.dwBackBufferCount = 1', a to tylko po to, że w następnej lekcji pokaże jak animować statek kosmiczny i będą do tego potrzebnych więcej buforów, w zależności od tego ile klatek animacjii będziemy chcieli umieścić. Kolejna różnica polega na tym, że pewne metody są wywoływane dwukrotnie:

    lpDDSObiekt->GetDC(&kontekst);
    BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
    lpDDSObiekt->ReleaseDC(kontekst);
    lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

    Dzieje się tak dlatego ,że trzeba wypełnić bufor, w przeciwnym razie obiekt by migotał w czasie przesówania. Dla tych którzy jeszcze nie zrozumieli wyjaśniam, że funkcję InitObject można by napisać tak:

    HDC kontekst;

    hdcmem = CreateCompatibleDC(NULL);
    hbmp = (HBITMAP)LoadImage(NULL, "obiekt.bmp", IMAGE_BITMAP, 60, 60, LR_LOADFROMFILE);
    SelectObject(hdcmem, hbmp);

    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = 60;
    ddsd.dwHeight = 60;
    lpDD->CreateSurface(&ddsd, &lpDDSObiekt, NULL);

    lpDDSObiekt->GetDC(&kontekst);
    BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
    lpDDSObiekt->ReleaseDC(kontekst);
    lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

    ...i w takiej formie funkcja zadziałała by prawidłowo, a statek kosmiczny nie migałby przy przesówaniu. Teraz jednak zdefiniujemy funkcję tworząc bufor, podobnie jak w przypadku funkcji InitBackground, funkcją InitObject tylko inicjuje obiekt - statek kosmiczny, wyświetlenie obiektu nastąpi dopiero po wywołaniu funkcji MyObject i tutej wystepuje kolejna różnica, a mianowicie wewnątrz funkcji Background przenosiliśmy do bufora lpDDSBufor tylko powierzchnię lpDDSBack zawierającą tło. Teraz jednak, wewnątrz funkcji MyObject trzeba ponownie przenieść do bufora powierzchnię lpDDSBack, a dopiero potem powierzchnię lpDDSObiekt, spowoduje to po prostu narysowanie na powierzchni lpDDSBufor najpierw tła, a nastepnie na tle zostanie narysowany statek kosmiczny w pozycji określonej przez rect:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::InitObject(void)
    {
     HDC kontekst;

     hdcmem = CreateCompatibleDC(NULL);
     hbmp = (HBITMAP)LoadImage(NULL, "obiekt.bmp", IMAGE_BITMAP, 60, 60, LR_LOADFROMFILE);
     SelectObject(hdcmem, hbmp);

     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
     ddsd.dwWidth = 60;
     ddsd.dwHeight = 60;
     ddsd.dwBackBufferCount = 1;
     lpDD->CreateSurface(&ddsd, &lpDDSObiekt, NULL);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);
    }
    //--------------------------------
    void __fastcall TForm1::MyObject(void)
    {
     HDC kontekst;

     lpDDSBufor->GetDC(&kontekst);
     lpDDSBufor->ReleaseDC(kontekst);

     RECT rect = {x, y, 60 + x, 60 + y};
     RECT tlo = {0 , 0, 1024, 768};
     lpDDSBufor->Blt(&tlo, lpDDSBack, NULL, DDBLT_WAIT, NULL);
     lpDDSBufor->Blt(&rect, lpDDSObiekt, NULL, DDBLT_WAIT | DDBLT_KEYSRC, NULL);
     lpDDSEkran->Flip(NULL, DDFLIP_WAIT);

     Transparent();
    }
    //--------------------------------


    Jak zapewne zauważyliście wewnątrz funkcji Background i MyObject znajdują się obiekty rect i tlo typu RECT, służą one do określania rozmiaru powierzchni lpDDSBufor na której rysowana jest grafika.
      Teraz zajmiemy się zdefiniowaniem funkcji Transparent, która będzie usuwała ze statku kosmicznego czarne tło. Tło nie musi być czarne, może to być dowolny kolor, do określenia przeźroczystego koloru służy metoda: Kolor.dwColorSpaceLowValue = 0; parametr 0 określa właśnie kolor, który chcemy uczynić przeźroczystym. Istnieje jeszcze metoda: Kolor.dwColorSpaceHighValue = 10; która decyduje o zakresie koloru przeźroczystego. Zamiast wartości liczbowych można podawać nazwy kolorów, np. clBlack (czarny), clRed (czerwony), clYellow (żółty) itp...:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::Transparent(void)
    {
     DDCOLORKEY Kolor;
     Kolor.dwColorSpaceLowValue = 0;
     Kolor.dwColorSpaceHighValue = 0;
     lpDDSObiekt->SetColorKey(DDCKEY_SRCBLT, &Kolor);
    }
    //--------------------------------


    Pozostało nam jeszcze zdefinowanie funkcji Ruchy, Funkcja ta nie wymaga w zasadzie wyjaśnień, ponieważ jedyne co robi to przechwytuje komunikaty o tym, który przycisk został wciśnięty i w zależności od tego zmienia wartość zmiennych globalnych x i y, a zmienne te z kolei zmieniają obszar kreślenia powierzchni lpDDSObject (patrz funkcja MyObject), na koniec wewnątrz funkcji Ruchy wywołaywana jest funkcja MyObject():

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::Ruchy(WORD &Key)
    {
     switch(Key){
                case VK_LEFT: x -= 4; break;
                case VK_RIGHT: x += 4; break;
                case VK_UP: y -= 4; break;
                case VK_DOWN: y += 4; break;
               }
     MyObject();
    }
    //--------------------------------


    Dołaczamy teraz obsługę zdarzenia OnKeyDown dla formularza Form1, wywołując funkcję Ruchy i dodając metodę kończącą działanie programu po naciśnięciu przycisku Escape, następnie w zdarzeniu OnShow - Form1 inicjujemy DirectDraw i wywołujemy funkcję MyObject, na koniec w zdarzeniu OnFormDestroy - Form1 kasujemy wszystkie zbędne obiekty. Jeśli uważnie prześledziliście lekcję to zauważyliście na pewno, że funkcja Background nie jest nigdzie wywoływana i w istocie jest zbędna ponieważ tło jest wyświetlane również w funkcji MyObject. Wywołanie funkcji Background spowoduje wyświetlenie tła. Umieściłem ją po to tylko, żeby uzmysłowić Wam, że powierzchnia lpDDSBack przechowująca tło jest takim samym obietem jak powierzchnia lpDDSObiekt przechowująca obiekt - statek kosmiczny:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
          TShiftState Shift)
    {
     if(Key == VK_ESCAPE)Close();
        Ruchy(Key);
    }
    //--------------------------------
    void __fastcall TForm1::FormShow(TObject *Sender)
    {
     Start();
     MyObject();
    }
    //--------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
     lpDDSEkran->Release();
     lpDDSBack->Release();
     lpDDSObiekt->Release();
     lpDD->RestoreDisplayMode();
     DeleteObject(hbmp);
     DeleteDC(hdcmem);
     lpDD->Release();
    }
    //--------------------------------

    plik tekstowy


    ...powrót do menu.


    Lekcja 5. Animowanie obiektów.

      Na tej lekcji zajmiemy się animowaniem obiektu - statku kosmicznego. Najpierw musimy przygotować sobie plik graficzny w formacie bmp zawierający kolejne klatki animacji. Jeśli komuś się nie chce może posłużyć się plikiem, który ja przygotowałem. Mój plik składa się z trzech klatek animacji w których jedyne co się zmienia, to tylko ogień z dysz statku kosmicznego:

    Rozmiar: 3659 bajtów


    Posłużymy się kodem z lekcji 4 wprowadzając na początek zmiany do funkcji InitObject. Zmianie uległ rozmiar pliku, oraz dodane zostały dodatkowe bufory, w tym przypadku 3, no i oczywiście dodane zostały dodatkowe metody wypełniające bufory. Zmiany zostały zaznaczone na żółto:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::InitObject(void)
    {
     HDC kontekst;

     hdcmem = CreateCompatibleDC(NULL);
     hbmp = (HBITMAP)LoadImage(NULL, "anim.bmp", IMAGE_BITMAP, 180, 60, LR_LOADFROMFILE);
     SelectObject(hdcmem, hbmp);

     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_BACKBUFFERCOUNT;
     ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
     ddsd.dwWidth = 60;
     ddsd.dwHeight = 60;
     ddsd.dwBackBufferCount = 3;
     lpDD->CreateSurface(&ddsd, &lpDDSObiekt, NULL);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 60, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 120, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);

     lpDDSObiekt->GetDC(&kontekst);
     BitBlt(kontekst, 0, 0, 60, 60, hdcmem, 0, 0, SRCCOPY);
     lpDDSObiekt->ReleaseDC(kontekst);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);
    }
    //--------------------------------


    Kolejnej zmiany dokonamy wewnątrz funkcji MyObject, a zmiana polega na dodaniu metody przełączającej kolejne klatki animacji:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::MyObject(void)
    {
     HDC kontekst;

     lpDDSBufor->GetDC(&kontekst);
     lpDDSBufor->ReleaseDC(kontekst);

     RECT rect = {x, y, 60 + x, 60 + y};
     RECT tlo = {0 , 0, 1024, 768};
     lpDDSBufor->Blt(&tlo, lpDDSBack, NULL, DDBLT_WAIT, NULL);
     lpDDSBufor->Blt(&rect, lpDDSObiekt, NULL, DDBLT_WAIT | DDBLT_KEYSRC, NULL);
     lpDDSObiekt->Flip(NULL, DDFLIP_WAIT);
     lpDDSEkran->Flip(NULL, DDFLIP_WAIT);

     Transparent();
    }
    //--------------------------------


    Animacja, czyli wyświetlanie kolejno następujących po sobie klatek animacji musi występować w jakimś zdarzeniu i najlepszym w naszym przypadku rozwiązaniem będzie wykorzystanie komponentu TTimer. Umieszczamy więc na formularzu obiekt Timer1 i ustawiamy jego właściwość Interval na 50, wartość może być dowolna trzeba jednak pamiętać, że szybkość taktowania zegara decyduje o szybkości animacji, zbyt małe lub zbyt duże wartości mogą sprawić, że animacja nie będzie przebiegała zbyt płynnie. Po umieszczeniu zegara (Timer1) na formularzu wywołujemy jego zdarzenie OnTimer i wywołujemy w nim funkcję MyObject:

    // Plik źródłowy np. Unit1.cpp
    //--------------------------------
    void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
     MyObject();
    }
    //--------------------------------


    To w zasadzie wszystkie zmiany, jeśli wszystko zrobiliście prawidłowo to po skompilowaniu i uruchomieniu programu statek kosmiczny powinien być animowany, można również tak zmodyfikować tylko framgmenty kodu, żeby w zależności od ruchu statku po planszy zmieniał się sposób animacji, ale o tym innym razem, właściwie jest to tak proste, że już sami powinniście wiedzieć jak to zrobić.

    ...powrót do menu.


    Opracował: Cyfrowy Baron