【Visual C 】遊戲開發筆記十二 遊戲輸入訊息處理 一 鍵盤訊息處理
淺墨歷時一年為遊戲程式設計愛好者鍛造的著作:《逐夢旅程:Windows遊戲程式設計之從零開始》如果你喜歡淺墨寫的【Visual C++】遊戲開發系列部落格文章,那麼你一定會愛上這本書。這是淺墨專門為熱愛遊戲程式設計的朋友們寫的入門級遊戲程式設計寶典。噹噹網|京東商城|亞馬遜------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
相信大家都熟悉《仙劍奇俠傳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++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的部落格,我一有空就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習和進步。
大家看過後覺得有啟發的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討程式設計相關的問題。最後,謝謝大家一直的支援~~~
The end
淺墨歷時一年為遊戲程式設計愛好者鍛造的著作:《逐夢旅程:Windows遊戲程式設計之從零開始》如果你喜歡淺墨寫的【Visual C++】遊戲開發系列部落格文章,那麼你一定會愛上這本書。這是淺墨專門為熱愛遊戲程式設計的朋友們寫的入門級遊戲程式設計寶典。噹噹網|京東商城|亞馬遜------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------