Flappybird(基于Unity)修改与完善
项目分析
在引擎中运行原游戏
这是一个经典的游戏Flappy Bird。在游戏中,我们需要点击空格键来使角色向上飞行来避开管道(障碍物),而不让小鸟碰到这些障碍物或飞出屏幕。

游戏实现原理分析
在Flappy Bird游戏中,有多种判定需要实现,包括小鸟和障碍物的碰撞判定、小鸟飞出屏幕的判定以及得分的计算判定。
通过阅读源代码,我分析出游戏中各项主要判定的实现方式如下:
- 小鸟和障碍物的碰撞判定:该判定需要检测小鸟是否与障碍物发生碰撞。实现方法是使用矩形碰撞检测。具体来说,将小鸟和障碍物分别添加矩形碰撞箱,并检测它们是否有重叠部分。如果有重叠部分,则判定为碰撞发生。
- 小鸟飞出屏幕的判定:该判定需要检测小鸟是否已经飞出了屏幕。实现方法是检测小鸟的纵坐标是否超出了屏幕的上下边界。
- 得分的计算判定:该判定需要检测小鸟是否成功通过了一组障碍物,并进行得分的计算。在此,游戏在管道中单独添加了无实体的碰撞箱,小鸟的碰撞体积与该碰撞箱接触后,通过Logic Manger使得分计数器加一。
探索可能的问题
问题1:帧率判定问题
问题1概述
首先,在我调试游戏时,我发现游戏并没有设定运行帧数的限制,而在这样简单的游戏中,unity引擎往往会以很高的帧率来运行它。查看game status,发现果然如此。FPS数值在400-1200间波动。

在Unity中,所有的Update脚本都是按帧运行的。因此,如果帧率极高而且不稳定,游戏中的变化将非常快,而且没有固定的时间间隔。这使得我们难以与游戏进行交互。
这里有一个简单的验证,加入我们把movespeed调成引擎中常规的数值(比如5这样的个位数),我们可以发现在极短的时间内,游戏中管道就已经移到x轴的-10000左右的位置,完全没有反应时间,游戏就已经失败了。

原游戏中为了使游戏运行起来,只能将movespeed设定为一个极低的数据,才使得游戏能正常运行(玩家能看见管道缓慢移动)。

这或许是最简单的解决方法,但它是极不合理的。通常Unity中的数据有一个较为统一的范围,使得每个数据都能和其他数据互动、运算、判定。这样极小的数据显然可互动性极差,导致后续设及移动有关的数据都需要做预处理。
这还会导致一个更为严重的问题,也是在其他项目中实际出现过的问题,“帧数跳过”(Frame Skipping)。具体而言,当游戏运行在高帧数(例如120帧每秒)或者帧数不稳定时,游戏伤害判定数值会有多次浮动。这是因为游戏的代码基于帧判定,确并没有运行在一个相对稳定的帧数、也没有对代码进行限制,导致游戏引擎快速跳过了一些帧,使得碰撞被多次判定。
游戏引擎使用的是时间步长(Time Step)机制来控制游戏中的时间流逝。时间步长是指游戏引擎在每一帧中处理游戏逻辑的时间长度,例如,如果时间步长为0.016秒,那么游戏引擎每16毫秒处理一次游戏逻辑。
在游戏中,许多动作都是基于时间步长来计算的,例如角色移动、怪物攻击等。当游戏引擎的帧率很低(例如30帧每秒)时,时间步长会相应地增加,以确保游戏逻辑在一定时间内得到处理。而当游戏引擎的帧率很高(例如120帧每秒)时,时间步长会相应地减少,以确保游戏逻辑能够在更短的时间内得到处理。
问题1的解决
这里,我选择了引入Time.deltaTime 来解决这个问题。通过查阅Unity引擎开发文档中时间和帧速率管理,我找到了这个解决方案。在Unity引擎中,DeltaTime是一个代表自上一帧(Frame)到当前帧所花费的时间的变量。它通常被用于控制游戏对象的移动速度、动画、物理模拟等。如果deltaTime的值在一帧之内非常小,那么游戏对象的移动速度将减慢,从而使游戏变得更加平稳。优化游戏的帧率和交互性是非常重要的,这样玩家才能够享受到游戏的乐趣并获得更好的游戏体验。

