Ve dvou p°edeÜl²ch lekcφch jsme rozebφrali celou knihovnu Display.dll, kterß obsahuje t°φdy CDisplay a CSurface. Tyto dv∞ t°φdy jsou jßdrem naÜeho grafickΘho systΘmu. Dnes na to navß₧eme a p°idßme novou knihovnu Input.dll. Tuto knihovnu jsme ji₧ napsali, staΦφ tedy pouze vlo₧it existujφcφ k≤d do naÜeho projektu.
Tuto knihovnu jsme podrobn∞ rozebφrali v 9. a 10. lekci kurzu DirectX. Mimo jinΘ jsme si °ekli, ₧e bude nutnΘ p°ed∞lat systΘm vykreslovanφ kurzoru. A prßv∞ to nßs Φekß v dneÜnφ lekci.
V prvnφm kroku tedy p°idejme knihovnu Input.dll do stßvajφcφho projektu z minul²ch lekcφ. Zkopφrujte tedy adresß° projektu Input do adresß°e Game. V tomto adresß°i budeme mφt tedy Common, Display, Input, Game a adresß°e Debug a Release. PotΘ z Visual C++ upravφme nßÜ projekt. Z menu Project vyberte polo₧ku Insert Project into Workspace. Vyberte soubor Input.dsp v adresß°i Input a v Classview se objevφ naÜe znßmß knihovna Input.dll.
Nynφ nastavme zßvislosti projekt∙ tak, aby projekt Input mohl vyu₧φt Display a projekt Game mohl vyu₧φt Input. Nastavte dialog Dependencies takto:
Pokud te∩ oznaΦφte jako aktivnφ projekt Game a zkompilujete ho, zkompilujφ se projekty postupn∞ v po°adφ Display, Input a Game.
Abychom mohli volat funkce u Input.dll musφte vlo₧it hlaviΦkov² soubor Input1.h do hlavnφho souboru projektu Game:
#include "..\Input\Input1.h"
Samoz°ejm∞ musφme brßt v ·vahu to, ₧e projekt Input je ve svΘm adresß°i.
Nynφ do funkce WinMain() dopl≥te volßnφ funkce inpCreateDirectInputSystem() a p°edejte si nßsledujφcφ parametry:
handle instance - hInstance
handle okna - g_hWnd
rozliÜenφ - funkce disGetResolution() toto rozliÜenφ vracφ. Z toho plyne ₧e volßnφ inpCreateDirectInputSystem() musφ b²t a₧ za disInit(), tedy za inicializacφ DirectDraw.
Pokud si vzpomφnßte, tak p°i tvorb∞ Input.dll jsme nepou₧φvali knihovnu Common.dll. Kdy₧ se podφvßte na k≤d, tak zjistφte, ₧e mφsto makra DXTRACE pou₧φvßme obyΦejnΘ TRACE apod. Pro sprßvnou funkci nenφ t°eba toto p°ed∞lßvat, ale bude dobrΘ, kdy₧ t∞chto pßr drobnostφ takΘ upravφme. Musφme proto jen vlo₧it Common.h do souboru Input1.cpp. Dßle musφte v nastavenφ projektu p°ilinkovat knihovnu Common.dll. Ud∞lßme to ·pln∞ stejn∞ jako u ostatnφch projekt∙ (nezapome≥te na Release):
Nynφ tedy p°epiÜte makra TRACE na makra DXTRACE nebo DXERR podle toho, jestli chceme rozluÜtit nßvratovou hodnotu nebo ne. Jß jsem vÜude krom∞ jednoho p°φpadu pou₧il makro DXERR. Nezapome≥te vymazat znaky \n, proto₧e od°ßdkovßnφ se provßdφ automaticky a takto byste od°ßdkovali 2x.
Nynφ se budeme v∞novat hlavnφmu tΘmatu dneÜnφ kapitoly. Jsme nßroΦnφ a chceme pou₧φvat i animovanΘ kurzory. CelΘ je to o trochu slo₧it∞jÜφ a v²sledek vypadß lΘpe.
Pou₧ijeme k tomu zcela novou t°φdu CCursor, kterß bude pracovat s DirectDraw. Tato t°φda se bude starat o vytvo°enφ a vykreslenφ kurzoru na obrazovku. Zde je deklarace t°φdy:
class CCursor
{
BOOL m_bInit;
int m_iWidth;
int m_iHeight;
CString m_csIntPath;
CSurface m_surCursor;
int m_iPhase;
int m_iSpeed;
int m_nPhases;
DWORD m_dwOldTime;
public:
HRESULT Create(CString csIntPath, int cx, int cy);
HRESULT Create(CString csIntPath, int cx, int cy, int nSpeed);
HRESULT Update(CPoint ptCursor);
public:
CCursor();
~CCursor();
};
Je to velice jednoduchΘ. V nßsledujφcφ tabulce vidφte popis prom∞nn²ch.
Typ prom∞nnΘ | JmΘno prom∞nnΘ | Popis |
int | m_iWidth | èφ°ka jednoho polφΦka kurzoru |
int | m_iHeight | V²Üka jednoho polφΦka kurzoru |
CString | m_csIntPath | Cesta k bitmap∞ kurzoru |
CSurface | m_surCursor | Povrch kurzoru |
int | m_iPhase | Aktußlnφ sekvence animovanΘho kurzoru |
int | m_iSpeed | AnimaΦnφ rychlost v milisekundßch |
int | m_nPhases | Celkov² poΦet animaΦnφch krok∙ |
DWORD | m_dwOldTime | Uplynul² Φas od poslednφho vykreslenφ |
Dßle si proberme metody:
JmΘno metody a parametry | Popis |
Create(CString csIntPath, int cx, int cy) | Tato verze metody Create() vytvß°φ statick² kurzor z bitmapy csIntPath a velikosti cx a cy. |
Create(CString csIntPath, int cx, int cy, int nSpeed) | Druhß verze je ji₧ zajφmav∞jÜφ. Vytvß°φ animovan² kurzor z bitmapy csIntPath a velikosti jednoho polφΦka cx a cy. AnimaΦnφ rychlost je urΦena parametrem nSpeed. |
Update(CPoint ptCursor) | Tato funkce vykreslφ kurzor na po₧adovan²ch sou°adnicφch. |
Konstruktor a destruktor t°φdy v seznamu neuvßdφm, ale to je samoz°ejmost. T°φdu m∙₧ete p°idat pomocφ Visual Studia nebo m∙₧ete vyu₧φt p°φkladu u lekce.
Animovan² kurzor je ulo₧en v bitmap∞ v tomto formßtu:
╚ernß okolnφ barva se oznaΦφ jako pr∙hlednß. JednotlivΘ sekvence se m∞nφ po uplynutφ doby urΦenΘ parametrem iSpeed. Dßle parametry cx a cy urΦujφ velikost jednoho polφΦka. Z t∞chto hodnot se takΘ vypoΦφtß, kolik mß kurzor animaΦnφch sekvencφ. Rozeberme si nynφ detailn∞ji co se odehrßvß v metodßch t°φdy CCursor.
Nejprve nßsleduje prvnφ verze metody Create():
HRESULT CCursor::Create(CString csIntPath,
int cx, int cy)
{
DWORD dwRet = 1;
if(!m_bInit) {
m_iWidth = cx;
m_iHeight = cy;
m_csIntPath = csIntPath;
dwRet = m_surCursor.Create(csIntPath,
DDFLG_COLORKEY);
if(dwRet != ERROR_SUCCESS) {
DXERR("Cannot
create surface for cursor due", dwRet);
return dwRet;
}
m_bInit = true;
dwRet = ERROR_SUCCESS;
}
return dwRet;
}
Metoda je krßtkß a srozumitelnß. Ulo₧φme si vstupnφ parametry a vytvo°φme povrch pro bitmapu csIntPath. To je celΘ.
Druhß verze je jen o mßlo slo₧it∞jÜφ:
HRESULT CCursor::Create(CString csIntPath,
int cx, int cy, int iSpeed)
{
DWORD dwRet = 1;
if(!m_bInit) {
//
// Call standard creation
dwRet = Create(csIntPath, cx, cy);
if(dwRet == ERROR_SUCCESS) {
m_iPhase = 0;
m_iSpeed =
iSpeed;
//
// Compute
number of phases
m_nPhases = (int)m_surCursor.Width()
/ cx;
//
// Check
validity of phases
if((m_nPhases
* cx) > (int)m_surCursor.Width()) m_nPhases--;
//
// Ok
m_bInit =
true;
}
}
return dwRet;
}
Nejprve zavolßme prvnφ verzi metody Create() (tφm vytvo°φme povrch pro bitmapu a ulo₧φme parametry kurzoru). Dßle objekt p°ipravφme pro spuÜt∞nφ animace tj. vynulujeme poΦφtadlo aktußlnφ sekvence, nastavφme animaΦnφ rychlost a vypoΦteme, kolik sekvencφ mß naÜe bitmapa. To provedeme jednoduÜe: vyd∞lφme Üφ°ku bitmapy Üφ°kou jednoho polφΦka. Proto₧e pot°ebujeme celΘ Φφslo a bitmapa nemusφ b²t p°esn∞ d∞litelnß (m∞la by b²t), musφme zkontrolovat zda-li poΦet sekvencφ odpovφdß i zp∞tn∞. Zkrßtka vynßsobφme vypoΦtenou hodnotu Üφ°kou jednoho polφΦka a zjistφme zda-li tato hodnota nep°ekraΦuje povrch kurzoru, to by toti₧ zp∙sobilo fatßlnφ chybu p°i vykreslovßnφ. Pokud tomu tak skuteΦn∞ je, tak jen snφ₧φme poΦet sekvencφ o jedniΦku (horÜφ u₧ to b²t toti₧ nem∙₧e).
Nakonec si popφÜeme vykreslovacφ metodu Update(), kterß je u₧ slo₧it∞jÜφ:
HRESULT CCursor::Update(CPoint ptCursor)
{
CRect rcDest, rcSrc;
DWORD dwRet = 1, newTime;
if(m_bInit) {
//
// For static cursors
if(m_nPhases == 0) {
//
// One static
place
rcSrc.top =
0;
rcSrc.left =
0;
rcSrc.right =
m_iWidth;
rcSrc.bottom
= m_iHeight;
}
//
// Compute move source rectangle
else {
//
// Compute
phase, increment phase if is time
newTime =
GetTickCount();
//
// Get time
from last update
if((newTime -
m_dwOldTime) > (DWORD)m_iSpeed) {
//
// If it right time, increment phase
m_iPhase++;
//
// Check to overflow phases
if(m_iPhase == m_nPhases) {
m_iPhase = 0;
}
//
// Save time of last update
m_dwOldTime = newTime;
}
//
// Compute rectangle from current
phase
//
// Static
vertical postion
rcSrc.top =
0;
rcSrc.bottom
= m_iHeight;
// Variable horizontal pos.
rcSrc.left = m_iPhase * m_iWidth;
rcSrc.right =
rcSrc.left + m_iWidth;
}
//
// Destionation rectangle is same for
both cursors
rcDest.left = ptCursor.x;
rcDest.top = ptCursor.y;
rcDest.bottom = rcDest.top + m_iHeight;
rcDest.right = rcDest.left + m_iWidth;
//
// Blit cursor to back surface
dwRet = disBlt(rcDest, rcSrc, &m_surCursor);
}
return dwRet;
}
Metoda je vlastn∞ jednoduchß jen pro statickΘ kurzory. Pokud je toti₧ poΦet animaΦnφch krok∙ nulov², jednß se o statick² kurzor. Je-li tomu tak, staΦφ jen zinicializovat zdrojov² a cφlov² obdΘlnφk pro funkci disBlt(). Zdrojov² je p°esn∞ velk² jako bitmapa. Rozm∞ry jsme si ulo₧ili do prom∞nn²ch m_iWidth a m_iHeight. Cφlov² obdΘlnφk je poka₧dΘ stejn² a je urΦen mφstem, kde mß b²t kurzor vykreslen. Hornφ lev² roh je p°φmo to mφsto a dolnφ prav² je jen posunuty o velikost polφΦka.
U animovanΘho kurzoru je jin² v²poΦet zdrojovΘho obdΘlnφka, proto₧e ten se musφ pohybovat po bitmap∞. V Φßsti urΦenΘ jen pro animovanΘ kurzory tedy za prvΘ modifikujeme aktußlnφ fßzi a za druhΘ inicializujeme tento pohybliv² obdΘlnφk. Fßzi v₧dy m∞nφme po uplynutφ Φasu m_iSpeed, dßle musφme kontrolovat hornφ mez urΦenou prom∞nnou m_iPhases. P°i ka₧dΘ zm∞n∞ navφc musφme ulo₧it aktußlnφ Φas, abychom mohli poΦφtat Φasov² rozdφl do dalÜφ zm∞ny. Za druhΘ tedy vypoΦteme zdrojov² obdΘlnφk. Ve vertikßlnφm sm∞ru je vlastn∞ stejn² jako u statickΘho kurzoru, proto₧e polφΦka jsou v bitmap∞ ulo₧eny horizontßln∞. Horizontßlnφ pozice zßle₧φ na aktußlnφ fßzi animace. Lev² okraj je tedy jen vynßsobenφ tΘto fßze a Üφ°ky polφΦka. Prav² okraj je oproti levΘmu posunut² o Üφ°ku polφΦka. Zbytek metody je toto₧n².
Nynφ pouze staΦφ vytvo°it ve t°φd∞ CInput jeden objekt typu CCursor a zavolat jeho metodu Create(). Dßle musφte upravit metodu UpdateCursor():
HRESULT CInput::UpdateCursor()
{
HRESULT dwRet = 1;
if(m_bInit && m_bShowCursor) {
m_theCursor.Update(m_ptCursor);
}
return dwRet;
}
Nynφ je metoda velice jednoduchß. Prost∞ jen zavolßme metodu kurzoru Update(), ale jen je-li kurzor viditeln².
TIP: P°edeÜl²m postupem jsme vytvo°ili jeden kurzor, co₧ je dosti nepru₧nΘ. Zkuste si ud∞lat dynamickΘ pole kurzor∙, potΘ vytvo°te metodu CInput::LoadCursor(DWORD, CString), kterß nahraje kurzor do tohoto pole. Parametr DWORD je n∞jakΘ ID kurzoru. Dßle ud∞lejte metodu SetCursor(DWORD), kterß vybere kurzor z tohoto pole podle ID a nastavφ ho jako aktußlnφ kurzor, kter² se bude vykreslovat.
Nakonec jeÜt∞ malinko upravφme projekt Game, aby bylo vid∞t, ₧e systΘm DirectInput pracuje:
void UpdateFrame()
{
disUpdateBackground();
inpProcessInput();
// Pri stisknuti
klavesy Esc ukoncime aplikaci
if(inpIsKeyDown(DIK_ESCAPE, FALSE)) {
PostMessage(g_hWnd, WM_DESTROY, 0,
0);
}
inpUpdateCursor();
disPresent();
}
Do funkce UpdateFrame() p°idejte barevn² k≤d. Nynφ se vßm bude zobrazovat animovan² kurzor a budete moci pou₧φt vÜechny exportovanΘ funkce t°φdy CInput. Pro p°φklad vyu₧φvßm klßvesu Esc jako v²stupnφ klßvesu z programu.
Poznßmka: JeÜt∞ nezapome≥te vymazat nepot°ebnΘ slo₧ky t°φdy CInput. Je to atribut HICON m_hCursor a metoda SetCursor(HICON).
DneÜnφ lekce byla pro zm∞nu trochu kratÜφ, ale urΦit∞ byla velk²m p°φnosem pro nßÜ projekt. Mo₧nß by se mohlo zdßt, ₧e p°φÜt∞ u₧ nebudeme mφt co d∞lat, ale opak je pravdou, proto₧e p°φÜt∞ ji₧ koneΦn∞ zaΦneme s tφm grafick²m menu. Doufßm, ₧e to bude zajφmav∞jÜφ.
DneÜnφ p°φklad si samoz°ejm∞ budete moci stßhnout v rubrice Downloads.
T∞Üφm se p°φÜt∞ nashledanou.