2010年2月27日星期六

C++开发游戏手柄代码 (Developing the Joystick Code)

还是坚持每周一博,照例写正文前废话一段:最近天气越来越好,不下雪了,偶尔下点雨,下了一次冰雹,但可以看到10+的温度了,晚上6点坐车回家的时候天还是是微亮。哈,德国的春天终于来了。
迫于项目时间的压力,项目只剩下最后一周的时间,本周的工作效率还不错,写了非常多的代码,突然发现,我最近最能集中心力,最具生产力的时段是下午5到6点,就是回家前一个小时,似乎所有最有创造力的生产和想法都是在这个小时产生的,很奇怪。最近,所里的咖啡机又坏了,苦了很多人,有一个意大利同事对我说说,没有咖啡就不能Coffee in, Code out,没法编程了。还好我不喝咖啡,每天只喝茶,所里提供的各种奇怪的茶包(德国的茶包的种类真是多的恐怖,各种奇怪的口味都有,喝过一种咖喱口味的茶)。

好了闲话不多谈,今天谈谈关于游戏手柄joystick的代码开发,最近接触了一些多媒体子系统的Win32 API,其实非常容易,通过一些google可以找到非常多的源代码和示例代码,可以非常快的利用多媒体的Win32 API开发出一些很有趣的东西,游戏手柄joystick代码开发就是一个很经典的例子。支持游戏手柄现在是一个Win32的标准的一部分,多媒体子系统单独的Win32 API,其中支持游戏手柄是一个组成部分。但是关于游戏手柄的功能和数据结构没有定义在标准windows.h头文件,而是位于mmsystem.h中头文件。
上代码之前,一些说明:

[1]

要使用游戏手柄的Win32 API
#include <windows.h>
#include <mmsystem.h>

编译器请使用VS2005以上版本。

[2]

当然了不要忘记加入winmm.lib

[3]

JOYSTATE数据类型包括描述游戏手柄的State
例如

1: typedef WORD JOYSTATE; 
2: const JOYSTATE JOY_NONE = 0x0000L, 
3: JOY_LEFT = 0x0001L, 
4: JOY_RIGHT = 0x0002L, 
5: JOY_UP = 0x0004L, 
6: JOY_DOWN = 0x0008L,


[4]

我们要做的事就是,写出以下方法:

BOOL InitJoystick(); 
void CaptureJoystick(); 
void ReleaseJoystick(); 
void CheckJoystick();

 

[5]

CaptureJoystick()ReleaseJoystick()的方法就是使用使用两个Win32 functions就是:joySetCapture()joyReleaseCapture()
JOYINFO structure用于检索游戏手柄状态通过joyGetPos() function。

[6]

以下代码结合了Qt,也可以不需要Qt运行,只需要删除Qt部分的代码即可,使用了Qt的signal slot机制,利用了QTime的timeout()不断对CheckJoystick()进行调用,以及时更新Joystick状态。

[7]

BTP_Joystick以下的代码的主体部分来自网上的一些示例代码,我进行了一些修改和裁剪,编译和使用没有任何问题,我使用的是北通小手柄 BTP-C024 USB游戏手柄(如附图所示)进行测试,运行很好。以下代码非常简单,应该很容易理解,不要问我,为什么不能用,或者如何使用,如果不能用,请google或再看看代码(你已经有了完整的代码),呵呵。

 

 

 

 

/*
 * \file GameEngine.h
 * \class GameEngine
 * \date   2010
 * \bug
 * \warning
 *
 */

