剛學windows GDI編程,對用代碼實現繪圖興奮不已,於是便先參考網上的一份代碼寫了個簡單的貪喫蛇練手。
程序是以消息處理回調函數爲核心,響應定時時間和按鍵消息,定時時間到則繪製蛇的移動已經死亡判斷,而按鍵消息響應裏進行蛇的移動操作以及遊戲控制。
先看主函數:
//------------ 以下初始化窗口類----------------
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInst,LPSTR lpszCmdLine,int nCmdShow)
{
MSG Msg ;
WNDCLASS wndclass ;
char lpszClassName[] = "Snake"; //窗口類名
char lpszTitle[]= "Snake"; //窗口標題名
//窗口類的定義
wndclass.style = 0; //窗口類型爲缺省類型
wndclass.lpfnWndProc = WndProc ; //窗口處理函數爲WndProc
wndclass.cbClsExtra = 0 ; //窗口類無擴展
wndclass.cbWndExtra = 0 ; //窗口實例無擴展
wndclass.hInstance = hInstance ; //當前實例句柄
wndclass.hIcon = LoadIcon( NULL, "ICON_SNAKE") ;
//窗口的最小化圖標爲缺省圖標
wndclass.hCursor = LoadCursor( NULL,IDC_ARROW); //窗口採用箭頭光標
wndclass.hbrBackground =(HBRUSH) GetStockObject(BLACK_BRUSH); //窗口背景爲白色
wndclass.lpszMenuName = NULL ; //窗口中無菜單
wndclass.lpszClassName = lpszClassName; //窗口類名爲"窗口示例"
//--------------- 以下進行窗口類的註冊 -----------------
if( !RegisterClass( &wndclass)) //如果註冊失敗則發出警告聲音
{
//MessageBeep(0) ;
return FALSE ;
}
//創建窗口
hwnd=CreateWindow(
lpszClassName, //窗口類名
lpszTitle, //窗口實例的標題名
WS_OVERLAPPEDWINDOW, //窗口的風格
CW_USEDEFAULT,
CW_USEDEFAULT, //窗口左上角座標爲缺省值
600,
500, //窗口的高和寬爲缺省值
NULL, //此窗口無父窗口
NULL, //此窗口無主菜單
hInstance, //創建此窗口的應用程序的當前句柄
NULL) ; //不使用該值
ShowWindow( hwnd, nCmdShow); //顯示窗口
UpdateWindow(hwnd); //繪製用戶區
while( GetMessage(&Msg, NULL, 0, 0)) //消息循環
{
TranslateMessage( &Msg) ;
DispatchMessage( &Msg) ;
}
return Msg.wParam; //消息循環結束即程序終止時將信息返回系統
}
主要是新建、初始化、註冊窗口類並創建、顯示窗口,然後進入消息循環。
移步消息處理回調函數:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
HFONT hF_black; //定義兩種字體句柄
PAINTSTRUCT ps;
TEXTMETRIC tm; //定義一個TEXTMETRIC結構,用以記錄字體信息
char lpsz_0[]="操作規則:";
char lpsz_1[]="輸入'f'開始遊戲,輸入'q'停止遊戲,輸入'p'或空格鍵暫停遊戲";
char lpsz_2[]="上--W 下--S 左--A 右--D, 或者使用方向鍵, 遊戲中不區分大小寫";
char lpsz_3[]="規則:";
char lpsz_4[]="1. 剛開始向右運動;";
char lpsz_5[]="2. 每次只能向左轉或向右轉;";
char lpsz_6[]="3. 每喫到4個物品就會出現一個“紅心";
char lpsz_7[]="4. 每喫完5個進入下一關, 每個基礎分值+1, 分值加倍;";
char lpsz_8[]="5. 碰到邊界或者與自身相交或者蛇佈滿整個整個畫面則遊戲結束!";
int X=0,Y=MOVESTARTY+MOVEHEITH;
// SIZE size; //定義一個SIZE類型的結構
//GetTextExtentPoint32(hdc,lpsz_2,strlen(lpsz_2),&size); //獲取字符串的寬度
switch (message)
{
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
hF_black=CreateFont //創建自定義字體
(
15, //字體的高度
0, //由系統根據高寬比選取字體最佳寬度值
0, //文本的傾斜度爲0,表示水平
0, //字體的傾斜度爲0
FW_NORMAL, //字體的粗度,FW_HEAVY爲最粗
0, //非斜體字
0, //無下劃線
0, //無刪除線
GB2312_CHARSET, //表示所用的字符集爲ANSI_CHARSET
OUT_DEFAULT_PRECIS, //輸出精度爲缺省精度
CLIP_DEFAULT_PRECIS, //剪裁精度爲缺省精度
DEFAULT_QUALITY, //輸出質量爲缺省值
DEFAULT_PITCH|FF_DONTCARE,//字間距和字體系列使用缺省值
"粗體字" //字體名稱
);
SetTextColor(hdc,RGB(0,255,0)); //設置文本顏色爲綠色
SetBkColor(hdc,RGB(0,0,0));
SelectObject(hdc,hF_black); //將自定義字體選入設備環境
GetTextMetrics(hdc,&tm); //獲取字體的信息,並寫入tm結構中
TextOut(hdc,X,Y,lpsz_0,strlen(lpsz_0)); //使用當前字體輸出文本
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_1,strlen(lpsz_1));
Y=Y+tm.tmHeight+tm.tmExternalLeading;//計算換行時下一行文本的輸出座標
TextOut(hdc,X,Y,lpsz_2,strlen(lpsz_2));//使用當前字體輸出文本
//換行繼續輸出文本,計算新行的起始Y座標位置
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_3,strlen(lpsz_3));
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_4,strlen(lpsz_4));
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_5,strlen(lpsz_5)); //輸出文本
//在該行繼續輸出文本
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_6,strlen(lpsz_6));//以當前字體輸出文本
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_7,strlen(lpsz_7));//以當前字體輸出文本
Y=Y+tm.tmHeight+tm.tmExternalLeading;
TextOut(hdc,X,Y,lpsz_8,strlen(lpsz_8));//以當前字體輸出文本
EndPaint(hwnd,&ps);
DeleteObject(hF_black); //刪除自定義字體句柄
break;
case WM_TIMER:
if (wParam==ID_TIMER)
{
moveDirect(*sSnk);
}
else if(wParam==ID_TIMEOVER){
f_start=0;
KillTimer(hwnd,ID_TIMEOVER);
delete sSnk;
}
break;
case WM_CHAR:
switch (wParam)
{
case 'f':
case 'F':
if(!f_start){
f_start=1;
sSnk=new Snake;
SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
}
break;
case 'p':
case 'P':
if(!f_start)break;
f_pause=f_pause==0?1:0;
if(f_pause){
KillTimer(hwnd,ID_TIMER);
}
else{
SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
}
break;
case 'w':
case 'W':
if(!f_start||f_pause)break;
sSnk->direct=0;
moveDirect(*sSnk);
break;
case 'd':
case 'D':
if(!f_start||f_pause)break;
sSnk->direct=1;
moveDirect(*sSnk);
break;
case 's':
case 'S':
if(!f_start||f_pause)break;
sSnk->direct=2;
moveDirect(*sSnk);
break;
case 'a':
case 'A':
if(!f_start||f_pause)break;
sSnk->direct=3;
moveDirect(*sSnk);
break;
case 'q':
case 'Q':
PostQuitMessage(0);
break;
case ' ':
if(!f_start)break;
f_pause=f_pause==0?1:0;
if(f_pause){
KillTimer(hwnd,ID_TIMER);
}
else{
SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
}
break;
default:
break;
}
case WM_KEYDOWN:
switch (wParam)
{
case VK_UP:
if(!f_start||f_pause)break;
sSnk->direct=0;
moveDirect(*sSnk);
break;
case VK_RIGHT:
if(!f_start||f_pause)break;
sSnk->direct=1;
moveDirect(*sSnk);
break;
case VK_DOWN:
if(!f_start||f_pause)break;
sSnk->direct=2;
moveDirect(*sSnk);
break;
case VK_LEFT:
if(!f_start||f_pause)break;
sSnk->direct=3;
moveDirect(*sSnk);
break;
default:
break;
}
break;//這個地方很容易忽略,導致一有其他消息程序就EXIT;
case WM_DESTROY:
PostQuitMessage(0); //調用PostQuitMessage發出WM_QUIT消息
break;
default: //缺省時採用系統消息缺省處理函數
return DefWindowProc(hwnd,message,wParam,lParam);
}
return (0);
}
其主要對窗口重繪、定時、按鍵消息三大類消息進行處理。
重繪消息處理是爲了能在窗體被遮擋等情況下進行重新繪製。
定時消息裏對貪喫蛇進行移動和遊戲邏輯判斷,比如是否觸及邊界game over或者喫到東西加分或者進入下一關。
按鍵消息分爲遊戲控制鍵,如f鍵開始遊戲,p鍵暫停,和玩家操控鍵,如上下左右方向鍵和W\S\D\A方向鍵。
對蛇的移動和移動後的狀態判斷處理打包成一個函數:moveDirect。
下面看下Snake的類:
class Snake
{
public:
typedef struct SnakeNode
{
int x;
int y;
}SnakeNode;
typedef struct SnakeFood
{
int x;
int y;
int special;
}SnakeFood;
SnakeNode data[100];
int curr; //
int score, tot, per, level; //score表示最終的得分, tot表示總共喫的物品, per表示現在每個物品的分數
int direct; //方向0,2 UP/DOWN 3,4 LEFT/RIGHT,順時針方向
int speed;
int nGameState;
SnakeFood food;
Snake(); //初始化
virtual ~Snake();
void MoveSnake(); //前進一步
void DrawSnake(); //畫出蛇的形狀
void UnDrawSnake(); //重新畫矩形進行覆蓋
void DrawFood(); //畫食物
void TurnSnake(); //蛇頭蛇尾交換
bool RandomFood(); //隨機生成一個食物,若成功則返回TRUE,否則即蛇佔滿整個畫面則返回FALSE
void EatFood(); //當前如果可喫,則進行相應的操作:累計等分,隨機生成食物,增大長度
bool GameOver(); //蛇撞死了 或者 通關??:幾乎不可能啊~~
};
蛇的身體用鏈表表示,每個節點的結構體爲SnakeNode,包含座標,不過這裏的座標是指遊戲地圖中的座標,並不是完全對應的像素,遊戲地圖中的每個座標對應一個方塊。其餘的成員函數和成員變量代碼中有註釋,這裏不再複述。
遊戲截圖如下:
代碼下載:https://download.csdn.net/download/atp1992/9897413
代碼等更多信息也可以參見原博客:http://www.straka.cn/blog/snake-game-by-windows-gdi/