DirectInputによるゲームパッド実装
-入力情報対応編-


概要

このページでは毎フレーム実行するDirectInputを使った
ゲームパッドの入力情報を取得方法の説明をします。
今回の説明は入力情報の取得に関する内容のみです。
初期化や終了処理についてはこちらの記事で書いています。

どのような入力情報を必要とするのか

ゲームパッドの入力情報は数多くありますが
今回はその中で最低限必要とされる以下の三つの項目について説明します。
  • アナログスティック
  • 十字キー
  • ボタン

サンプル

サンプルはここからダウンロードできます。
環境については以下の内容となっています。

開発環境
VSのバージョン VisualStudio 2019
DirectXのバージョン DirectX9
説明 ゲームパッドを接続して起動しないと動作しません。

取得方法

キーボードの入力情報のIDirectInputDevice8の「GetDeviceState関数」を使用します。
directx_0040
この関数は引数に取得するデバイスの入力情報保存用の変数と
その変数のサイズの指定をしますが、どのデバイスで取得するかの指定はしません。
何故かというと、取得するデバイスの指定は初期化時の「SetDataFormat関数」で
完了しているからです。
あの関数の引数でGetDeviceStateを使用時に取得できる情報が決まっています。
以下は定数と取得時に必要とする変数の型についての表です。

デバイス毎の入力データ
定数 対応デバイス
c_dfDIKeyborad キーボード バイト型の配列(要素数256)
c_dfDIMouse マウス DIMOUSESTATE構造体
c_dfDIJoystick ゲームパッド DIJOYSTATE構造体
c_dfDIJoystick2 ゲームパッド DIJOYSTATE2構造体
今回のサンプルや説明ではc_dfDIJoystickを使用していますので、 入力情報の取得にはDIJOYSTATE構造体が必要です。 directx_0123 // ゲームパッドの入力情報取得 DIJOYSTATE pad_data; HRESULT hr = g_GamePadDevice->GetDeviceState(sizeof(DIJOYSTATE), &pad_data); if (FAILED(hr)) { // 取得失敗 } これで第二引数のDIJOYSTATE構造体に入力情報が保存されるので、 この情報を使用して入力判断が可能になります。

アナログスティック

アナログスティックの判断はDIJOYSTATE構造体の「lX」「lY」を使用します。
もう少し正確に書くと左のアナログスティックを傾けたかどうかの判断を
「lX」「lY」で行うということです。
右スティックの判定は別の値を使用しますがこれについては
別の記事にしたいと考えていますので、今回は「lX」「lY」を使用して
左のアナログスティックの入力判断をします。

directx_0122
スティックの位置情報は上の図のようなX軸とY軸の二軸上で設定され、
  • lXとlYともに原点の場合、スティックは傾けられてない
  • 0以外の値が代入されていたらスティックは傾けられている
と判断します。
※値の最大値と最小値はSetProperty関数で設定した値です。

// スティックの方向判定
// 方向だけを調べる方法
if (pad_data.lX < 0)
{
	// 左に傾けた
}
else if (pad_data.lX > 0)
{
	// 右に傾けた
}

// 傾きの比率を調べる方法
DWORD length = 1000; // 原点から最小、最大までの長さ
float y_vec = pad_data.lY / length;

上のコードのようにアナログスティックの入力情報は
  • スティックを傾けたかどうか
  • どれくらい傾けたか
を使用して判定を行います。

注意点

アナログスティックの位置を一度動かしたら、位置情報が原点(0,0)に
戻らないデバイスがあるので注意してください。
そのようなデバイスの対策として、入力を受け付けない範囲を設定します。

// スティックの方向判定
// 無反応範囲
DWORD unresponsive_range = 200;
// 方向だけを調べる方法
if (pad_data.lX < -unresponsive_range)
{
	// 左に傾けた
}
else if (pad_data.lX > unresponsive_range)
{
	// 右に傾けた
}

// 傾きの比率を調べる方法
DWORD length = 1000; // 原点から最小、最大までの長さ
float y_vec = (pad_data.lY - unresponsive_range) / (length - unresponsive_range);

上のコードでは各軸ともに-200~200の範囲の値だった場合は傾いてないとしています。

ボタン

ボタンを押されたかは「rgbButtons配列」に格納されている要素の
最上位ビット(8ビット目)で判断できます。
このビットが「1になっていたら押されている、0のままなら押されていない」です。