10 #ifndef GAMEENGINE_H_
11 #define GAMEENGINE_H_
12
13 #include <QtGui>
14
15 #include <windows.h>
16 #include <mmsystem.h>
17
18 //-----------------------------------------------------------------
19 // Joystick Flags
20 //-----------------------------------------------------------------
21 typedef WORD    JOYSTATE;
22 const JOYSTATE  JOY_NONE  = 0x0000L,
23                 JOY_LEFT  = 0x0001L,
24                 JOY_RIGHT = 0x0002L,
25                 JOY_UP    = 0x0004L,
26                 JOY_DOWN  = 0x0008L,
27                 JOY_FIRE1 = 0x0010L,
28                 JOY_FIRE2 = 0x0020L,
29                 JOY_FIRE3 = 0x0030L,
30                 JOY_FIRE4 = 0x0040L;
31
32 class GameEngine : public QObject
33 {
34     Q_OBJECT
35
36 public:
37
38     GameEngine();
39     ~GameEngine();
40
41     void HandleJoystick(JOYSTATE jsJoystickState);
42    
43     BOOL InitJoystick();
44     void CaptureJoystick();
45     void ReleaseJoystick();
46    
47     UINT m_uiJoystickID;
48     RECT m_rcJoystickTrip;
49     HWND m_hWindow;
50
51 protected:
52
53 private slots:
54     void CheckJoystick();
55
56 signals:
57     void joystickLeft();
58     void joystickRight();
59     void joystickUp();
60     void joystickDown();
61     void joystickButton1();
62     void joystickButton2();
63     void joystickButton3();
64     void joystickButton4();
65
66 private:
67     QTimer *_poller;
68
69 };
70
71 #endif /* GAMEENGINE_H_ */

 

