分享19级同学大一上学期用C语言实现的火柴人试炼之地。分步骤代码、图片音乐素材、可执行程序可以从百度网盘下载:
链接:https://pan.baidu.com/s/1XelHD8GVnXfR8coAYOAC9w
提取码:dbu3
1 设计思路:
本游戏参考了网络上的同名游戏,并通过EasyX插件用C语言编写实现,玩家通过鼠标进行游戏界面中的选择,通过键盘输入进行游戏主干的操作,并尽可能使界面更加美观,更加符合人性化操作要求,并且加入存档功能和外置游戏数据,前者目的为提高游戏体验,后者目的为方便游戏关卡内容升级。
2 功能描述:
游戏主体基本功能就是接受玩家的鼠标和键盘输入,并做出相应的反应,实现界面选择和游戏功能。其分部分介绍如下:
2.1 初始化
初始化函数为startup()函数,其基本功能有一下几点:
1.初始化画布(initgraph()函数),而且为了避免玩家在游戏过程中关闭窗口,造成无法存档,我在初始化画布时,添加了相应的参数,禁用了关闭按钮和最小化按钮,使游戏在运行时不会被意外关闭。
2.导入并播放音乐。因为考虑到游戏风格,我并不打算在游戏添加人物活动的音效,所以,只有重复播放的背景音乐。
3.导入所有所需要的图片。
4.初始化所有变量。
5.防止在加载图片时出现长时间黑屏,显示加载界面。
2.2 读取游戏进度
读取游戏进度为readGameStageFromFile()函数,用于读取存档,并且在读取失败时默认将游戏进度置为1,然后返回。
2.3 读取游戏存档
读取游戏存档为readGameDataFromFile(int num)函数,通过接收传入的指定关卡,读取相应的关卡,并且,在读取失败时提醒并退出游戏。
2.4 写入游戏进度
写入游戏进度为writeGameStageToFile(int num)函数,接收目前的关卡状态(即游戏进度),并写入存档。该函数在执行exit(0)前执行。
2.5 欢迎界面
欢迎界面为welcomeUI()函数,将图片序列依次输出显示,其重点为遇到键盘输入或鼠标点击时能跳过,因此其中的主要部分为判断。
2.6 作者声明
作者声明为poweredByUI()函数,强制输出作者信息,以声明作者创作权。
2.7 主界面
主界面为mainPageUI()函数,显示游戏的选择主界面,其分为背景图片序列显示和文字和按钮显示,为了提供比较美观的游戏界面,我去查询了EasyX的帮助文档,了解了旋转图片的函数,使标题文字可以旋转,提高美观性,而对于按钮,通过变量控制坐标,使其在鼠标移动上去时产生抖动效果,提高美观性。
2.8 游戏选择界面
游戏选择界面为chooseGameUI()函数,其分为两个部分,第一部分为询问是否读取上一次游戏的存档界面,并采用和主界面类似的界面美观设计,第二部分为游戏关卡选择,通过gameRecord变量的信息来输出相应的关卡选择图案,并且进行一定的美观设计,当鼠标移上去时,颜色发生变化,然后在点击后,改变gameStage变量,以进入相应的关卡。
2.9 主游戏界面
主游戏界面为gameUI()函数,用于输出游戏中物体,其通过几个标记变量来判断输出不同的人物状态,除此之外,通过maps中储存的地图信息来显示地图。
2.10 主游戏操作输入
主游戏操作输入为gameInput()函数,为了使游戏体验更加流畅,我使用了异步输入GetAsyncKeyState()函数,并且同时兼容了ASDW和方向按键,以提升玩家体验。每个按键中操作除了坐标改变外,还有相应标记变量的改变。此外,函数重点为人物跳跃落地判断,此处我通过判断当前坐标对每个方块的宽度取余,以对应到maps数据中用了判断。而且,为了游戏效果更佳逼真,我采用了重力加速度的计算方法,来让人物有加速下落的效果。
2.11 暂停界面
暂停界面为pauseUI()函数,其界面设计与读取存档界面统一,并采用一样的按键美化方法。
2.12 游戏结束界面
游戏结束界面为endGameUI()函数,其界面设计与暂停界面和读取存档界面统一,并采用一样的按键美化方法。在点击确认的情况下,清除相应的数据,将游戏进度写入存档,并安全退出。
2.13 主函数
主函数仅为对各种函数的调用,并通过while(1)来实现不断显示,而界面切换是通过switch来判断stage数据,以进入相应的函数。
3 实现过程中的重点和难点
3.1 chooseGameUI界面中按钮显示异常问题(难点)
在chooseGameUI中,由于按键有不同类型,因此其判断是分开进行的,结果在初期调试时发现,只有第一部分按钮有动态效果,其他按钮无反应,经过反复分析,我发现,在这函数的一次执行中,有多个并列的if(MouseHit())语句,而MouseHit()对鼠标情况的读取总是使程序进入第一部分的按钮判断中,而后面的MouseHit()都被第一个MouseHit()阻塞,导致对后面的按钮判断不能被执行。
解决方法为:加入一段语句,使后面的判断进入条件共用一个MouseHit()函数。(代码如下)
int mFlag = 0;
if (MouseHit())
{mFlag = 1;m = GetMouseMsg();
}
后面的判断都使用mFlag,而不是MouseHit()。
3.2 游戏CPU占用过高引起的低配置电脑运行卡顿问题(难点)
由于游戏中使用了while(1),相当于程序在死循环,而为了游戏的流畅效果,不能直接使用Sleep()函数,所以,程序全程都在对CPU进行高占用,而不同帧数对数据处理时间不同,直接的无限循环就导致了刷新帧率有巨大波动,影响游戏效果,且过度消耗电脑资源。在程序优化前,游戏程序在任意界面下都几乎占满一个CPU核心(数据为i7-9750H处理器在连接电源情况下保持4.2GHz左右的时钟频率工作下测得),而我通过查询,找到了一个MyTimer.h的自定义库,可直接调用实现非阻塞式的延时,从而统一刷新帧率,对游戏实现优化。在程序优化后,游戏程序在任意界面下都可以实现低占用率,在电脑省电模式下的极端测试,游戏程序不会占用一个CPU核心的50%以上(数据为i7-9750H处理器在不接通电源下开启极限省电模式保持0.9GHz左右的时钟频率工作下测得)。
3.3 存档问题(重点)
我们的游戏离不开存档和数据读取功能,因此对文件操作要求较高,因此我加入了许多对于文件操作的异常情况的处理,如:存档文件无法打开时游戏进度自动归零,地图数据无法打开时游戏报错提醒等,在这里我还使用了MessageBox函数,此外,为了防止玩家意外按到关闭按钮退出而丢失游戏进度数据,我还禁用了窗口的关闭按钮。
4 感悟
此次大作业中,诚然知识的要求很重要,但是,我感觉我收获最多的是自主学习,自主纠错的能力,程序,谁都能写,但是对于一些细节的东西,整体框架的整洁度,逻辑是否合理,是否规范才是最重要的地方。在本次作业中,有多处体现了这中思维的重要性,如对于每个界面结束时界面残余数据的清除置零,MouseHit()的冲突与干扰,还有人物运动与显示的逻辑,这些都是考验。而查询帮助文档和用法,是我的另一大收获,编程里面东西很多,如果只靠别人说,难以学深,更多的就是依靠自己学习自己查询帮助文档。
总之,大作业让我获益匪浅。