项目分析
在引擎中运行原游戏
这是一个经典的游戏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;
}
}