65.9K
CodeProject 正在变化。 阅读更多。
Home

Joystick Win32 和 MFC 项目

2016年12月19日

CPOL

5分钟阅读

viewsIcon

21484

downloadIcon

1403

Joystick Win32 和 MFC 项目模板,即开即用。

引言

当我买了一个游戏手柄时,我确信在 CodeProject 上能找到相关的代码和说明。这个想法与 CodeProjectWork 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_XYIDC_STATIC_Z,以及两个静态文本资源 IDC_STATIC_XY_TEXTIDC_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 的出色工作。我希望我的文章对开发人员也有用。

最重要的是,我的孙女们将会非常高兴地玩这个游戏手柄

© . All rights reserved.