DirectInput(2000/10/10更新)

■はじめに
ゲームプログラミングにおいて非常に重要になってくるのが,キーボード,マウス,ゲームパッドといった入力装置のコントロールです。
DirectInputを用いることで,これらの制御を非常に簡単に行うことができます。

ここで,プログラムの目的を考えてみましょう。まず、主目的としては、

・キーボードとゲームパッドのどちらでもゲームを遊ぶことができる

を挙げることが出来ます。さらにここでは、

・ゲームパッドは一つのみの使用が可能であれば良い
・ユーザの設定でキーボード/パッドを選択するのではなく,自動認識させたい

という状態を目標とすることにします。


■DirectInputの準備
まず、DirectInputの関数を使用するために、ライブラリを追加する必要があります。
このライブラリを追加し、ヘッダファイルをincludeすることで、DirectInput関連の 関数群を使用することが出来るようになります。

@プロジェクト>設定>リンク でライブラリ「dinput.lib dxguid.lib」を追加します。
Aインクルードファイルを追加します。#include<dinput.h>

ライブラリの追加の手順は,「DirectDraw」の章で詳しく解説しています。


■サンプルプログラム(DirectInputの初期化)
DirectInputの初期化を行う関数を以下に示します。
変数を全然まとめずにグローバル変数でゴチャゴチャになっていますが,細かいことは気にせず先に進みましょう;^^)。

LPDIRECTINPUT pDInput;
LPDIRECTINPUTDEVICE2 pDInputDevice;
int joypad=0;

/*-------------------------------------------
	ジョイスティックを取得
---------------------------------------------*/
BOOL CALLBACK GetJoystickCallback(LPDIDEVICEINSTANCE lpddi,LPVOID pvRef)
{
	HRESULT ret;
	LPDIRECTINPUTDEVICE pDev;

	//ジョイスティック用にデバイスオブジェクトを作成
	ret = pDInput->CreateDevice(lpddi->guidInstance,&pDev,NULL);
	if(ret != DI_OK){
		return DIENUM_CONTINUE;
	}

	pDev->QueryInterface(IID_IDirectInputDevice2,(LPVOID *)&pDInputDevice);

	return DIENUM_STOP;
}

/*-------------------------------------------
	Direct Input 初期化
---------------------------------------------*/
int InitDInput(void)
{
	HRESULT ret;

	ret = DirectInputCreate(hInstApp,DIRECTINPUT_VERSION,&pDInput,NULL);
	if(ret != DI_OK){
		return FALSE;
	}

	//ジョイスティックを探す
	pDInputDevice = NULL;
	pDInput->EnumDevices(DIDEVTYPE_JOYSTICK,(LPDIENUMDEVICESCALLBACK)GetJoystickCallback,NULL,DIEDFL_ATTACHEDONLY);
	if(pDInputDevice == NULL){
		RELEASE(pDInput);
		return TRUE;
	}
	
	//データフォーマットを設定
	ret = pDInputDevice->SetDataFormat(&c_dfDIJoystick);
	if(ret != DI_OK){
		RELEASE(pDInputDevice);
		RELEASE(pDInput);
		return FALSE;
	}

	//モードを設定
	//ret = pDInputDevice->SetCooperativeLevel(hwnd,DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
	ret = pDInputDevice->SetCooperativeLevel(hwnd,DISCL_NONEXCLUSIVE  | DISCL_BACKGROUND);

	if(ret != DI_OK){
		RELEASE(pDInputDevice);
		RELEASE(pDInput);
		return FALSE;
	}

	DIPROPRANGE diprg; 
 
	// 軸の値の範囲を設定
	diprg.diph.dwSize	= sizeof(diprg); 
	diprg.diph.dwHeaderSize	= sizeof(diprg.diph); 
	diprg.diph.dwObj	= DIJOFS_X; 
	diprg.diph.dwHow	= DIPH_BYOFFSET; 
	diprg.lMin	= -1000; 
	diprg.lMax	= +1000; 
 	pDInputDevice->SetProperty(DIPROP_RANGE, &diprg.diph);
	diprg.diph.dwObj	= DIJOFS_Y; 
 	pDInputDevice->SetProperty(DIPROP_RANGE, &diprg.diph);

	pDInputDevice->Acquire();

	joypad=1;
	return TRUE;
}
ほとんど参考書の受け売りなので,著者の方に殴られそうな気がしますが,
要するに,InitDInput();関数を1回呼び出しておけば,ゲームパッド(あるいはジョイスティック)を探して
入力を受け付けるようにすることができるようになります。

そして,ゲームパッドからの入力情報が初期化済みのpDInputDeviceに格納される,という仕組みです。
これらの初期化関数で,ゲームパッドが見つかれば,joypadに1が格納されます。

