DirectX (10.) V dnešní jubilejní lekci budeme pokračovat v implementaci nové třídy CInput, kterou jsme rozdělali v minulé lekci. Výsledkem dnešní lekce, by měla být fungující myš i klávesnice, ale bohužel zatím nemáme co ovládat. V několika příštích lekcích však hodlám sestavit větší projekt, který bude využívat znalostí, které máte. Bude se jednat o trochu složitější systém grafického menu, ale více se o tom dovíte až příště. 10.1. Funkce ProcessInput() a UpdateCursor()Obě tyto funkce jsou společné tím, že je budeme volat z funkce UpdateFrame() v našem hlavním programu. Problém je ale v tom, že funkce ProcessInput() musíte volat dříve, než obnovíte grafiku. Kdybyste ale obnovili grafiku dříve než kurzor, byl by kurzor skryt za pozadím a to nechceme. Funkce ProcessInput() stahuje data z klávesnice a myší, případně posune souřadnice kurzoru. Na tyto nové souřadnice se posléze nakreslí kurzor z funkce UpdateCursor(). Abychom mohli tyto dvě hlavní funkce implementovat, musíme přidat pár nových proměnných a metod, které s těmito funkcemi spolupracují. Za prvé to bude inline funkce MoveCursor(), která je vskutku primitivní: void MoveCursor(int dx, int dy) {m_ptCursor += CPoint(dx, dy);} Vidíte, že musíme přidat atribut m_ptCursor. To je objekt typu CPoint (pokud nemáte rádi MFC, můžete požít strukturu POINT) a obsahuje aktuální absolutní souřadnice kurzoru na monitoru. Hodnoty x-ové souřadnice se pohybují od 0 do hodnoty rozlišení v x-ovém směru. Ve vertikálním směru je to obdobné. Nyní už víte, proč jsme si ukládali aktuální rozlišení. Funkce ProcessInput() vypadá následovně:
HRESULT dwRet = 1; Přibyly ještě další proměnné. Proměnná m_bShowCursor nám říká, zda-li je kurzor vidět či nikoliv. Pokud ne, je zbytečné stahovat data z myši a vůbec pracovat s kurzorem. První co v této funkci uděláme je, že stáhneme data z klávesnice. Funkce GetDeviceState() naplní pole o 256 byte prvcích. Pole m_arKeyboard je tedy další atribut. V tomto poli je uložena informace o stavu všech kláves. To samé provedeme s myší (pokud je kurzor zobrazen). Za prvé stáhneme data z myši tentokrát do struktury DIMOUSESTATE, která obsahuje vše co potřebuje: přírůstky pozice kurzoru a stavy tlačítek. V dalším kroku přičteme relativní přírůstky kurzoru k absolutní pozici - to provádí výše definovaná funkce MoveCursor(). Všimněte si, že používáme atributy lX a lY struktury DIMOUSESTATE. Struktura DIMOUSESTATE obsahuje následující atributy:
LONG
lX;
lX a lY jsou přírůstky ve směru osy x a y. lZ je přírůstek otočení kolečka myši (pokud myš nemá kolečko, je tato hodnota rovna 0). Poslední pole rgbButtons[] obsahuje stavy čtyř tlačítek. V poli je na prvním místě levé tlačítko, pak pravé a na dalších dvou indexech jsou další tlačítka, pokud myš nějaké má. Pokud je požadované tlačítko stisknuto, je v poli nenulová hodnota jinak 0. V posledním kroku funkce, provedeme tzv. clipping kurzoru. Musíme zabránit tomu, aby kurzor mohl vyjet mimo obrazovku. Konečně využijeme rozlišení, které jsme si uložili. A to je vše. Dále si rozeberme funkci UpdateCursor(). I k této funkci budeme potřebovat jednu funkce navíc. Bude to SetCursor(), která nastaví handle kursoru, který se bude vykreslovat na pozici určené atributem m_ptCursor. Opět se jedná o jednoduchou inline funkci: HRESULT SetCursor(HICON hCursor) {m_hCursor = hCursor; return 0;} Pouze přiřadíme nový handle.
Samotná funkce UpdateCursor() bude také celkem
primitivní: Nejdříve otestujeme správnost handlu m_hCursor a zobrazíme kurzor jen když má být skutečně zobrazen (m_bShowCursor). Jako druhý a poslední krok zavoláme funkci DrawIcon(), která vykreslí požadovaný kurzor na správných souřadnicích. Možná si teď právě myslíte, proč nevyužit k vykreslení kurzoru DirectDraw. Bylo by správné to tak udělat, ale v současné době máme kód DirectDraw v modulu .exe souboru a ten využívá knihovny Input.dll. Tudíž Input.dll nemůže být závislá na .exe souboru, kde je potřebné DirectDraw. Později projekt upravíme tak, že i DirectDraw bude v samostatné knihovně a poté budeme kurzor vykreslovat pomocí DirectDraw. 10.2. Exportování funkcíNyní jsme dospěli do stavu, kdy náš systém DirectInput má něco dělat a potřebovali bychom to vyzkoušet. To znamená, že potřebuje volat funkce jako je ProcessInput() a UpdateCursor(). Toto je dá provést několika způsoby (o těchto způsobech jsem psal v minulých lekcích). Vytvořme tedy globální objekt CInput: CInput g_theInput; Tento řádek napište na začátek souboru input1.cpp. Nyní do hlavičkového souboru input1.h doplníme seznam exportovaných globálních funkcí:
INPUT_API HRESULT
inpCreateDirectInputSystem(HINSTANCE hInst, HWND hWnd, CSize csResolution); Konstanta INPUT_API je definována na dvou místech různě. V implementační souboru input1.cpp definujte konstantu takto: #define INPUT_API __declspec(dllexport) Tuto definici musíte provést před vložením hlavičkového souboru input1.h. Za druhé připište následují řádky na začátek souboru input1.h:
#ifndef INPUT_API Tyto řádky zařídí to, že pokud se pokusíme vložit tento hlavičkový soubor mimo knihovnu input.dll, nadefinuje se makro COMMON_API pro import výše uvedených funkcí. Naopak v modulu knihovny je konstanta definována pro export funkcí. Od teď, pokud chcete použít funkce s prefixem inp, musíte pouze vložit hlavičkový soubor input1.h. Zbývá jen nadefinovat exportované funkce. Definice bude velmi jednoduchá. Stačí využít globálního objektu k volaní jednotlivých metod:
// Dále upravíme druhý projekt DirectDraw tak, abychom konečně mohli zavolat nové funkce. Do souboru control.cpp vložte hlavičkový soubor input1.h tímto způsobem:
#include "..\Input\Input1.h" Do funkce InitDD() přidejte následující řádky:
dwResult = inpCreateDirectInputSystem(AfxGetInstanceHandle(),
hWnd, CSize(RES_X, RES_Y));
Zaprvé je nutno zavolat funkci, která zinicializuje DirectInput. Funkce
AfxGetInstanceHalndle() vrací handle instance, což
je přesně to, co potřebujeme. Dále posíláme handle okna a nakonec strukturu
CSize s rozlišením obrazovky. V druhém kroku nastavíme kurzor tzn., že nahrajeme
požadovaný kurzor do paměti a nastavíme handle funkcí
inpSetCursor(). Kurzor přidejte do zdrojů projektu DirectDraw do složky
Icons.
void CControl::UpdateFrame()
//... Na začátku zavoláme funkci inpProcessInput() a na konci před prohozením bufferů inpUpdateCursor(). Po zkompilování a spuštění vskutku uvidíte kurzor (poznámka: myš musí mít nastavený cooperative level DISCL_EXCLUSIVE, jinak bude dělat neočekávané věci). Používám funkce pro získání a uvolnění kontextu zařízení našeho zadního bufferu (což je v tuto chvíli nejlepší řešení, i když jsou funkce pomalé). Pokud bychom použili kontext zařízení okna, kurzor by problikával. Nezapomeňte kontext zařízení uvolnit. Ještě jsme zapomněli na jednu funkci. Je to funkce ShowCursor(), která podle parametru buď zobrazí či skryje kurzor. Jistě tušíte, že funkce bude velmi krátká. Zkrátka nastaví svým jediným parametrem atribut třídy m_bShowCursor. I tuto funkci exportujte z knihovny: input1.h: void ShowCursor(BOOL bShow = TRUE) {m_bShowCursor = bShow;}
INPUT_API void inpShowCursor(BOOL bShow =
TRUE); input1.cpp:
void inpShowCursor(BOOL bShow) 10.3. Detekce stisku klávesy a tlačítka myšiBudeme pokračovat v implementaci zbylých funkcí, které zajišťují detekci stisku ať klávesy na klávesnici nebo tlačítka myši. Jsou to funkce:
IsKeyDown() Abychom mohli obsloužit jak klávesnici tak myš, musíme mít další pole, kde bude nastaveno zda-li byla klávesa obsloužena či nikoliv (TRUE nebo FALSE). Funkce IsRButtonDown():
// Definuji je rovnou pro obě tlačítka, protože je zřejmé, že obě funkce budou totožné právě až na tyto konstanty. Nyní se konečně dostaneme k principu funkce. První podmínka (1) zjišťuje zda-li je pravé resp. levé tlačítku stisknuté. Testujeme hodnotu v poli rgbButtons, o kterém již byla řeč. Pokud není žádné tlačítko stisknuté (8), musíme vynulovat obsluhu, abychom zaznamenali nový stisk. V případě, že tlačítko stisknuté je, musíme zjistit, zda-li chce uživatel použít obsluhu (2). Pokud ne (7), jednoduše vrátíme TRUE. Problém nastává, když uživatel požaduje obsluhu. Zase tak velký problém to není. Zkrátka se podíváme do obslužného pole (3) a zjistíme, jestli už tlačítko bylo obslouženo či nikoliv. Pokud ano, vracíme FALSE (6) a pokud ne vracíme TRUE (5), ale navíc je potřeba nastavit (4), že tlačítko bylo právě obslouženo. Funkce IsRButtonUp(): return (m_MouseState.rgbButtons[MOUSEBUTTON_LEFT]) ? FALSE : TRUE; Tato funkce je proti svému opaku velmi jednoduchá. Prostě jen vracíme TRUE, pokud je tlačítko nahoře a FALSE pokud je stisknuté. Opět jsou funkce pro pravé a levé tlačítko identické. Zbývá metoda IsKeyDown():
// Funkce je v principu naprosto stejná jako u myši. Musíme ovšem definovat makro KEYDOWN: #define KEYDOWN(name, key) (name[key] & 0x80) Makro zjistí hodnotu v obslužném poli klávesnice, které má tentokrát 256 prvků (pro každou klávesu jeden). Na začátku je test vstupního parametru, který pouze hlídá, aby uživatel nepřesáhl meze pole. Zbytek funkce je naprosto analogický. Všech 5 uvedených funkcí budeme exportovat: input1.h:
INPUT_API BOOL inpIsRButtonDown(BOOL _Handling
= TRUE); input1.cpp:
BOOL inpIsRButtonDown(BOOL _Handling) 10.4. Funkce RestoreDevices()A je tu úplně poslední funkce! Je to funkce, která obnoví přístup k zařízením, když aplikace ztratí fokus (vlastně, když ho opětovně dostane). Funkce bude velmi jednoduchá:
HRESULT CInput::RestoreDevices() Prostě zavoláme funkci Acquire() pro každé zařízení, které máme. I tuto funkci exportujte. To je vše k naší třídě. Nyní ještě malinko upravíme projekt DirectDraw, abychom vyzkoušeli detekční funkce. Vložte hlavičkový soubor input1.h rovněž do souboru directdraw.cpp a pak funkci MainWndProc() upravte takto:
LRESULT CALLBACK MainWndProc( HWND hWnd,
UINT msg, WPARAM wParam, LPARAM lParam ) Tato úprava zajistí správnou funkčnost zařízení i pro ztracení a opětném navrácení fokusu okna. Na úplný závěr ještě drobně upravte funkci UpdateFrame() takto:
void CControl::UpdateFrame()
//... Všimněte si, že naše funkce inpIsKeyDown() přijímá parametr DIK_SPACE. To je konstanta, která je unikátní pro každou klávesu. Úplný seznam těchto konstant najdete na této stránce. Vyzkoušejte si, co by program dělal, kdybyste nepoužili obsluhu (funkce bude naprosto nepoužitelná). Po této úpravě změní raketka směr po každém stisku mezerníku. Dnešní kód si samozřejmě můžete stáhnout jako obvykle v sekci Downloads. 10.5. ZávěrTímto dílem jsme úplně dokončili kurz DirectInput, který samozřejmě nebyl zcela úplný a podrobný, ale základy jsme zvládli. Jak jsem naznačil minule i na začátku dnešní lekce, hodlám příště začít větší příklad, kde kompletně využijeme třídy CInput avšak mohutně upravíme DirectDraw. Těším se příště nashledanou. © 2001
|