★ DirectX Class ★ * Chapter 13: キーボードの利用 *
< キーボード入力の処理 >
さて、描画関連も一通り終わったので、入力を扱っていきたいと思います。
キーボードかジョイスティックかどちらを先に扱うべきか悩んだのですが、
恐らく一般家庭のPCに標準で付属している確立の高いキーボードの入力を扱っていきます。

Windowsアプリケーションの開発経験が豊富な方は、ウィンドウプロシージャでキーの入力を処理することをご存知のことと思います。
しかし、DirectXでは『 DirectInput 』というものを利用してキーの入力を処理します。
ウィンドウプロシージャで処理しても良いのですが、DirectInputのほうが色々と便利です。

それではキーボードの入力をDirectInputで処理するプログラムを見ていきましょう。
< BCCで開発するときの注意 >
BCCでDirectInputを扱う場合、そのままだとc_dfDIKeyboardが未解決とか言われる場合があります。
原因は不明なのですが、要はその変数がどこにも定義されていないと言っているようです。
ですので、その変数をどこかで定義する必要があります。

プログラムの最初のほうならどこに定義しても良いのですが、結構長いものなので、
私は別途『 dinputex.h 』というヘッダファイルにしてしまって、それを読み込んでいます。

dinputex.h
本講座では、このヘッダファイルを読み込む前提で解説していきますが、
気に入らない方はお好きにしてください。
ただのヘッダファイルですので。
私もただの人間ですので。
< プリプロセッサ部 >
まず、『 INITGUID 』を定義します。
何か分かりませんが、これがないとエラーになります。

#define INITGUID

次に、以下のヘッダファイルをインクルードします。

#include <dinput.h>
#include <dinputex.h>

さらに、ライブラリをリンクします。

#pragma comment( lib , "dinput8.dll" )

プリプロセッサ部は以上です。
< グローバル変数 >
DirectInputオブジェクト、及びDirectInputデバイスのオブジェクトを宣言します。

LPDIRECTINPUT8       g_lpDI;
LPDIRECTINPUTDEVICE8 g_lpDIDevice;

グローバル変数は以上です。
< DirectInput/DirectInputデバイスの作成 >
まず最初にDirectInputオブジェクトを作成します。

DirectInput8Create( hInst , DIRECTINPUT_VERSION , IID_IDirectInput8 ,
                    (void**)&g_lpDI , NULL );

第1引数には親ウィンドウのインスタンスを指定します。
第2引数にはDirectInputのバージョンを指定します。この定数を宣言せずに使用した場合、デフォルトのバージョンが自動的に設定されます。
第3引数はよく分かりませんが、説明書にこう書いてありました。
第4引数には最初に宣言したDirectInputオブジェクトへのポインタを指定します。
第5引数は大抵NULLで良いようです。

作成に成功したら、今後アプリケーションを終了する前にDirectInputオブジェクトを開放する必要があります。

続いて、デバイスを作成します。
それは以下の関数で実現できます。

g_lpDI->CreateDevice( GUID_SysKeyboard , &g_lpDIDevice , NULL );

第1引数には作成したいデバイスの種類を定数で設定します。キーボードのときは 『 GUID_SysKeyboard 』で良いようです。
第2引数には最初に宣言したデバイスへのポインタを設定します。
第3引数は大抵NULLで良いようです。

続いてデータのフォーマットを指定します。

g_lpDIDevice->SetDataFormat( &c_dfDIKeyboard );

引数は 『 dinputex.h 』の中で宣言されているフォーマットへのポインタです。

