上一節我們講解了鍵盤消息處理相關的知識。鍵盤加鼠標作爲目前人機交互方式依舊的主流,在講完鍵盤消息處理之後接着講鼠標消息處理,自然是理所當然的。
這一節主要介紹各種鼠標消息的處理方式以及一些相關函數的運用方法,然後用一個小實例來鞏固本節所學。
一,鼠標消息的處理方式
大家都知道,目前市場上主流鼠標規格爲兩個按鍵加上一個滾輪。那麼,我們先列出Windows中這種鼠標設備輸入時的消息:
WM_LBUTTONDBLCLK 雙擊鼠標左鍵消息
WM_LBUTTONDOWN 單擊鼠標左鍵消息
WM_LBUTTONUP 鬆開鼠標左鍵消息
WM_MBUTTONDBLCLK 雙擊鼠標中鍵(滾輪)消息
WM_MBUTTONDOWN 單擊鼠標中鍵(滾輪)消息
WM_MBUTTONUP 鬆開鼠標中鍵(滾輪)消息
WM_RBUTTONDBLCLK 雙擊鼠標右鍵消息
WM_RBUTTONDOWN 單擊鼠標右鍵消息
WM_RBUTTONUP 鬆開鼠標右鍵消息
WM_MOUSEMOVE 鼠標移動消息
WM_MOUSEWHEEL 鼠標滾輪轉動消息
處理鼠標消息的方法與處理鍵盤消息的方法類似,同樣是在消息處理函數中加入要處理的鼠標消息類型,當鼠標消息發生時,輸入的參數“wParam”與“lParam”則儲存了鼠標狀態的相關信息。
下面我們分別來展開講解一下“wParam”與“lParam”參數以及滾輪消息。
<l>Param參數
lParam參數的值可分爲高位字節與低位字節兩個部分,其中高節部分儲存的是鼠標光標所在的X座標值,低位字節部分存儲的則是鼠標光標所在的Y座標值。
我們可以用下面兩個函數來取得鼠標的座標值:
WORD LOWORD(lParam參數); //返回鼠標光標所在的X座標值
WORD HIWORD(lParam參數); //返回鼠標光標所在的Y座標值
這兩個兩個函數所返回的鼠標光標位置的座標是相對於內部窗口左上點座標的。
<2>wParam參數
"wParam"參數的值記錄着鼠標按鍵及鍵盤【Ctrl】鍵與【Shift】鍵的狀態信息,通過下面的這些定義在“WINUSER.H”中的測試標誌與“wParam”參數來檢查上述按鍵的按下狀態。
MK_LBUTTON 按下鼠標右鍵
MK_MBUTTON 按下鼠標中(滾輪)鍵
MK_RBUTTON 按下鼠標右鍵
MK_SHIFT 按下【Shift】鍵
MK_CONTROL 按下【Ctrl】鍵
【例子1】例如某一鼠標消息發生時,要測試鼠標左鍵是否也被按下,程序代碼如下:
if(wParam && MK_LBUTTON)
{
//鼠標左鍵被按下
}
這是利用wParam參數與測試標誌來測試鼠標鍵是否被按下的方法。當按鍵被按下時,條件式“wParam && MK_LBUTTON”所傳回的結果會爲“true”。當然,若消息函數接收到“WM_LBUTTONDOWN”消息,同樣也可以知道鼠標鍵被按下而不必再去額外做這樣的測試。
【例子2】如果要測試鼠標左鍵與【Shift】鍵的按下狀態,那麼程序代碼如下:
- If(wParam && MK_LBUTTON)
- {
- If(wParam && MK_SHIFT)
- {
- //單擊鼠標左鍵
- //按下【Shift】鍵
- }
- else
- {
- //單擊鼠標左鍵
- //未按下【Shift】鍵
- }
- }
- else
- {
- If(wParam && MK_SHIFT)
- {
- //未單擊鼠標左鍵
- //按下【Shift】鍵
- }
- else
- {
- //未單擊鼠標左鍵
- //未按下【Shift】鍵
- }
- }
我們通過這個例子可以清楚,如何利用“wParam”參數與測試標誌來測試鼠標鍵及【Shift】鍵和【Ctrl】鍵是否被按下的方法。
<3>滾輪消息
這裏我們要特別提一下鼠標滾輪轉動消息(WM_MOUSEWHEEL)。當鼠標滾輪轉動消息發生時,“lParam”參數中的值同樣是記錄光標所在的位置的,而“wParam”參數則分爲高位字節與低位字節兩部分,低位字節部分跟前面一樣是儲存鼠標鍵與【Shift】【Ctrl】鍵的狀態信息的,而高位字節部分的值會是“120”或“-120”。“120”表示鼠標滾輪向前轉動,而“-120”則表示向後轉動。
這裏“wParam”高位組值與低位組值所在的函數同樣是HIWORD( )與LOWORD( )。
HIWORD(wParam);//高位組,值爲“120”或“-120”
LOWORD(wParam);//低位組,鼠標鍵及【Shift】和【Ctrl】鍵的狀態信息
二,相關函數的講解
對各種鼠標輸入消息及鼠標狀態信息的獲取方法有了基本認識之後,下面我們將介紹一些遊戲程序中以鼠標來做輸出設備時常用到的函數。
1.獲取窗口外鼠標消息的函數
爲了確保程序可以正確地取得鼠標的輸入消息,需要在必要的時候以下面的函數來設定窗口,以取得鼠標在窗口外所發出的消息。
HWND SetCapture(HWND hWnd) ; //設定獲取窗口外的鼠標消息
如果調用了上面的SetCapture( )函數,並輸入要取得鼠標消息的窗口代號,那麼便可取得鼠標在窗口外所發出的消息。這種方法也適用於多窗口的程序,與SetCapture( )函數相對應的函數爲ReleaseCapture( )函數,用於釋放窗口取得窗口外鼠標消息的函數。
BOOL ReleaseCapture(VOID); //釋放獲取窗口外的鼠標消息
2.設定鼠標光標位置的函數
BOOL SetCursorPos(int X座標,int Y座標); //設定鼠標光標位置
上面這個SetCursorPos()函數中所設定的座標是相對於屏幕左上角的屏幕座標而言。實際上,我們經常需要將這個屏幕座標轉換爲遊戲窗口中的遊戲窗口座標。因此需要用到API中的一個將窗口座標轉換到屏幕座標的函數,即ClientToScreen()。
屏幕座標和窗口座標轉換的函數
BOOL ClientToScreen(HWND hWnd, //屏幕座標轉換爲窗口座標
LPPOINT lpPoint屏幕點座標);
同理,我們得到:
窗口座標轉換爲屏幕座標的函數:
BOOL ScreenToClient( LPPOINT lpPoint窗口點座標 ) //窗口座標轉換爲屏幕座標
3.顯示與隱藏鼠標光標的函數
Int ShowCursor(BOOL true或flase); //隱藏及顯示鼠標光標
其中,true代表顯示光標,false代表隱藏光標。
4.限制鼠標光標移動區域的函數
Windows API中提供的ClipCursor()函數可以用來設置限制鼠標光標的移動區域和解除鼠標光標移動區域的限制。
BOOL ClipCursor(CONST RECT 移動區域矩形); //限制鼠標光標移動區域
BOOL ClipCursor(NOOL); //解除限制
這裏有一個RECT移動區域矩形,我們在MSDN中找出它的聲明:
- typedef struct tagRECT {
- LONG left; //矩形區域右上點X坐
- LONG top; //矩形區域右上點Y座標
- LONG right; //矩形區域左上點X座標
- LONG bottom; //矩形區域左上點Y座標
- } RECT;
5.取得窗口外部區域及內部區域的API函數
我們還需知道取得窗口外部區域及內部區域的API函數。
BOOL GetWindowRect(HWND hWND,LPRECT 矩形結構);//取得窗口外部區域矩形
BOOL GetClientRect(HWND hWnd,LPRECT 矩形結構體); //取得窗口內部區域矩形
這裏需要注意的是,GetWindowRect()返回的座標類型是屏幕座標。
GetClientRect()返回的座標類型是窗口座標。
由於限制鼠標光標移動區域的ClipCursor()函數中輸入的矩形區域必須是屏幕座標,因此如果取得的是窗口內部區域,那麼還必須將窗口座標轉換爲屏幕座標的操作,下面我們以一段程序代碼來說明將鼠標光標限制在窗口內部區域移動的過程:
- RECT rect;
- POINT lt,rb;
- GetClientRect(hWnd,&rect); //取得窗口內部矩形
- //將矩形左上點座標存入lt中
- lt.x = rect.left;
- lt.y = rect.top;
- //將矩形右下座標存入rb中
- rb.x = rect.right;
- rb.y = rect.bottom;
- //將lt和rb的窗口座標轉換爲屏幕座標
- ClientToScreen(hWnd,<);
- ClientToScreen(hWnd,&rb);
- //以屏幕座標重新設定矩形區域
- rect.left = lt.x;
- rect.top = lt.y;
- rect.right = rb.x;
- rect.bottom = rb.y;
- //限制鼠標光標移動區域
- ClipCursor(&rect);
三,在實例中融會貫通
講了這麼多的windows API函數了,也早該到了我們的showtime了,依然,我們通過一個實例來把本節所講的內容融會貫通。
這個實例處理鼠標移動消息使飛機在窗口中移動,並且處理單擊鼠標左鍵消息來讓飛機發射子彈,而且設定了鼠標光標的位置,隱藏了鼠標光標,還有限制了鼠標光標移動的區域。(背景貼圖採用循環背景滾動,其實很簡單,就是每次都把窗口右邊多餘的部分再貼到窗口座標來,以後有機會我會作爲一節筆記具體講)
同樣的,我們貼出詳細註釋好的代碼:
- #include "stdafx.h"
- #include <stdio.h>
- //定義結構體
- struct BULLET //bullet結構體代表飛機子彈
- {
- int x,y; //子彈座標
- bool exist; //子彈是否存在
- };
- //全局變量聲明
- HINSTANCE hInst;
- HBITMAP bg,ship,bullet; //存儲背景圖,飛機圖,子彈圖
- HDC hdc,mdc,bufdc;
- HWND hWnd;
- DWORD tPre,tNow;
- int x,y,nowX,nowY; //x,y代表鼠標光標所在位置,nowX,nowY代表飛機座標,也是貼圖的位置
- int w=0,bcount; //w爲滾動背景所要裁剪的區域寬度,bcount記錄飛機現有子彈數目
- BULLET b[30]; //聲明一個“bullet”類型的數組,用來存儲飛機發出的子彈
- //全局函數聲明
- 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 | CS_DBLCLKS;
- 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);
- }
- //****初始化函數*************************************
- // 1.設定飛機初始位置
- // 2.設定鼠標光標位置及隱藏
- // 3.限制鼠標光標移動區域
- BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
- {
- HBITMAP bmp;
- POINT pt,lt,rb;
- RECT rect;
- 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);
- bmp = CreateCompatibleBitmap(hdc,640,480);
- SelectObject(mdc,bmp);
- bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,648,480,LR_LOADFROMFILE);
- ship = (HBITMAP)LoadImage(NULL,"ship.bmp",IMAGE_BITMAP,100,148,LR_LOADFROMFILE);
- bullet = (HBITMAP)LoadImage(NULL,"bullet.bmp",IMAGE_BITMAP,10,20,LR_LOADFROMFILE);
- //設定鼠標光標的x,y值,並設定飛機貼圖座標的“nowX”和“nowY”的值爲(300,300)
- x = 300;
- y = 300;
- nowX = 300;
- nowY = 300;
- //設定光標位置
- pt.x = 300;
- pt.y = 300;
- ClientToScreen(hWnd,&pt);
- SetCursorPos(pt.x,pt.y);
- ShowCursor(false); //隱藏鼠標光標
- //限制鼠標光標移動區域
- GetClientRect(hWnd,&rect); //取得窗口內部矩形
- //將矩形左上點座標存入lt中
- lt.x = rect.left;
- lt.y = rect.top;
- //將矩形右下座標存入rb中
- rb.x = rect.right;
- rb.y = rect.bottom;
- //將lt和rb的窗口座標轉換爲屏幕座標
- ClientToScreen(hWnd,<);
- ClientToScreen(hWnd,&rb);
- //以屏幕座標重新設定矩形區域
- rect.left = lt.x;
- rect.top = lt.y;
- rect.right = rb.x;
- rect.bottom = rb.y;
- //限制鼠標光標移動區域
- ClipCursor(&rect);
- MyPaint(hdc);
- return TRUE;
- }
- //****自定義繪圖函數*********************************
- // 1.設定飛機座標並進行貼圖
- // 2.設定所有子彈座標並進行貼圖
- // 3.顯示真正的鼠標光標所在座標
- void MyPaint(HDC hdc)
- {
- char str[20] = "";
- int i;
- //貼上背景圖
- SelectObject(bufdc,bg);
- BitBlt(mdc,0,0,w,480,bufdc,640-w,0,SRCCOPY);
- BitBlt(mdc,w,0,640-w,480,bufdc,0,0,SRCCOPY);
- //計算飛機的貼圖座標,設定每次進行飛機貼圖時,其貼圖座標(nowX,nowY)會以10個單位慢慢向鼠標光標所在的目的點(x,y)接近,直到兩個座標相同爲止
- if(nowX < x)
- {
- nowX += 10;
- if(nowX > x)
- nowX = x;
- }
- else
- {
- nowX -=10;
- if(nowX < x)
- nowX = x;
- }
- if(nowY < y)
- {
- nowY += 10;
- if(nowY > y)
- nowY = y;
- }
- else
- {
- nowY -= 10;
- if(nowY < y)
- nowY = y;
- }
- //貼上飛機圖
- SelectObject(bufdc,ship);
- BitBlt(mdc,nowX,nowY,100,74,bufdc,0,74,SRCAND);
- BitBlt(mdc,nowX,nowY,100,74,bufdc,0,0,SRCPAINT);
- //子彈的貼圖,先判斷子彈數目“bcount”的值是否爲“0”。若不爲0,則對子彈數組中各個還存在的子彈按照其所在的座標(b[i].x,b[i].y)循環進行貼圖操作
- SelectObject(bufdc,bullet);
- if(bcount!=0)
- for(i=0;i<30;i++)
- if(b[i].exist)
- {
- //貼上子彈圖
- BitBlt(mdc,b[i].x,b[i].y,10,10,bufdc,0,10,SRCAND);
- BitBlt(mdc,b[i].x,b[i].y,10,10,bufdc,0,0,SRCPAINT);
- //設置下一個子彈的座標。子彈是又右向左發射的,因此,每次其X軸上的座標值遞減10個單位,這樣貼圖會產生往左移動的效果。而如果子彈下次的座標已超出窗口的可見範圍(h[i].x<0),那麼子彈設爲不存在,並將子彈總數bcount變量值減1.
- b[i].x -= 10;
- if(b[i].x < 0)
- {
- bcount--;
- b[i].exist = false;
- }
- }
- //顯示鼠標座標
- sprintf(str,"鼠標X座標爲%d ",x);
- TextOut(mdc,0,0,str,strlen(str));
- sprintf(str,"鼠標Y座標爲%d ",y);
- TextOut(mdc,0,20,str,strlen(str));
- BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
- tPre = GetTickCount();
- w += 10;
- if(w==640)
- w = 0;
- }
- //****消息處理函數***********************************
- // 1.處理WM_LBUTTONDOWN消息發射子彈
- // 2.處理WM_MOUSEMOVE消息設定飛機貼圖座標
- // 3.在窗口結束時恢復鼠標移動區域
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int i;
- switch (message)
- {
- case WM_KEYDOWN: //按鍵按下消息
- if(wParam==VK_ESCAPE) //按下【Esc】鍵
- PostQuitMessage(0);
- break;
- case WM_LBUTTONDOWN: //單擊鼠標左鍵消息
- for(i=0;i<30;i++)
- {
- if(!b[i].exist)
- {
- b[i].x = nowX; //子彈x座標
- b[i].y = nowY + 30; //子彈y座標
- b[i].exist = true;
- bcount++; //累加子彈數目
- break;
- }
- }
- case WM_MOUSEMOVE:
- x = LOWORD(lParam); //取得鼠標X座標
- if(x > 530) //設置臨界座標
- x = 530;
- else if(x < 0)
- x = 0;
- y = HIWORD(lParam); //取得鼠標y座標
- if(y > 380)
- y = 380;
- else if(y < 0)
- y = 0;
- break;
- case WM_DESTROY: //窗口結束消息
- ClipCursor(NULL); //恢復鼠標移動區域
- DeleteDC(mdc);
- DeleteDC(bufdc);
- DeleteObject(bg);
- DeleteObject(bullet);
- DeleteObject(ship);
- ReleaseDC(hWnd,hdc);
- PostQuitMessage(0);
- break;
- default: //其他消息
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
下面是這個例子的效果圖:
我們移動鼠標,小飛機會跟着鼠標的移動而移動;點擊鼠標,小飛機就會發射出綠色的子彈來;按下Esc鍵,這個小遊戲就退出了。
當然,這個小遊戲還有些的不足,比如小飛機對鼠標的跟隨有小小的延遲,也沒有進行WndProc函數中case分支的拆分(在這裏感謝yao050421103的提醒)。
關於這個小遊戲的改進,是以後我們需要去做的。比如後面我們會找機會用DirectX中的DirectInput函數(也是處理輸出消息的函數)來寫一下這個小遊戲的升級版,無論是畫面上還是實現效果上都將進行升級。
在之前的筆記裏有朋友(感謝pxg789的提醒)提到,用MFC和ATL共享的新類CImage進行貼圖會更加簡單和先進,在這裏說明一下,在後面的筆記裏面會專門花一節來講CImage類,目前和之前的筆記還是採用傳統的GDI進行貼圖操作。
筆記十三到這裏就結束了。
本節筆記的源代碼請點擊這裏下載: 【Visual C++】Code_Note_13
感謝一直支持【Visual C++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的專欄,我一有時間就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習,共同進步。
大家看過後覺得值得一看的話,可以頂一下這篇文章,讓更多的朋友有機會看到它。
如果文章中有什麼疏漏的地方,也請大家指正。也希望大家可以多留言來和我探討編程相關的問題。
最後,謝謝你們一直的支持~~~
The end.
原文地址:http://blog.csdn.net/zhmxy555/article/details/7405479