我的家園

          我的家園

          本系列文章由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





          作者:zhmxy555 發表于2012-3-24 18:30:22 原文鏈接
          閱讀:14162 評論:62 查看評論

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 连州市| 怀集县| 金华市| 赞皇县| 盐源县| 开鲁县| 噶尔县| 龙江县| 雷山县| 奎屯市| 屯留县| 建湖县| 旬阳县| 安多县| 石景山区| 金阳县| 彝良县| 溆浦县| 孝感市| 商丘市| 土默特左旗| 保康县| 衡山县| 全椒县| 太谷县| 二手房| 嘉鱼县| 包头市| 汝城县| 乳山市| 方山县| 阳西县| 武宁县| 巫山县| 建昌县| 龙海市| 海南省| 板桥市| 泽州县| 青冈县| 咸宁市|