1   /*
2    * \file GameEngine.cpp
3    * \class GameEngine
4    * \date   2010
5    * \bug
6    * \warning
7    *
8    */
9  
10  #include <QDebug>
11 
12  #include "GameEngine.h"
13 
14 
15  GameEngine::GameEngine()
16  {
17   
18      InitJoystick();
19      CaptureJoystick();
20 
21      //start timer to trigger every 100 ms 
22      _poller = new QTimer(this);
23      connect(_poller, SIGNAL(timeout()), this, SLOT(CheckJoystick()));
24      _poller->start(100); 
25  }
26 
27  GameEngine::~GameEngine()
28  {
29 
30  }
31 
32  BOOL GameEngine::InitJoystick()
33  {
34      // Make sure joystick driver is present
35      UINT uiNumJoysticks;
36      if ((uiNumJoysticks = joyGetNumDevs()) == 0)
37          return FALSE;
38 
39      // Make sure the joystick is attached
40      JOYINFO jiInfo;
41      if (joyGetPos(JOYSTICKID1, &jiInfo) != JOYERR_UNPLUGGED)
42          m_uiJoystickID = JOYSTICKID1;
43      else
44          return FALSE;
45     
46      // Calculate the trip values
47      JOYCAPS jcCaps;
48      joyGetDevCaps(m_uiJoystickID, &jcCaps, sizeof(JOYCAPS));
49      DWORD dwXCenter = ((DWORD)jcCaps.wXmin + jcCaps.wXmax) / 2;
50      DWORD dwYCenter = ((DWORD)jcCaps.wYmin + jcCaps.wYmax) / 2;
51      m_rcJoystickTrip.left = (jcCaps.wXmin + (WORD)dwXCenter) / 2;
52      m_rcJoystickTrip.right = (jcCaps.wXmax + (WORD)dwXCenter) / 2;
53      m_rcJoystickTrip.top = (jcCaps.wYmin + (WORD)dwYCenter) / 2;
54      m_rcJoystickTrip.bottom = (jcCaps.wYmax + (WORD)dwYCenter) / 2;
55      return TRUE;
56  }
57 
58  void GameEngine::CaptureJoystick()
59  {
60      // Capture the joystick
61      if (m_uiJoystickID == JOYSTICKID1)
62      {
63         joySetCapture(m_hWindow, m_uiJoystickID, NULL, TRUE);
64          qDebug() << " Capture Joystick success!";
65      }
66  }
67 
68  void GameEngine::ReleaseJoystick()
69  {
70      // Release the joystick
71      if (m_uiJoystickID == JOYSTICKID1)
72      joyReleaseCapture(m_uiJoystickID);
73  }
74 
75  void GameEngine::CheckJoystick()
76  {
77  if (m_uiJoystickID == JOYSTICKID1)
78  {
79     JOYINFO jiInfo;
80     JOYSTATE jsJoystickState = 0;
81     if (joyGetPos(m_uiJoystickID, &jiInfo) == JOYERR_NOERROR)
82     {
83          // Check horizontal movement
84          if (jiInfo.wXpos < (WORD)m_rcJoystickTrip.left)
85              jsJoystickState |= JOY_LEFT;
86            else if (jiInfo.wXpos > (WORD)m_rcJoystickTrip.right)
87              jsJoystickState |= JOY_RIGHT;
88             // Check vertical movement
89             if (jiInfo.wYpos < (WORD)m_rcJoystickTrip.top)
90              jsJoystickState |= JOY_UP;
91             else if (jiInfo.wYpos > (WORD)m_rcJoystickTrip.bottom)
92              jsJoystickState |= JOY_DOWN;
93             // Check buttons
94             if(jiInfo.wButtons & JOY_BUTTON1)
95              jsJoystickState |= JOY_FIRE1;
96             if(jiInfo.wButtons & JOY_BUTTON2)
97              jsJoystickState |= JOY_FIRE2;
98          if(jiInfo.wButtons & JOY_BUTTON3)
99              jsJoystickState |= JOY_FIRE3;
100         if(jiInfo.wButtons & JOY_BUTTON4)
101             jsJoystickState |= JOY_FIRE4;
102     }
103
104     // Allow the game to handle the joystick
105     HandleJoystick(jsJoystickState);
106     }
107 }
108
109 void GameEngine::HandleJoystick(JOYSTATE jsJoystickState)
110 {
111     //这里加入你自己对jsJoystickState的处理,以下只是极简单的示范
112     //通过HandleJoystick得到JOYSTATE
113    
114     //qDebug() << " get information about Joystick!" ;
115     if (jsJoystickState == JOY_UP)
116     {
117         qDebug() << " get Joystick -- JOY_UP!" ;   
118     }
119     else if (jsJoystickState == JOY_DOWN)
120     {
121         qDebug() << " get Joystick -- JOY_DOWN!" ;
122     }
123     else if (jsJoystickState == JOY_LEFT)
124     {
125         qDebug() << " get Joystick -- JOY_LEFT!" ;
126     }
127     else if (jsJoystickState == JOY_RIGHT)
128     {
129         qDebug() << " get Joystick -- JOY_RIGHT!" ;
130     }
131     else if (jsJoystickState == JOY_FIRE1)
132     {
133         qDebug() << " get Joystick -- JOY_FIRE1!" ;
134     }
135     else if (jsJoystickState == JOY_FIRE2)
136     {
137         qDebug() << " get Joystick -- JOY_FIRE2!" ;
138     }
139     else if (jsJoystickState == JOY_FIRE3)
140     {
141         qDebug() << " get Joystick -- JOY_FIRE3!" ;
142     }
143     else if (jsJoystickState == JOY_FIRE4)
144     {
145         qDebug() << " get Joystick -- JOY_FIRE4!" ;
146     }
147 }
148

Enjoy!

2010年2月20日星期六

Qt 自定义 Combo Box

这星期效率还不错,延续了上周的状态,写了非常多的代码,但是大部分都是低复杂度的”简单代码”,写了一些Gui。和一些同事不一样,我很喜欢写Gui,因为写完马上就可以看到成果,很有成就感,而且用Qt写Gui感觉就像堆积木,很简单,只要找到自己需要的API,我个人非常喜欢Qt风格的C++的应用程序接口,很清晰且简单,相当直觉化

customize_combo_box_qt这里和大家很简单谈一些我最近用Qt写Gui的发现的好玩特性,也许Qt的标准控件已经不能满足你了,其实,我们同样可以很容易地进行自定义控件,这里使用Combo Box作为例子。

以下代码会构建出我们自定义的Combo Box,如附图所示,这里使用了Qt Style Sheet

 

 

// set some self defined colors
QColor royalBlue;
QColor orange;
royalBlue.setRgb(65, 105, 225);
orange.setRgb(255, 165, 000);

// set default color
myComboBox->setStyleSheet("* { background-color: rgb(65, 105, 225) }");

