Chinaunix首页 | 论坛 | 博客
  • 博客访问: 160220
  • 博文数量: 22
  • 博客积分: 828
  • 博客等级: 上士
  • 技术积分: 290
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-01 18:16
文章分类

全部博文(22)

文章存档

2012年(22)

分类: C/C++

2012-05-11 16:59:52

相信大家都熟悉《仙剑奇侠传98柔情版》的人机交互方式,用的仅仅是键盘。在那个物质并不充裕的时代,一台配置并不高的电脑,一款名叫《仙剑奇侠传》的游戏,却能承载一代人对梦想的追逐。虽然在这十几年间,各种新潮的游戏层出不穷,但是《仙剑奇侠传98柔情版》,作为国产单机游戏无法被超越的传奇,已经永远留在了我们这代人的心中。那是一个永远无法被取代的,最最唯美的梦。



从这节笔记开始,我们就开始讲解游戏输入消息的处理,开始人机交互,开始真正意义上的游戏开发。

这一节里我们主要讲解键盘消息的处理。


键盘作为基本的输出装置,在每一款优秀的游戏研发中都有着至关重要的地位(当然我们在这里暂时不讨论ios和android平台)。

首先我们对Windows系统下键盘的基本概念及键盘消息的处理方式做一个简单介绍。

1.虚拟键码

所有键盘的按键都被定义出一组通用的“虚拟键码”,也就是说在Windows系统下所有按键都会被视为虚拟键(包含鼠标键在内),而每一个虚拟键都有其对应的一个虚拟键码。


2.键盘消息

Windows系统是一个消息驱动的环境,一旦使用者在键盘上进行输入操作,那么系统便会接收到对应的键盘消息,下面我们列出最常见的3种键盘消息:

WM_KEYDOWN        按下按键的消息

WM_KEYUP            松开按键消息

WM_CHAR             字符消息

当某一按键被按下时,伴随着这个操作所产生的是以虚拟键码类型传送的WM_KEYDOWN与WM_KEYUP消息。当程序接收到这些消息时。便可由虚拟键码的信息来得知是哪个按键被按下。

此外,WM_CHAR则是当按下的按键为定义于ASCⅡ中的可打印字符时,便发出此字符消息。


3.系统键

Windows系统本身定义了一组“系统键”,这些按键通常都是【Alt】与其他按键的组合,系统键对于Windows系统本身有一些特定的作用,Windows中也特别针对系统键定出了下面的相关消息

WM_SYSKEYDOWN             按下系统键消息

WM_SYSKEYUP                 松下系统键消息

消息代号中加入“SYS”代表系统键按下消息,然而实际上程序中很少处理系统键消息,因为当这类消息发生时Windows会自行处理并进行相应的工作。

以上便是键盘在Windows系统下关于其定义及输出处理的一些基本概念。

下面我们来详细讲解这节笔记的主角——键盘消息处理。

键盘消息同样是在消息处理函数中加来以定义处理的,按下按键事件一定会紧随着一个松开按键的事件,因此WM_KEYDOWN与WM_KEYUP两种消息必须是成对发生的。但通常仅在程序中对WM_KEYDOWN消息进行处理,而忽略WM_KEYUP消息。

我们观察消息处理函数中所输入的两个参数wParam和lParam:

点击(此处)折叠或打开

  1. LRESULT CALLBACK WndProc(HWND hWnd,
  2. UINT message,
  3. WPARAM wParam,
  4. LPARAM lParam)

当键盘消息触发时,wParam的值为按下按键的虚拟键码,Windows中所定义的虚拟键码是以“VK_”开头的,lParam则储存按键的相关状态信息,因此,如果程序要对使用者的键盘输入操作进行处理,那么消息处理函数的内容可以定义如下:

