无知就是无知,谁也没由权力相信它能够衍生出任何东西。------------弗洛伊德
贪吃蛇是我们小时候经常玩的一款经典趣味小游戏,作为技术人员,看着这款游戏,我们内心也经常会有自己实现一款这样小游戏的想法:
开发工具:visual studio 2019
开发组件:easyx
一、游戏思路
- 构建一个窗口画布,大小为800*600
- 定义蛇对象,蛇有长度、运动方向、下一运动点、颜色属性。
- 定义食物对象,食物即为一个block
- 画布中首先初始化蛇和食物,然后画出蛇和食物
- 与用户交互,响应按键消息,蛇随按键调整方向,期间吃食物长长
- 刷新画布,更新蛇和食物,并进行因素判断,如果碰壁或碰自己游戏结束。
二、代码实现
2.1,头文件
我们新建一个gsnake.h的头文件,将我们需要声明的变量和函数都声明好,代码如下:
#ifndef __SNAKE_H
#define __SNAKE_H
#include <graphics.h> //包含exayx库函数的头文件
#include <vector>
using namespace std;
#define BLOCK_UNIT 10
#define WIDTH (BLOCK_UNIT*80)
#define HEIGHT (BLOCK_UNIT*60)
/*坐标点结构体*/
struct Point
{
int x;//x坐标
int y;//y坐标
};
/*定义蛇结构体*/
struct Snake
{
vector<Point> sp;//每个蛇节点的坐标
int len;//蛇长度,即蛇节点个数
int position;//蛇的方向
vector<COLORREF> color;//每个蛇节点的颜色
Point next;//蛇运动的下一个坐标点
}snake;
struct Food
{
Point fp[5];//一个用来存放屏幕中食物个数坐标数组
int grade;//食物分数
int num=1;//食物个数
COLORREF color[5];//食物颜色
}food;
//蛇前进方向枚举类型
enum position
{
up,//上
down,//下
left,//左
right//右
};
void init();//初始化操作,包括初始化蛇,初始化食物
void initSnake();//初始化蛇
void initFood(int num);//初始化食物
void drawSnake();//画蛇
void drawFood();//画食物
void keyMsg();//键盘消息
void snakeMove();//蛇移动
void snakeEatFood();//蛇吃食物
void drawGrade();//画分数
bool gameOver();//游戏是否结束
void drawGameOver();//游戏撞墙结束画前蛇状态
#endif // __SNAKE_H
2.2,函数实现
初始化蛇和食物
/*
* 初始化函数
*/
void init()
{
initSnake();
initFood(0);
}
/*
* 初始化蛇
*/
void initSnake()
{
Point pxy;//坐标点对象
pxy.x = 20;
pxy.y = 0;
snake.sp.push_back(pxy);//初始化蛇头点
snake.color.push_back(RGB(rand() % 256, rand() % 256, rand() % 256));//设置第一个节点随机颜色
pxy.x = 10;
pxy.y = 0;
snake.sp.push_back(pxy);//加入第二个蛇节点
snake.color.push_back(RGB(rand() % 256, rand() % 256, rand() % 256));//设置第二个节点随机颜色
pxy.x = 0;
pxy.y = 0;
snake.sp.push_back(pxy);//加入第三个蛇节点
snake.color.push_back(RGB(rand() % 256, rand() % 256, rand() % 256));//设置第三个节点随机颜色
//加入节点后,确定节点个数
snake.len = 3;
//初始化运动方向
snake.position = right;
}
/*
* 初始化食物,数组中每个食物随机化放置
*/
void initFood(int num)
{
food.fp[num].x = rand() % 80 * BLOCK_UNIT;//随机生成食物的x坐标点
food.fp[num].y = rand() % 60 * BLOCK_UNIT;//随机生成食物的y坐标点
int flag = 1;//标记变量,如果为1代表食物生成在蛇身上,食物需要重新生成
while (flag)
{
for (int i = 0; i < snake.len; i++)//遍历蛇身
{
if (food.fp[num].x == snake.sp[i].x && food.fp[num].y == snake.sp[i].y)//如果食物的坐标与其中某个蛇节点坐标一致,说明食物和节点重合
{
food.fp[num].x = rand() % 80 * BLOCK_UNIT;//随机生成食物的x坐标点
food.fp[num].y = rand() % 60 * BLOCK_UNIT;//随机生成食物的y坐标点
flag = 1;//代表食物与蛇身冲突
break;//跳出循环
}
flag = 0;
}
}
}
画蛇和食物
/*
* 画蛇函数
*/
void drawSnake()
{
for (int i = 0; i < snake.len; i++)//遍历蛇身
{
setfillcolor(snake.color[i]);//设置每个蛇节点的颜色
fillrectangle(snake.sp[i].x, snake.sp[i].y, snake.sp[i].x + BLOCK_UNIT, snake.sp[i].y + BLOCK_UNIT);//画蛇节点方块
}
}
/*
* 画食物
*/
void drawFood()
{
for (int i = 0; i < food.num; i++)//遍历食物个数
{
setfillcolor(food.color[i] = RGB(rand() % 256, rand() % 256, rand() % 256));//为每个食物随机生成颜色
fillrectangle(food.fp[i].x, food.fp[i].y, food.fp[i].x + BLOCK_UNIT, food.fp[i].y + BLOCK_UNIT);//画食物块
}
}
按键交互
/*
* 按键交互,实现蛇随键盘方向键移动
*/
void keyMsg()
{
char userKey = _getch();//获取键盘输入字符键
if (userKey == -32) // 表明这是方向键
userKey = -_getch(); // 获取具体方向,并避免与其他字母的 ASCII 冲突
switch (userKey)//判断按键
{
case 'w':
case 'W':
case -72://方向上键
if (snake.position != down)
{
snake.position = up;
}
break;
case 's':
case 'S':
case -80://方向下键
if (snake.position != up)
{
snake.position = down;
}
break;
case 'a':
case 'A':
case -75://方向左键
if (snake.position != right)
{
snake.position = left;
}
break;
case 'd':
case 'D':
case -77://方向右键
if (snake.position != left)
{
snake.position = right;
}
break;
}
}
移动蛇
/*
* 移动蛇身,通过设置vector里面n=n-1方式来实现蛇身子前移动
*/
void snakeMove()
{
//因为设置n=n-1,所以需要提前保存好尾节点
snake.next = snake.sp[snake.len - 1];
//通过n=n-1前移蛇身子
for (int i = snake.len - 1; i >= 1; i--)
{
snake.sp[i] = snake.sp[i - 1];
}
//移动1至n号节点信息后,根据移动方向,移动蛇的头部
switch (snake.position)
{
case right:
snake.sp[0].x += BLOCK_UNIT;//如果向右移动,则x坐标增加一个方块单位
break;
case left:
snake.sp[0].x -= BLOCK_UNIT;//如果向左移动,则x坐标减少一个方块单位
break;
case up:
snake.sp[0].y -= BLOCK_UNIT;//如果向上移动,则y坐标减少一个方块单位
break;
case down:
snake.sp[0].y += BLOCK_UNIT;//如果向下移动,则y坐标增加一个方块单位
break;
}
}
蛇吃食物
/*
* 蛇头节点与食物节点重合,则代表吃到食物,食物加入蛇身上,那么蛇身变长
*/
void snakeEatFood()
{
for (int i = 0; i < food.num; i++)//遍历食物
{
if (snake.sp[0].x == food.fp[i].x && snake.sp[0].y == food.fp[i].y)//如果某个食物与蛇头重合,代表吃食物
{
snake.len++;//蛇身子长度增加
snake.sp.push_back(snake.next);//把食物加入预留的尾部节点
snake.color.push_back(food.color[i]);//根据食物颜色设置蛇尾食物颜色
food.grade += 100;//分数加100
initFood(i);//吃掉食物,那么就新增加一个食物
if (food.num < 5 && food.grade % 500 == 0 && food.grade != 0)//每得500分,增加一个食物,但是食物总数不超过5个
{
food.num++;
initFood(food.num - 1);
}
break;
}
}
}
显示分数
/*
* 根据食物分数显示游戏获取得分数
*
*/
void drawGrade()
{
TCHAR grade[20] = ""; //一个字符串数组
sprintf_s(grade, "score:%d", food.grade);//写入分数
outtextxy(650, 50, grade);//画布写出分数
}
判断是否游戏结束
/*
* 根据蛇头是否碰到墙壁和蛇头是否碰到蛇身来判断游戏是否结束
*/
bool gameOver()
{
if (snake.sp[0].y <= -10 && snake.position == up)//上移过程中碰到墙壁
return true;
if (snake.sp[0].y >= (HEIGHT + BLOCK_UNIT) && snake.position == down)//下移过程碰到墙壁
return true;
if (snake.sp[0].x >= (WIDTH + BLOCK_UNIT) && snake.position == right)//右移过程碰到墙壁
return true;
if (snake.sp[0].x <= -10 && snake.position == left)//左移过程碰到墙壁
return true;
//如果蛇头碰到蛇身
for (int i = 1; i < snake.len; i++)
{
//如果右移与蛇身子任何一个节点交叉重叠,说明碰到蛇身自己
if (snake.position == right && snake.sp[0].x + 10 >= snake.sp[i].x && snake.sp[0].x <= snake.sp[i].x && snake.sp[0].y == snake.sp[i].y)
return true;
//如果左移与蛇身子任何一个节点交叉重叠,说明碰到蛇身自己
if (snake.position == left && snake.sp[0].x <= snake.sp[i].x + 10 && snake.sp[0].x >= snake[i].x && snake.sp[0].y == snake.sp[i].y)
return true;
//如果上移与蛇身子任何一个节点交叉重叠,说明碰到蛇身自己
if (snake.position == up && snake.sp[0].x == snake.sp[i].x && snake.sp[0].y <= snake.sp[i].y + 10 && snake.sp[0].y >= snake.sp[i].y)
return true;
//如果下移与蛇身子任何一个节点交叉重叠,说明碰到蛇身自己
if (snake.position == down && snake.sp[0].x == snake.sp[i].x && snake.sp[0].y + 10 >= snake.sp[i].y && snake.sp[0].y <= snake.sp[i].y)
return true;
}
return false;
}
游戏撞墙结束,则恢复撞墙前状态
//撞墙后,重画蛇撞前状态
void drawGameOver()
{
if ((snake.sp[0].x >= WIDTH && snake.position == right) || (snake.sp[0].x <= -10 && snake.position == left) || (snake.sp[0].y <= -10 && snake.position == up) || (snake.sp[0].y >= HEIGHT && snake.position == down))//根据撞墙条件判断
{
for (int i = 0; i < snake.len - 2; i++)//遍历蛇节点
{
snake.sp[i] = snake.sp[i + 1];//重置蛇节点
}
snake.sp[snake.len - 1] = snake.next;//补上蛇尾
drawSnake();//画出蛇
}
}
2.3,主函数完成,游戏完成
至此,贪吃蛇游戏运行所需函数都已完成,接下来我们只要在主函数中自上而下完成各个函数的调用,即可完成整个游戏:
/*
主函数,实现游戏全部流程
*/
int main()
{
//初始化窗口画布大小
initgraph(WIDTH, HEIGHT);
//设置窗口背景颜色
setbkcolor(RGB(243, 183, 72));
srand((unsigned)time(NULL));
settextcolor(BLUE);//设置字体颜色
setbkmode(TRANSPARENT); // 设置文字输出模式为透明
//初始化蛇和食物
init();
drawSnake();//画蛇
while (!gameOver())//判断是否游戏结束
{
Sleep(150);//休眠0.15秒
BeginBatchDraw();
cleardevice();
if (_kbhit())
{
keyMsg();
}
snakeMove();
snakeEatFood();
drawFood();
drawSnake();
drawGrade();
EndBatchDraw();
}
drawGameOver();
_getch();
}
喜欢的朋友可以扫描以下二维码进行关注,公众号将经常更新文章: