目录
-
前言
-
游戏设计
-
游戏三部曲
-
函数说明
-
优化设计
1
前言
终极目标:打造酷炫贪吃蛇游戏
制作环境:
VS2015(支持VC++2010,VS各个版本)
easyx图形库(稍微改下VC6.0也可以实现)
2
游戏设计
贪吃蛇的制作思路就是蛇头带动蛇尾移动,主要还是看蛇头和蛇头的下一个位置。蛇头的下一个位置只有空地,墙,食物,和自己的身体。
普通模式:蛇头的下一个位置是墙、自己的身体,游戏gameover!
穿墙模式:蛇头的下一个位置是自己的身体,游戏gameover!蛇头的下一个位置是墙,将下一个位置换成墙另一边的空地(这个时候蛇头带动蛇尾移动就不需要考虑那么多了)。
无敌模式:可穿墙,咬到自己的身体也可以继续游戏(屏蔽了gameover功能)。
我们先来分析蛇头下一个位置的可能结果
-
空地
蛇头当然是直接过去
case SPACE://直接移动
map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE;//地图蛇尾所在地置空
for (int i = SnakeSize - 1; i > 0; i--) //蛇尾到蛇头整体移动一位
{
snake[i] = snake[i - 1];
}
map[snake[0].X][snake[0].Y] = SNAKE; //蛇头置 蛇
snake[0] = next; //将下一个位置赋值给蛇头
map[snake[0].X][snake[0].Y] = HEAD; //设置头
break;
-
墙
普通模式就直接gameover了,穿墙模式就需要设计蛇头的下一个为墙另一边的空地,然后蛇头带动蛇尾移动过去。
case WALL:
if (mode) //模式1模式2可穿墙
{
map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE;//蛇尾置空
for (int i = SnakeSize - 1; i > 0; i--) //蛇尾到蛇头整体移动一位
{
snake[i] = snake[i - 1];
}
map[snake[0].X][snake[0].Y] = SNAKE; //蛇头置 蛇
switch (SnakeDir) //穿墙
{
case'A':next.Y = COL - 2; break;
case 'D':next.Y = 1; break;
case 'W': next.X = ROW - 2; break;
case 'S':next.X = 1; break;
default:
break;
}
snake[0] = next; //蛇头移动到新位置
map[snake[0].X][snake[0].Y] = HEAD; //新的蛇头所在的位置
}
else {
MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
exit(0);
}
break;
-
食物
蛇吃掉食物,身体变长,然后重新出现一个食物
case FOOD://食物 蛇尾不变
for (int i = SnakeSize; i > 0; i--) //蛇尾到蛇头整体移动一位
{
snake[i] = snake[i - 1];
}
map[snake[0].X][snake[0].Y] = SNAKE; //蛇头 置 蛇
snake[0] = next; //将下一个位置赋值给蛇头
score++; //分数加一
(SnakeSize)++; //蛇尺度加一
map[snake[0].X][snake[0].Y] = HEAD; //地图上重置蛇头
addfood();
break;
-
身体
普通模式,穿墙模式gameover!无敌模式,就屏蔽掉gameover功能
case SNAKE:
if (mode == 2) //模式二无敌
{
break;
}
else {
MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
exit(0);
}
break;
-
还有就是方向,当蛇往右走的时候你不能将方向变成向左。
case'A':
case'a':
case 75:
if (SnakeDir != 'D') SnakeDir = 'A'; //蛇不能后退
break;
-
添加食物
食物出现的地方只能是空地,不能出现在墙上,自己的身体上
void addfood()
{
int row, col;
do
{
row = rand() % (ROW - 1) + 1;
col = rand() % (COL - 1) + 1;
} while (map[row][col] != SPACE); //当map[row][col]为空地时结束循环
map[row][col] = FOOD; //空地变食物
}
大致思路就是这个样子滴,贪吃蛇就是判断蛇头的下一个位置是什么东西,然后分别考虑。需要区分的是地图有一个数组map存放,蛇有另外一个数组snake存放蛇的位置(为了控制蛇的移动)。
3
游戏三部曲
1、加载游戏数据(初始化 init();)
2、绘制图形(绘图 DrawMap();)
3、玩家操作(数据更新 move();ChangeDir();)
init(); //初始化游戏
while(1)
{
DrawMap(); //绘制游戏
move(); //数据更新
}
先把大纲列出来
01
主函数 main()
定义两个时间控制移动速度
DWORD t1, t2;
主函数将大致思路拟好
int main()
{
initgraph(640, 480); //窗口大小
init(); //初始化
while (1)
{
DrawMap();
if (kbhit())
{
ChangeDir();
move();
t2 = GetTickCount(); //从新获取时间,为了更好的游戏体验
t1 = t2;
}
t2 = GetTickCount(); //不断获取时间,控制移动速度
if (t2 - t1 > 50)
{
move();
t1 = t2;
}
}
closegraph();
return 0;
}
然后呢需要定义一些变量来管理
#define ROW 46
#define COL 64
//枚举
enum game
{
SPACE, WALL, SNAKE, FOOD, HEAD//空地 墙 蛇 食物
};
/************全局变量************/
int mode = 0; //游戏模式
int score = 0; //分数
DWORD t1, t2; //定义两个时间控制移动速度
int map[ROW][COL]; //地图大小
COORD snake[1024]; //蛇 typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;
size_t SnakeSize; //蛇的尺度 typedef unsigned int size_t;
char SnakeDir; //蛇移动方向
02
初始化函数 init()
我们需要初始化那些东西呢!
-
初始化地图,将墙给定义出来
-
地图上初始化蛇的位置,先来条小蛇3个格子
-
初始化蛇的一些属性,长度,方向,还有游戏分数
-
添加食物
void init()
{
srand((unsigned)time(NULL));
setbkcolor(WHITE); //设置背景颜色
memset(map, SPACE, sizeof(map));
//每一行的 第一个 和 最后一个 是墙
for (int i = 0; i < ROW; i++)
{
map[i][0] = map[i][COL - 1] = WALL;
}
//每一列的 第二个 和 倒数第二 个是墙
for (int j = 1; j < COL - 1; j++)
{
map[0][j] = map[ROW - 1][j] = WALL;
}
//定义蛇头和蛇的身体
map[3][5] = HEAD;
map[3][4] = map[3][3] = SNAKE;
//初始化蛇
SnakeSize = 3; //蛇 长
SnakeDir = 'D'; //蛇方向向右
snake[0].X = 3;
snake[0].Y = 5;
snake[1].X = 3;
snake[1].Y = 4;
snake[2].X = 3;
snake[2].Y = 3;
addfood();
}
03
绘图函数 DrawMap()
绘制地图就用填充矩形绘图函数fillrectangle();这样可以填充一些颜色,让蛇看起来很nice,梦凡还将蛇头弄成彩色的,感觉逼味十足。地图上的空地,食物,蛇墙用来枚举,这样表达起来很清楚。
void DrawMap()
{
BeginBatchDraw(); //开始绘图
setbkcolor(WHITE); //设置背景颜色为白色
settextcolor(RGB(238,0,0));
cleardevice(); //清屏
WCHAR arr[10]; //保存成绩
wsprintf(arr, L"总分:%d", score); //将成绩格式化输出到字符串arr中
outtextxy(0, 0, arr); //显示成绩
for (int y = 0; y < ROW; y++) //y轴方向向下
{
for (int x = 0; x < COL; x++) //x轴方向下上
{
switch (map[y][x])
{
case SPACE:
break;
case WALL:
setlinecolor(BLACK);
setfillcolor(RGB(238, 233, 233)); //灰色
fillrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case SNAKE:
setlinecolor(RGB(0, 245, 255)); //绿色
setfillcolor(WHITE);
fillrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case HEAD:
//画七彩蛇头
switch (rand() % 7)
{
case 0:
setfillcolor(RGB(255, 0, 0)); //红色 255 0 0
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 1:
setfillcolor(RGB(255, 165, 0)); //橙 255 165 0
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 2:
setfillcolor(RGB(255, 255, 0)); //黄 255 255 0
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 3:
setfillcolor(RGB(0, 255, 0)); //绿色 0, 255, 0
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 4:
setfillcolor(RGB(0, 255, 255)); //青 0 255 255
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 5:
setfillcolor(RGB(0, 0, 255)); //蓝 0 0 255
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
case 6:
setfillcolor(RGB(160, 32, 240)); //紫 160 32 240
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
default:
break;
}
break;
case FOOD:
setfillcolor(RGB(255, 0, 0)); //红色
solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
break;
default:
break;
}
}
}
EndBatchDraw();
}
04
数据更新函数 move()
数据更新,不断获取玩家操作,蛇每隔0.05s移动一次
-
先来获取玩家操作
void ChangeDir()
{
switch (getch())
{
case'A':
case'a':
case 75:
if (SnakeDir != 'D') SnakeDir = 'A'; //蛇不能后退
break;
case'D':
case'd':
case 77:
if (SnakeDir != 'A') SnakeDir = 'D';
break;
case'W':
case'w':
case 72:
if (SnakeDir != 'S') SnakeDir = 'W';
break;
case'S':
case's':
case 80:
if (SnakeDir != 'W') SnakeDir = 'S';
break;
case 32:
getch();
break;
default:
break;
}
}
-
蛇的移动,需要先判断蛇头的下一个位置是什么
COORD next; //蛇头的下一个位置
switch (SnakeDir)
{
case'A':
next.X = snake[0].X;
next.Y = snake[0].Y - 1;
break;
case'W':
next.X = snake[0].X - 1;
next.Y = snake[0].Y;
break;
case'D':
next.X = snake[0].X;
next.Y = snake[0].Y + 1;
break;
case'S':
next.X = snake[0].X + 1;
next.Y = snake[0].Y;
break;
default:
break;
}
-
然后根据下一个位置进行相应的匹配,开头已经说明了各项功能,这里就不列举了。
switch (map[next.X][next.Y])
{
case SPACE://直接移动
break;
case WALL:
break;
case SNAKE:
break;
case FOOD://食物 蛇尾不变
break;
default:break;
}
4
函数说明
-
先来介绍一个easyx提供的结构体COORD,很简单
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
-
用的还算多的变量 size_t
typedef unsigned int size_t; //保证变量为正整数
-
获取时间的函数 GetTickCount();
返回值为DWORD类型
typedef unsigned long DWORD;
特点:精准度高
5
程序打包
为什么需要程序打包?
当你的程序有依赖库,各种素材文件时需要打包保证用户不会因为失误删掉一些对程序有影响的文件。
6
优化设计
优化的话就是利用链表代替数组来创作贪吃蛇,数组有局限性,定义了多大就多大,本程序定义的是1024,如果哪位小伙伴分数达到了1024分就会出现Bug。