DirectX (11.)

V tΘto lekci nakousneme pon∞kud rozsßhlejÜφ p°φklad vyu₧φvajφcφ znalosti DirectDraw a DirectInput, kterΘ mßte. V minulΘ lekci jsme dokonΦili t°φdu CInput, kterou i v tomto p°φkladu vyu₧ijeme. Naopak zcela p°ed∞lßme systΘm DirectDraw tak, aby skuteΦn∞ n∞co um∞l a nejen kreslil primitivnφ obrßzky po obrazovce. Dßle budeme vyu₧φvat n∞kterΘ dalÜφ knihovny, abychom nemuseli vÜechno psßt znovu.

11.1. Nov² projekt

Proto₧e struktura programu bude radikßln∞ odliÜnß od p°edchozφch jednoduch²ch struktur, bude asi nejlepÜφ vytvo°it zcela nov² projekt. Vytvo°te tedy Blank Workspace, do kterΘho postupn∞ budeme p°idßvat tyto knihovny:

a samoz°ejm∞ nesmφme zapomenout na program:

Knihovny Zip.dll a Common.dll psßt nebudeme. Tyto dv∞ knihovny Vßm dßm k dispozici a budeme z nich vyu₧φvat jen n∞kterΘ funkce.

11.1.1. Struktura projektu

Na nßsledujφcφm obrßzku vidφte zßvislosti jednotliv²ch knihoven:

               

Z°ejm∞ na vrcholu bude program, kter² vyu₧ije vÜechny knihovny pod sebou. èipka vyjad°uje sm∞r vklßdßnφ, to znamenß, ₧e t°eba knihovna Engine.dll je vklßdß do Game.exe a to znamenß, ₧e Game.exe je zßvisl² na Engine.dll. DalÜφ zajφmavost, kterß Vßs mo₧nß p°ekvapφ je, ₧e Input.dll je zßvislß na Display.dll. Minule jsem se zmi≥oval o tom, ₧e kurzor budeme pozd∞ji vykreslovat pomocφ DirectDraw, z toho je z°ejmΘ, ₧e Input.dll musφ b²t zßvislß na Display.dll.

Projekt zaΦneme stav∞t od spodu. Za prvΘ si °ekneme, jak napojφme knihovny Common.dll a Zip.dll do naÜeho projektu. Budete toti₧ mφt k dispozici pouze tyto soubory:

Soubor .lib obsahuje reference na exportovanΘ funkce z .dll a tento soubor vyu₧ijeme v projektu. Jist∞ si pamatujete, jak jsme vklßdali soubory dinput8.lib nebo ddraw.lib. Stejn²m zp∙sobem vlo₧φme common.lib. V hlaviΦkovΘm souboru pak naleznete deklarace exportovan²ch funkcφ, abyste je v∙bec mohli pou₧φt ve vaÜem k≤du. O knihovnu Zip.dll se vlastn∞ nemusφme v∙bec starat, proto₧e tu intern∞ vyu₧φvß common.dll a z ostatnφch knihoven nebudeme vyu₧φvat ₧ßdnou jejφ funkci.

11.1.2. Knihovna Common.dll

Tato knihovna obsahuje n∞kolik funkcφ, kterΘ nßm trochu usnadnφ ₧ivot. Funkce jsou rozd∞leny nßsledovn∞:

Deklarace t∞chto funkcφ naleznete v souboru common.h. Budeme zejmΘna pou₧φvat makra DXTRACE a DXERR, kterΘ fungujφ podobn∞ jako makro TRACE s tφm rozdφlem, ₧e zapisujφ °et∞zec do souboru log.txt a navφc makro DXERR dokß₧e rozluÜtit chybov² k≤d chyby. Dßle pou₧ijeme funkce stgGetFile3() k nahrßvßnφ bitmap z datovΘho souboru, kter² otev°eme funkcφ stgOpenDataFile3(). Ostatnφ funkce pou₧ijeme p°φle₧itostn∞, jak se nßm to bude hodit.