myComboBox->insertItem(0, "blue");
myComboBox->setItemData(0, royalBlue, Qt::BackgroundRole);

myComboBox->insertItem(1, "red");
myComboBox->setItemData(1, Qt::red, Qt::BackgroundRole);

myComboBox->insertItem(2, "green");
myComboBox->setItemData(2, Qt::green, Qt::BackgroundRole);

myComboBox->insertItem(3, "yellow");
myComboBox->setItemData(3, Qt::yellow, Qt::BackgroundRole);

myComboBox->insertItem(4, "orange");
myComboBox->setItemData(4, orange, Qt::BackgroundRole);

myComboBox->insertItem(5, "cyan");
myComboBox->setItemData(5, Qt::cyan, Qt::BackgroundRole);

myComboBox->insertItem(6, "purple");
myComboBox->setItemData(6, Qt::magenta, Qt::BackgroundRole);

 

 

也许还不过瘾,我们还可以通过QPalette来自定义我们的Combo Box,以下是一个简单例子。

 QPalette myPalette = myComboBox->palette();

 // outline around the menu
 myPalette.setColor(QPalette::Window, Qt::red);    
 myPalette.setColor(QPalette::WindowText, Qt::white);

 // customize myComboBox button
 myPalette.setColor(QPalette::Button, Qt::darkCyan);  
 myPalette.setColor(QPalette::ButtonText, Qt::white);
 
 // customize myComboBox menu
 myPalette.setColor(QPalette::Base, Qt::darkCyan);  
 myPalette.setColor(QPalette::Text, Qt::white);

 // customize highlight button and menu
 myPalette.setColor(QPalette::Highlight, Qt::blue);   
 myPalette.setColor(QPalette::HighlightedText, Qt::red);
 
 // customize the disabled color
 myPalette.setColor(QPalette::Disabled, QPalette::Button, Qt::gray);
 myPalette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::lightGray);
 
 myComboBox->setPalette(myPalette);

2010年2月13日星期六

牛年最后一周碎碎念

虎年快乐 最近实在太冷了,天天下雪,附图是法国鳄鱼牌的一个平面广告,我觉得很应景,简单改造成贺卡,祝大家虎年快乐!!每到春节,就好想回国过年。当然了,在这里,我还是有一些小聚餐,譬如吃一些远不如国内丰盛的火锅,哈,聊补思乡之情。

 

关于工作,这个星期开始集中使用Googletest进行单元测试(Googletest架构不仅可以很好自动化执行,可以很方便地包含在持续开发构建中,更重要的是极度简单易用,我非常喜爱),新项目的“业务逻辑层”一部份采用了测试驱动开发。这个星期比较能够集中心力,大概写了>3000行的代码,这对于我来说已经是一个相当相当的高产的一周,个人相当满意进度。

 

本周还继续丢弃了一些旧模块(内部结构很混乱,复杂),重新设计并重写,其实很早以前就想重写,但是一直下不了决心,在代码的复杂度面前总是显得相当软弱,只有当代码几乎彻底“腐烂”,不能适应新系统,才不得不重写,这真是非常不好的习惯。一定要改掉这个恶习。这次重构采用了同事介绍的一个很简单的方法,我认为还不错,个人命名为“小任务驱动重构”。方法如下:先彻底地理解一下以前的代码,然后重新设计结构接口等等,再对整个重构写一个非常细致(一定要非常非常细致)的计划,然后得到一个长长的由一个个小任务组成的“计划书”,以极微小的“粒度”进行重写,每完成一个小任务,在计划中将其删除,这样做比较有成就感,重写的过程就像自己和自己竞赛,例如可以给自己定一些每天必须要完成小任务的数量,并时刻注意代码风格,变量命名和错误处理,如果觉得编码枯燥时,可以进行一些文档编写。

 

我最近写的机器人控制程序,只剩下一些系统同步模块还需完善,完成后就可以进行和机器人的联机测试了,很是兴奋,因为可以使用所里最新购买的迷你机器人Khepera 3进行实验,对这个新的“大玩具”很是期待。