// ボタン判定
for (int i = 0; i < 32; i++)
{
	if (!(pad_data.rgbButtons[i] & 0x80))
	{
		continue;
	}

	switch (i)
	{
	case 0:
		is_push[ButtonKind::Button01] = true;
		break;
	case 1:
		is_push[ButtonKind::Button02] = true;
		break;
	}
}

上のコードのように目的のビット(0x80)のみが立っているかどうかを
判定したいときは「AND(&)」を使用します。
押されたボタンに対してどのようなボタンを割り振るのかは開発側で決めます。
今回のコードではインデックス0にはButton01をインデックス1にはButton02を割り当てています。

注意点

ゲームパッドへのボタンの割り当ては自動で行われるので、
パッドの種類によっては予期せぬ割り当てになる可能性があります。
可能ならばキーコンフィグ設定を実装できるようにしましょう。

十字キー

十字キーの入力情報は「rgdwPOV配列」の0番目に代入されています。
代入されている値は上方向を0度とした度数法で表された角度です。
例えば上を押したらrgdwPOVには0が、左を押したら27000が、右下を押したら13500が
代入されています。
例を読んでお気づきと思いますが、角度は100倍にされて代入してあります。
※POVはPointOfViewの略です。

判定方法ですが、まずはrgdwPOVに有効な値が代入されたかを調べます。
有効ではない値は「0xFFFFFFFF」が代入されているので、
そちらの確認を行います。

// 有効値チェック
if (pad_data.rgdwPOV[0] != 0xFFFFFFFF)
{
	// 有効
}

有効値のチェックが終わったら、次は角度から方向を割り出し方として
以下の二つが考えられます。
  • 角度を利用して三角関数で割り出す
  • 全8方向の値をifやswitchで判定
まずは三角関数を使用した方法から説明します。
以下のコメントでも書いていますが、0度の位置が右からではないので、
cosとsinを反対にして使っています。

// 角度を利用した方法
float rad = D3DXToRadian((pad_data.rgdwPOV[0] / 100.0f));
// 本来はxがcos、yがsinだけど、rgdwPOVは0が上から始まるので、
// cosとsinを逆にした方が都合がいい
float x = sinf(rad);
float y = cosf(rad);

if (x < -0.01f)
{
	is_push[ButtonKind::LeftButton] = true;
}	
else if (x > 0.01f)	
{
	is_push[ButtonKind::RightButton] = true;
}

if (y > 0.01f)
{
	is_push[ButtonKind::UpButton] = true;
}
else if (y < -0.01f)
{
	is_push[ButtonKind::DownButton] = true;
}

次は八方向、全ての値の対応を書く方法です。

// 八方向、全てを書く

switch(pad_data.rgdwPOV[0])
{
// 上
case 0:
	is_push[ButtonKind::UpButton] = true;
	break;
// 右上
case 4500:
	is_push[ButtonKind::UpButton] = true;
	is_push[ButtonKind::RightButton] = true;
	break;
// 右
case 9000:
	is_push[ButtonKind::RightButton] = true;
	break;
// 右下
case 13500:
	is_push[ButtonKind::DownButton] = true;
	is_push[ButtonKind::RightButton] = true;
	break;
// 下
case 18000:
	is_push[ButtonKind::DownButton] = true;
	break;
// 左下
case 22500:
	is_push[ButtonKind::DownButton] = true;
	is_push[ButtonKind::LeftButton] = true;
	braek;
// 左
case 27000:
	is_push[ButtonKind::LeftButton] = true;
	break;
/ 左上
case 31500:
	is_push[ButtonKind::UpButton] = true;
	is_push[ButtonKind::LeftButton] = true;
	break;
}

八方向対応は全てのケースを書くだけなので計算が苦手な人は
こちらの方がいいかもしれません。

取得失敗時の対応

入力情報の対応の説明は終了したので最後は情報の取得を失敗したときの説明をします。
ウィンドウを非アクティブ状態にするとデバイスをロストする可能性があります。
ロストしたらデバイスから情報を取得できなくなってしまいます。
デバイスの入力情報取得に失敗したら再度「Acquire関数」と
「Poll関数」を実行してください。

// 失敗時の対応
HRESULT hr = g_GamePadDevice->GetDeviceState(sizeof(DIJOYSTATE), &pad_data);
if (FAILED(hr))
{
	if (FAILED(g_GamePadDevice->Acquire()))
	{
		for (int i = 0; i < ButtonKind::ButtonKindMax; i++)
		{
			g_ButtonStates[i] = ButtonState::ButtonStateNone;
		}
	}
	g_GamePadDevice->Poll();
	return;
}

これでゲームパッドの基本的な入力取得は終了です。