作爲一款經典遊戲,貪喫蛇相信很多朋友都玩過,規則簡單易上手。但您有沒有想過自己製作一款貪喫蛇遊戲呢?鄙人最近有意學習MFC,所以先拿貪喫蛇練了一下手。接下來就介紹一下這款最簡陋的貪喫蛇的開發歷程。
首先是開發環境的搭建,我的選擇是visual studio2013,在微軟官網上下載。安裝好以後,打開vs13,選擇菜單欄中的文件 ->新建 ->項目,然後在彈出列表中選擇 visual c++下的MFC應用程序,輸入自己的工程名稱,我的是自制貪喫蛇,點擊下一步,選擇 單文檔和MFC標準,然後直接點擊完成,至此我們就生成了一個MFC空框架。但這個空框架什麼也幹不了,如果我們想在它的基礎上構建一款遊戲,那麼自然需要我們手動DIY點什麼,所以接下來,是真正的步入正軌:
1)首先找到【自制貪喫蛇】的資源管理器,在頭文件裏面,找到自制貪喫蛇view.h的視圖類,在C自制貪喫蛇view類體聲明中,添加幾個private變量,如圖所示:
源代碼是:
private:
int exist; //用它來記錄蛇是否還活着
bool flag; //用它來記錄地圖中的食物是否被喫掉,是則重新生成食物
int head; //它表示蛇頭位置
int tail; //它表示蛇尾位置
int trend; //它表示當前蛇的走向
int map_arr[61 * 61]; //這是蛇所能活動的整個地圖
const int ROW = 61; //它表示地圖的寬度
2)接着在源文件中,打開自制貪喫蛇view.cpp文件,在裏面找到C自制貪喫蛇view類的構造函數的定義,最後在函數體中添加這幾行代碼:
for (int i = 0; i < 61 * 61; i++) map_arr[i] = 0; //先把地圖清空
score = 0;
flag = false;
exist = 1;
head = 62; //遊戲開始,我們先把蛇放在左上角的位置
tail = 62; //同上
trend = ROW; //開始時蛇的默認走向是向下走
map_arr[head] = 1;
3)貪喫蛇作爲一款單機遊戲,它是需要玩家與機器交互的,既然是交互,那麼程序就需要接收並處理外部消息,比如鼠標按下、鍵盤被按下等,在這裏我們只需要得到鍵盤被按下的消息就行了,所以我們在框架中增加一個消息相應函數OnChar,用它來處理鍵盤被按下時的消息,具體做法如下:在菜單欄中點擊工程->類嚮導,在彈出的對話框中的類名一欄中選擇C自制貪喫蛇view,然後在消息欄之下找到WM_CHAR,如圖所示:
再然後雙擊它,點擊編輯代碼,最後我們在新生成的OnChar函數體中添加如下代碼:
switch (nChar)
{
case 119: //按下w表示向上走
if (trend == ROW) break;
trend = -ROW;
break;
case 97: //a向左走
if (trend == 1) break;
trend = -1;
break;
case 115: //s向下走
if (trend == -ROW) break;
trend = ROW;
break;
case 100: //d向右走
if (trend == -1) break;
trend = 1;
break;
}
4)我最早接觸貪喫蛇遊戲,是在我四叔的諾基亞手機上,借完他的手機時,我想和他開個玩笑:衝擊貪喫蛇的榜單top1,然後把玩家ID改成【我是大傻逼】。。。記得當時我使用了N中策略:先是使用快蛇得分王,無奈咱反應跟不上,然後又用慢蛇蝸牛爬,又沒那耐心,所以最後未能如願(笑)。但快蛇與慢蛇,它們的速度是怎麼調節的呢?好吧,在遊戲設置裏面。但我想您肯定明白我問的不是這個,所以,蛇的速度在內部代碼層裏,究竟是在怎麼調節的呢??有一種方法是使用定時器策略:在微軟下,通過user32.dll中的SetTimer()函數告訴系統一個時間間隔,然後系統每隔一段時間,就會觸發我們的程序一次,既相當於告訴我們的應用程序中的時間相應函數:“嗨,又輪到你了,該幹嘛幹嘛吧”。。。具體的做法如下,首先,就像第三步驟中創建OnChar函數一樣:打開項目中的類嚮導,在消息欄下找到WM_CREATE,然後雙擊,點擊編輯代碼,在新生成的OnCreate函數體中添加如下一行代碼:
SetTimer(1, 130, NULL);
這裏我們主要注意第二個參數,就是那個130,它就是我們設置的時間間隔,單位是毫秒。這裏的意思也就是說,每隔0.13秒,我們的貪喫蛇遊戲會更新一次。
5)設置了時間間隔,那麼我們的MFC程序就要捕獲並處理這個時間間隔,就像我們創建OnChar函數一樣,在類嚮導裏面創建一個消息響應函數OnTimer(),然後在裏面寫上我們自己的代碼。所以接下來纔是整個程序的重點:如何讓我們的小蛇走位、轉彎、喫東西、變長變粗。。。可以說它纔是這個MFC程序的靈魂所在。關於貪喫蛇,每個程序猿都可能有自己的一套實現算法。但我個人喜歡質樸簡潔,不想把本來很簡單的遊戲規則用太複雜的算法實現,比如又是結構體又是類什麼的,這款MFC的簡單不費力,纔是我坐在這裏寫這篇博客的主要原因。但接下來的代碼,可能讀者朋友閱讀起來有些困難,所以我儘量多添加註釋,以方便您理解:
srand((unsigned)time(NULL));
int newhead1 = head , newhead2=head;
newhead1 += trend;
newhead2 += (trend * 2); //蛇前進使用的是二連進方式
if (!exist) goto gameover;
if (newhead2 < 0 || newhead2 >= 3700 || (trend*trend < 2 && newhead2 / ROW != head / ROW) || map_arr[newhead2] == 1)
exist = 0; //檢測蛇是否撞到牆壁或自己,是的話蛇死
else
{
/***********檢測蛇是否喫到食物,**********/
if (-1 == map_arr[newhead2])
{
map_arr[newhead1] = 1;
map_arr[newhead2] = 1;
head = newhead2;
score++;
flag = false;
}
/***********是則加分並更新蛇頭位置*******/
else
{
map_arr[newhead1] = 1;
map_arr[newhead2] = 1;
head = newhead2; //以上三行代碼用於更新蛇頭位置
int temp = (map_arr[tail - ROW] > 0)*(tail - ROW) + (map_arr[tail + 1] > 0)*(tail + 1) +
(map_arr[tail + ROW] > 0)*(tail + ROW) + (map_arr[tail - 1] > 0)*(tail - 1) - tail;
map_arr[tail] = 0;
map_arr[tail + temp] = 0;
tail = tail + temp * 2; //以上代碼用於更新蛇尾位置
}
if (false == flag) //檢測食物是否被喫到,是的話隨機生成一個新的食物
{
flag = true;
int i, j;
crefood:
i = (rand() % 30) * 2 + 1;
j = (rand() % 30) * 2 + 1;
if (map_arr[i*ROW + j] > 0) goto crefood;
else map_arr[i*ROW + j] = -1;
}
}
gameover:
OnDraw((CDC*)this); //調用該函數繪畫新局面
對了,將上面這些代碼插入到OnTimer函數之前,您需要先在C自制貪喫蛇view.cpp文件的前面,加上一句【#include <time.h>】,因爲裏面要用到隨機生成函數。
6)遊戲的主邏輯都已經完成,但現在只有電腦能理解我們遊戲的狀態。如果我們玩家想要實時瞭解蛇的動態該怎麼辦?這就需要在視圖類中繪製遊戲了。一般的繪製,都是先做好N個素材圖片,然後保存在N個path下,最後在需要的時候露下臉。但我說過我是個簡單的傢伙,所以這款貪喫蛇,不光算法簡單,連外觀界面也醜陋到家了【自己不會美工,又懶得去網絡上摳圖,將就湊合吧】:蛇身子我用的是大寫字母O,食物用的是大寫F,空地用的是空格,兩面牆壁用的是簡單的兩條黑線。。。但麻雀雖醜,五臟俱全,讀者朋友能夠通過這個遊戲瞭解這個程序的真諦,纔是我感到欣慰的。閒言打住,正文開始:我把所有的繪製代碼都放在一個OnDraw函數中了,這個函數不需要我們新建,在一個MFC程序生成時,vs在view類中會自動幫我們創建一個OnDraw函數,只需在函數體的適當位置添加如下代碼即可:
CClientDC dc(this);
CString nul(" ");
CString snake("O");
CString food("F");
dc.MoveTo(450, 0);
dc.LineTo(450, 450);
dc.MoveTo(0, 451); //如果這裏是450,因爲相同區域重繪的緣故,牆壁會忽隱忽現
dc.LineTo(451, 451);
for (int y = 0; y < 30;y++)
for (int x = 0; x < 30; x++)
{
if (1 == map_arr[(y * 2 + 1)*ROW + (x * 2 + 1)]) //繪畫蛇身子
dc.TextOut(x * 15, y * 15, snake);
else if (-1 == map_arr[(y * 2 + 1)*ROW + (x * 2 + 1)]) //繪畫食物
dc.TextOut(x * 15, y * 15, food);
else dc.TextOut(x * 15, y * 15, nul); //繪畫空地
}
wchar_t sco_str[30];
if (!exist) swprintf_s(sco_str, L"遊戲結束,您分數是:%d", score);
else swprintf_s(sco_str, L"您已得了%d分", score);
dc.TextOutW(451, 0, sco_str);
7)最後的最後,讓我們來看一下游戲效果: