文章C语言GUI编程之数字记忆游戏——项目目录结构和初步的窗口布局完成了基本的项目框架,接下来介绍游戏的核心代码开发。
美化UI
设置窗口背景图
增加棋盘格子的背景图,以及整个窗口的背景图,效果还是挺明显的。
增加游戏背景图添加窗口的背景图原理是先创建一个IMAGE对象,然后把背景图片加载进来,最后用putimage()函数把IMAGE对象画到默认窗口上去。
设置格子背景图
设置棋盘格子背景图比较简单,使用EasyX库里的setfillstyle()函数来完成。这里有一点需要注意,setfillstyle()用自定义图片来填充背景的时候是从(0,0)的位置开始按照设置的背景图片尺寸平铺开的,并不是从每一个格子的左上定点开始平铺的。
优化格子UI
首先把棋盘格子由原来的的矩形改为圆角矩形;但是还有一个问题就是格子连在一起显得很拥挤,下面我们来缩小格子。
第一步,缩小所有格子在游戏区内的区域,就是在计算格子大小时减去空出来的距离,我们把这个缩小的距离记为padding。
缩小整体格子区域第二步,让所有格子的左顶点坐标(对应left、top)分别增加padding的值,这样就能使所有格子中间空出来相同的距离。
格子设置间距注意,窗口默认原点坐标在左上角,所有格子缩小实际上是left和top都增加了。
EasyX相关特性
EasyX里的设备
EasyX里把绘制对象叫作设备,整个窗口是默认的绘制设备,设备这个词叫不太好和UI界面对应,理解为“图层”会更加直观一点。在项目里定义了三个图层:
窗口,为最底层的图层,目前绘制了背景图
左侧游戏区图层,高于窗口图层,绘制了最基础的格子UI
右侧功能区图层,高于窗口图层,和游戏区图层平级,要绘制一些功能设置UI
一开始只用了一个图层,但是后面做动画相关的交互需要整体刷新,就显得很笨重,所以把图层做了进一步拆分,刷新的时候显得“轻便”。
EasyX里的更新
EasyX的世界里,窗口里显示的任何东西都叫是图像,不管是文字还是线条,吐过要更新一个文字就需要在文字区域重新绘制,这个和网页以及常规APP开发中大不相同。
比如,网页上要改一个文字,只需要使用JS获取相应的DOM节点,然后更改里面的文字,而在EasyX里需要这样做:
方法一,重新绘制一个背景把文字区域盖住,然后再绘制上文字
方法二,如果文字所在的区域可以清空(比如矩形区域),那就清空区域并绘制背景,最后再绘制上文字
但是在开发中往往要重新绘制大片的区域,甚至重新绘制一个图层,后面会开发中遇到这样的情况,到时候结合实例来理解这点。
EasyX里的动画
EasyX里使用BeginBatchDraw()、FlushBatchBraw()、EndBatchDraw()这三个函数来暂存一段绘图效果,在全部绘制结束后再输出到相应的图层已达到动画的效果。
EasyX的这三个函数在游戏中用到的地方很多,通过这种方式不会让画面有明显的闪烁,能做到比较顺畅的动画效果。
游戏核心玩法的开发
流程图
先来看一下游戏的玩法展示——流程图。整体来看游戏的玩法比较直观,在开发的过程中尽量把C语言的知识点都融汇进去以熟练运用,这才是我们做项目的目的。
游戏开发流程图拆分棋盘格子模块
之前本来想把游戏相关的代码都归在game.cpp模块,但是这样显得有点乱,所以先拆分出来棋盘格子cell.cpp模块,后续随着开发的进行很可能还会再拆分出新的模块。
game.cpp模块主要控制游戏的进程,cell.cpp模块控制格子和数字直接的逻辑:绑定、解除、更新等,相应的格子UI上的变化也由cell.cpp模块控制。
格子随机绑定数字
根据关卡N个数字随机选中N个格子,并按照选中的顺序给格子赋值,如下图展示了第15关卡的随机数字分布。
游戏第15关展示向格子里输出字符串主要使用了EasyX库里的drawtext()函数。为了使游戏的体验性更好,在输出数字的时候加上了过渡动画的效果——让数字显示高度从小到大显示,效果如下。
显示数字过渡动画效果游戏倒计时
倒计时过后数字隐藏开始闯关。倒计时的逻辑很简单:前3关直接3秒倒计时,后面每增加一关多一秒的记忆时间,倒计时都是在最后三秒出现。
游戏最后3秒倒计时倒计时的关键点在于UI层面的实现。既然是倒计时,肯定也和上面一样用到了动画,过渡效果是字体高度从大变小。
表面上看着只有倒计时的数字在变化,其实整个游戏区的图层都在更新,因为要消除每一帧倒计时的那个数字,所以要在倒计时数字展示之前就要把游戏区图层重新绘制一遍来达到遮盖住倒计时数字的目的。如果没有做图层更新,就会出现下面的效果。
倒计时数字未被覆盖隐藏数字
隐藏格子上的数字,用户开始凭借记忆按照顺序点击格子。这个功能实现逻辑是先把数字所在的格子清空,然后再填充上背景,或者干脆直接填充一层背景也可以。
为了提升视觉上的体验,不要让数字消失的那么突兀,在隐藏数字的时候也可以加上一段过渡动画——让数字显示高度逐渐变小。
隐藏数字过渡动画效果计时器
整个游戏过程中的时间记录,这个模块放在右侧功能区显示,下个阶段再开发。
监听格子点击事件
创建一个新模块event.cpp来监听鼠标点击,根据位置来判断点击了哪个格子。
在EasyX中,使用getmessage()获取消息事件并保存在一个结构体类型为ExMessage的变量里,然后就可以根据结构体里的成员获取坐标,再使用坐标去判断玩家的点击区域。
EasyX里的消息结构体这里注意一点,在正式开始游戏之前要执行flushmessage()函数来清空消息,否在在游戏还未正式开始以及游戏关卡之间的转换(动画效果)时的事件都会被接受传递,而这就会造成一些类似bug的影响。
最直接的实验方式是在程序里执行Sleep()函数,然后一直点击鼠标,最后看看是不是sleep()期间的点击事件最后也被传递执行了。
下一关卡
进入到下一关卡,格子里的数字信息要清空重置,然后重新生成下一关的数字。
这里注意的点是,移除UI上的数据和解除格子数据的绑定是分开操作的。虽然我们想要的效果是解除数据绑定自动移除UI上的数字,但是在游戏逻辑里有隐藏数字让玩家去点击格子这个操作,所以此时不能解除数据绑定,因此把视图层和逻辑层分开来处理会方便一些,也不容易出现bug。
关于代码
因为篇幅所限以及为了阅读方便,并没有截图代码。笔者想通过游戏实现过程的描述和解读,让读者能够把游戏算法的实现描绘在脑海里,有了整体的思路再去着手开发程序,这才是正解。
如果有想要代码参考学习的可以留言或者私信~