2010年2月7日星期日

本周编程乱弹及《编程匠艺》书评

snow_upb_2010 附图是家附近的雪景,大概一个月雪都没化,德国今年真是非常非常冷,雪也下得相当疯狂,地球变暖?还是真的似乎进入了“小冰河时期”。这个星期当然还是继续坚持“每周五天,信息斋戒”。

这星期接触了ICE,相当不错并很有趣(简介:“Internet Communications Engine”,是一个中间件平台。作为一个高性能的互联网通信平台,ICE包含了很多分层的服务和插件,并且简单、高效和强大。与硬件架构无关,编程语言无关,完全线程化,采用TCP、IP 和UDP作为传输协议,客户端和服务器代码都不需要了解底层的传输机制。),并实现了一个应用。还在做一个重构,进行中,采用了一个方法,就是对这个重构任务进行分解,把他们分成非常小的工作单元。并使用了原型设计过程,创建一些可丢弃的原型。个人认为原型设计过程在一定程度上可以说是非常高效和稳定。最近还改了一些编码风格,以便更为适应我们内部的编码风格。

这个星期也和一些bug战斗很久,因为一些指针的输入错误或糟糕的指针算法在以前的代码中,产生了很多的段错误(简介:也称为“保护缺陷”。源自程序访问那些并没有分配给它们的存储单元,涉及,都非常容易造成这种错误。说白了就是“访问了不可访问的内存”)。个人经验教训就是: 千万不能盲目,一定要清楚这些代码如何运行,编写代码一定要谨慎,并做到更谨慎做更多“防御性编程”,这样才能更好地防止bug出现。

但是这个星期最大的经验教训,就是明白了一个道理:“程序的记录分类很重要,一定要要好好总结整理”。以前编过一个测试服务器网络的程序,居然忘记曾经编过,这个星期四又编了一遍,而且还不会编了:-( 极度痛苦,最后,突然发现以前的代码,当时真是泪流满面。这个星期,师兄对我上传的代码进行了代码审查据说这是提高自己的最佳方式。这也的确提高了我对代码的责任感,强迫自己提高代码质量,修正自己的代码更加易于理解,消除所有重复代码。

最近进行了很多思考,项目压力下,反而进行了更多的思考,总结。常常在想如何能够“创作出组织良好而且易于理解的代码”,并阅读了《编程匠艺》,挺值得推荐的。这里将我在豆瓣的书评附上。

 

据说,传说中理想的程序员应该具有以下品质:
政治家。必须很老练,去应付那些怪异代码猴子的小过失,能够协调人员。
亲切。可以愉快的和别人合作。
艺术感。可以设计出优雅的解决方案。
技术天才。编写的代码可靠耐用。
也许我们还远未达到这种地步。但是从这本书中可以体会并学习:理想的程序员的“一些实践经验,一些思考方式,一些正确的心态”。
我很喜欢阅读这种类型的书,读起来非常轻松,不是什么纯技术类的图书,但是从中又可以获得很多系统化的知识。这本书共有24章,很厚,但是每个章节间是“松耦合”,每章的结构相似,先分要点讨论一些问题如编程的各个要素或一些编程问题,最后总结,思考,再提出一些深入思考等等,我们大可以每天随意的找一些有兴趣的东西读一读。
书中有很多生动,睿智之语,有一句话印象比较深刻:“构建软件就像犯罪一样:当有组织时,就会做得更好。”
个人认为,程序员不要一味学习高深的编程技术,要更加关注编程的思想,方法,态度,这本书绝对适合放在案头,偶尔翻一翻,提醒自己是否像优秀的程序员一样思考和行动。当然了知易行难,将这些思想,方法,态度应用在实际工作中是一个很困难或“痛苦”的过程,但如果可以坚持实践,并结合自己进行深入思考,这样定可以向传说中理想的程序员前进。
附注,网上有很好的读书笔记,做的很仔细很完整,推荐,如果没有时间阅读本书,可以直接阅读这份笔记,网址如下:
http://www.cnblogs.com/wing011203/tag/编程匠艺/