无题鸭游戏

目录

项目介绍

该项目是南京大学2022问题求解(二)的课程项目,要求基于Qt框架制作一个类似泡泡堂的游戏。游戏第一阶段使用命令行界面实现;第二阶段使用Qt,双玩家,并配有两个机器人作为玩家的敌对势力;第三阶段优化机器人ai。

因本项目实现较好,被评为优秀项目

项目仓库 点击此处

项目展示视频 点击此处

第二阶段实验报告

完成进度

本项目第二阶段的必做部分在5月3日全部完成,详细说明如下:

  • 素材收集:本项目中全部贴图都是我亲手画出来的,使用的网站是这个。贴图的基本尺寸是 64 * 64,有一些大了。

  • 开始菜单:本游戏中,开始菜单有“教程”、“PVP 模式”、“PVE 模式”和“退出游戏”。“教程”使用了一个单独的游戏场景,摆放了软墙和游戏说明,玩家可以在这个场景下操作并熟悉控制方式,同时了解道具功能。“退出游戏”就是退出游戏。

  • 地图:目前的地图是品类齐全的,并且具备 20 * 15 的标准尺寸。

  • 玩家:玩家会根据朝向改变形象,且有基于坐标位置切换的逐帧动画(这使得移动速度快时动画切换频率高,反之亦然)。

  • 机器人:目前,机器人会尝试向玩家移动,通过计算炸弹位置和爆炸范围躲避炸弹,并以一定时间间隔释放炸弹。目前智能不高。同样也配有动画。

  • 炸弹:炸弹在爆炸时会产生冲击波,冲击波根据炸弹等级向四周延伸。在每一格冲击波产生后,会在该格进行伤害判定。同一个炸弹不同距离的冲击波并不是同时完成伤害判定,而是向四周延伸。

  • 道具

    • “增加速度”:将玩家的速度大幅提升(机器人吃掉该道具会获得同样效果);
    • “增加炸弹威力”:炸弹的爆炸范围由1增加至5(机器人吃掉该道具会获得同样效果);
    • “增加炸弹个数”:玩家允许在场景中国放下的炸弹总数由1增加至8(由于机器人以固定时间间隔投放炸弹,该道具对机器人暂时没有效果);
    • “移动炸弹”:玩家与某炸弹位于同一格时,可以使用方向键将炸弹踢到下一堵墙之前(该道具对机器人暂时没有效果)。
    • buff限制规则:玩家和机器人都不可以无限制地叠加效果。在同时拥有的效果大于等于3(多次获得同一个效果以多次计算)时,新获得的效果会洗掉最先获得的旧效果。
  • 其他显示:玩家、机器人实时得分和获得效果情况统一在状态栏显示。此外,在状态栏上还有玩家、机器人的名字和血条;暂停按钮已经实装。

过程记录

在这里主要介绍遇到的问题以及解决方案:

来自过去的遗产:Game Manager

在游戏中有一些不可避免的集中管理问题:玩家类需要获取场景信息做出判断,炸弹类和冲击波类需要访问玩家和机器人类,同时也需要访问场景进行全局修改……诸如此类。这时候可以使用一个在每个场景中唯一存在的GameManager(或者GameController)来处理这类需要访问场景信息来进行处理的问题。实际上这并不是我自己新想出来的,而是早期进行基于GameMaker和unity进行游戏编程时学习的通用做法。

我的具体实现中,在GameManager类中封装了以整型数组形式存储的实时地图和炸弹信息,以及访问地图中所有炸弹、墙、玩家、机器人的接口。同时,在地图中以确定坐标摆放炸弹、冲击波、道具;展示游戏胜利、结束、暂停的UI;管理背景音乐这些全局操作也实际上由GameManager类完成。

实际上,我尽量尝试着将地图生成时所有操作搬入GameManeger类的onAttach()函数中,使MainWindow.cpp更加简洁,这样易于管理。但是由于一开始在MainWindow.cpp中完成的大量工作不适合迁移,所以暂时搁置了。

基于四点检测的碰撞检测

我规避了ItemAt和collider的做法,自行使用了一个基于四个检测点的碰撞检测方法。

