28:ゲームパッドによる入力
最近のゲームパッドには、パッドで押されたボタンをキーボードのキー入力としてOSに通知する機能が備わっていますが、だからといって、「このゲームはキーボード専用なのでゲームパッドを使いたい方はそういうソフトを使ってください」というのもなんだかみっともないです。そこで今回はDirectInputによるゲームパッド入力の受け付けを行うプログラムを組むことにします。この複数のデバイスを取得するにはIDirectInput8::EnumDevices()を使います。この関数が実行されると、取得したデバイス情報が第2引数で指定した関数で、作成可能なデバイスが次々と呼び出されるので、その中からゲームに使用したい機器を選択することになります。
/* 省略 */
// デバイス列挙関数に渡すデータを格納する構造体
struct enumdata
{
LPDIRECTINPUT8 pInput; // デバイスを作成するためのインターフェイス
LPDIRECTINPUTDEVICE8 *ppPadDevice; // 使用するデバイスを格納するポインタのポインタ
};
class CInput : public CGameObject
{
private:
/* 省略 */
LPDIRECTINPUTDEVICE8 pPadDevice;
DIJOYSTATE2 paddata, lastpaddata;
// クラス内で記述するシステムから呼び出される(コールバック)関数はstatic属性にしなくてはならない
static BOOL CALLBACK EnumJoypad(const DIDEVICEINSTANCE* pInstance, LPVOID pContext);
/* 省略 */
};
/* 省略 */
void CInput::Init()
{
if(pInput == NULL){
// インターフェイスの取得
HRESULT hr;
hr = DirectInput8Create(
GetHInstance(), // ソフトのインスタンスハンドル
DIRECTINPUT_VERSION, // DirectInputのバージョン
IID_IDirectInput8, // 取得するインターフェイスのタイプ
(LPVOID*)&pInput, // インターフェイスの格納先
NULL // COM集成の制御オブジェクト(使わないのでNULL)
);
/* 省略 */
// ジョイパッドの作成
// コールバック関数に転送したいデータを格納
enumdata ed;
ed.pInput = pInput;
ed.ppPadDevice = &pPadDevice;
pInput->EnumDevices(
DI8DEVCLASS_GAMECTRL, // ゲームコントローラーが対象
EnumJoypad, // 列挙する関数
&ed, // 列挙関数に渡したいデータはここに入れる
DIEDFL_ATTACHEDONLY // インストール・接続済みのデバイスのみ取得
);
pPadDevice->SetCooperativeLevel(GetHWnd(),
DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
// ゲームパッドの入力情報はDIJOYSTATE2に格納されるので
// データフォーマットにはc_dfDIJoystick2を指定
hr = pPadDevice->SetDataFormat(&c_dfDIJoystick2);
if(FAILED(hr)) RELEASE(pPadDevice);
}
}
BOOL CALLBACK CInput::EnumJoypad(const DIDEVICEINSTANCE* pInstance, LPVOID pContext)
{
enumdata *ed = (enumdata*)pContext;
// このプログラムは単一機器のみ接続されていることが前提。
// ゲームパッドが複数接続されているときに、特定の機器のみを使いたいときは
// それ以外のデバイスが呼び出されたときに弾くようにするとよい
/*
if(_tcscmp(pInstance->tszProductName, _T("TNK Controler")) != 0){
return DIENUM_CONTINUE;
}
*/
HRESULT hr;
hr = ed->pInput->CreateDevice
(pInstance->guidInstance, ed->ppPadDevice, NULL);
if(FAILED(hr)) return DIENUM_CONTINUE; // デバイスが作成できないので列挙を続ける
// 希望するデバイスが作成できたので列挙を終了する
return DIENUM_STOP;
}
void CInput::Exec()
{
if(pKeyDevice){
/* 省略 */
}
if(pPadDevice){
// ジョイパッドデータの取得
pPadDevice->Poll();
// 入力の受付開始
pPadDevice->Acquire();
memcpy(&lastpaddata, &paddata, sizeof(DIJOYSTATE2));
pPadDevice->GetDeviceState(sizeof(DIJOYSTATE2), &paddata);
}
}
#pragma once
/* 省略 */
// パッドの方向キー用
#define PP_UP 0x1
#define PP_RIGHT 0x2
#define PP_DOWN 0x4
#define PP_LEFT 0x8
/* 省略 */
class CInput : public CGameObject
{
private:
/* 省略 */
LPDIRECTINPUTDEVICE8 pPadDevice;
DIJOYSTATE2 paddata, lastpaddata;
static BOOL CALLBACK EnumJoypad(const DIDEVICEINSTANCE* pInstance, LPVOID pContext);
public:
/* 省略 */
BYTE GetPovPosition(); // 十字キーの位置を取得
BOOL IsButtonDown(int pos); // ボタンが押され続けているか
BOOL IsButtonPressed(int pos); // ボタンが押された瞬間か
BOOL IsButtonReleased(int pos); // ボタンが放された瞬間か
/* 省略 */
};
/* 省略 */
BYTE CInput::GetPovPosition()
{
// paddata.rgdwPOV[0]に押された方向が角度×100という整数で格納されている
// 真上が0で時計回りに36000まで範囲がある
// デジタル入力方式のゲームパッドの場合、45度単位で取得してもまず問題ない
switch(paddata.rgdwPOV[0]){
case 0:
return PP_UP;
case 4500:
return PP_UP | PP_RIGHT;
case 9000:
return PP_RIGHT;
case 13500:
return PP_RIGHT | PP_DOWN;
case 18000:
return PP_DOWN;
case 22500:
return PP_DOWN | PP_LEFT;
case 27000:
return PP_LEFT;
case 31500:
return PP_LEFT | PP_UP;
}
return result;
}
BOOL CInput::IsButtonDown(int pos)
{
if(pPadDevice == NULL) return FALSE;
return paddata.rgbButtons[pos];
}
BOOL CInput::IsButtonPressed(int pos)
{
if(pPadDevice == NULL) return FALSE;
if(paddata.rgbButtons[pos] && !lastpaddata.rgbButtons[pos]) return TRUE;
return FALSE;
}
BOOL CInput::IsButtonReleased(int pos)
{
if(pPadDevice == NULL) return FALSE;
if(!paddata.rgbButtons[pos] && lastpaddata.rgbButtons[pos]) return TRUE;
return FALSE;
}