DirectInput

In deze tutorial ga ik jullie wat vertellen over DirectInput. DI , DirectInput dus, zorgt voor de invoerapparaten. Daarmee bedoelen we dus toetsenbord, muis, joysticks, racestuur en dat soort apparaten. DI is sneller dan de vertrouwde Win32 programmeertaal. Dit zorgt dus voor een betere reactie, waardoor er minder vertraging tussen de beweging van de speler en de werkelijke beweging op het scherm, waardoor de speler een groter spelplezier beleeft!

Voordat ik verder ga, wil ik even kwijt dat ik in deze tutorial niet de Force Feedback apparaten ga bespreken. Dit doe ik in een latere tutorial.

Goed, DI is net als alle andere DirectX componenten volgens de COM methode opgebouwd. Dit betekent dat het dus uit een aantal objecten bestaat, wel te weten:

  • DirectInput
  • DirectInputDevice
  • DirectInputEffect

    DirectInput is de motor van het hele DirectInput verhaal. Hiermee zeg je dat je DI wilt gebruiken voor je spel. DirectInputDevice gebruik je om aan te geven welk invoerapparaat je gaat gebruiken dus of je het toetsenbord ofzo gaat gebruiken. DirectInputEffect gebruik je voor Force Feedback apparaten, maar dat behandelen wij in deze tutorial niet, dus heb je daar weinig mee te maken.

    DirectInput declareren

    Je begint met het DirectInput object declareren, maar om het goed te kunnen compileren heb je wel de goed header en library files nodig. Bij je project settings moet je dinput.lib toevoegen. In je cpp file voeg je de volgende regel toe:

    #include ;
    

    Nu ben je klaar om te beginnen met DirectInput, laten we gauw beginnen.

    Allereerst gaan we DirectInput declareren. Dat doen we met de helpfunctie DirectInputCreate(). Het prototype ziet er zo uit:

    HRESULT WINAPI DirectInputCreate( HINSTANCE hinst, DWORD dwVersion,
    			LPDIRECTINPUT* lplpDirectInput, LPUNKNOWN punkOuter);
    

    hinst is een instantie pointer. Hinst kan je van je WinMain vandaan halen. Met dwVersion geef je aan welke versie van DI je wilt gebruiken. Je kan hier het beste DIRECTINPUT_VERSION gebruiken. Je krijgt dan gewoon de nieuwste versie. lplpDirectInput is de pointer naar het DirectInput object. punkOuter is een geavanceerde pointer die bijna nooit word gebruik, gewoon op NULL zetten.

    Nu heb je met succes een DirectInput object gemaakt. De volgende stap is aangeven welk apparaat je gaat gebruiken. Dus of de muis of het toetsenbord enzovoort. Dit doen we met het DirectInputDevice() object. Hiervoor gebruiken we de functie CreateDevice, die met behulp van de DirectInput pointer word aangeroepen. Dit maak ik duidelijk met een voorbeeld.

    Eerst maak ik het DI object aan en dan wijs ik het toetsenbord aan als input device.

    
    LPDIRECTINPUT	lpDI;	// pointer aanwijzing
    
    // DI object declareren
    if (FAILED(DirectInputCreate(hinst, DIRECTINPUT_VERSION, &lpDI, NULL))) 
    {
    	// FOUT!!
    	return(0);
    }
    
    LPDIRECTINPUTDEVICE pKeyboard;  // pKeyboard is pointer naar Input Device
    HRESULT	hr;			// handle voor het resultaat
    
    hr = lpDI->CreateDevice(GUID_SysKeyboard, &pKeyboard, NULL);
    if(!hr)
    {
    	// FOUT!!
    	return(0);
    }
    
    hr = pKeyboard->SetDataFormat(&c_dfDIKeyboard);
    

    Even pauze! Tot zover was alles bekend, totdat we bij de SetDataFormat komen. Dat is nieuw, dus dat ga ik nu maar even uitleggen. DI vereist dat je voor ieder apparaat een gegevensindeling opgeeft. Gelukkig biedt DI wel de optie om hier voor standaard indelingen voor te gebruiken. Dat doen we hier eigenlijk. We hebben nu het apparaat object gemaakt. Met SetDataFormat stellen wij de gegevensindeling in. &c_dfDIKeyboard is die voor het toetsenbord. Als je dit moeilijk vind om te onthouden, kan je het een beetje te ontleden om het makkelijker te maken. DF staat voor data format, DI is direct input dus dat valt wel te onthouden.

    
    hr = pKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    
    

    Allereerst geef je de windowhandle door. Zo weet de computer hoe het apparaat onder de programma's verdeeld moet worden. DISCL_FOREGROUND, betekent dat dit allen geld als de window op de voorgrond staat en dus voor de user actief is. We stellen het toetsenbordf niet exclusief in, omdat het a) niet toegestaan word door DirectInput. b) Het zou bepaald combinaties onmogelijk maken, zoals ctrl + alt + del.

    
    pKeyboard->Acquire();
    
    
    Met deze functie zeggen we van. Okay alles ingesteld, nu mag je alle invoer van het toetsenbord gaan aflezen en gebruiken voor mijn programma. Nu gaan we de toetsenbord status opslaan. Hier gebruiken we een speciale buffer voor.

    
    char buffer[256];
    hr = pKeyboard->GetDeviceState(sizeof(buffer), (LPVOID)&buffer);
    
    
    Nu word dus de status van de toetsen in die buffer opgeslagen, maar nu weet je nog niet welke toets er word ingedrukt. Dat controleren we met deze macro.

    
    #define KEYDOWN(buf, key) (buf[key] & 0x80)
    
    

    De 0x80 komt uit de Assembler taal. Dit is gewoon machinetaal. Nu kun je op de volgende manier of de linkerpijltjes toets word ingedrukt of de rechter. if(KEYDOWN(buffer, DIK_LEFT)) // ga naar links if(KEYDOWN(buffer, DIK_RIGHT)) // ga naar rechts Hier kan je alle constanten lezen van het toetsenbord. -> http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dx8_c/directx_cpp/Input/CPP_Ref/Consts/keyboard_device_constants.asp Na gebruik van DI moet je weer alles vrijgeven. Dat doe je op de volgende manier.

    
    pKeyboard->Unacquire();		// stop met toetsenbord afluisteren
    pKeyboard->Release();		// geef pointer vrij
    pKeyboard = NULL;		// voorkom gevaarlijke pointers
    
    if(lpDI)
    {
    	lpDI->Release();
    	lpDI = NULL;
    }
    
    Dit is dan alleen nog voor het toetsenbord. De muis werkt net iets anders.

    De muis

    Het grootste verschil is nu dat je wat meer berekeningen moet uitvoeren. Huh, zul je misschien wle denken, nou het is heel simpel. De muis geeft alleen maar door hoeveel ze verschoven is, niet waar ze zich bevind. Je moet dus keer op keer zelf de positie berekenen.

    LPDIRECTINPUT	lpDI;	// pointer aanwijzing
    
    // DI object declareren
    if (FAILED(DirectInputCreate(hinst, DIRECTINPUT_VERSION, &lpDI, NULL))) 
    {
    	// FOUT!!
    	return(0);
    }
    
    LPDIRECTINPUTDEVICE	pMouse;	// pMouse is pointer naar Input Device
    HRESULT	hr;			// handle voor het resultaat
    
    hr = lpDI->CreateDevice(GUID_SysMouse, &pMouse, NULL);
    if(!hr)
    {
    	// FOUT!!
    	return(0);
    }
    
    hr = pMouse->SetDataFormat(&c_dfDIMouse);
    
    // NU WEL EXCLUSIEF!!
    hr = pMouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE); 
    

    Nu moet je eerst een gebeurtenis maken, dit doe je om een buffer te maken. Een event maak je op de volgende manier.

    hevtMouse = CreateEvent(0,0,0,0);
    hr = pMouse->SetEventNotification(hevtMouse);
    

    Nu moet je de buffergrootte instellen. Dit doe je door de eigenschap DIPROP_BUFFERSIZE in te stellen. Hievoor moet je eerst een DIPROPDWORD-header-structuur invullen. Dat kan op deze manier.

    DIPROPDWORD dipdw =
    {
    	{
    		sizeof(DIPROPDWORD),
    		sizeof(DIPROPHEADER),
    		0,
    		DIPH_DEVICE,
    	},
    	32
    };
    

    Waar het om gaat is 32. Die stelt het aantal items in die in de buffer kan worden opgeslagen. Het aantal items van 32 is hier de gulde middenweg. Hoger is vaak te groot, lager vaak te klein. Het bufferformaat stellen we in met behulp van SetProperty().

    
    hr = pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
    
    

    Nu kun je gewoon de muis afluisteren met behulp van Acquire().

    Nu kun je uit 2 keuzes kiezen.

  • Je wacht tot windows je informeert dat er iets met de muis is gebeurt.
  • Poll de gegevens in de spellus.

    Waar hangt je keuze nou vanaf. Heel simpel. De eerste optie is voor een spel waar reactie niet van belang is. Bijvoorbeeld een simulatiespel als Command & Conquer of heel simpel Patience. Voor een schietspel is een snelle reactie vereist en is de 2e keuze de juiste optie.

    Ik kies er nu voor om de 2e optie te behandelen. De 1e is niet heel moeilijk en kan je in de SDK of in de MSDN hulp opzoeken.

    Als eerste moeten we een data structuur samenstellen. Ook wel de DIDEVICEOBJECTDATA structuur genoemd. Dit werkt heel simpel.

    DIDEVICEOBJECTDATA	data;
    memset(&data, 0, sizeof(DIDEVICEDOBJECTDATA);
    

    Nu is het nog maar een kwestie van het maken van een oneindige lus die alle muisgegevens ophaalt en opslaat in de data structuur. Dit is een voorbeeld.

    
    BOOL bDone = FALSE;
    DWORD dNumElements = 1;
    int iDX = 0, iDY = 0;
    BOOL buttonDown[2];
    
    while(!bDone)
    {
    	// haal gegevens op van de muis
    	if (pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), &data,
    		&dwNumElements, 0) == DIERR_INPUTLOST)
    		{
    			//verwerf de muis weer en probeer het nog eens
    			if (pMouse->Acquire() == DI_OK)
    			hr = pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), 
    			&data, &dwNumElements, 0
    		}
    		// reageer op muisinvoer
    		switch(data.dwOfs)
    		{
    		case DIMOFS_X:		// als de horizontale as gewijzigd is
    			iDX += data.dwData;
    		break;
    
    		case DIMORFS_Y:
    			iDY += data.dwData;
    		break;
    
    		case DIMOFS_BUTTON0:
    		if(data.dwData & 0x80)
    			buttonDown[0] = TRUE;
    		else
    			buttonDown[0] = FALSE;
    		break;
    
    		case DIMOFS_BUTTON1:
    		if(data.dwData & 0x80)
    			buttonDown[1] = TRUE;
    		else
    			buttonDown[1] = FALSE;
    		break;
    	}
    
    	if (dwNumElements == 0)
    	bDone = TRUE;
    }
    
    

    Okay hiermee hebben we het toetsenbord en de muis behandeld. Als je nog vragen hebt kun je altijd in het forum reageren.

    Groetjes,
    InSiEj