InitDInput();関数はSetupDD();関数の後にでも呼び出しておきましょう(「DirectDraw」の章を参照)。


■サンプルプログラム(DirectInputからの入力取得)
さて,いよいよパッドからの入力をpDInputDeviceに格納し,その入力をゲームの操作に反映させなければなりません。
ここで,今までのプログラムソースを再利用するために,キーボード入力に関してはDirectInputを用いずに,
従来通りmove[x]に値を格納することにします。
以下に,キー操作およびゲームパッドからの入力を取得するサンプルプログラム(の一部)および変数を示します。

変数を全然まとめずにグローバル変数でゴチャゴチャになっていますが,細かいことは気にせず先に進みましょう;^^)。
@グローバル変数群
int move[6];// キーボードからの入力が格納されている
int move2[6];// ジョイパッド用
int bflag[6]={0,0,0,0,0,0};// 押し続け防止フラグ
int move3[6];//最終的な入力が格納される
これらの変数は,グローバル変数として宣言します。

A入力受付部分
case WM_ACTIVATE:
		if(joypad==1){
			if(pDInputDevice == NULL)
				break;
			if(wParam == WA_INACTIVE){
				pDInputDevice->Unacquire();
			}
			else{
				pDInputDevice->Acquire();
			}
		}
		bActive = wParam;
WndProc()関数内で、入力を受け付けます。

Bゲーム入力処理部分
DIJOYSTATE dijs;

// ゲームパッドの情報取得
	if(joypad==1){
		pDInputDevice->Poll();
		ret = pDInputDevice->GetDeviceState(sizeof(DIJOYSTATE),&dijs);
		
		if(ret==DI_OK){
			move2[0]=0;
			move2[1]=0;
			if(dijs.lX>100)move2[0]=1;
			if(dijs.lX<-100)move2[1]=1;
			if(abs(dijs.lX)==1){
				
				bflag[0]=0;
			}
			move2[2]=0;
			move2[3]=0;
			if(dijs.lY>100)move2[2]=1;
			if(dijs.lY<-100)move2[3]=1;
			if(abs(dijs.lY)==1){
				
				bflag[2]=0;
			}
			if(dijs.rgbButtons[0] & 0x80){
				move2[4]=1;
			}else{
				move2[4]=0;
				bflag[4]=0;			
			}
			if(dijs.rgbButtons[1] & 0x80){
				move2[5]=1;
			}else{
				move2[5]=0;
				bflag[5]=0;		
				pl.buf=0;
			}
		}else if(ret==DIERR_INPUTLOST){
			pDInputDevice->Acquire();
		}
	}else{
		for(i=0;i<6;i++){
			bflag[i]=0;
		}
	}

	move3[5]=0;move3[0]=0;move3[1]=0;move3[2]=0;move3[3]=0;move3[4]=0;
	for(i=0;i<6;i++){
		move3[i]=move[i]|move2[i];
	}
これらのコードは,BOOL myBLT( HWND hwnd )関数(「DirectDraw」の章を参照)内の先頭に挿入されることになります。

ここで,move[x],move2[x],move3[x]は0/1の値を持ち,move[x]はキーボード入力,move2[x]はパッド入力が格納され、
対応するキー/パッドボタンが押されている時のみ1になります。
move3[x]はmove[x]とmove2[x]のorをとったものであるため,キーボード/パッドボタンのどちらかが押されても,move3[x]の値が変化します。
またゲームパッドがない時(joypad=0の時)は、move2[x]は全て0になり、move3[x]=move[x]となります。
つまり,ゲームパッドがあろうがなかろうが、move3[x]の値のみを参照して,ゲーム中の操作を行うプログラムを書けばよいのです。

また、move3[x]とキーの対応表は,
x=0 x=1 x=2 x=3 x=4 x=5
カーソルキー→
あるいは
十字パッド右
カーソルキー←
あるいは
十字パッド左
カーソルキー↓
あるいは
十字パッド下
カーソルキー↑
あるいは
十字パッド上
zキー
あるいは
パッドAボタン
xキー
あるいは
パッドBボタン
となります。

例えばカーソルキーあるいは十字ボタンが押されたら自キャラをその方向に10ドット動かす,というプログラムを
これでもかというぐらい分かりやすく記述すると,
int player_x;// 自キャラのx座標
int player_y;// 自キャラのy座標

if(move3[0]==1){
	player_x=player_x+10;
}

if(move3[1]==1){
	player_x=player_x-10;
}

if(move3[2]==1){
	player_y=player_y+10;
}

if(move3[3]==1){
	player_y=player_y-10;
}

となります。


メニューに戻る