V tomto článku si ukážeme, jak pracovat s joystickem v „běžné“ aplikaci, tedy nikoliv a aplikaci DirectX.
Vytvořme si aplikaci, jejíž jediné (hlavní) okno bude popup přes celou obrazovku. Ukončit samozřejmě půjde pomocí Alt-F4, a navíc si přidáme možnost ukončení na <Escape>. Vytvoříme tedy takto hlavní okno aplikace:
BOOL CJoystickDemoApp::InitInstance()
{
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
m_mainWnd = new CMainWnd();
m_mainWnd->CreateEx(WS_EX_TOPMOST,
AfxRegisterWndClass(0, ::LoadCursor(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDC_CROSSHAIR)),
(HBRUSH)(COLOR_WINDOW+1), ::LoadIcon(NULL, IDI_APPLICATION)),
"Joystickmain", WS_POPUP, 0,0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),NULL, NULL, NULL);
m_pMainWnd = m_mainWnd;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
Do zdrojů jsem předtím přidal vlastní kurzor, „zaměřovací kříž“, který bude reagovat na pohyb joysticku. Dále je do zdrojů přidaná ještě jedna ikona, která se bude zobrazovat při stisknutí 1. tlačítka joysticku v místě aktuální polohy kurzoru.
Pokud chceme, aby naše okno dostávalo příslušné zprávy od joysticku, musíme mu ho přiřadit funkcí joySetCapture:
MMRESULT joySetCapture(
HWND hwnd,
UINT uJoyID,
UINT uPeriod,
BOOL fChanged
);
Jednotlivé parametry mají tento význam:
- hwnd – handle okna, které bude dostávat zprávy od joysticku
- uJoyID – identifikátor joysticku (JOYSTICKID1 nebo JOYSTICKID2)
- uPeriod – perioda (v milisekundách), ve které bude okno dostávat zprávy joysticku
- fChanded – pokud je nastaven na TRUE, zprávy o pohybu joysticku jsou posílány pouze tehdy, pokud změna polohy přesáhne prahovou hodnotu nastavenou funkcí joySetThreshold; v opačném případě jsou zprávy posílány v intervalu uvedeném v parametru uPeriod.
MMRESULT joySetThreshold(
UINT uJoyID, / identifikátor joysticku
UINT uThreshold // prahová hodnota
);
Uvedli jsme si již 2 ze tří klíčových funkcí pro práci s joystickem. Po použití joysticku ho uvolníme funkcí joyReleaseCapture:
MMRESULT joyReleaseCapture(
UINT uJoyID // identifikátor joysticku
);
Tím jsme si ukázali začátek a konec, nyní k tomu hlavnímu, co je „uprostřed“. Ještě upozorním, že si trochu zjednodušíme situaci tím, že budeme uvažovat pouze jeden joystick. Když se podíváte do dokumentace na funkce a zprávy , kterými se zde budu zabývat, zjistíte, že všechny mají svůj ekvivalent lišící se pouze tím, že místo „1“ je v jejich názvu „2“. Existují v zásadě 4 dvojice zpráv joysticku, jejich názvy již napovídají o jejich významu:
MM_JOY1BUTTONDOWN (resp. MM_JOY2BUTTONDOW)
Došlo ke stlačení některého z tlačítek joysticku. Význam jednotlivých parametrů je následující: wParam určuje, která tlačítka jsou aktuálně stačená a stav kterého se změnil:
- JOY_BUTTON1CHG – změnil se stav 1. tlačítka
- JOY_BUTTON2CHG - změnil se stav 2. tlačítka
- JOY_BUTTON3CHG - změnil se stav 3. tlačítka
- JOY_BUTTON4CHG - změnil se stav 4. tlačítka
- JOY_BUTTON1 – je stisknuto 1. tlačítko
- JOY_BUTTON2 – je stisknuto 2. tlačítko
- JOY_BUTTON3 – je stisknuto 3. tlačítko
- JOY_BUTTON4 – je stisknuto 4. tlačítko
V parametru lParam jsou pak uloženy souřadnice joysticku takto
x-pozice = LOWORD(lParam)
y-pozice = HIWORD(lParam)
Souřadnice joysticku mají v obou osách v rozsahu 0 – 65535 (16bitové číslo), jak uvidíme za chvíli.
MM_JOY1BUTTONUP (resp. MM_JOY2BUTTONUP)
Došlo k uvolnění některého tlačítka joysticku. Význam parametrů je stejný u zprávy MM_JOY1BUTTONDOWN.
MM_JOY1MOV (resp. MM_JOY2MOVE)
Došlo ke změně polohy joysticku (v ose x nebo y). Parametr wParam určuje stav tlačítek joysticku, může mít některou z hodnot:
- JOY_BUTTON1 – je stisknuto 1. tlačítko
- JOY_BUTTON2 – je stisknuto 2. tlačítko
- JOY_BUTTON3 – je stisknuto 3. tlačítko
- JOY_BUTTON4 – je stisknuto 4. tlačítko
V parametru lParam jsou pak uloženy souřadnice joysticku:
x-pozice = LOWORD(lParam)
y-pozice = HIWORD(lParam)
MM_JOY1ZMOVE (resp. MM_JOY2ZMOVE)
Došlo ke změně z-souřadnice joysticku. wParam určuje stav tlačítek joysticku
- JOY_BUTTON1 – je stisknuto 1. tlačítko
- JOY_BUTTON2 – je stisknuto 2. tlačítko
- JOY_BUTTON3 – je stisknuto 3. tlačítko
- JOY_BUTTON4 – je stisknuto 4. tlačítko
lParam pak určuje z-souřadnici joysticku.
Přesný význam z-souřadnice nemohu vyzkoušet, mám k disposici obyčejný 2osý joystick se 4 tlačítky, ale mělo by jít o nějaký ten „klobouček“ na vrcholu páky, o němž však vím jen teoreticky, popř. některé joysticky mají jakýsi „slider“, řekněme „plynové kolečko“. Pokud máte někdo k disposici sofistikovanější joystick, můžete to vyzkoušet a podělit se o přesné poznatky z praxe.
Nyní se vraťme k naší ukázkové aplikaci. Řekněme, že budeme chtít, aby při vyklonění joysticku v jedné ose se v příslušném směru posunul kurzor na obrazovce, dále pokud při tomto posunu bude současně drženo stlačené 2. tlačítko, aby kurzor kreslil čáru ve stopě svého pohybu, a nakonec aby při stisknutí 1. tlačítka se v místě kurzoru nakreslila ikona.
Změnu kurzoru okna již máme zajištěnu jeho načtením ze zdrojů (ve funkci CWnd::CreateEx):
//....
AfxRegisterWndClass(0, ::LoadCursor(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDC_CROSSHAIR)),
//....
Ikonu si načteme do členské proměnné okna (typu HICON) třeba hned v konstrukturu:
CMainWnd::CMainWnd()
{
m_hIconShot = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_SHOT));
}
Budeme tedy zachytávat zprávy MM_JOY1BUTTONDOWN a MM_JOY1MOVE. Musíme tedy přepsat proceduru okna - WindowProc:
LRESULT CMainWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message )
{
case MM_JOY1BUTTONDOWN:
OnMM_JOY1BUTTONDOWN(wParam, lParam);
break;
case MM_JOY1MOVE:
OnMM_JOY1MOVE(wParam, lParam);
break;
case WM_KEYDOWN:
if ( wParam == VK_ESCAPE )
DestroyWindow();
break;
}
return CWnd::WindowProc(message, wParam, lParam);
}
Vzhledem k tomu, že musíme nechat joysticku nějaký rozsah souřadnic kolem středu, který budeme považovat za klidovou polohu, řekněme třeba, že za výkyv budeme považovat souřadnice menší než 28000 a větší než 36000. Funkce „ošetřující pohyb joysticku pak vypadá takto:
#define _JOY_STEP 10
void CMainWnd::OnMM_JOY1MOVE(WPARAM wParam, LPARAM lParam)
{
POINT ptCursor;
HDC hdc;
GetCursorPos(&ptCursor);
int x = LOWORD(lParam);
int y = HIWORD(lParam);
BOOL Button2Down = wParam & JOY_BUTTON2;
if ( Button2Down )
{
hdc = ::GetDC(m_hWnd);
MoveToEx(hdc, ptCursor.x, ptCursor.y, NULL);
}
if ( x < 28000 )
ptCursor.x -= _JOY_STEP;
if ( x > 36000 )
ptCursor.x += _JOY_STEP;
if ( y < 28000 )
ptCursor.y -= _JOY_STEP;
if ( y > 36000 )
ptCursor.y += _JOY_STEP;
SetCursorPos(ptCursor.x, ptCursor.y);
if ( Button2Down )
{
LineTo(hdc, ptCursor.x, ptCursor.y);
::ReleaseDC(m_hWnd, hdc);
}
}
Změnou hodnoty _JOY_STEP ovlivníme rychlost pohybu joysticku.
Vykreslení ikony na stačení 1. tlačítka joysticku pak vypadá takto:
void CMainWnd::OnMM_JOY1BUTTONDOWN(WPARAM wParam, LPARAM lParam)
{
POINT ptCursor;
HDC hdc;
GetCursorPos(&ptCursor);
if (wParam & JOY_BUTTON1CHG)
{
hdc = ::GetDC(m_hWnd);
DrawIcon(hdc, ptCursor.x - 16, ptCursor.y - 16, m_hIconShot);
PlaySound(MAKEINTRESOURCE(IDR_WAVE_SHOT), AfxGetInstanceHandle(),
SND_RESOURCE | SND_ASYNC);
::ReleaseDC(m_hWnd, hdc);
}
}
Jak vidíte, kromě nakreslení ikony ještě přehrajeme pomocí funkce PlaySound zvuk (formátu WAVE) přidaný do zdrojů.
Ukázkový projekt si můžete stáhnout zde: joystick.zip.