以游戏角色的判定点(在这一点判定该游戏角色“在哪一格”),分别向上、向下、向左、向右画线,这四条射线与碰撞箱的交点作为判断点。例如游戏角色要向右移动,则考察上、下两个端点,若上、下两个端点所在的格右方有墙,则限制游戏角色目前可以向右行进的成都。以此类推,上下移动是判断左右两端点——就像猫的胡子一样。碰撞箱的尺寸比一格小很多,避免严丝合缝的检测对玩家操作精确度的过高要求。

事件序列遗留引发的场景多次加载问题

在本阶段中遇到了一个大bug——在通过按钮事件重载场景时,有一定概率场景被加载多次,导致卡顿等错误。这是因为Qt中,鼠标事件和onUpdate的调用并无明显关联。在一次点击下,按钮的点击事件可能被调用多次,使得多个“重载”事件进入Qt的事件队列。当第一个“重载”事件被执行后,旧场景中item被清除(这是基于我实现的将场景中物体全部detach的函数),新场景被加载:然而这时候还有“重载”事件遗留在事件队列里,造成多次重载。最后的解决方式添加一个“使能信号”,在按钮被按下一次后,它将“丧失活性”,点击它将不再具有任何作用。

机器人

这次的机器人采用了贪心随机游走,定时释放炸弹的算法。在“普通”状态下,机器人会尽量朝靠近离它最近的玩家的方向移动(当某玩家死亡时,机器人转为靠近唯一存活的玩家),若无法靠近则随机游走;机器人不会走“有危险(即在某炸弹爆炸范围内)”的格子。

在“紧急”状态(机器人坐在的格子是“危险”的,可能被炸到)下,机器人放弃接近玩家,以逃离危险区域为最高优先级。逃跑路线是随机的。当机器人发现已经到达安全区域,它会等待稍长于炸弹引线的一段时间。这是为了防止机器人在炸弹爆炸时再次走进爆炸范围(前文已经提到,冲击波有一定延时和顺序。冲击波造成伤害时,炸弹可能已经消失,机器人无法判定)。

最后呈现出的效果还不错。

第三阶段实验报告

完成情况

本项目第三阶段的必做部分在7月2日全部完成,详细说明如下:

  • 对第二阶段的继续完善
    • 画面呈现:在第二阶段,我已经实现了基于玩家和机器人运动状况(坐标)实现的动画、弹幕提示、状态栏等功能,也添加了音乐和音效。所以在画面表现方面,我没有做出改动;
    • bug修复:在第二阶段,存在“当炸弹在道具处爆炸时,爆炸范围不会延伸”的bug,现在已经修复;
    • 更好的炸弹:在第二阶段,炸弹没有碰撞体积。当炸弹被踢飞时,它们在第一道墙前停止。经过优化后,现在它们在第一个炸弹前停止,并把动量传递到下一个炸弹,牛顿欣慰地流下了眼泪。
  • 机器人:改进后的机器人使用A*算法进行寻路,使用普通bfs躲避炸弹。详情将在下一板块进行说明。

过程记录

机器人寻路算法

如果要寻找最短路,bfs确实可以解决问题(此处在讲解视频中可能有一些口误)。但是在寻路终点明确确定的情况下,我使用了效率更高的A*算法进行优化。

A*优化的灵魂是:$F = G + H$,其中$G$是路径当前消费,$H$是后续预期消费,$F$是总消费。$G$的计算十分简单,在我的算法中是每格加10花费;$H$的计算则是基于机器人可以八向移动的前提(尽管实际上它们并不能八向移动……这是为了避免向曼哈顿距离的退化),算式为$10 * \left| x_{distance} - y_{distance} \right| + 14 * min {x_{distance}, y_{distance}}$。

相比于传统的bfs,A*使用优先级队列,总花费最小的路径永远在顶部优先处理。这样就实现了有明确倾向的寻路,较无目的地向四周广搜提升了性能。

但是由于在躲避炸弹时,并没有一个明确的终点,所以仍采用bfs进行寻路。

在游戏中可以使用组合键 shift + g 切换机器人难度,使用组合键 shift + h 显示或隐藏寻路结果——这是一个调试功能,我进行了一个保留。

运行效果还是可以接受的。