学习内容:
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;
//按键选项初始化0
option = 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图形库资源,音乐资源网上可下载。
版权声明:本文为lwx1051046458原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。