クラスまみれのゲームプログラミング入門

29:ゲームパッドによるアナログ入力

 ゲームパッドの中には十字キーによる入力をアナログ形式で伝達する天の邪鬼な機器も存在します。今回は、そんなアナログ入力に対応するための方法をご紹介。

 アナログ機器の位置情報はプログラム側で設定しなければいけません。このアナログデータで返すキーやボタンの情報はIDirectInputDevice8::EnumObjects()で、引数にDIDFT_AXISを選択した上で、列挙用関数内で値を指定します。

 列挙用関数では、DIPROPRANGE構造体に、システムから送られてきたパッドのアナログ箇所に対する設定を代入し、対象のIDirectInputDevice8に対し、SetProperty関数でこの構造体のDIPROPHEADERのポインタを用いれば、アナログ入力の範囲が反映されます。今回のサンプルプログラムでは、-10~0~10の21段階になるようにしています。

Input1.h
/* 省略 */

class CInput : public CGameObject
{
private:
	/* 省略 */

	static BOOL CALLBACK EnumJoypad(const DIDEVICEINSTANCE* pInstance, LPVOID pContext);
	static BOOL CALLBACK EnumObject(LPCDIDEVICEOBJECTINSTANCE pInstance, LPVOID pvRef);
public:
	/* 省略 */
};

Input1.cpp
/* 省略 */

void CInput::Init()
{
	if(pInput == NULL){
		/* 省略 */

		// ジョイパッドの作成
		enumdata ed;
		ed.pInput = pInput;
		ed.ppPadDevice = &pPadDevice;
		pInput->EnumDevices(
			DI8DEVCLASS_GAMECTRL, EnumJoypad,
			&ed, DIEDFL_ATTACHEDONLY);

		if(pPadDevice){
			// アナログキーのデータを設定
			// 2番目の引数にpPadDeviceを指定しておくと、
			// EnumObject関数のpvRefに、このpPadDeviceが格納されるようになるため、
			// pPadDeviceをスタティックやグローバル変数にしなくて済む。
			pPadDevice->EnumObjects(EnumObject, pPadDevice, DIDFT_AXIS);

			pPadDevice->SetCooperativeLevel(GetHWnd(),
				DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
			hr = pPadDevice->SetDataFormat(&c_dfDIJoystick2);
			if(FAILED(hr)) RELEASE(pPadDevice);
		}
	}
}

BOOL CALLBACK CInput::EnumObject(LPCDIDEVICEOBJECTINSTANCE pInstance, LPVOID pvRef)
{
	DIPROPRANGE range;
	range.diph.dwSize = sizeof(DIPROPRANGE);
	range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
	range.diph.dwObj = pInstance->dwType;	// ボタンやスティックのデータ
	range.diph.dwHow = DIPH_BYID;			// タイプで取得することを指定
	range.lMin = -10;						// 値の最小値
	range.lMax = +10;						// 値の最大値
	
	LPDIRECTINPUTDEVICE8 pInputDev = (LPDIRECTINPUTDEVICE8)pvRef;
	pInputDev->SetProperty(DIPROP_RANGE, &range.diph);

	return DIENUM_CONTINUE;
}

 あとは、キー入力を取得する関数に、アナログ入力のデータに基づいた値を返す処理を追加するだけです。まっとうなアナログ入力であれば、横方向はDIJOYSTATE2.lXに、縦方向はDIJOYSTATE2.lYに格納されているはずなので、この値が-5より小さければ左(上)、5より大きければ右(下)と返しています(十字キーなら最小値、最大値、その中間のいずれかにしかならないはずですが念のため)。

Input2.cpp
BYTE CInput::GetPovPosition()
{
	// デジタル入力から判断
	switch(paddata.rgdwPOV[0]){
		/* 省略 */
	}

	// アナログ入力から判断
	BYTE result = 0x0;
	if(paddata.lX > 5) result |= PP_RIGHT;
	else if(paddata.lX < -5) result |= PP_LEFT;
	if(paddata.lY > 5) result |= PP_DOWN;
	else if(paddata.lY < -5) result |= PP_UP;

	return result;
}

 前回紹介した戦闘機からミサイルを発射するデモのゲームパッド対応版の一部を掲載しておきます。

ship.cpp
void CShip::Exec()
{
	BYTE pos = input->GetPovPosition();
	if(input->IsKeyDown(DIK_LEFT) || (pos & PP_LEFT)){
		x -= 1.0f;
		sprite.SetFrame(0);
	}else if(input->IsKeyDown(DIK_RIGHT) || (pos & PP_RIGHT)){
		x += 1.0f;
		sprite.SetFrame(2);
	}else{
		sprite.SetFrame(1);
	}

	if(input->IsKeyDown(DIK_UP) || (pos & PP_UP)) y -= 1.0f;
	if(input->IsKeyDown(DIK_DOWN) || (pos & PP_DOWN)) y += 1.0f;

	if(input->IsKeyPressed(DIK_SPACE) || input->IsButtonPressed(0)){
		// ミサイル発射
		AppendObject(new CShoot(), 900, true);
	}

	sprite.Draw(x, y);
}