Nynφ tedy ud∞lejme prvnφ krok a vytvo°me Blank WorkSpace. V²vojovΘ prost°edφ zatφm vytvo°φ adresß° se souborem .dsw. Do tohoto adresß°e (nazv∞me ho nap°φklad Game), vytvo°te podadresß° Common, do kterΘho nakopφrujte soubory common.h a strings.h. Dßle vytvo°te adresß°e Debug a Release a do obou nakopφrujte soubory common.lib, common.dll a zip.dll. Pokud budete chtφt pou₧φt knihovnu common.dll, musφte vlo₧it hlaviΦkov² soubor do vaÜeho projektu p°φkazem include:

#include    "..\Common\Common.h"

a pak v menu Project-Settings nastavit n∞co takovΘho:

           

Tφm jste p°ekladaΦi a linkeru °ekli, ₧e chcete p°ilinkovat knihovnu Common.dll do vaÜeho projektu. Rovn∞₧ si vÜimn∞te, ₧e v²stupnφ soubor (v tomto p°φpad∞ Testing.exe) je vytvß°en v adresß°i Release celΘho projektu (tedy v tom adresß°i, kter² jsme p°ed chvilkou vytvo°ili), jinak by program knihovnu nenaÜel.

11.1.3. ┌prava projektu

Vlo₧me nynφ do projektu projekt aplikace, na kterΘm budeme testovat funkce z ostatnφch knihoven. Z tΘto aplikace budeme postupn∞ tvo°it program Game.exe, o kterΘm byla °eΦ v²Üe. Tentokrßt tuto aplikaci vytvo°φme jinak. Neud∞lßme Φist² projekt MFC, ale p°ed∞lßme Win32 API projekt na projekt Win32 API s podporou MFC. Tak₧e budeme moci vyu₧φvat n∞kterΘ v²hody MFC a zßrove≥ se nemusφme dr₧et t°φd CMainFrame apod. Projekt se takΘ snßze upravuje.

Vytvo°te tedy projekt Win32 Application, nezapome≥te zaÜkrtnout volbu Add to current project. V AppWizardovi zvolte druhou volbu A simple Win32 Application. Nynφ v Settings projektu nastavte na prvnφ kart∞, ₧e chcete pou₧φvat MFC ve sdφlen²ch DLL (rovnou v tomto dialogu nastavte, ₧e se soubor Game.exe mß vytvß°et v adresß°i Debug resp. Release celΘho projektu). Nakonec staΦφ do souboru stdafx.h p°ipsat nßsledujφcφ dva °ßdky:

#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions

Vymazali jsme °ßdek: #include <windows.h>

11.1.4. Okno aplikace

Nynφ p°idßme pßr funkcφ, kterΘ jsou nutnΘ pro b∞h Win32 aplikace. Jsou to WinInit() a MainWndProc(). Prvnφ z t∞chto funkcφ vlastn∞ nenφ ani nutnß, proto₧e zde vytvß°φme okno aplikace a tento k≤d m∙₧eme napsat rovnou do p°ipravenΘ funkce WinMain(). Naopak MainWndProc() pot°eba bude urΦit∞. Jednß se o tzv. proceduru okna, kterß je volßna pokud oknu p°ijde zprßva od systΘmu. Dßle vytvo°me globßlnφ handle na naÜe okno. Tento handle budeme prßv∞ inicializovat ve funkci WinMain(). Kdo znß programovßnφ s API, m∙₧e nßsledujφcφ °ßdky prakticky p°eskoΦit, d∙le₧itΘ jsou pouze atributy okna.

Vytvo°me t∞lo funkce WinInit():