最後に、協調レベルを設定します。
g_lpDIDevice->SetCooperativeLevel( hWnd , DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

第1引数には親ウィンドウのハンドルを指定します。
第2引数には協調の設定を、定数で設定します。

定数 意味
DISCL_BACKGROUND バックグラウンドでも入力を受け付けます。
DISCL_EXCLUSIVE デバイスからの入力を独占します。
DISCL_FOREGROUND フォアグラウンド(前面)で入力を受け付けます。
DISCL_NONEXCLUSIVE デバイスからの入力を独占しません。
DISCL_NOWINKEY ウィンドウズキーを無効にします。

この例では非排他でフォアグラウンドの入力を受け付けるように設定されます。
通常はこれで構わないと思います。

これで設定は完了です。
< 入力受付の開始・終了/DirectInputの開放処理 >
入力の受付を開始するには、以下のプログラムを実行します。

if ( g_lpDIDevice ) g_lpDIDevice->Acquire();

これでキーボードからの入力を受け付けるようになります。
入力を受け付けないようにするには、以下のプログラムを実行します。

g_lpDIDevice->Unacquire();

これでキーボードの入力を受け付けなくなります。
尚、DirectInputのオブジェクトやデバイスを開放する際、
入力を受け付けないようにしてから開放しなくてはなりません。
その例を以下に示します。

g_lpDIDevice->Unacquire();

if ( g_lpDIDevice != NULL )
	g_lpDIDevice->Release();

if ( g_lpDI != NULL )
	g_lpDI->Release();

これで初期化、終了の説明は終わりです。
< 実際に入力を受け取る >
実際に入力を受け取るには以下のコードを使います。

char buffer[256];
g_lpDIDevice->GetDeviceState( sizeof( buffer ) , (LPVOID)&buffer );

このコードを実行すると、文字型配列変数bufferに、現在のキーの状態が返されます。

第1引数には受け取るバッファのサイズを、
第2引数にはバッファのアドレスのアドレスを渡します。

キーが押されている場合、バッファの中の当該キーと0x80の論理積が真となります。
例えば、『 ↑ 』キーが押されているかどうかを調べるには以下のコードを記述します。

if ( buffer[DIK_UP] & 0x80 ) /* 押されている場合の処理   */
else                         /* 押されていない場合の処理 */

配列の添字に使われている定数は全てのキーに関して定義されており、
複数のキーの状態を調べたい場合にはこのようなコードをいくつか書いていくことになります。
キーの定数表が意外と見つからないので以下に置いておきます。

キー定数表

< サンプル >
このサンプルを実行すると、真っ白なウインドウが表示されます。
そしてキーボードの上下左右、及びEnter、BackSpaceを押すと、
それぞれキーが押されている間だけ、タイトルバーにそのキーが表示されます。
但し、キーボードの同時押し制限により、3つ以上のキーを同時に押している場合に正常に表示されない場合があります。
ウィンドウハンドルをグローバル変数に格納していますが、
これはタイトルバーに表示するためのものであり、DirectInputとは関係ありません。

★まとめ★
これで2D画像の描画からキー入力までできるようになったので、一応ゲームの開発は可能になりました(まだ音がありませんが)。
キー入力の受け取りそのものは工夫も何もありませんので、後はその入力された情報をどう利用するかです。
全章までの2D描画と今回のキー入力を併せて、キャラクターを動かしたりして遊んでみてください。

ポイントはピンク色で示してあります。

//-----------------------------------------------------------------
//
//    DirectInput Sample Program.
//
//-----------------------------------------------------------------
#define INITGUID

#include <stdio.h>
#include <windows.h>
#include <dinput.h>
#include <dinputex.h>

#pragma comment( lib , "dinput8.dll" )



//-----------------------------------------------------------------
//    Grobal Variables.
//-----------------------------------------------------------------
LPDIRECTINPUT8       g_lpDI;
LPDIRECTINPUTDEVICE8 g_lpDIDevice;

HWND                 g_hWnd;



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HWND    InitApp( HINSTANCE , int );
BOOL    InitDirectInput( HWND );
BOOL    ReadInput();
BOOL    CleanupDirectInput();
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );



//-----------------------------------------------------------------
//    Main.
//-----------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInst , HINSTANCE hPrevinst , LPSTR nCmdLine , int nCmdShow )
{
	MSG msg;
	
	g_hWnd = InitApp( hInst , nCmdShow );
	if ( !g_hWnd ) return FALSE;
	
	if ( !InitDirectInput( g_hWnd ) ) return FALSE;
	
	while( msg.message != WM_QUIT ){
		if ( PeekMessage( &msg , NULL , 0 , 0 , PM_REMOVE ) ){
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}else{
			ReadInput();
		}
		Sleep( 1 );
	}
	
	return msg.wParam;
}