点击(此处)折叠或打开

  1. 01.LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  2. 02.{
  3. 03. switch (message)
  4. 04. {
  5. 05. case WM_KEYDOWN: //按下键盘消息
  6. 06. switch (wParam)
  7. 07. {
  8. 08. case VK_ESCAPE: //按下【Esc】键
  9. 09. //定义消息处理程序
  10. 10. break;
  11. 11. case VK_UP: //按下【↑】键
  12. 12. //定义消息处理程序
  13. 13. break;
  14. 14. case WM_DESTROY: //窗口结束消息
  15. 15. PostQuitMessage(0);
  16. 16. break;
  17. 17.default: //其他消息
  18. 18. return DefWindowProc(hWnd, message, wParam, lParam);
  19. 19. }
  20. 20. return 0;
  21. 21.}

针对这个消息处理函数中键盘消息处理的程序关键说明如下:

<1>第5行:定义处理“WM_KEYDOWN”消息。

<2>第6行:以“switch”叙述判断“wParam”的值来得知哪个按键被按下,并运行对应“case”中的按键消息处理程序。



同样的,我们用一个实例来让大家熟悉和实践一下本节的知识。

这个范例会让玩家以【↑】【↓】【←】【→】键进行输入,控制画面中人物的移动,这里使用了人物在4个不同方向上走动的连续图案


废话也不多说了,直接上详细注释的代码:

点击(此处)折叠或打开

  1. 01.#include "stdafx.h"
  2. 02.#include <stdio.h>
  3. 03.
  4. 04.//全局变量声明
  5. 05.HINSTANCE hInst;
  6. 06.HBITMAP girl[4],bg;
  7. 07.HDC hdc,mdc,bufdc;
  8. 08.HWND hWnd;
  9. 09.DWORD tPre,tNow;
  10. 10.int num,dir,x,y; //x,y变量为人物贴图坐标,dir为人物移动方向,这里我们中以0,1,2,3代表人物上,下,左,右方向上的移动:num为连续贴图中的小图编号
  11. 11.
  12. 12.//全局函数声明
  13. 13.ATOM MyRegisterClass(HINSTANCE hInstance);
  14. 14.BOOL InitInstance(HINSTANCE, int);
  15. 15.LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
  16. 16.void MyPaint(HDC hdc);
  17. 17.
  18. 18.//****WinMain函数,程序入口点函数***********************
  19. 19.int APIENTRY WinMain(HINSTANCE hInstance,
  20. 20. HINSTANCE hPrevInstance,
  21. 21. LPSTR lpCmdLine,
  22. 22. int nCmdShow)
  23. 23.{
  24. 24. MSG msg;
  25. 25.
  26. 26. MyRegisterClass(hInstance);
  27. 27.
  28. 28. //初始化
  29. 29. if (!InitInstance (hInstance, nCmdShow))
  30. 30. {
  31. 31. return FALSE;
  32. 32. }
  33. 33.
  34. 34. GetMessage(&msg,NULL,NULL,NULL); //初始化msg
  35. 35. //消息循环
  36. 36. while( msg.message!=WM_QUIT )
  37. 37. {
  38. 38. if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
  39. 39. {
  40. 40. TranslateMessage( &msg );
  41. 41. DispatchMessage( &msg );
  42. 42. }
  43. 43. else
  44. 44. {
  45. 45. tNow = GetTickCount();
  46. 46. if(tNow-tPre >= 40)
  47. 47. MyPaint(hdc);
  48. 48. }
  49. 49. }
  50. 50.
  51. 51. return msg.wParam;
  52. 52.}
  53. 53.
  54. 54.//****设计一个窗口类,类似填空题,使用窗口结构体*******************
  55. 55.ATOM MyRegisterClass(HINSTANCE hInstance)
  56. 56.{
  57. 57. WNDCLAS*** wcex;
  58. 58.
  59. 59. wcex.cbSize = sizeof(WNDCLAS***);
  60. 60. wcex.style = CS_HREDRAW | CS_VREDRAW;
  61. 61. wcex.lpfnWndProc = (WNDPROC)WndProc;
  62. 62. wcex.cbCl***tra = 0;
  63. 63. wcex.cbWndExtra = 0;
  64. 64. wcex.hInstance = hInstance;
  65. 65. wcex.hIcon = NULL;
  66. 66. wcex.hCursor = NULL;
  67. 67. wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  68. 68. wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  69. 69. wcex.lpszMenuName = NULL;
  70. 70. wcex.lpszClassName = "canvas";
  71. 71. wcex.hIconSm = NULL;
  72. 72.
  73. 73. return RegisterClas***(&wcex);
  74. 74.}
  75. 75.
  76. 76.//****初始化函数*************************************
  77. 77.// 加载位图并设定各种初始值
  78. 78.BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
  79. 79.{
  80. 80. HBITMAP bmp;
  81. 81. hInst = hInstance;
  82. 82.
  83. 83. hWnd = CreateWindow("canvas", "绘图窗口" , WS_OVERLAPPEDWINDOW,
  84. 84. CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
  85. 85.
  86. 86. if (!hWnd)
  87. 87. {
  88. 88. return FALSE;
  89. 89. }
  90. 90.
  91. 91. MoveWindow(hWnd,10,10,640,480,true);
  92. 92. ShowWindow(hWnd, nCmdShow);
  93. 93. UpdateWindow(hWnd);
  94. 94.
  95. 95. hdc = GetDC(hWnd);
  96. 96. mdc = CreateCompatibleDC(hdc);
  97. 97. bufdc = CreateCompatibleDC(hdc);
  98. 98.
  99. 99.
  100. 100. //建立空的位图并置入mdc中
  101. 101. bmp = CreateCompatibleBitmap(hdc,640,480);
  102. 102. SelectObject(mdc,bmp);
  103. 103.
  104. 104.
  105. 105. //设定人物贴图初始位置和移动方向
  106. 106. x = 300;
  107. 107. y = 250;
  108. 108. dir = 0;
  109. 109. num = 0;
  110. 110.
  111. 111. //载入各连续移动位图及背景图
  112. 112. girl[0] = (HBITMAP)LoadImage(NULL,"girl0.bmp",IMAGE_BITMAP,440,148,LR_LOADFROMFILE);
  113. 113. girl[1] = (HBITMAP)LoadImage(NULL,"girl1.bmp",IMAGE_BITMAP,424,154,LR_LOADFROMFILE);
  114. 114. girl[2] = (HBITMAP)LoadImage(NULL,"girl2.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
  115. 115. girl[3] = (HBITMAP)LoadImage(NULL,"girl3.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
  116. 116. bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
  117. 117.
  118. 118. MyPaint(hdc);
  119. 119.
  120. 120. return TRUE;
  121. 121.}
  122. 122.
  123. 123.//****自定义绘图函数*********************************
  124. 124.// 人物贴图坐标修正及窗口贴图
  125. 125.void MyPaint(HDC hdc)
  126. 126.{
  127. 127. int w,h;
  128. 128.
  129. 129. //先在mdc中贴上背景图
  130. 130. SelectObject(bufdc,bg);
  131. 131. BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);
  132. 132.
  133. 133. //按照目前的移动方向取出对应人物的连续走动图,并确定截取人物图的宽度与高度
  134. 134. SelectObject(bufdc,girl[dir]);
  135. 135. switch(dir)
  136. 136. {
  137. 137. case 0:
  138. 138. w = 55;
  139. 139. h = 74;
  140. 140. break;
  141. 141. case 1:
  142. 142. w = 53;
  143. 143. h = 77;
  144. 144. break;
  145. 145. case 2:
  146. 146. w = 60;
  147. 147. h = 74;
  148. 148. break;
  149. 149. case 3:
  150. 150. w = 60;
  151. 151. h = 74;
  152. 152. break;
  153. 153. }
  154. 154. //按照目前的X,Y的值在mdc上进行透明贴图,然后显示在窗口画面上
  155. 155. BitBlt(mdc,x,y,w,h,bufdc,num*w,h,SRCAND);
  156. 156. BitBlt(mdc,x,y,w,h,bufdc,num*w,0,SRCPAINT);
  157. 157.
  158. 158. BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
  159. 159.
  160. 160. tPre = GetTickCount(); //记录此次绘图时间
  161. 161.
  162. 162. num++;
  163. 163. if(num == 8)
  164. 164. num = 0;
  165. 165.
  166. 166.}
  167. 167.
  168. 168.//****消息处理函数***********************************
  169. 169.// 1.按下【Esc】键结束程序
  170. 170.// 2.按下方向键重设贴图坐标
  171. 171.LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  172. 172.{
  173. 173. switch (message)
  174. 174. {
  175. 175. case WM_KEYDOWN: //按下键盘消息
  176. 176. //判断按键的虚拟键码
  177. 177. switch (wParam)
  178. 178. {
  179. 179. case VK_ESCAPE: //按下【Esc】键
  180. 180. PostQuitMessage( 0 ); //结束程序
  181. 181. break;
  182. 182. case VK_UP: //按下【↑】键
  183. 183. //先按照目前的移动方向来进行贴图坐标修正,并加入人物往上移动的量(每次按下一次按键移动10个单位),来决定人物贴图坐标的X与Y值,接着判断坐标是否超出窗口区域,若有则再次修正
  184. 184. switch(dir)
  185. 185. {
  186. 186. case 0:
  187. 187. y -= 10;
  188. 188. break;
  189. 189. case 1:
  190. 190. x -= 1;
  191. 191. y -= 8;
  192. 192. break;
  193. 193. case 2:
  194. 194. x += 2;
  195. 195. y -= 10;
  196. 196. break;
  197. 197. case 3:
  198. 198. x += 2;
  199. 199. y -= 10;
  200. 200. break;
  201. 201. }
  202. 202. if(y < 0)
  203. 203. y = 0;
  204. 204. dir = 0;
  205. 205. break;
  206. 206. case VK_DOWN: //按下【↓】键
  207. 207. switch(dir)
  208. 208. {
  209. 209. case 0:
  210. 210. x += 1;
  211. 211. y += 8;
  212. 212. break;
  213. 213. case 1:
  214. 214. y += 10;
  215. 215. break;
  216. 216. case 2:
  217. 217. x += 3;
  218. 218. y += 6;
  219. 219. break;
  220. 220. case 3:
  221. 221. x += 3;
  222. 222. y += 6;
  223. 223. break;
  224. 224. }
  225. 225.
  226. 226. if(y > 375)
  227. 227. y = 375;
  228. 228. dir = 1;
  229. 229. break;
  230. 230. case VK_LEFT: //按下【←】键
  231. 231. switch(dir)
  232. 232. {
  233. 233. case 0:
  234. 234. x -= 12;
  235. 235. break;
  236. 236. case 1:
  237. 237. x -= 13;
  238. 238. y += 4;
  239. 239. break;
  240. 240. case 2:
  241. 241. x -= 10;
  242. 242. break;
  243. 243. case 3:
  244. 244. x -= 10;
  245. 245. break;
  246. 246. }
  247. 247. if(x < 0)
  248. 248. x = 0;
  249. 249. dir = 2;
  250. 250. break;
  251. 251. case VK_RIGHT: //按下【→】键
  252. 252. switch(dir)
  253. 253. {
  254. 254. case 0:
  255. 255. x += 8;
  256. 256. break;
  257. 257. case 1:
  258. 258. x += 7;
  259. 259. y += 4;
  260. 260. break;
  261. 261. case 2:
  262. 262. x += 10;
  263. 263. break;
  264. 264. case 3:
  265. 265. x += 10;
  266. 266. break;
  267. 267. }
  268. 268. if(x > 575)
  269. 269. x = 575;
  270. 270. dir = 3;
  271. 271. break;
  272. 272. }
  273. 273. break;
  274. 274. case WM_DESTROY: //窗口结束消息
  275. 275. int i;
  276. 276.
  277. 277. DeleteDC(mdc);
  278. 278. DeleteDC(bufdc);
  279. 279. for(i=0;i<4;i++)
  280. 280. DeleteObject(girl[i]);
  281. 281. DeleteObject(bg);
  282. 282. ReleaseDC(hWnd,hdc);
  283. 283.
  284. 284. PostQuitMessage(0);
  285. 285. break;
  286. 286. default: //其他消息
  287. 287. return DefWindowProc(hWnd, message, wParam, lParam);
  288. 288. }
  289. 289. return 0;
  290. 290.}

程序运行结果如下图,我们可以用键盘操作这个小人的上下左右移动,用Esc退出:


这样,一个简单的小游戏就完成了。

我们也可以通过在消息处理函数中取得按键虚拟键码的方式,很简单地对键盘输入操作进行处理。

本系列文章由zhmxy555编写转载 http://blog.csdn.net/zhmxy555/article/details/7390624

源代码太大 留邮箱我传一下


 


 


 



 




 




 

阅读(3312) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

金色の闪光2012-05-16 16:19:35

好吧 c语言也是能写出牛程序的O(∩_∩)O