HRESULT WinInit(HINSTANCE hInstance)
{
    DWORD dwRet;
    WNDCLASSEX wc;
   
//
   
// Register the Window Class

    wc.cbSize =
sizeof
(WNDCLASSEX);
    wc.lpszClassName = "GameWin";
    wc.lpfnWndProc = MainWndProc;
    wc.style = CS_VREDRAW|CS_HREDRAW;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance,"IDI_PROGRAM_ICON");
    wc.hCursor = NULL;
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;   
    wc.hIconSm = LoadIcon(hInstance, "IDI_PROGRAM_ICON");
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;

   
//
   
// Register window class
   
if(RegisterClassEx(&wc) == 0 ) {
        dwRet = GetLastError();
        DXERR("Cannot register the window class due", dwRet);
       
return dwRet;
    }
   
//
    // Create new main window

    g_hWnd = CreateWindow("GameWin", "Game",
                            WS_OVERLAPPEDWINDOW, 0, 0, 0,
                            0, NULL, NULL, hInstance, NULL);
   
if(!g_hWnd) {
        dwRet = GetLastError();
        DXERR("Cannot create the window due", dwRet);
       
return dwRet;
    }
    ShowWindow(g_hWnd, SW_SHOW);
    UpdateWindow(g_hWnd);
    dwRet = ERROR_SUCCESS;
   
return dwRet;
}

Nynφ si vysv∞tlφme, jak funkce pracuje. V prvnφm kroku musφme zaregistrovat tzv. t°φdu okna. Naplnφme strukturu WNDCLASSEX a potΘ zavolßme funkci RegisterClassEx(). Pak teprve m∙₧eme vytvo°it okno jist²ch vlastnostφ tak, jak jsme ho zaregistrovali. Do struktury WNDCLASSEX postupn∞ plnφme velikost struktury v bytech, jmΘno t°φdy, kterΘ pak vyu₧ijeme p°i tvorb∞ okna, ukazatel na proceduru okna (v naÜem p°φpad∞ MainWndProc), dßle styl, instanci aplikace, handle ikony, handle kurzoru, handle Üt∞tce pozadφ, jmΘno zdroje menu, handle malΘ ikony. Aby se nahrßla sprßvnß ikonka okna, musφte mφt vlo₧en² p°φsluÜn² zdroj do projektu. Ikona ale nenφ tak d∙le₧itß, proto₧e stejnΘ budeme pou₧φvat fullscreen. V dalÜφm kroku vytvo°φme a zobrazφme okno. Funkce CreateWindow() mß n∞kolik parametr∙: prvnφ je °et∞zec t°φdy okna, kterou jsme p°ed chvilkou zaregistrovali, dalÜφ je titulek okna, pak styl, pozice, Üφ°ka a v²Üka, handle okna rodiΦe, handle menu,  handle na instanci aplikace a poslednφ parametr p°edstavuje dodateΦnΘ informace p°edanΘ oknu. Funkce ShowWindow() a UpdateWindow() okno zobrazφ a to je celΘ.

Funkce MainWndProc() je deklarovßna nßsledovn∞:

LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Jednß se o tzv. callback funkci. P°edßte jejφ adresu prost°ednictvφm registrace t°φdy okna systΘmu a ten pak volß tuto funkci jako reakci na zprßvy poslanΘ tomuto oknu. T∞lo vypadß zatφm takto jednoduÜe:


LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

Postupn∞ ale budeme p°idßvat vlastnφ obsluhy n∞kter²ch zprßv. Funkce DefWindowProc() poÜle zprßvu zp∞t systΘmu a ten ji zpracuje standardn∞.

Funkci WinInit() budeme volat hned na prvnφm mφst∞ z funkce WinMain():

HRESULT dwRet;
dwRet = WinInit(hInstance);
if(dwRet != ERROR_SUCCESS) {
   DXTRACE("Nemohu vytvorit okno aplikace.");
   return dwRet;
}

VÜimn∞te si pou₧itφ maker DXTRACE a DXERR. DXERR mß dva povinnΘ parametry, z nich₧ druh² je k≤d chyby, kterou se funkce diagTrace() pokusφ rozluÜtit. Naopak DXTRACE mß povinn² parametr °et∞zce a pak libovoln² poΦet dalÜφch parametr∙ nap°φklad jako funkce printf(). Pokud mßte v souboru setup.ini nastavenu hodnotu TraceToFile na 1, budou se hlßÜky rovn∞₧ zapisovat i do souboru log.txt.

11.1.5. SmyΦka zprßv

Te∩ , kdy₧ aplikaci spustφte, vytvo°φ se okno a aplikace skonΦφ. Je to tφm, ₧e jsme nespustili smyΦku zprßv. Do funkce WinMain() dopiÜte nßsledujφcφ k≤d:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
    HRESULT dwRet;
    MSG msg;

   
dwRet = WinInit(hInstance);
    if(dwRet != ERROR_SUCCESS) {
        DXTRACE("Nemohu vytvorit okno aplikace.");
        return dwRet;
    }

   
// Run message loop
    while( TRUE )
    {
      
// Look for messages, if none are found then
       // update the state and display it

       if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
       {
          if( 0 == GetMessage(&msg, NULL, 0, 0 ) )
          {
         
   // WM_QUIT was posted, so exit
             return (int)msg.wParam;
          }
          // Translate and dispatch the message
          TranslateMessage( &msg );
          DispatchMessage( &msg );
       }
       else
       {
           UpdateFrame();
       }
    }

    return 0;
}

P°ipsanß Φßst pracuje stejn∞ jako funkce Run() v naÜem p°edchozφm projektu, vlastn∞ je prakticky stejnß. VÜimn∞te se dalÜφ globßlnφ funkce UpdateFrame(), tuto funkci musφte deklarovat na zaΦßtku souboru game.cpp a definovat n∞kde dole. I u p°edchozφch funkcφ nezapomφnejte na funkΦnφ prototypy.

11.1.6. UkonΦenφ aplikace

Nynφ ji₧ aplikace neskonΦφ a okno z∙stane zobrazenΘ v rohu. ProblΘm je ten, ₧e po zav°enφ okna z∙stane aplikace viset ve smyΦce zprßv, kterou nikdo neukonΦil. Ta se ukonΦφ zprßvou WM_QUIT a tu posφlß funkce PostQuitMessage(). Upravte tedy obslu₧nou funkci nßsledovn∞:

LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch(msg) {
    case WM_DESTROY:
        // Cleanup and close the app
        PostQuitMessage( 0 );
        return 0L;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

P°φkazem switch rozd∞lφme obsluhu jednotliv²ch zprßv na jednotlivΘ v∞tve. P°i zav°enφ okna, dostane okno zprßvu WM_DESTROY. Reakcφ na tuto zprßvu bude celΘ ukonΦenφ aplikace. Velice d∙le₧it² je p°φkaz return 0L, proto₧e zabrßnφ v provedenφ funkce DefWindowProc() - zprßvu jsme p°ece obslou₧ili a obsluha systΘmu tedy nenφ pot°eba. Nynφ u₧ bude aplikace fungovat oΦekßvan²m zp∙sobem i p°i ukonΦenφ.

 

11.2. Zßv∞r

Funkce UpdateFrame() bude mφt naprosto stejnou ·lohu jako v p°edchßzejφcφm projektu. P°φÜt∞ a₧ vytvo°φme knihovnu Display.dll, p°ipφÜeme do tΘto funkce volnφ UpdateBackground() a Present(). Z toho tedy vypl²vß, co budeme d∞lat p°φÜt∞. Pokusφme se postavit kompletn∞ knihovnu Display.dll a potom k nφ napojφme knihovnu Input.dll s upravenou t°φdou CInput tak, aby pou₧φvala DirectDraw na vykreslovßnφ kurzoru.

T∞Üφm se p°φÜt∞ nashledanou.

Ji°φ Formßnek