Joystick Win32 和 MFC 项目





5.00/5 (12投票s)
Joystick Win32 和 MFC 项目模板,即开即用。
引言
当我买了一个游戏手柄时,我确信在 CodeProject 上能找到相关的代码和说明。这个想法与 CodeProject 上 Work Damnit! 发表的文章中的想法一样。
“当我想要创建一个游戏手柄驱动程序,以便通过插入电脑的游戏手柄来控制我的机器人时,我做的第一件事就是寻找一个简单的游戏手柄驱动程序,它能为我设置好一切,并提供我想要的读数,而我无需自己编写任何代码,甚至无需了解其工作原理。不幸的是,尽管在我之前有很多人在他们的程序和项目中使用游戏手柄,但我似乎找不到有人创建过类似我想要的东西并在线分享。”
Work Damnit! 的作者在游戏手柄驱动开发方面做得很好。可惜的是,这仅限于 DirectX 环境。
背景
但他“即插即用”项目的想法被证明是有用的。毕竟,CodeProject 是开发者所需资源的重要来源。
我还发现了由 Alexander Böcken 撰写的优秀文章《使用原始输入 API 处理游戏手柄输入》。其想法和子脚本代码都很好。对于游戏手柄驱动处理的开发来说,已经不需要更多东西了。
只是出于常规,演示的实现效果似乎不那么好。每次发生 WM_INPUT
消息时都重绘整个对话框是一个很糟糕的主意。
对话框看起来在振荡且不稳定。因此,我冒昧地通过标准的 MSVS AppWizard 程序来改进其性能。
1. Win32 游戏手柄项目
1.1. 使用 AppWizard 创建 Win32 项目模板
选择菜单 File->New->Project。在出现的新建项目对话框中,选择 Win32->Win32 项目。按照提供的说明即可创建 Win32 项目模板 JoystickWin32
。
1.2. 使用资源 AppWizard 处理对话框
接下来,在资源窗口中,使用 AppWizard 标准程序创建新的对话框模板 IDD_JOYSTICKTEST_DIALOG
。
然后将游戏手柄的背景图片作为位图资源插入,并创建了 13 个编号的复选框 IDC_CHECK1...IDC_CHECK13
和 8 个未编号的复选框 IDC_HAT1...IDC_HAT8
。
创建了两个相同大小的静态资源 IDC_STATIC_XY
和 IDC_STATIC_Z
,以及两个静态文本资源 IDC_STATIC_XY_TEXT
和 IDC_STATIC_Z_TEXT
,它们分别位于相应的静态框下方。
将上下文帮助属性设置为 TRUE
。
此外,在 IDD_ABOUTBOX
对话框中,对授权信息进行了一些修正。
1.3. 全局变量和过程
全局变量和 ParseRawInput
过程完全从 Alexander Böcken 的项目中借用,没有任何更改。
需要的额外全局变量
int idBtn[] = { IDC_CHECK1,IDC_CHECK2,IDC_CHECK3,IDC_CHECK4,
IDC_CHECK5,IDC_CHECK6, //Numerated Check Boxes Array
IDC_CHECK7,IDC_CHECK8,IDC_CHECK9,IDC_CHECK10,IDC_CHECK11,IDC_CHECK12,IDC_CHECK13, }; //
int idHat[] = { IDC_HAT1,IDC_HAT2,IDC_HAT3,IDC_HAT4,IDC_HAT5,
IDC_HAT6,IDC_HAT7,IDC_HAT8, };//Unnumerated Check Boxes Array
POINT ptXY; //Point in IDC_STATIC_BOX Static Box
POINT ptZ; //Point in IDC_STATIC_Z Static Box
POINT ptXY0; //Initial Point Position
double m_scaleX = 1; //Scale in X direction
double m_scaleY = 1; //Scale in Y direction
_TCHAR stroka[256]; //Text Info String
以及在同一位置下次绘制时替换自身的十字准线绘制过程
void DrawCrossXOR(HWND hwnd, POINT pt)
{
HDC hDC = GetDC(hwnd); //Handle DC of the window
int r2 = SetROP2(hDC, R2_NOTXORPEN); //Set DC XOR style
MoveToEx(hDC, pt.x, pt.y + 5, NULL); //drawing cross
LineTo(hDC, pt.x, pt.y - 5);
MoveToEx(hDC, pt.x +5, pt.y, NULL);
LineTo(hDC, pt.x -5, pt.y );
SetROP2(hDC, r2); //restore XOR style
}
1.4. 初始化安装
在 InitInstance
过程中,由于使用了预先设计的对话框,所有的 RegisterClass
步骤都被替换为以下代码。
hWnd = CreateDialog(hInst, MAKEINTRESOURCE(IDD_JOYSTICKTEST_DIALOG),
nullptr, (DLGPROC)WndProc);
游戏手柄初始化步骤完全从 Alexander Böcken 的项目中借用,没有任何更改。
为了实现图形性能,需要进行一些初始计算。
//Center Point and Scale calculations(V.Petrov)
HWND hXY = GetDlgItem(hWnd, IDC_STATIC_XY); //Get the XY Static Box window
RECT rr;
GetClientRect(hXY, &rr); //Rect Size of the window
ptZ.x = ptXY.x = ptXY0.x = (rr.right + rr.left) / 2; //X Center Calculation
ptZ.y = ptXY.y = ptXY0.y = (rr.bottom + rr.top) / 2; //Y Center Calculation
m_scaleX = (rr.right - rr.left) / 256.; //X Scale Calculation
m_scaleY = (rr.bottom - rr.top) / 256.; //Y Scale Calculation
DrawCrossXOR(hXY, ptXY); //Draw First Cross in the XY Static Box window
HWND hZ = GetDlgItem(hWnd, IDC_STATIC_Z);
DrawCrossXOR(hZ, ptXY); //Draw First Cross in the Z Static Box window
1.5. 消息处理
在 WndProc
过程的 case WM_INPUT
中:代码从 Alexander Böcken 项目的相同位置借用,直到绘图部分。在 ParseRawInput
之后,插入了用于处理当前游戏手柄状态的代码。
复选框状态
for (int i = 0; i < 12; i++) //Update All the Numerated Check Boxes
{
CheckDlgButton(hWnd, idBtn[i], bButtonStates[i] ? BST_CHECKED : BST_UNCHECKED);
if(i == 1)
CheckDlgButton(hWnd, idBtn[12], bButtonStates[i] ? BST_CHECKED : BST_UNCHECKED);
}
for (int i = 0; i < 8; i++) //Update All the UnNumerated Check Boxes
CheckDlgButton(hWnd, idHat[i],i == lHat ? BST_CHECKED : BST_UNCHECKED);
静态 XY 框状态
int xx = ptXY0.x + (int)(lAxisX*m_scaleX); //Calc Joystick X position
int yy = ptXY0.y + (int)(lAxisY*m_scaleY); //Calc Joystick Y position
if (xx != ptXY.x || yy != ptXY.y) //If any position changed
{
HWND hXY = GetDlgItem(hWnd, IDC_STATIC_XY); //Get Static XY window
DrawCrossXOR(hXY, ptXY); //Clear the Cross in Previous position
ptXY.x = xx; //Apply new pos to Global var
ptXY.y = yy;
DrawCrossXOR(hXY, ptXY); //Draw Cross in the new pos
hXY = GetDlgItem(hWnd, IDC_STATIC_XY_TEXT); //Get Text Info Window
_stprintf_s(stroka, _T("x = %d y = %d"), lAxisX, lAxisY);//Type position
SetWindowText(hXY, stroka); //Show new position
}
静态 Z 框状态
xx = ptXY0.x + (int)(lAxisRz*m_scaleX); //Calc Joystick Rz position
yy = ptXY0.y + (int)(lAxisZ*m_scaleY); //Calc Joystick Z position
if (xx != ptZ.x || yy != ptZ.y) //If any position changed
{
HWND hXY = GetDlgItem(hWnd, IDC_STATIC_Z); //Get Static Z window
DrawCrossXOR(hXY, ptZ); //Clear the Cross in Previous position
ptZ.x = xx; //Apply new pos to Global var
ptZ.y = yy;
DrawCrossXOR(hXY, ptZ); //Draw Cross in the new pos
hXY = GetDlgItem(hWnd, IDC_STATIC_Z_TEXT); //Get Text Info Window
_stprintf_s(stroka, _T("x = %d y = %d"), lAxisRz, lAxisZ);//Type position
SetWindowText(hXY, stroka); //Show new position
}
要调用关于对话框,请单击标题栏中的问号;调用关于对话框的代码由 V.Petrov 插入。
case 0x2a2:
if (!bMod)
{
bMod = TRUE;
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
bMod = FALSE;
}
break;
插入了全局变量 bMod
,因为主对话框本身是模态的。如果关于对话框启动,所有发往主对话框的消息都将被忽略。
2. MFC 游戏手柄项目
2.1. 使用 AppWizard 创建 MFC 项目模板
选择菜单 File->New->Project。在出现的新建项目对话框中,选择 MFC->MFC 应用程序。按照提供的说明创建 Win32 项目模板 JoystickMFC
,并选择基于对话框。
2.2. 对话框预设计
对话框模板已由 Master AppWizard 创建。只需将 Win32 项目中每个对话框的所有组件复制到新的对话框中即可。
2.3. 变量和过程
所有遇到的变量都声明为 CJoystickMfcDlg
类的内部变量(名称也已更改)。
//Joystick Condition Variables
int m_x; //X pos of XY box
int m_y; //Y pos of XY box
int m_zr; //X pos of Z box
int m_z; //Y pos of Z box
int m_h; //hat switch number pressed
CPoint m_ptXY; //Point in IDC_STATIC_BOX Static Box
CPoint m_ptZ; //Point in IDC_STATIC_Z Static Box
CPoint m_ptXY0; //Initial Point Position
double m_scaleX; //Scale in X direction
double m_scaleY; //Scale in Y direction
UINT g_NumberOfButtons; //Number of numerated buttons
BOOL bButtonStates[MAX_BUTTONS]; //Array of numerated buttons conditions
在从 Alexander Böcken 项目借用的 ParseRawInput
过程中,变量也相应地进行了更改。
需要的额外全局变量
int idBtn[] = { IDC_CHECK1,IDC_CHECK2,IDC_CHECK3,IDC_CHECK4,
IDC_CHECK5,IDC_CHECK6, //Numerated Check Boxes Array
IDC_CHECK7,IDC_CHECK8,IDC_CHECK9,IDC_CHECK10,IDC_CHECK11,IDC_CHECK12,IDC_CHECK13, }; //
int idHat[] = { IDC_HAT1,IDC_HAT2,IDC_HAT3,IDC_HAT4,IDC_HAT5,
IDC_HAT6,IDC_HAT7,IDC_HAT8, };//Unnumerated Check Boxes Array
以及在同一位置下次绘制时替换自身的十字准线绘制过程
void DrawCrossXOR(CDC * pDC, CPoint pt)
{
int r2 = pDC->SetROP2(R2_NOTXORPEN); //Set DC XOR style
pDC->MoveTo(pt + CSize(0, 5)); //drawing cross
pDC->LineTo(pt + CSize(0, -5));
pDC->MoveTo(pt + CSize(5, 0));
pDC->LineTo(pt + CSize(-5, 0));
pDC->SetROP2(r2); //restore XOR style
}
2.4. 初始化安装
游戏手柄初始化步骤从 Alexander Böcken 的项目中借用,并在 CJoystickMFCDlg::OnInitDialog
过程中完全保持不变。
为了实现图形性能,需要在 CJoystickMFCDlg::OnInitDialog
过程中进行一些初始计算。
//Center Point and Scale calculations(V.Petrov)
CStatic * pWnd = (CStatic *)GetDlgItem(IDC_STATIC_XY); //Get the XY Static Box window
CRect rect;
pWnd->GetClientRect(&rect); //Rect Size of the window
m_ptXY0 = CPoint(rect.Width() / 2, rect.Height() / 2);//Center Point Calculation
m_scaleX = rect.Width() / 256.; //X Scale Calculation
m_scaleY = rect.Height() / 256.; //Y Scale Calculation
m_ptXY = m_ptZ = CPoint(-1000, -1000); //Initially point located out of rects
2.5. 消息处理
为了处理 WM_INPUT
消息,使用类向导命令创建了过程 void CJoystickMFCDlg::OnRawInput(UINT nInputcode, HRAWINPUT lParam)
。代码从 Alexander Böcken 项目的 WM_INPUT
消息处理过程借用,直到绘图部分。在 ParseRawInput
之后,我插入了用于处理当前游戏手柄状态的代码。
复选框状态
for (int i = 0; i < 12; i++) //Update All the Numerated Check Boxes
{
CheckDlgButton( idBtn[i], bButtonStates[i] ? BST_CHECKED : BST_UNCHECKED);
if (i == 1)
CheckDlgButton( idBtn[12], bButtonStates[i] ? BST_CHECKED : BST_UNCHECKED);
}
for (int i = 0; i < 8; i++) //Update All the UnNumerated Check Boxes
CheckDlgButton(idHat[i], i == m_h ? BST_CHECKED : BST_UNCHECKED);;
静态 XY 框和静态 Z 框状态
CPoint pt = m_ptXY0 + CSize((int)(m_x*m_scaleX),
(int)(m_y*m_scaleY)); //Calc Joystick XY position
CPoint ptZ = m_ptXY0 + CSize((int)(m_zr*m_scaleX),
(int)(m_z*m_scaleY)); //Calc Joystick zr and z position
if (pt != m_ptXY || ptZ != m_ptZ) //If any position changed
{
DrawScene(); //Clear the Cross in Previous position
m_ptXY = pt; //Apply new pos as current
m_ptZ = ptZ;
DrawScene(); //Draw Cross in the new pos
CStatic * pWnd = (CStatic *)GetDlgItem(IDC_STATIC_XY_TEXT);//Get Text Info Window
CString aString;
aString.Format(_T("x = %d y = %d"), m_x, m_y);//Type position
pWnd->SetWindowText(aString); //Show new position
pWnd = (CStatic *)GetDlgItem(IDC_STATIC_Z_TEXT);//Get Text Info Window
aString.Format(_T("z = %d rz = %d"), m_z, m_zr);//Type position
pWnd->SetWindowText(aString); //Show new position
}
要调用关于对话框,只需单击关于按钮。
重要提示
所提供的项目是使用 MSVS-2015 pro 开发的,并使用了 MSVS-2010 的工具集。因此,EXE 文件甚至对 Windows-XP 也有效。
问题在于使用了外部库 hid.lib。如果您使用 MSVS-2015 的工具集,那么使用所提供的项目预计不会有任何问题。
在使用 MSVS-2010 的工具集构建所提供的项目之前,您必须准备好两点:
- 在项目属性框的配置属性->VC++目录->包含目录中,选择更改命令,并将目录设置为 ..\Windows Kits\8.1\Include\shared(仅为了使用
#include <hidsdi.h>
命令)。 - 库
hid.lib
最好通过菜单 PROJECT->Add Existing Item 从目录 ..\Windows Kits\8.1\Lib\winv6.3\um\x86\hid.lib 直接包含。
兴趣点和致谢
我再次确信,几乎所有用途的代码和说明都可以在 CodeProject 上找到。就在三天前,我对游戏手柄处理一无所知(我不是游戏玩家,以前从未碰过这东西)。
而现在,我已经在我的真实船舶模拟器程序中使用了游戏手柄。这就像将这个 JoystickMFCDlg.cpp 程序插入到环境中一样简单,只需将其设置为非模态和不可见(并且不要忘记移除位图图片,因为它会使 EXE 文件变得巨大)。它的工作方式完全符合本文开头引用的 Work Damnit! 的话。
非常感谢 Alexander Böcken 的出色工作。我希望我的文章对开发人员也有用。
最重要的是,我的孙女们将会非常高兴地玩这个游戏手柄。