本系列文章由zhmxy555編寫,轉載請注明出處。 http://blog.csdn.net/zhmxy555/article/details/7390624
作者:毛星云 郵箱: happylifemxy@qq.com 歡迎郵件交流編程心得
相信大家都熟悉《仙劍奇俠傳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則儲存按鍵的相關狀態信息,因此,如果程序要對使用者的鍵盤輸入操作進行處理,那么消息處理函數的內容可以定義如下:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN: //按下鍵盤消息
switch (wParam)
{
case VK_ESCAPE: //按下【Esc】鍵
//定義消息處理程序
break;
case VK_UP: //按下【↑】鍵
//定義消息處理程序
break;
case WM_DESTROY: //窗口結束消息
PostQuitMessage(0);
break;
default: //其他消息
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
針對這個消息處理函數中鍵盤消息處理的程序關鍵說明如下:
<1>第5行:定義處理“WM_KEYDOWN”消息。
<2>第6行:以“switch”敘述判斷“wParam”的值來得知哪個按鍵被按下,并運行對應“case”中的按鍵消息處理程序。
同樣的,我們用一個實例來讓大家熟悉和實踐一下本節的知識。
這個范例會讓玩家以【↑】【↓】【←】【→】鍵進行輸入,控制畫面中人物的移動,這里使用了人物在4個不同方向上走動的連續圖案




廢話也不多說了,直接上詳細注釋的代碼:
#include "stdafx.h"
#include <stdio.h>
//全局變量聲明
HINSTANCE hInst;
HBITMAP girl[4],bg;
HDC hdc,mdc,bufdc;
HWND hWnd;
DWORD tPre,tNow;
int num,dir,x,y; //x,y變量為人物貼圖坐標,dir為人物移動方向,這里我們中以0,1,2,3代表人物上,下,左,右方向上的移動:num為連續貼圖中的小圖編號
//全局函數聲明
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void MyPaint(HDC hdc);
//****WinMain函數,程序入口點函數***********************
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
MyRegisterClass(hInstance);
//初始化
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
GetMessage(&msg,NULL,NULL,NULL); //初始化msg
//消息循環
while( msg.message!=WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
tNow = GetTickCount();
if(tNow-tPre >= 40)
MyPaint(hdc);
}
}
return msg.wParam;
}
//****設計一個窗口類,類似填空題,使用窗口結構體*******************
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "canvas";
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//****初始化函數*************************************
// 加載位圖并設定各種初始值
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HBITMAP bmp;
hInst = hInstance;
hWnd = CreateWindow("canvas", "繪圖窗口" , WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
MoveWindow(hWnd,10,10,640,480,true);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hdc = GetDC(hWnd);
mdc = CreateCompatibleDC(hdc);
bufdc = CreateCompatibleDC(hdc);
//建立空的位圖并置入mdc中
bmp = CreateCompatibleBitmap(hdc,640,480);
SelectObject(mdc,bmp);
//設定人物貼圖初始位置和移動方向
x = 300;
y = 250;
dir = 0;
num = 0;
//載入各連續移動位圖及背景圖
girl[0] = (HBITMAP)LoadImage(NULL,"girl0.bmp",IMAGE_BITMAP,440,148,LR_LOADFROMFILE);
girl[1] = (HBITMAP)LoadImage(NULL,"girl1.bmp",IMAGE_BITMAP,424,154,LR_LOADFROMFILE);
girl[2] = (HBITMAP)LoadImage(NULL,"girl2.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
girl[3] = (HBITMAP)LoadImage(NULL,"girl3.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
MyPaint(hdc);
return TRUE;
}
//****自定義繪圖函數*********************************
// 人物貼圖坐標修正及窗口貼圖
void MyPaint(HDC hdc)
{
int w,h;
//先在mdc中貼上背景圖
SelectObject(bufdc,bg);
BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);
//按照目前的移動方向取出對應人物的連續走動圖,并確定截取人物圖的寬度與高度
SelectObject(bufdc,girl[dir]);
switch(dir)
{
case 0:
w = 55;
h = 74;
break;
case 1:
w = 53;
h = 77;
break;
case 2:
w = 60;
h = 74;
break;
case 3:
w = 60;
h = 74;
break;
}
//按照目前的X,Y的值在mdc上進行透明貼圖,然后顯示在窗口畫面上
BitBlt(mdc,x,y,w,h,bufdc,num*w,h,SRCAND);
BitBlt(mdc,x,y,w,h,bufdc,num*w,0,SRCPAINT);
BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
tPre = GetTickCount(); //記錄此次繪圖時間
num++;
if(num == 8)
num = 0;
}
//****消息處理函數***********************************
// 1.按下【Esc】鍵結束程序
// 2.按下方向鍵重設貼圖坐標
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN: //按下鍵盤消息
//判斷按鍵的虛擬鍵碼
switch (wParam)
{
case VK_ESCAPE: //按下【Esc】鍵
PostQuitMessage( 0 ); //結束程序
break;
case VK_UP: //按下【↑】鍵
//先按照目前的移動方向來進行貼圖坐標修正,并加入人物往上移動的量(每次按下一次按鍵移動10個單位),來決定人物貼圖坐標的X與Y值,接著判斷坐標是否超出窗口區域,若有則再次修正
switch(dir)
{
case 0:
y -= 10;
break;
case 1:
x -= 1;
y -= 8;
break;
case 2:
x += 2;
y -= 10;
break;
case 3:
x += 2;
y -= 10;
break;
}
if(y < 0)
y = 0;
dir = 0;
break;
case VK_DOWN: //按下【↓】鍵
switch(dir)
{
case 0:
x += 1;
y += 8;
break;
case 1:
y += 10;
break;
case 2:
x += 3;
y += 6;
break;
case 3:
x += 3;
y += 6;
break;
}
if(y > 375)
y = 375;
dir = 1;
break;
case VK_LEFT: //按下【←】鍵
switch(dir)
{
case 0:
x -= 12;
break;
case 1:
x -= 13;
y += 4;
break;
case 2:
x -= 10;
break;
case 3:
x -= 10;
break;
}
if(x < 0)
x = 0;
dir = 2;
break;
case VK_RIGHT: //按下【→】鍵
switch(dir)
{
case 0:
x += 8;
break;
case 1:
x += 7;
y += 4;
break;
case 2:
x += 10;
break;
case 3:
x += 10;
break;
}
if(x > 575)
x = 575;
dir = 3;
break;
}
break;
case WM_DESTROY: //窗口結束消息
int i;
DeleteDC(mdc);
DeleteDC(bufdc);
for(i=0;i<4;i++)
DeleteObject(girl[i]);
DeleteObject(bg);
ReleaseDC(hWnd,hdc);
PostQuitMessage(0);
break;
default: //其他消息
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
程序運行結果如下圖,我們可以用鍵盤操作這個小人的上下左右移動,用Esc退出:




這樣,一個簡單的小游戲就完成了。
我們也可以通過在消息處理函數中取得按鍵虛擬鍵碼的方式,很簡單地對鍵盤輸入操作進行處理。
筆記十二到這里就結束了。
本節源代碼請點擊這里下載: 【Visual C++】Code_Note_12
感謝一直支持【Visual C++】游戲開發筆記系列專欄的朋友們,也請大家繼續關注我的博客,我一有空就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通游戲開發的路還很長很長,非常希望能和大家一起交流,共同學習和進步。
大家看過后覺得有啟發的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討編程相關的問題。最后,謝謝大家一直的支持~~~
The end