学习内容:1.游戏的存储与读写
2.制作游戏菜单
3.制作定时器
目录
一、需要用到的头文件、全局变量和一些函数
二、定时器
三、数据的初始化和绘制
四、蛇的运行和吃食物
五、游戏菜单界面
六、游戏存储与读写
七、其他函数
八、主函数入口
主菜单
游戏内容
一、需要用到的头文件、全局变量和一些函数
#include<stdio.h>#include<easyx.h>#include<time.h>#include<conio.h>#pragma comment (lib,"winmm.lib")#define WIDTH 600//屏幕宽度与高度#define HEIGHT 500#define MAX_SNAKE 30//最大蛇长度//全局变量int game_state = 1;//游戏状态:1.游戏菜单选项 2.运行游戏 3.退出游戏 int option;//菜单栏状态bool game_pause;bool snake_die;MOUSEMSG m1;MOUSEMSG m2;//定一个方向枚举enum DIR{up,down,left,right};//蛇结构体struct Snake{POINT coord[MAX_SNAKE];int num;int len;int exis;int score;int dir;}snake;//食物结构体struct Food{POINT fd;int exis;}food;//需要用到的函数void InitData();//初始化数据void DrawImag();//绘制图画void ReadGame();//读取要保存的数据void SaveGame();//保存数据void EatFood();//吃食物void GameOver();//游戏结束void GameMenu();//游戏菜单void UpdataWithInput();//有数据输入void UpdataWithoutInput();//无数据输入void SnakeMove();//蛇的移动void KeyControl();//键盘输入
二、定时器
用static 定义 start避免循环时被覆盖
开始start与end处以同一时间,start保存的时间不变,end时间一直在变化,直至end和start时间差大于等于ms,当相等时,再将start与end时间同步,重复以上操作,用时间差来控制程序运行快慢。
作用:满足时间要求时可进行某个操作,也不影响其他操作的进行。(可用于蛇的运动快慢)
bool timer(int ms, int id){static int start[6] = { clock() };int end = clock();if (end - start[id] >= ms){start[id] = end;return true;}return false;}
三、数据的初始化和绘制
void InitData(){initgraph(WIDTH, HEIGHT, 1);//1表示显示控制台//播放背景音乐mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);mciSendString("play bgm1 repeat", NULL, 0, NULL);//初始化蛇snake.len = 10;snake.num = 3;snake.dir = right;snake.score = 0;snake.coord[2].x = 0;snake.coord[2].y = 0;snake.coord[1].x = snake.len;snake.coord[1].y = 0;snake.coord[0].x = 2 * snake.len;//作为蛇头snake.coord[0].y = 0;// 画食物food.fd.x = rand() % (WIDTH / 10) * 10;food.fd.y = rand() % (HEIGHT / 10) * 10;food.exis = 1;//按键选项初始化0option = 0;//蛇是否死亡snake_die = false;//游戏是否暂停初始化为否game_pause = false;}//绘制图形画面void DrawImag(){setbkcolor(GREEN);cleardevice();BeginBatchDraw();while (game_state == 1){GameMenu();}//画蛇for (int i = snake.num - 1; i >= 0; i--){setfillcolor(YELLOW);setlinecolor(BLACK);fillrectangle(snake.coord[i].x, snake.coord[i].y, snake.coord[i].x + snake.len, snake.coord[i].y + snake.len);//printf("%d,%d\n", snake.coord[i].x, snake.coord[i].y);}//画食物setfillcolor(RED);solidellipse(food.fd.x, food.fd.y, food.fd.x + snake.len, food.fd.y + snake.len);settextstyle(25, 0, "黑体");outtextxy(20, 20,"得分:");TCHAR c[3];sprintf(c, "%02d", snake.score);outtextxy(90, 20, c);setbkmode(TRANSPARENT);//画出暂停与返回菜单界面按键char button_name1[10] = { "暂停游戏" };char button_name2[10] = { "继续游戏" };char button_name3[10] = { "返回菜单" };//按键框(仅供参考,可以自己在做一个小按键隐藏按钮,避免遮挡视线)setfillcolor(WHITE);fillrectangle(10, 400, 130, 440);fillrectangle(10, 450, 130, 490);setlinecolor(WHITE);//当鼠标不再范围内边框变白rectangle(10, 400, 130, 440);rectangle(10, 450, 130, 490);setbkmode(TRANSPARENT);settextcolor(BLACK);outtextxy(10 + 60 - textwidth(button_name3) / 2, 450 + 20 - textheight(button_name3) / 2, button_name3);if (game_pause){outtextxy(10 + 60 - textwidth(button_name1) / 2, 400 + 20 - textheight(button_name1) / 2, button_name2);}else{outtextxy(10 + 60 - textwidth(button_name2) / 2, 400 + 20 - textheight(button_name2) / 2, button_name1);}if (m2.x >= 10 && m2.x <= 130 && m2.y >= 400 && m2.y <= 440){setlinecolor(RED);//鼠标在方形区域时边框变红rectangle(10, 400, 130, 440);option = 1;}if (m2.x >= 10 && m2.x <= 130 && m2.y >= 450 && m2.y <= 490){setlinecolor(RED);rectangle(10, 450, 130, 490);option = 2;}//当游戏结束时if (snake_die){setbkmode(TRANSPARENT);settextcolor(RED);settextstyle(40, 20, "黑体");outtextxy(100, 200, "吃到自己了,游戏结束!!");outtextxy(100, 240, "是否继续游戏!!");outtextxy(100, 280, "继续请按Y 否请按N");}FlushBatchDraw();}
四、蛇的运行和吃食物
//蛇的移动void SnakeMove(){for (int i = snake.num - 1; i > 0; i--){snake.coord[i].x = snake.coord[i - 1].x;snake.coord[i].y = snake.coord[i - 1].y;}//snake.coord[0].x += 10;switch (snake.dir){case up:snake.coord[0].y -= 10;if (snake.coord[0].y +10 == 0){snake.coord[0].y = HEIGHT;}break;case down:snake.coord[0].y += 10;if (snake.coord[0].y - 10 == HEIGHT){snake.coord[0].y = 0 ;}break;case right:snake.coord[0].x += 10;if (snake.coord[0].x -10 == WIDTH){snake.coord[0].x = 0;}break;case left:snake.coord[0].x -= 10;if (snake.coord[0].x +10 == 0){snake.coord[0].x = WIDTH;}break;default:break;}//判断蛇是不是吃到自己了for (int i = 3; i < snake.num; i++)//当蛇身只有三个时吃不到自己{if (snake.coord[0].x == snake.coord[i].x&&snake.coord[0].y == snake.coord[i].y){snake_die = true;}}}//用键盘控制void KeyControl(){if (GetAsyncKeyState(VK_UP) && snake.dir != down){snake.dir = up;}if (GetAsyncKeyState(VK_DOWN) && snake.dir != up){snake.dir = down;}if (GetAsyncKeyState(VK_RIGHT) && snake.dir != left){snake.dir = right;}if (GetAsyncKeyState(VK_LEFT) && snake.dir != right){snake.dir = left;}}//吃食物void EatFood(){srand((unsigned int)time(NULL));if (snake.coord[0].x == food.fd.x&&snake.coord[0].y == food.fd.y){food.fd.x = rand() % (WIDTH / 10) * 10;food.fd.y = rand() % (HEIGHT / 10) * 10;snake.num++;snake.score++;}}
五、游戏菜单界面
//游戏 界面菜单void GameMenu(){cleardevice();BeginBatchDraw();//游戏名字char game_name[20] = { "贪吃蛇" };setbkcolor(TRANSPARENT);settextcolor(GREEN);settextstyle(40, 20, "华文琥珀");outtextxy(WIDTH / 2 - textwidth(game_name) / 2, 40, game_name);if (MouseHit()){m1 = GetMouseMsg();if (m1.uMsg == WM_LBUTTONDOWN){//开始新游戏if (option == 1) {game_state = 2;mciSendString("close bgm1", NULL, 0, NULL);Sleep(400);mciSendString("open .\\resource\\bk2.mp3 alias bgm2", NULL, 0, NULL);mciSendString("play bgm2 repeat", NULL, 0, NULL);}else if (option == 2) { ReadGame(); game_state = 2; mciSendString("close bgm1", NULL, 0, NULL);Sleep(400);mciSendString("open .\\resource\\bk2.mp3 alias bgm2", NULL, 0, NULL);mciSendString("play bgm2 repeat", NULL, 0, NULL);}//读取进度开始游戏else if (option == 3) { SaveGame(); closegraph(); _exit(0); }//保存并退出游戏}}option = 0;char select1[9] = { "游戏开始" };char select2[9] = { "继续游戏" };char select3[9] = { "保存退出" };int textw = 140;int texth = 40;settextcolor(WHITE);//开始游戏按钮if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.3&&m1.y <= HEIGHT*0.3 + texth){//settextcolor(RED);settextstyle(35, 0, "华文彩云");//悬浮在按钮上outtextxy(WIDTH*0.39, HEIGHT*0.3, select1);option = 1;//getchar();}else { settextstyle(35, 0, "黑体"); //不在按钮上outtextxy(WIDTH*0.39, HEIGHT*0.3, select1);}//继续游戏if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.5&&m1.y <= HEIGHT*0.5 + texth){settextstyle(35, 0, "华文彩云");outtextxy(WIDTH*0.39, HEIGHT*0.5, select2);option = 2;}else{settextstyle(35, 0, "黑体");outtextxy(WIDTH*0.39, HEIGHT*0.5, select2);}//保存并退出游戏if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.7&&m1.y <= HEIGHT*0.7 + texth){settextstyle(35, 0, "华文彩云");outtextxy(WIDTH*0.39, HEIGHT*0.7, select3);option = 3;}else{settextstyle(35, 0, "黑体");outtextxy(WIDTH*0.39, HEIGHT*0.7, select3);}FlushBatchDraw();//clearrectangle(0, 0, WIDTH - 1, HEIGHT - 1);//用背景色清除当前区域}
六、游戏存储与读写
文件打开失败没有连接其他功能,可自行添加
打开文件之后需要关闭文件
//存档void SaveGame(){char ch[20];sprintf(ch, "del storage.txt");system(ch);//删除已有存档FILE* stor;if ((stor = fopen("storage.txt", "w+")) == NULL)//打开失败{cleardevice();setbkmode(TRANSPARENT);settextcolor(RED);settextstyle(40, 20, "黑体");outtextxy(200, 300, "存档失败!!");}else{fwrite(&(snake.num), sizeof(snake.num), 1, stor);fwrite(&(snake.score), sizeof(snake.score), 1, stor);fwrite(&(snake.score), sizeof(snake.score), 1, stor);fwrite(&(snake.dir), sizeof(snake.dir), 1, stor);fwrite(&(snake.len), sizeof(snake.len), 1, stor);fwrite(&(food.fd), sizeof(food.fd), 1, stor);fwrite(&(food.exis), sizeof(food.exis), 1, stor);for (int i = 0; i <= snake.num; i++){fwrite(&snake.coord[i], sizeof(snake.coord[i]), 1, stor);}}fclose(stor);}//读取游戏void ReadGame(){FILE* stor;if ((stor = fopen("storage.txt", "r")) == NULL){cleardevice();setbkmode(TRANSPARENT);settextcolor(RED);settextstyle(40, 20, "黑体");outtextxy(200, 300, "读写失败,可能没有存档!!");getchar();}else{fread(&(snake.num), sizeof(snake.num), 1, stor);fread(&(snake.score), sizeof(snake.score), 1, stor);fread(&(snake.exis), sizeof(snake.exis), 1, stor);fread(&(snake.dir), sizeof(snake.dir), 1, stor);fread(&(snake.len), sizeof(snake.len), 1, stor);fread(&(food.fd), sizeof(food.fd), 1, stor);fread(&(food.exis), sizeof(food.exis), 1, stor);for (int i = 0; i <= snake.num ; i++){fread(&snake.coord[i] , sizeof(snake.coord[i]) , 1, stor);}}fclose(stor);}
七、其他函数
//当蛇吃到自己时游戏结束void GameOver(){if (snake_die){char ch;ch = _getch();switch (ch){case 'Y':case 'y':closegraph();InitData();mciSendString("close bgm1", NULL, 0, NULL);break;case'N':case'n':EndBatchDraw();closegraph();_exit(0);break;default:break;}}}//需输入更新void UpdataWithInput(){KeyControl();//判断是否有鼠标介入while (MouseHit()){m2 = GetMouseMsg();if (m2.uMsg == WM_LBUTTONDOWN){if (option == 1){game_pause = true;}else if (option == 2){game_state = 1;mciSendString("close bgm2", NULL, 0, NULL);mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);mciSendString("play bgm1 repeat", NULL, 0, NULL);}}//游戏暂停将会一直在此循环直到点击继续游戏或者返回主页菜单while (game_pause){DrawImag();while (MouseHit()){m2 = GetMouseMsg();if (m2.uMsg == WM_LBUTTONDOWN){if (option == 1){game_pause = false;}else if (option == 2){game_state = 1;mciSendString("close bgm2", NULL, 0, NULL);mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);mciSendString("play bgm1 repeat", NULL, 0, NULL);}}}}}}//无需输入更新void UpdataWithoutInput(){if (timer(100, 0)){SnakeMove();}GameOver();EatFood();}
八、主函数入口
//主入口int main(){InitData();while (true){DrawImag();UpdataWithInput();UpdataWithoutInput();}closegraph();getchar();return 0;}
总结:难点在于设计菜单需要用到多重循环,以及按键效果,数据存储时避免遗漏。
此游戏功能简单,仅供学习参考。(有不足的地方欢迎大家指出,希望能和大家一起学习进步)
有兴趣可以尝试完成其他功能,比如封装、添加ai蛇,速度随着长度增加,障碍物或用链表写一个贪吃蛇等。
easyx图形库资源,音乐资源网上可下载。
如果觉得《用C语言写一个简单的贪吃蛇游戏(用到easyx图形库)》对你有帮助,请点赞、收藏,并留下你的观点哦!