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