[声明] 大二非专业学生拙作, 写出来只是为了整理自己的思路
此项目基于C语言EsayX函数库,运行环境VS2022
功能模块
- 游戏画面初始化, 刷新函数
- 蛇的身体坐标更改, 蛇身刷新函数
- 食物刷新函数
- 碰撞检测函数
- 键盘输入响应函数
思路
众所周知, 游戏有刷新率这样的说法, 也就是1s内画面更新的次数, 这也是在设计游戏时的核心思路, 即: 在一次刷新间隔内, 使不同的函数模块互相配合工作, 使得在下一次刷新后画面出现相应的更改, 往复循环, 即构成了动态的游戏画面
这里, 笔者使用了while(1)
无限循环, 并搭配延迟响应函数Sleep()
来控制画面刷新率
在main()
函数中, 不同功能模块之间的调用顺序是一大重点, 错误的调用顺序可能导致意料之外的变动(如食物不能正常刷新, 蛇身缺失等, 我都遇到过)
代码实现
/*贪吃蛇项目实战*/
#include <stdio.h>
#include <graphics.h> //用于图形界面绘制 EsayX
#include <conio.h>
#include <windows.h> //延迟函数sleep()库
#include <time.h>
#define TIME 80 //游戏刷新率(ms)
/*蛇的坐标参数, 长度, 初始方向*/
#define DIRECTION up // 方向
#define MAXSIZE 50 //最大蛇身长度
struct Snake {
int x[MAXSIZE];
int y[MAXSIZE];
int length; //当前长度
int speed;
int direction;
};
struct Food {
int x;
int y;
int r = 6;
};
enum Direction
{
up = 1,
down ,
left ,
right
};
void CreatFood();
/*界面初始化函数*/
#define WIDTH 640
#define HEIGHT 480
void SetMap() {
initgraph(WIDTH, HEIGHT);
setbkcolor(RGB(65, 131, 168));
cleardevice();
}
/*初始化蛇 并 画出蛇的基本部分*/
struct Snake snake;
void SetSnake() {
snake.x[0] = WIDTH / 2;
snake.y[0] = HEIGHT / 2;
snake.length = 18;
snake.speed = 10;
snake.direction = right;
setfillcolor(RGB(0, 0, 0)); //设置填充内容的颜色
for (int i = 0; i < snake.length; i++) { //遍历填充数组坐标
snake.x[i] = snake.x[0] - i * 10; //初始向左填充
snake.y[i] = snake.y[0];
printf("done\n");
fillrectangle(snake.x[i], snake.y[i], snake.x[i] + 10, snake.y[i] + 10); //起始坐标是
}
}
void MoveSnake() {
for (int i = snake.length - 1; i > 0; i--) {
snake.x[i] = snake.x[i - 1]; //从蛇尾开始,依次获取前一个节点的坐标
snake.y[i] = snake.y[i - 1];
}
}
/*用于每次刷新蛇的身体*/
wchar_t str3[100] = L"refresh run";
void refresh() {
BeginBatchDraw();
setbkcolor(RGB(65, 131, 168));
cleardevice();
setfillcolor(RGB(0, 0, 0));
for (int i = 0; i < snake.length; i++) {
fillrectangle(snake.x[i], snake.y[i], snake.x[i] + 10, snake.y[i] + 10); //左和上做定位,画一个10*10的矩形
fillcircle(snake.x[i], snake.y[i], 2);
}
EndBatchDraw();
outtextxy(10, 10, str3);
}
/*从键盘获取输入,转化为枚举类型并返回 将key转换为枚举*/
void keyInput() {
char key = _getch();
switch (key) {
case 'w':
if (snake.direction == down) { //当前方向为下时,禁止向上转
break;
}
snake.direction = up;
break;
case 'a':
if (snake.direction == right) { //下面同理
break;
}
snake.direction = left;
break;
case 's':
if (snake.direction == up) {
break;
}
snake.direction = down;
break;
case 'd':
if (snake.direction == left) {
break;
}
snake.direction = right;
break;
default: break;
}
}
/*根据键盘的输入输出对应的控制*/
void Controller() {
switch (snake.direction) {
case up:
snake.y[0] -= snake.speed;
break;
case down:
snake.y[0] += snake.speed;
break;
case left:
snake.x[0] -= snake.speed;
break;
case right:
snake.x[0] += snake.speed;
break;
}
}
/*随机生成食物的坐标 并 创建食物*/
Food food = { 0,0 };
int food_flag = 0;
void CreatFood() {
if (food_flag == 0) {
srand((unsigned int)time(NULL)); //设置时间随机种子,以系统时间戳作为随机数
food.x = rand() % (int)(WIDTH / 1.25) + 32; //生成width 80%的大小,并向右平移5% 范围(32, 544) (5%, 85%)
food.y = rand() % (int)(HEIGHT / 1.25) + 24; //同理 (24, 408) (5%, 85%)
food_flag = 1;
Sleep(500); //当吃到食物时延迟0.5s
}
BeginBatchDraw();
setfillcolor(RGB(255,36,0));
fillcircle(food.x, food.y, food.r);
EndBatchDraw();
}
wchar_t str[100] = L"CrashTest run";
/*食物碰撞检测*/
struct Flag { //设置蛇头的中点为判定点
int x;
int y;
};
struct Flag flag;
void CrashTest() {
flag = {
(snake.x[0] + snake.x[0] + 10) / 2 ,
(snake.y[0] + snake.y[0] + 10) / 2
};
setfillcolor(RED);
fillcircle(flag.x, flag.y, 2); //蛇头的判定点
if (flag.x >= food.x - food.r && flag.x <= food.x + food.r &&
flag.y >= food.y - food.r && flag.y <= food.y + food.r && food_flag == 1) {
snake.length++;
food_flag = 0;
setfillcolor(RGB(0, 0, 0));
outtextxy(10, 10, str);
}
}
/*死亡检测*/
wchar_t str2[100] = L"deathTest run";
bool deathTest() {
if ((flag.x < 0 || flag.x > WIDTH) ||
(flag.y < 0 || flag.y > HEIGHT)) {
return TRUE;
outtextxy(10, 10, str2);
}
for (int i = 1; i < snake.length; i++) {
if (snake.x[0] == snake.x[i] &&
snake.y[0] == snake.y[i]) {
return TRUE;
}
}
return FALSE;
}
/*
===============
注: while()循环中的函数运行顺序不可更改
===============
*/
int main() {
SetMap();
SetSnake();
Direction key = right; //默认方向
/*循环体,不断刷新界面*/
while (1) {
refresh(); //更新画面,根据当前数组坐标绘制蛇身(必须放最上面)
CrashTest(); //碰撞检测应该放在controller()前面,因为用到了头坐标
if (deathTest()) { //死亡碰撞判断
break;
}
if (_kbhit()) { //键盘输入检测
keyInput();
}
MoveSnake(); //移动蛇身坐标,不更新画面 注意movesnake必须放在controller前面
Controller(); //注意放在if外面 不断更改蛇头坐标,不更新画面
CreatFood(); //建立食物坐标,更新食物画面
//refresh放在creatfood下面会导致食物不能被刷新,或者是覆盖(未解决) <<<=================
Sleep(TIME);
}
wchar_t endMessage[30] = L"Game Over, Any keys to quit";
outtextxy(40, 40, endMessage);
if (_getch()) {
}
return 0;
}