C++基于EasyX制作贪吃蛇游戏(五)第三版文档
本文最后更新于:2020年8月5日 中午
继续完善贪吃蛇,改用面向对象的思想完成代码,引入界面UI以及排行榜。
上接 C++基于EasyX制作贪吃蛇游戏(三)第二版文档 继续更新制作贪吃蛇游戏的一些相关设计。
程序展示
以下是B站视频
上面视频不能播放请移步:https://www.bilibili.com/video/BV1fZ4y1T7xo/
改用面向对象
原先两版程序都是使用的面向过程方式编写的,函数以及全局变量在整个文件之中飘……,本次决定改用面向对象的方式重写代码,毕竟挺缺少面向对象的练习,可能写出来的代码不是很好,但是我会尽量去完善的。
改用面向对象之后,我会尽力将绘制与数据计算这两者分开,不让两者混杂在一个函数内。所以重写的代码会改变以前两个版本的代码,不过核心流程还是一样的。
公共数据 common.h
1 |
|
公共数据头文件,定义以及存储一些常用的数据结构。
Dir
是枚举方向类。
Point
是点的结构体,重载了==
操作符, 便于两个点集的比较。
PlayerMes
是用来存储游玩信息。SortPlayerMsg
重载了()
操作符, 便于两个PlayerMes
的sort
排序。详情请看:STL专题-sort、reverse
Snake类的设计 —— 贪吃蛇类
1 |
|
贪吃蛇类,开始使用STL中的list
作为蛇的链表,不再使用自定义的链表。链表中存储Point类型的值,及节点的横纵坐标。
额外还需要蛇的方向、长度以及速度这几个参数。Point m_tail;
参数在EatFood()
函数那里进行说明。
三个 public const int
的速度是预先设置好的速度等级,方便之后使用。
bool setSpeed(int speed);
函数用于改变蛇的速度,如若改变的蛇的速度超过最大值,那就将蛇的速度设置为最大值;最小值同理。如果修改速度成功就返回true
。void Move();
函数向蛇的方向移动一格,蛇的除蛇头以外的全部节点均向前复制一格。对应链表的操作可以用去除链表末尾的节点,复制链表头部的节点再插入头部,然后额外改变头部的值。void EatFood();
函数主要描述蛇吃到食物之后的动作。在本游戏中,我设定蛇吃到食物后,尾部增长一格。因此需要一个变量来保存蛇刚刚走过的尾部节点,即Point m_tail;
。蛇吃到食物后,将这个尾部节点加入链表即可。void ChangeDir(Dir dir);
改变方向,本来想起函数名为setDir(Dir dir)
的,但是名字不太直观就换了。改变方向时,不是同方向或者不是反方向才能改变。void Dead();
死亡效果,因为蛇碰撞死后效果不太直观,就用随机函数改变一下各个节点的位置。但是效果很难看。ColideWall
、ColideSnake
以及ColideFood
来检测蛇的头部有没有碰撞到什么。std::list<Point> GetSnakeAllNode();
用于获取蛇的全部结点,主要用于食物生成检测时使用。
Food类的设计 —— 食物类
1 |
|
- 两个数据成员:食物位置以及食物状态。
Food();
构造参数,其内设定了初始的食物位置,之后的位置需要使用Generate
函数生成void Generate(Snake *snake);
生成食物函数,因为生成食物不能与蛇的节点重合,所以需要蛇的节点信息。
RankList类的设计 —— 排行榜类
1 |
|
- 排行榜类主要作用是存储管理用户游玩结束之后的游戏数据,涉及了读写文件操作。
- 使用
vector
来存储用户的游玩数据,上限是10条,即MAX_RANK
。也就是排行榜只保存前10名的数据。固定的读写文件名为retro
。 - 私有函数中
void WriteTime(PlayerMsg &msg);
来写入用户达成成绩的时间。ReadFile()
读取配置文件数据,存入到vector
中。WriteFile()
将vector
中的数据写回配置文件中。 - 构造函数
RankList();
中调用ReadFile()
来初始化vector
。 void SaveMsg(PlayerMsg msg);
是保存用户数据到vector
中,如果其排名在10名之外,则不会保存成功。void SaveToRank();
是将vector
中的数据写回文件,实际调用的是WriteFile()
函数。
Game类的设计 —— 游戏控制类
1 |
|
Game
类是游戏的控制类,也是游戏的主体,所以融合了上述全部的类。
Game
主要被用于主函数调用,所以只有构造函数以及三个函数是public
,其余全部private
。
程序状态
m_GameState
,标识程序的运行状态,是在主界面?在游戏中?在排行榜中?还是在游戏帮助中,方便控制程序。void Init();
初始化,主要是进行图形库的初始化。void Close();
结束,主要是图形库释放资源。void Run();
用来运行程序,展示UI,等待用户操作。构造函数
Game();
主要是初始化一些数据,最主要的是设置程序状态m_GameState
为0,以及初始化RankList
,便于访问排行榜时可以看到数据。InitData()
初始化一些在开始游戏时才需要用到的数据,比如Snake
以及Food
,重置PlayMsg
,防止原来的数据对新开一局的数据产生干扰。PlayGame()
则是游戏的控制函数,主要完成游戏中的全部控制,留在下面细说。ChangeChooseUI
这个函数主要就是改变选中选项的效果,重新绘制这个按钮的样式,增加程序与用户的交互。
UI设计
相较于之前的两版程序增加了UI,更加方便用户的控制,同时增加了鼠标的点选,更加直接。
例如上图左上角的返回键可以点击。
鼠标点击操作
1 |
|
MouseHit()
来检测有没有鼠标点击事件,有的话为true。GetMouseMsg()
来获取鼠标点击消息,返回一个MOUSEMSG
类型的数据。FlushMouseMsgBuffer()
来清空鼠标消息缓冲区,防止残存的消息对其他函数产生干扰。
游戏控制 - PlayGame()
相较于前两版程序,我换用了重绘机制。原版程序使用的是仅消除蛇的尾端,局部擦除与重绘的方式。
但是由于数据运算与绘制的分离,原版的方式不容易实现,于是现在使用的是每一次循环就重新绘制一次游戏界面的方式,也就是最常规的方式。
以下是伪流程:
1 |
|
具体的内容可以在函数实现里看到
批量绘图
上述循环完成之后,界面每一次重新绘制都有些不太稳定,有闪烁的情况,这时就需要使用批量绘图。
BeginBatchDraw();
开始批量绘图,其后的任何绘图操作暂时都不会进行绘制,直到执行FlushBatchDraw()
或EndBatchDraw()
才将之前的绘图输出。FlushBatchDraw()
用于执行绘制任务。EndBatchDraw()
结束批量绘图模式,并将还没有绘制的图完成绘制。
这三者加入到PlayGame()
函数中,保证画面的流畅性。
结束语
至此,面向对象版贪吃蛇程序完成。这版程序主要做了一些事情:
- 改用面向对象方式编写程序
- 换用蛇的数据结构为STL的
list
,操作更加方便。 - 将数据运算与绘制操作分离
- 增加UI与用户交互效果
- 增加排行榜机制,使用了文件读写操作。
一些不足:
- 食物类的生成算法需要检测蛇的节点保证不覆盖,因此效率可能比较差,实际运行时会有卡顿现象。考虑后续引入多线程解决。
- UI还是挺难看的……
- 等待补充……
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!