Kurz DirectX (8.)
V tΘto lekci zcela zakonΦφme komponentu DirectDraw a s chutφ se pustφme do
dalÜφ nemΘn∞ zajφmavΘ Φßsti a to DirectInput. DirectInput zajiÜ¥uje
programßtorovi p°φm² p°φstup k vstupnφm za°φzenφm PC. M∙₧ou to b²t ·pln∞ ty
nejzßkladn∞jÜφ za°φzenφ jako je klßvesnice a myÜ nebo ty pokroΦilejÜφ jako
je joystick nebo volant (a samoz°ejm∞ mnoho dalÜφch za°φzenφ).
8.1 RozlouΦenφ s DirectDraw
I kdy₧ se s DirectDraw louΦφme, tak v∞zte, ₧e je toho jeÜt∞ hodn∞, co je pot°eba
se nauΦit. I po dneÜnφ lekci vßm t°eba n∞kterΘ v∞ci nebudou zcela jasnΘ nebo vßs
bude zajφmat n∞jak² konkrΘtnφ problΘm, kter² tu m∙₧eme spolu vy°eÜit. StaΦφ
napsat email.
8.1.1 Ztrßcenφ povrch∙
Fakt, ₧e povrch se m∙₧e ztratit jsem Vßm zprvu zatajil, ale v dneÜnφ lekci se
dozvφte, ₧e se takovß v∞c m∙₧e stßt a nauΦφte se, jak nalo₧it se ztracen²m
povrchem.
Slovo ztracen² z nφ dost divn∞. Vlastn∞ si asi nedokß₧ete p°edstavit, ₧e
by se kousek pam∞ti ztratil. Mßte pravdu! Povrch se vlastn∞ neztratφ, jen se
dealokuje mφsto v pam∞ti a vy musφte tuto pam∞¥ znovu alokovat. Ukazatel na povrch je i
po "ztracenφ" platn², tak₧e m∙₧ete zavolat Φlenskou funkci
Restore(), kterß realokuje pam∞¥, ale u₧ nenahraje do povrchu p∙vodnφ
bitmapu, to musφte ud∞lat zcela sami (pokud je to t°eba).
Kdy ke ztrßcenφ vlastn∞ dochßzφ?
Je to nap°φklad tehdy, kdy₧ zm∞nφte rozliÜenφ b∞hem chodu vaÜeho programu.
Nap°φklad, p°epnete-li se do Windows pomocφ klßves Alt-Tab (nebo jinak),
ztratφ se Front i Back buffer. M∞li byste zastavit flipovacφ smyΦku a
spustit ji znovu a₧ tehdy, kdy₧ se u₧ivatel vrßtφ zp∞t do programu. P°i nßvratu
se rozliÜenφ p°epne zp∞t a prßv∞ v teto chvφli (d°φve ne₧ spustφte smyΦku),
musφte volat funkce Restore() vÜech povrch∙ kterΘ
se ztratili.
Funkce Blt() a Flip() vracejφ hodnotu
DDERR_SURFACELOST, kdy₧ povrch je ztracen² a
je pot°eba volat metodu Restore(). Tak₧e byste asi
m∞li testovat prßv∞ tuto nßvratovou hodnotu.
Jak ji₧ bylo °eΦeno, funkce Restore() pouze
realokuje pam∞¥ pro povrch, ale u₧ nenahraje p∙vodnφ bitmapu. Pokud volßte n∞kde
funkci Blt(), budete pot°ebovat testovat hodnotu
DDERR_SURFACELOST a p°φpadn∞ povrch obnovit a
naplnit p∙vodnφ bitmapou. V p°φpad∞, ₧e volßte funkci Flip(),
tak tam staΦφ pouze obnovit hlavnφ povrchy (viz dßle).
Zprßva WM_ACTIVE
Abyste odhalili, kdy aplikace ztrßcφ fokus a p°ichßzφ o v²hradnφ prßva na
fullscreen, musφte odchytnout zprßvu WM_ACTIVATE.
Tu dostane okno ve chvφlφ, kdy je bu∩ aktivovßno nebo deaktivovßno. Zprßva s
sebou nese dva parametry (jako ka₧dß zprßva) typu WPARAM a
LPARAM, co₧ jsou 32-bitovΘ hodnoty (jako
DWORD). Dolnφch 16-bit∙ (low word) parametru
WPARAM nßm °φkß, zda-li okno bylo aktivovßno
nebo deaktivovßno (p°φpadn∞ jak²m zp∙sobem). M∙₧e nab²vat t∞chto hodnot:
Hodnota |
Popis |
WA_ACTIVE |
Tato hodnota nßm °φkß, ₧e okno bylo aktivovßno
nap°φklad pomocφ klßvesnice nebo funkcφ
SetActiveWidows(). |
WA_CLICKACTIVE |
Tato hodnota pouze up°es≥uje, ₧e okno bylo aktivovßno
tlaΦφtkem myÜφ. |
WA_INACTIVE |
Okno bylo deaktivovßno. |
Ostatnφ hodnoty nßs nezajφmajφ, ale jen pro
·plnost si je stejn∞ uvedeme.
Hornφch 16-bit∙ (high word) parametru WPARAM (druhß polovina
WPARAM) nßm °φkß, zda-li je okno minimalizovanΘ Φi
nikoliv. Druh² parametr LPARAM je handle
na okno, ale zßvisφ na hodnot∞ WPARAM. Pokud je
dolnφch 16-bit∙ parametru WPARAM rovno
WA_INACTIVE, pak je to handle okna,
kterΘ je deaktivovßno (tzn. na naÜe okno). Pokud je hodnota
WA_ACTIVE nebo WA_CLICKACTIVE,
pak handle pat°φ oknu, kterΘ bylo
deaktivovßno, aby naÜe okno mohlo b²t aktivovßno (tj. p°edchozφ aktivnφ okno).
Poznßmka: Hornφch a dolnφch 16-bit∙ z typu
DWORD dostaneme pomocφ maker
HIWORD() a LOWORD().
P°φklad
Nynφ ji₧ zaΦneme upravovat nßÜ p°φklad a dovedeme ho tak k ·pln∞ "dokonalosti".
Za prvΘ bychom m∞li zastavit flipovacφ smyΦku, tak₧e musφme odchytit zprßvu
WM_ACTIVE. Mßme definovanou proceduru okna
MainWndProc(), ve kterΘ to celΘ provedeme. KonkrΘtn∞ do p°φkazu
switch vlo₧φme ke zprßv∞ WM_SETCURSOR
zprßvu WM_ACTIVE a do t∞la v∞tve napφÜeme
nßsledujφcφ k≤d:
case WM_ACTIVATE:
low = LOWORD(wParam);
// Start
flipping loop if app is activated
if(low == WA_ACTIVE || low == WA_CLICKACTIVE) {
theApp.GetControl()->Activate(TRUE);
}
// Stop
flipping loop if app is deactivated
if(low == WA_INACTIVE) {
theApp.GetControl()->Activate(FALSE);
}
break;
Jak vidφte pou₧φvßme novou metodu t°φdy
CControl Activate(), kterß jako parametr
p°ijφmß hodnotu BOOL a nastavuje atribut
m_bReady. Pro ·plnost jsem do t°φdy doplnil i
metodu pro opaΦn² sm∞r, kterß vracφ BOOL, zda-li je smyΦka aktivnφ Φi nikoliv. Dßle
si vÜimn∞te makra LOWORD(), kterΘ vracφ dolnφch
16-bit∙ hodnoty wParam (tj. parametr
WPARAM). Jinak zßpis je velmi jednoduch²: pokud je
okno aktivovßno (jak²mkoliv zp∙sobem), pak je smyΦka spuÜt∞na, v opaΦnΘm p°φpad∞
je smyΦka zastavena.
Nynφ se podφvßme do funkce Present()
t°φdy CDisplay.
HRESULT CDisplay::Present()
{
HRESULT hr;
if( NULL == m_pddsFrontBuffer && NULL == m_pddsBackBuffer
)
return E_POINTER;
while( 1 )
{
if( m_bWindowed )
hr = m_pddsFrontBuffer->Blt(
&m_rcWindow, m_pddsBackBuffer, NULL, DDBLT_WAIT, NULL );
else
hr = m_pddsFrontBuffer->Flip(
NULL, 0 );
if( hr == DDERR_SURFACELOST )
{
m_pddsFrontBuffer->Restore();
// Funkce Restore() pro front buffer
m_pddsBackBuffer->Restore();
// Funkce Restore() pro back buffer
}
if( hr != DDERR_WASSTILLDRAWING )
return hr;
}
}
Zde nic upravovat nebudeme, ale vÜimn∞te si funkce
Restore() pro oba hlavnφ povrchy. Jsou volßny pokud
Flip() vracφ DDERR_SURFACESLOST,
jak bylo zmφn∞no v²Üe.
Nynφ ovÜem musφme takto upravit vÜechny mφsta, kde volßme
funkci Blt(), proto₧e ta takΘ vracφ
DDERR_SURFACELOST. V naÜem jednoduchΘm p°φklad∞ je
to jen na dvou mφstech a to ve t°φd∞ CSprite a p°i
p°ekreslovßnφ pozadφ ve t°φd∞ CControl.
CSprite
Zde musφte p°idat nov² atribut, proto₧e je nutnΘ si pamatovat jmΘno bitmapy
odkud se vytvß°φ povrch spritu. Zvolil jsem typ CString,
ale m∙₧e to b²t jednoduch² °et∞zec char. Tento
atribut inicializujeme p°i vytvß°enφ spritu ve funkci
CreateStaticSprite() a kupodivu do n∞j zkopφrujeme cestu bitmapy, ze
kterΘ vytvß°φ povrch. A proΦ pot°ebujeme tento °et∞zec? U hlavnφch povrch∙ jsme
pouze realokovali pam∞¥, ale u t∞chto off-screen povrch∙ je navφc pot°eba
obnovit p∙vodnφ obsah a prßv∞ k tomu bude slou₧it tento °et∞zec. Dßle musφte ve
funkci DrawSprite() testovat nßvratovou hodnotu
funkce Blt() na DDERR_SURFACELOST
a v tomto p°φpad∞ obnovit povrch spritu a nahrßt do n∞j znovu bitmapu. Upravenß
Φßst funkce vypadß takto:
//
// Draw sprite at specified location
dwRet = m_pDisplay->ColorKeyBlt(m_ptPos.x, m_ptPos.y, m_psurSpriteSurface->GetDDrawSurface(),
&rcSrc);
if(dwRet != DD_OK) {
if(dwRet == DDERR_SURFACELOST) {
m_psurSpriteSurface->GetDDrawSurface()->Restore();
m_psurSpriteSurface->DrawBitmap((TCHAR*)(LPCSTR)m_csBitmap,
0, 0);
}
else {
TRACE("Cannot render sprite due %d\n",
dwRet);
}
}
VÜimn∞te si novΘ Φßsti v podmφnce s
DDERR_SURFACELOST. Za prvΘ volßme metodu Restore()
povrchu a za druhΘ volßme funkci DrawBitmap(),
kterß je Φlenem t°φdy CSurface. Tato metoda
zkopφruje obsah bitmapy ze souboru do povrchu a je p°φmo urΦena pro tyto ·Φely.
A zde je to vÜe!
CControl a pozadφ
Za druhΘ musφme trochu upravit t°φdu CControl. Op∞t
p°idejte nov² atribut, °et∞zec, v n∞m₧ bude ulo₧ena cesta k bitmap∞ pozadφ (je
to stejn² princip jako u p°edchozφ t°φdy). A pak upravte funkci
UpdateFrame() tam, kde p°ekreslujete pozadφ takto:
dwRet = m_theDisplay.Blt(0, 0, m_surBackground,
rcBackground);
if(dwRet != DD_OK) {
if(dwRet == DDERR_SURFACELOST) {
m_surBackground->GetDDrawSurface()->Restore();
m_surBackground->DrawBitmap((TCHAR*)(LPCSTR)m_csBackground,
0, 0);
}
}
Op∞t je zde vid∞t stejn² postup: nejd°φve obnovφme povrch
funkcφ Restore() a potΘ zkopφrujeme bitmapu
vesmφru.
A to je v tΘto Φßsti vÜe. Nynφ si zkuste program spustit.
PotΘ se v pr∙b∞hu p°epn∞te do Windows (nap°φklad p°es Alt-Tab). M∞lo
by se sprßvn∞ p°epnout rozliÜenφ (do p∙vodnφho rozliÜenφ plochy) a aplikace mß z∙stat minimalizovanß
dole na liÜt∞.
Pak se p°epn∞te zp∞t (nap°φklad klepn∞te na tlaΦφtko na liÜt∞) a program by se
m∞l cel² obnovit a vÜe pokraΦovat dßl, jako by se nechumelilo.
P°φklad si m∙₧ete stßhnout v sekci Downloads jako
DirectDraw.exe.
8.1.2. Uvol≥ovßnφ objekt∙ DirectDraw
V ·pln∞ poslednφ Φßsti o DirectDraw se budeme zab²vat tφm poslednφm, co musφte
ud∞lat p°i ukonΦenφ aplikace pracujφcφ na bßzi DirectDraw. Vytvo°ili jste p°ece
n∞jakΘ objekty a ty se musφ zruÜit. DirectDraw a celΘ DirectX pou₧φvß
COM (Component Object Model), tak₧e vlastn∞ nevlastnφte ukazatel p°φmo na
objekt DirectDraw, ale pouze ukazatel na jeho rozhranφ, pomocφ kterΘho s objektem
pracujete.
U COMu platφ, ₧e samotn² objekt se zruÜφ sßm, pokud neexistujφ ji₧
₧ßdnΘ odkazy (reference) na jeho rozhranφ (interface). Tφm, ₧e vytvo°φte objekt DirectDraw pomocφ funkce DirectDrawCreateEx()
zφskßte prßv∞ odkaz na jeho rozhranφ. Objekt COMu si to zapamatuje,
proto₧e si intern∞ poΦφtß reference (inkrementuje nebo dekrementuje poΦet
referencφ). Pokud toto poΦφtadlo dosßhne hodnoty 0, objekt se sßm zruÜφ. Jde
pouze o to, uvolnit vÜechny rozhranφ, kter² na ten kter² objekt mßte. V naÜem
p°φpad∞ je to jedinΘ rozhranφ IDirectDraw7.
Rozhranφ se uvol≥ujφ pomocφ funkce Release(). Po
zavolßnφ tΘto metody se snφ₧φ poΦet referencφ na objekt. Metoda vracφ aktußlnφ
poΦet referencφ.
Poznßmka: VÜimn∞te si, ₧e nevytvß°φme ₧ßdn² objekt
CDirectDraw7 (ten za nßs vytvo°φ COM). TakΘ si vÜimn∞te jak²m
zp∙sobem zφskßvßme ukazatel. Nikde ₧ßdn² operßtor new!
Co musφme uvol≥ovat?
Obecn∞ musφme uvolnit vÜechny zφskanΘ rozhranφ. UrΦit∞ to bude objekt
DirectDraw. Pokud vytvß°φte paletu nebo clipper, pak oba tyto
objekty musφte uvolnit (my pou₧φvßme t°φdu CDisplay,
kterß vÜe d∞lß za nßs, staΦφ se podφvat do funkce
DestroyObject())
(tΘto funkci si jeÜt∞ vÜimn∞te navrßcenφ cooperative
levelu na hodnotu normal). NutnΘ je ovÜem uvol≥ovat i odkazy na
veÜkerΘ povrchy! Op∞t pokud pou₧φvßte objekt CSurface,
nemusφte se o nic starat, proto₧e v destruktoru najdete uvoln∞nφ vnit°nφho
povrchu.
Poznßmka: U slo₧it∞jÜφch objekt∙, kde je pot°eba uvol≥ovat n∞jakΘ objekty
i p°i chyb∞ programu a nßsilnΘmu ukonΦenφ, je dobrΘ si vytvo°it funkci
Clean(), kterß se bude volat nejen z destruktoru,
ale prßv∞ i p°i nßsilnΘm ukonΦenφ. V tΘto funkci pak uvol≥ujte nejen rozhranφ
COM, ale i dynamicky vytvo°enΘ objekty, vlßkna apod.
8.2. ┌vod do DirectInput
Tφm, ₧e jste se rozlouΦili s DirectDraw zdaleka neznamenß, ₧e jste se rozlouΦili
s DirectX. DirectDraw v dneÜnφm DirectX vlastn∞ u₧ ani neexistuje a je pln∞
nahrazeno Direct3D. DalÜφ zajφmavou a d∙le₧itou Φßstφ DirectX je DirectInput.
Ji₧ v ·vodu jsem nastφnil, Φφm se zab²vß. Seznamovßnφ nßm p∙jde rychleji,
proto₧e princip je podobn² jako u DirectDraw.
Pokud budete programovat n∞jakou multimedißlnφ aplikaci (nap°φklad hru), m∙₧ete
pou₧it bu∩ standardnφ zprßvy Windows WM_KEYDOWN, WM_KEYUP,
WM_RBUTTONDOWN, WM_RBUTTONUP atd. nebo m∙₧ete pou₧φt komponentu
DirectInput. M∙₧ete si zkusit chytat klßvesovou zprßvu a t°eba pohybovat spritem
podle Üipek. Zjistφte ale nßsledujφcφ problΘm. SystΘm neposφlß zprßvy
WM_KEYDOWN ·pln∞ pravideln∞ (to proto, ₧e t∞ch
zprßv posφlß vφc a vaÜe zprßva si holt musφ n∞kdy poΦkat). Tato nepravidelnost
se projevuje trhan²m pohybem spritu. M∙₧eme si pomoci tak, ₧e pokud okno zachytφ
WM_KEYDOWN nastavφ n∞jak² flag a "nepustφ ho",
dokud nep°ijde opaΦnß zprßva WM_KEYUP. Zdß se, ₧e
problΘm s trhavostφ je vy°eÜen, ale nynφ si zkuste stisknout p∞t klßves
najednou. Musφte mφt p∞t flag∙ pro ka₧dΘ tlaΦφtko (rovnou si vytvo°te pole pro
102 klßves). Sami jist∞ uznßte, ₧e pokud stisknete vφce tlaΦφtek, tak zaΦne b²t
ve zprßvßch p∞kn² gulßÜ.
Pro takovΘ p°φpady tu mßme DirectInput. I kdy₧ se bez polφ neobejdete (budou
dokonce dv∞), p°esto tφm, ₧e DirectInput nenφ zalo₧en na zprßvßch Window, se
nem∙₧e stßt, ₧e by n∞jakΘ tlaΦφtko bylo ignorovßno, kv∙li jinΘmu, kterΘ bylo
stisknuto o 5 ms d°φv.
JeÜt∞ v∞tÜφ v²hody najdete u pou₧φvßnφ myÜi. Zkuste pou₧φvat myÜ v DirectDraw
bez pou₧itφ DirectInput a budete velice neÜ¥astnφ. Na ka₧dΘm poΦφtaΦi d∞lß myÜ
n∞co jinΘho. Na prvnφm nemusφ b²t vid∞t v∙bec, na druhΘm zase bude blikat a na
t°etφm bude vid∞t, ale zase bude potrhßvat. I tento problΘm °eÜφ DirectInput
beze zbytku. M∙₧ete si ud∞lat animovanΘ kurzory, libovoln∞ velikΘ kurzory a vÜe
bude chodit velmi p∞kn∞ (o tlaΦφtkßch ani nemluv∞).
Navφc DirectInput m∙₧ete pou₧φvat i u naprosto b∞₧nΘ "oknovΘ aplikace". Pro
zajφmavost vßm p°ilo₧φm p°φklad, kter² vyu₧φvß DirectInput a p°itom se jednß o
okno. Tento p°φklad takΘ najdete u SDK 8.0 (8.1). Stßhnout si ho m∙₧ete v
sekci Downloads jako Scrawl.
Poznßmka: VÜimn∞te si, ₧e uvßdφm verzi 8.1do zßvorek za 8.0. To proto, ₧e
my budeme vyu₧φvat rozhranφ 8, ale mßte nainstalovanΘ SDK verze 8.1. Rozdφly
mezi 8.0 a 8.1 jsou v DirectInput minimßlnφ (p°φstup k nim samoz°ejm∞ mßte).
Seznam novinek naleznete v nßpov∞d∞. Navφc n∞kdo z vßs m∙₧e mφt nainstalovanΘ
SDK 8.0 a v tomto p°φpad∞ mu to nebude vadit.
8.2.1. Objekt DirectInput
Zßkladem vÜeho je objekt DirectInput (₧e u₧ jste to n∞kde slyÜeli?). Tento
objekt se vytvo°φ velice podobn∞ jako objet DirectDraw. Jist∞ jste si vÜimli, ₧e
rozhranφ v DirectDraw m∞ly verzi 7 (IDirectDraw7, IDirectDrawSurface7).
To souvisφ s tφm, co jsem psal na zaΦßtku. Verze DX 8.0 ji₧ nemß DirectDraw
samostatn∞ a tudφ₧ nevznikly ani novΘ rozhranφ (rozhranφ IDirectDraw8
neexistuje, i kdy₧ to vypadß p∞kn∞). DirectInput ovÜem verzi 8.0 mß,
tak₧e koneΦn∞ budeme pln∞ vy₧φvat nainstalovanΘ SDK 8.0 (8.1), konkrΘtn∞ rozhranφ
objektu DirectInput IDirectInput8.
8.2.2. Objekty za°φzenφ
V DirectInput pracujeme s tzv. za°φzenφmi (devices), kterΘ p°edstavujφ t°eba myÜ
nebo klßvesnici. K objektu za°φzenφ budeme p°istupovat pomocφ rozhranφ IDirectInputDevice8. P°edpoklßdejme, ₧e budeme
chtφt ovlßdat klßvesnici a myÜ. To jsou dv∞ za°φzenφ, pro ka₧dΘ bude vytvo°en
zvlßÜ¥ objekt typu za°φzenφ. U₧ jist∞ p°em²Ülφte, jak bude vypadat novß t°φda
CInput.
O konkrΘtnφm °eÜenφ si vÜak povφme a₧ v p°φÜtφ lekci.
T∞Üφm se p°φÜt∞ nashledanou.
Ji°φ Formßnek
|