本文首發於我的個人博客www.colourso.top,歡迎來訪。
原文鏈接:http://www.colourso.top/c-snake-pro0/
原文寫於2019-06-21,本次重新整理。
前提:EasyX
EasyX 是針對 C++ 的圖形庫,可以幫助 C 語言初學者快速上手圖形和游戲編程。
官網鏈接:https://easyx.cn/
制作流程
先開始寫文檔,分析要實現哪些功能,然后對功能進行細分,梳理操作。以下80%的內容都是當時寫貪吃蛇的文檔,后期整理一下發博客記錄。
草稿:想要實現的功能
貪吃蛇游戲的簡單版本實現
- 貪吃蛇吃食物身體加長
- 食物被吃后隨機出現(不能夠出現在蛇的身上)
- 無操作時貪吃蛇自動向蛇頭方向前進
- 上下左右移動位置
- 撞到身體或者牆壁死亡
- 顯示時間與分數
功能梳理
蛇的操作
- 上下左右四個按鍵控制蛇頭的朝向。當按鍵方向與蛇頭方向相同或者相反時無反應。
- 不受操作時蛇自動按照蛇頭方向前行
- 蛇頭吃到食物之后,身體加長
- 蛇頭撞到自身或者牆壁后死亡
- 蛇頭控制方向,其余結點均是復制前一個結點的坐標。
食物的操作
- 初始時刻食物出現在某個固定位置
- 食物被吃掉后隨機出現在其他地方,但是不能夠出現在蛇的身上。
界面顯示
- 分為游戲區與功能區,游戲區展現蛇與食物,功能區展示時間、分數、玩法介紹以及作者信息
- 蛇使用方塊表示,蛇頭與蛇身容易區分。食物使用圓形表示。
- 游戲結束后,顯示“GAME OVER”,然后顯示游戲時長以及游戲分數。
界面大小設置
- 坐標系。這是一個純2D游戲,一般來說坐標都是在最左上角,為了便於計算(個人習慣),將坐標原點放置在左下角,x軸向右,y軸向上。
實際上游戲的坐標都是采用原點在左上角,這里因為我設置了坐標軸的原因導致了后面文字擺放無法放置正確,也是因為這個原因,要重寫了第二版代碼。
- 整體界面大小為:長640px,寬480px。游戲區為 480 * 480的正方形區域,位居界面左側。功能區為 160 * 480的區域,位居界面右側。
- 游戲區全部分為20 * 20 px的小正方形區域,便於蛇與食物的放置即坐標計算。蛇使用方塊,食物使用圓形。采取圖形中心點代表整個圖形的方式,即(10,10)表示一個圖形。為了防止邊界的圖像顏色干擾,設置蛇的方塊中心點到四邊的距離為9px,食物的半徑為8px。
變量結構設置
食物結構體設置
對於食物而言,一次只會出現一個食物,只需要保存它的坐標位置,以及存在狀態即可。
struct Food //食物結構體
{
int x; //橫坐標
int y; //縱坐標
bool exist; //是否存在,1表示存在
};
蛇結構體設置
對於蛇而言,蛇是由一個個節點構成的,每吃一個食物就會增加一個節點。並且所有節點都連接起來,采用鏈表是一個不錯的選擇。
結構體設置模仿自嚴蔚敏老師的數據結構鏈表的設置。定義蛇節點結構體,然后定義蛇的結構體。
本來采用的單向鏈表,但后續處理蛇的移動算法時發現需要從后向前復制節點的操作,於是改成了雙向鏈表。
這里也可以采用數組啦、STL的vector容器啦來處理蛇的結構體。采用鏈表復習復習數據結構。
typedef struct Node //蛇的節點
{
int x; //橫坐標
int y; //縱坐標
struct Node * next; //指向下一個節點的指針
struct Node * pre; //指向前一個節點的指針
}* LinkNode;
struct Snake //蛇的結構體
{
LinkNode head; //指向頭節點的指針
LinkNode tail; //指向尾節點的指針
int direction; //蛇頭方向
int num; //節點數目
};
游戲核心算法
蛇身移動算法
貪吃蛇的移動完全是復制頭部節點,蛇頭控制方向與前進,其他節點均是重復前一個節點的行為。
而在處理蛇移動的時候,先要用后面節點的保存了它的前一個節點的坐標信息。故蛇身移動的算法,是從最后一個節點(tail)向前復制前一個節點的坐標,達成移動效果。
想到這里就把蛇的結構體改為雙向鏈表。便於我的操作。
LinkNode linknode = snake.tail;
while (linknode != snake.head)
{
linknode->x = linknode->pre->x;
linknode->y = linknode->pre->y;
linknode = linknode->pre;
}
食物生成算法
食物生成要具有隨機性,並且要求食物不能夠生成在蛇的身體上。
隨機數函數rand()產生隨機數,rand()函數需要的頭文件是:<stdlib.h>。
rand()會返回一個范圍在0到RAND_MAX(32767)之間的偽隨機數(整數)。
在調用rand()函數之前,可以使用srand()函數設置隨機數種子,如果沒有設置隨機數種子,rand()函數在調用時,自動設計隨機數種子為1。隨機種子相同,每次產生的隨機數也會相同。
srand( usigned int seed)函數用來設置rand()產生隨機數時的隨機數種子。參數seed是整數,通常可以利用time(0)的返回值作為seed。srand()函數需要的頭文件是:<stdlib.h>
例如:生成0-6之間的任意一個隨機數
srand(time(0));
int num = (rand()%7)//模求余
食物不能夠在蛇的身體上,即生成坐標之后遍歷蛇的節點進行匹配,若有對應的就直接重新生成一次。
int x;
int y;
while (1)
{
srand(time(0));
x = (rand() % 24) * 20 + 10;
y = (rand() % 24) * 20 + 10;
//食物位置檢測算法
LinkNode linknode = snake.head;
bool cont = true;
while (linknode != snake.tail->next)//從頭遍歷到尾巴
{
if (linknode->x == x && linknode->y == y)
{
cont = false;
break;
}
linknode = linknode->next;
}
if (cont)
{
break; //如果食物不在蛇的身體上,就break退出循環
}
}
food.x = x;
food.y = y;
food.exist = true;
按鍵處理問題
_khbit()
函數用來檢測是否有鍵盤輸入。如果有按鍵被按下,會返回一個1,否則返回值為0.它是一個非阻塞函數,無論有沒有按鍵被按下,他都會立即返回結果。可以用來做循環的條件判斷是否有按鍵,或者等待輸入。
_getch()
函數作用是從控制台獲取輸入的字符,並返回獲取到的字符值。而且這個函數是阻塞性函數,必須要獲取輸入字符后才會返回。
_kbhit()
與_getch()
都是位於conio.h
頭文件中。(console IO)
int keys = 0;
int key;
while (1)
{
if (keys = _kbhit())
{
key = _getch();
cout << "按鍵的值key:" << key << endl;
}
}
由此可以測的我們按鍵對應的鍵值是多少。
方向鍵↑ 對應值為72。
方向鍵↓ 對應值為80。
方向鍵← 對應值為75。
方向鍵→ 對應值為77。
但是相信執行這一段代碼會發現按一次鍵會有兩個key被打印出來。但是寫switch語句(代碼中的按鍵處理模塊)時卻完全和第一個key值無關。這個問題暫時還沒有解決!路過的大佬歡迎留言~
這個方法我是從某潭某州的公開廣告課上學到的。
其他參考鏈接:C++中_kbhit()函數與_getch()函數
偽流程
int main()
{
初始化資源;
繪制界面;
開始游戲:
while(1)
{
if(!食物存在)
{
刷新食物並顯示
}
if(按鍵)
{
相應操作:蛇頭方向:上下左右
}
向蛇頭方向前進1格。繪制蛇頭
(switch case結構)
消除蛇尾圖像。
judge(蛇頭吃食物)
{
蛇結點+1,尾巴加長並顯示出來
分數增加
食物狀態刷新
}
judge(撞牆or撞自己)
{
死亡
game over
break
}
sleep(200)
}
結束 回收資源;
}
詳細的第一版代碼實現:C++基於EasyX制作貪吃蛇游戲(二)第一版代碼與程序