//-----------------------------------------------------------------
//    Initialize Application.
//-----------------------------------------------------------------
HWND InitApp( HINSTANCE hInst , int nCmdShow )
{
	WNDCLASS wc;
	HWND hWnd;
	char szClassName[] = "DirectInput Test";
	
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.hInstance     = hInst;
	wc.hCursor       = LoadCursor( NULL , IDC_ARROW );
	wc.hIcon         = LoadIcon( NULL , IDI_APPLICATION );
	wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
	wc.lpszClassName = szClassName;
	wc.lpszMenuName  = NULL;
	wc.lpfnWndProc   = WndProc;
	wc.cbWndExtra    = 0;
	wc.cbClsExtra    = 0;
	if ( !RegisterClass( &wc ) ) return FALSE;
	
	hWnd = CreateWindow( szClassName , "Direct3D Test" , WS_OVERLAPPEDWINDOW ,
	                     CW_USEDEFAULT , CW_USEDEFAULT , 640 , 480,
	                     NULL , NULL , hInst , NULL );
	if ( !hWnd ) return FALSE;
	
	ShowWindow( hWnd , nCmdShow );
	UpdateWindow( hWnd );
	
	return hWnd;
}



//-----------------------------------------------------------------
//    Initialize DirectInput.
//-----------------------------------------------------------------
BOOL InitDirectInput( HWND hWnd )
{
	HINSTANCE hInst;
	HRESULT   hr;
	
	hInst = (HINSTANCE)GetWindowLong( hWnd , GWL_HINSTANCE );
	
	hr = DirectInput8Create( hInst , DIRECTINPUT_VERSION , IID_IDirectInput8 ,
	                         (void**)&g_lpDI , NULL );
	if ( FAILED( hr ) ){
		MessageBox( hWnd , "Can't create DirectInput object." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDI->CreateDevice( GUID_SysKeyboard , &g_lpDIDevice , NULL );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't create DirectInput device." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDIDevice->SetDataFormat( &c_dfDIKeyboard );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't set data format." , "Error" , MB_OK );
		return FALSE;
	}
	
	hr = g_lpDIDevice->SetCooperativeLevel( hWnd ,
	                                        DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );
	if ( FAILED( hr ) ){
		CleanupDirectInput();
		MessageBox( hWnd , "Can't set cooperative level." , "Error" , MB_OK );
		return FALSE;
	}
	
	if ( g_lpDIDevice ) g_lpDIDevice->Acquire();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Cleanup DirectInput.
//-----------------------------------------------------------------
BOOL CleanupDirectInput()
{
	g_lpDIDevice->Unacquire();
	
	if ( g_lpDIDevice != NULL )
		g_lpDIDevice->Release();
	
	if ( g_lpDI != NULL )
		g_lpDI->Release();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Window Proc.
//-----------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd , UINT msg , WPARAM wp , LPARAM lp )
{
	switch( msg ){
		case WM_DESTROY:
			CleanupDirectInput();
			PostQuitMessage( 0 );
			break;
		default:
			return DefWindowProc( hWnd , msg , wp , lp );
	}
	
	return 0L;
}



//-----------------------------------------------------------------
//    Read Input.
//-----------------------------------------------------------------
BOOL ReadInput()
{
	char    buffer[256];
	HRESULT hr;
	char    titlebar[32];
	
	hr = g_lpDIDevice->GetDeviceState( sizeof( buffer ) , (LPVOID)&buffer );
	if ( FAILED( hr ) ) return FALSE;
	
	titlebar[0] = '\0';
	
	if ( buffer[DIK_UP] & 0x80 )     strcat( titlebar , "上" );
	if ( buffer[DIK_DOWN] & 0x80 )   strcat( titlebar , "下" );
	if ( buffer[DIK_LEFT] & 0x80 )   strcat( titlebar , "左" );
	if ( buffer[DIK_RIGHT] & 0x80 )  strcat( titlebar , "右" );
	if ( buffer[DIK_RETURN] & 0x80 ) strcat( titlebar , "Enter" );
	if ( buffer[DIK_BACK] & 0x80 )   strcat( titlebar , "Back Space" );
	
	SetWindowText( g_hWnd  , titlebar );
	
	return TRUE;
}

実行結果

クリックすると実物大で表示されます。
執筆: 2009/07/18 (SAT)