1.在一个窗口绘制贪吃蛇,肯定少不了绘制的API,所以必不可少需要引入graphics.h这个图形界面库(里面封装了WIN 32大部分绘制API,如果没有的话,可以用GDI)。
2.贪吃蛇吃掉食物就会增长一点,可以把它看成是一节一节的。
3.蛇要移动,肯定会有坐标的变化。
4.既然蛇会移动,那么蛇吃掉食物的时候食物当然也是随机变化的,所以食物也有坐标。
5.蛇的碰撞检测(蛇头碰到边界或者碰到自己的身体某部分,就死亡(game over))。
6.蛇要有方向才可以
7.需要读取键盘操作,改变蛇的方向
1.创建一个绘制窗口
API:
void initgraph(int width,int height) //创建宽x,高y的图形窗口
2.清除窗口屏幕所有绘制
API:
void cleardevice() //清空整个窗口的绘制
3.在指定位置输出文字
API:
void settextxy(int x,int y, LPCTSTR str) //在坐标为(x,y)处输出文字str
void outtextxy( int x, int y, TCHAR c ) // 两种字符串类型
4.绘制矩形
API:
fillrectangle(int left, int top, int right, int bottom) //画填充矩形,从起点(left,top) 到终点(right,bottom)
5.绘制圆角矩形
API:
fillroundrect(int left, int top, int right, int bottom, int ellipsewidth, int ellipseheight) //画填充矩形,从起点(left,top) 到终点(right,bottom) ellipsewidth构成圆角矩形的圆角的椭圆的宽度。ellipseheight构成圆角矩形的圆角的椭圆的高度。若后两个相当相当于绘制一个圆形 不过不是以圆心的 是按照矩形的。
6.设置当前文字填充色
API:
settextcolor(COLOR color) //设置当前文字也就是下一行文字的颜色 宏定义RED,BLUE,YELLOW等
7.设置当前文字风格
API:
settextstyle(int nHeight, int nWidth, LPCTSTR lpszFace, int nEscapement, int nOrientation, int nWeight, bool bItalic, bool bUnderline, bool bStrikeOut) //设置文字宽高 如果 width = 0就是自适应大小,LPCTSTR输出的文字后面可以省略掉
三、如何创建蛇和食物
蛇和食物有一个共同的特点就是都有坐标
所以
//创建坐标
struct Coor { // x y 坐标结构体
int x;
int y;
};
其次创建蛇和食物结构(其实可以见蛇和食物的对象都可以的)
//蛇
struct Snake { //蛇结构
int n; //当前的节数
Coor szb[SNAKELENGTH]; //蛇坐标 最大长度是500
Ch ch; //蛇的方向
}snake; //结构体变量
//食物
struct Food
{
Coor fzb; //食物的坐标 只有一个
int flag; //是否被吃的标志
}food;
当然,少不了方向 方向无非就是上下左右 可以使会用枚举类型定义
enum Ch
{
up = 72,
down = 80,
left = 75,
right = 77
};
四、编写思路(过程)
定义完这些结构体后 我们知道 这个蛇也就是snake.szb[0].x 和snake.szb[0].y就是蛇头
所以只需要移动蛇头就可以。
为什么?because 首先蛇是一节一节的也就是绘制的一个一个小矩形,并且这个矩形是根据上面定义的snake结构的坐标结构数组存放的x,y坐标来绘制的,当蛇移动的时候 只需要将坐标加一个步长就可以了。
这样就真的对了吗?每次移动就在那个放下加或者减?
其实不对,为什么?because 如果是蛇变了很多次方向就像这样
应该使用怎么样的思路来进行所有蛇节方向改变呢?
可以让蛇的最后一节n的x,y坐标每次绘制的时候等于蛇的n-1的坐标 这样不就可以改变方向并且按蛇头走的路原封不动的走一遍吗? 对吧
所以 移动蛇的时候应该有一个循环,根据蛇节数来遍历的
for (int i = snake.n - 1; i > 0; i--)
{
snake.szb[i].x = snake.szb[i - 1].x;
snake.szb[i].y = snake.szb[i - 1].y;
}
创建蛇(这里一共创建了4个蛇节 初始化的时候就是四个蛇节):
snake.szb[3].x = 50; //当前节数的X坐标
snake.szb[3].y = 0; //当前节数的Y坐标
snake.szb[2].x = 40;
snake.szb[2].y = 0;
snake.szb[1].x = 30;
snake.szb[1].y = 0;
snake.szb[0].x = 20;
snake.szb[0].y = 0;
那么蛇的如何判断方向呢(你需要引入头文件
//*****这是判断按键 来改变方向
int move = _getch();
switch (move)
{
case up: //不能向下走
if(snake.ch!=down)
snake.ch = up;
break;
case down:
if (snake.ch != up)
snake.ch = down;
break;
case right:
if (snake.ch != left)
snake.ch = right;
break;
case left:
if (snake.ch != right)
snake.ch = left;
break;
}
//***********/
//********根据方向变化来移动蛇头
//先将后一节的坐标等于前一节的坐标
for (int i = snake.n - 1; i > 0; i--)
{
snake.szb[i].x = snake.szb[i - 1].x;
snake.szb[i].y = snake.szb[i - 1].y;
}
switch (snake.ch)
{
case up:
snake.szb[0].y -= NUM;
break;
case down:
snake.szb[0].y += NUM;
break;
case right:
snake.szb[0].x += NUM;
break;
case left:
snake.szb[0].x -= NUM;
break;
}
//********/
这里的_getch 用于获取按键ASCII 比如 _getch 获取键盘按键 _kbhit() 检测是否按下某个键无则返回0;
接下来就是每次移动绘制蛇:
//绘制蛇 (根据存储的蛇节n)来
for (int i =0; i < snake.n ; i++)
{
if (i == 0)
{
setfillcolor(YELLOW); //填充颜色
}
else {
setfillcolor(RED); //填充颜色
}
fillrectangle(snake.szb[i].x, snake.szb[i].y, snake.szb[i].x + size, snake.szb[i].y + size);
}
接下来应该是食物了
食物是随机变化的 并且必须是蛇头吃掉食物才能再次随机生成一个食物。
所以开始的时候应该封装一个函数进行初始化食物
food.fzb.x = (rand() % WINDOW_WIDTH / 10) * 10; // 0 1 2 3 4 .. 639
food.fzb.y = (rand() % WINDOW_HEIGHT / 10) * 10; // 0 1 2 3 ... 479;
//蛇的坐标是10 20 30 40 有规律的 10的倍数
food.flag = 1; //没有吃掉 1 随机出现
为什么要 *10?因为设置的窗口是640 * 480 食物的坐标必须和蛇节大到小和每次的步长一致,这样当蛇头与食物重合就可以判断吃掉了食物
//绘制食物
void DrawFood()
{
setfillcolor(GREEN);
//绘制圆形食物
fillroundrect(food.fzb.x, food.fzb.y, food.fzb.x + 10, food.fzb.y + 10,10,10);
}
有了上面说的碰撞的思路
部分代码:
if (snake.szb[0].x < 0 || snake.szb[0].x >= WINDOW_WIDTH || snake.szb[0].y >= WINDOW_HEIGHT || snake.szb[0].y < 0)
{
return true;
}
。下面是main
//初始化随机种子
srand((unsigned int)time(NULL));
//初始化后会有一个像素矩阵,可以使用坐标系去表示它
InitSnake();
/*
改变方向.按键之后
按键之前程序在继续移动和绘图
*/
char buffer[100];
while (1)
{
while (!_kbhit()) //检测是否按下某个键 没有按下就返回0
{
if (food.flag==0)
{
CoorFood();
}
cleardevice(); //清除重绘
if (isCrash()) //如果碰撞了
{
NUM = 0; //表示撞墙 每次移动的步伐10 变成0 不走了
}
if (NUM != 0) //
{
rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
settextstyle(30, 0, TEXT("微软雅黑"));
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 30, 50, TEXT("贪吃蛇"));
settextstyle(25, 0, TEXT("微软雅黑"));
settextcolor(YELLOW);
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 60, 80, TEXT("分数:"));
settextstyle(60, 0, TEXT("微软雅黑"));
settextcolor(WHITE);
_itoa_s(FenShu, buffer, _countof(buffer), 10);
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 60, 120, LPCTSTR(buffer));
MoveSnake();
DrawFood();
DrawSnake(10);
EatFood();
Sleep(100);
}
else {
break;
}
}
if (NUM == 0)
{
break;
}
ChangeSnakeCh(); //按下后执行方向改变函数
}
settextstyle(65, 0, TEXT("仿宋"));
outtextxy((WINDOW_WIDTH + 200) / 2 - 120, WINDOW_HEIGHT / 2 - 50, TEXT("你挂了!"));
settextstyle(33, 0, TEXT("仿宋"));
settextcolor(YELLOW);
outtextxy((WINDOW_WIDTH + 200) / 2 - 120, WINDOW_HEIGHT / 2 + 20, TEXT("分数:"));
outtextxy((WINDOW_WIDTH + 200) / 2 , WINDOW_HEIGHT / 2 + 20, LPCTSTR(buffer));
我的贪吃蛇的源代码已经在github上面
github地址:https://github.com/IAmWilling/SnakeGame
源码里面有详细的注释,可以对照代码根据这篇总结,来读。