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.
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:
Input.dll
Display.dll
Engine.dll
Common.dll
Zip.dll
a samoz°ejm∞ nesmφme zapomenout na program:
Game.exe
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.
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:
common.h
common.lib
common.dll
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.
Tato knihovna obsahuje n∞kolik funkcφ, kterΘ nßm trochu usnadnφ ₧ivot. Funkce jsou rozd∞leny nßsledovn∞:
diagnostickΘ funkce
funkce pro prßci s datov²m souborem
funkce pro prßci s konfiguraΦnφm souborem setup.ini
obecn∞jÜφ funkce pro prßci z adresß°i a soubory
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.
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>
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.
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.
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φ.
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.