间而言之,从上一帧到当前帧的间隔Unity 可能每帧多次调用它。而deltaTime函数能使脚本基于具体时间判定而非帧。
原代码如下:
// Update is called once per frame
void Update()
{
transform.position = transform.position + (Vector3.left * moveSpeed);
}
修改后代码如下:
public class PipeMoveScript : MonoBehaviour
{
public float moveSpeed = 5;
public float deadZone = -45;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = transform.position + (Vector3.left * moveSpeed)*Time.deltaTime;
}
}
解决方式检验
我们可以看到,即使movespeed常规设定,管道移动速度也变得合理。

问题2:资源占用问题
问题2概述
在运行游戏时,我们可以发现左侧场景栏中的游戏对象(Game Object)会随着时间的推移不断增多,逐渐占用硬件资源,而且这个过程是无限的。也就是说,当游戏进行到一定时间后,必然因场景内资源堆积过多而卡顿,然后崩溃。

在Unity中,当不再需要一个GameObject或其他资源时,它们通常会保留在内存中,这会占用计算机的资源并可能导致应用程序性能下降。因此,需要主动删除这些废弃资源,保证程序可运行性。
问题2的解决
而在实际的游戏过程中,我们只需要显示在屏幕范围内的管道存在即可。阅读游戏代码可发现,管道的产生原理是在屏幕右边生成然后向左边移动。也就是说,如果想要解决资源占用问题,我们只需要销毁已经从左侧移出画面的管道即可。
查阅Unity开发文档,我发现有两种销毁资源的方式,手动删除或是使用Unity的垃圾回收机制来自动销毁它们。
对于本项目中的管道对象,由于此处判定过的管道不存在复用的可能,我选择使用Destory函数,在帧结束后销毁游戏对象。在产生管道的代码中,将每个管道对象添加到一个列表中,并在管道移动到屏幕左侧时,将其标记为一次性资源并设置销毁时间。这样,我们就可以避免不必要的资源占用并保证游戏的性能和稳定性。
原代码如下:
public class PipeMoveScript : MonoBehaviour
{
public float moveSpeed = 5;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = transform.position + (Vector3.left * moveSpeed)*Time.deltaTime;
}
}
修改后代码如下:
public class PipeMoveScript : MonoBehaviour
{
public float moveSpeed = 5;
public float deleteSpace = -45;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = transform.position + (Vector3.left * moveSpeed)*Time.deltaTime;
if (transform.position.x<deleteSpace)
{
Destroy(gameObject);
}
}
}
解决方式检验
在删除函数中加入Debug语句。这样一来,我们可以清楚的看到超过一定范围的管道被销毁了,管道数量稳定在个位数。

总结
以上,就是我游玩了Github上开源的FlappyBird游戏,并结合源代码找到的两个问题:帧率同步和资源占用,以及解决方案。我总共做了这些:
- 通过多次游玩原游戏来测试,搞清楚游戏的运行机制与判定方法,同时通过以往的游戏经验来思考可能存在的问题。
- 查看游戏在Unity引擎中的运行状态以及源代码,确定具体问题。
- 翻阅Unity开发文档,浏览开发者社区,寻找可行的解决问题方向。
在探索软件缺陷与二次开发的过程中,我具体使用了以下解决方案:
- 针对Unity中Update脚本的运行机制,我引入了Time.deltaTime来解决,这个变量代表自上一帧到当前帧所花费的时间,可以用于控制游戏对象的移动速度、动画、物理模拟等。
- 针对游戏对象会随着时间的推移不断增多,逐渐占用硬件资源问题,我选择了自动销毁已经从左侧移出画面的管道,以保证游戏的性能和稳定性。
当然,如果要实际完善这款游戏到可发行的程度,它还有许多地方可以修改。这次实践,我主要是为了探索与思考寻找软件缺陷与尝试二次开发的流程。我修改了原游戏中较为严重的问题,使游戏更有可玩性和流畅运行。在此过程中,我学习了软件开发的有效流程,并实际解决了问题,总而言之,收获颇丰。
参考资料:
原Unity程序:https://github.com/Britishgaming/GMTK-Unity-Tutorial
Unity开发文档参考部分:https://docs.unity3d.com/ScriptReference/Time-deltaTime.html
Debug:https://docs.unity3d.com/ScriptReference/Debug.html
参考书籍:Level Up! The Guide to Great Video Game Design
Frame Skipping在NVIDIA社区中:https://www.nvidia.com/en-us/geforce/forums/gaming-pcs/8/274795/frame-skipping-on-good-computer/

浙公网安备 33010602011771号