当前位置: 首页 > article >正文

游戏设计模式阅读 - 游戏循环

游戏与普通程序最大的不同点在于:

        游戏不像其他大多数软件,游戏即使在没有玩家输入时也继续运行。 如果你站在那里看着屏幕,游戏也不会冻结。动画会持续播放。视觉效果继续闪烁。 如果运气不好的话,怪物会继续暴揍你的角色。

那么维持这一切的必要条件是什么,在代码层面又是如何维持你的游戏世界不陷入时停的呢?

这个概念就是:循环

循环的意图

        将游戏的进行和玩家的输入解耦,和处理器速度解耦。

事件循环

        游戏即使在没有玩家输入时也继续运行。

        这是真实游戏循环的第一个关键部分:它处理用户输入,但是不等待它

while (true) {
  processInput();
  update();
  render();
}

// processInput()处理上次调用到现在的任何输入。
// update()让游戏模拟一步。 运行AI和物理。
// render()绘制游戏,

什么是帧率

        我们用实际时间来测算游戏循环运行的速度,就得到了游戏的“帧率”(FPS)。如果游戏循环的更快,FPS就更高,游戏运行得更流畅、更快。

两个因素决定了帧率:

1、每帧要做多少工作

        复杂的物理,众多游戏对象,图形细节都让CPU和GPU繁忙,这决定了需要多久能完成一帧。

2、底层平台的速度

         更快的芯片可以在同样的时间里执行更多的代码。 多核,GPU组,独立声卡,以及系统的调度都影响了在一定时间内能够做多少东西。

每秒的帧数

        早期的游戏被仔细地编码,一帧只做一定的工作,开发者可以让游戏以想要的速率运行。

        但是如果你想要在快些或者慢些的机器上运行同一游戏,游戏本身就会加速或减速。

一个游戏循环在游戏运行过程中被不断执行。 每一次循环,它无阻塞地处理玩家输入更新游戏状态渲染游戏。 追踪时间的消耗并控制游戏的速度

优化上面的简易循环代码

        上面代码的问题是无法控制游戏运行得有多快

        假设游戏需要以60FPS运行,那么每帧约等于16ms。只要用少于这个时长处理游戏内的所有内容,就可以以稳定的帧率运行。所需要做的就是处理这一帧,然后等待,直到处理下一帧。

while (true) {
  double start = getCurrentTime();
  processInput();
  update();
  render();
  sleep(start + MS_PER_FRAME - getCurrentTime());
}

但如果每次循环消耗的时间超过16ms,那它永远也跟不上

需要解决的问题:

1、每次更新将游戏时间推动一个固定量。

2、消耗一定量的真实时间来处理它。

因此还可以基于上帧到现在有多少真实时间流逝来选择前进的时间。这一帧花费的时间越长,游戏的间隔越大。

double lastTime = getCurrentTime();
while (true){
  double current = getCurrentTime();
  double elapsed = current - lastTime;
  processInput();
  update(elapsed);
  render();
  lastTime = current;
}

每一帧,计算上次游戏更新到现在有多少真实时间过去了。在更新游戏状态时将其传入,然后游戏引擎内推进一定的时间量。

假设有一颗子弹跨过屏幕。 使用固定的时间间隔,在每一帧中根据它的速度移动它。 使用变化的时间间隔,根据过去的时间拉伸速度。 随着时间间隔增加,子弹在每帧间移动得更远。 无论是二十个快的小间隔还是四个慢的大间隔,子弹在真实时间里移动同样多的距离。

但这个方案也不合理:游戏不再是确定的了,也不再稳定。

游戏时间追逐真实时间

        计算上一次游戏循环过去了消耗了多少真实时间。 为游戏的“当前时间”模拟推进相同长度的时间,以追上玩家的时间。

double previous = getCurrentTime();
double lag = 0.0;
while (true) {
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;
  processInput();
  while (lag >= MS_PER_UPDATE){
    update();
    lag -= MS_PER_UPDATE;
  }
  render();
}
// 在每帧的开始,根据过去了多少真实的时间来更新lag。 这个变量表明了游戏世界时钟比真实世界落后了多少。
// 使用一个固定时间步长的内部循环进行追赶。 一旦追上真实时间,就执行渲染然后开始新一轮循环
// MS_PER_UPDATE只是更新游戏的间隔。 这个间隔越短,就需要越多的处理次数来追上真实时间。

// 可以通过限制内层循环的最大次数来保证游戏不会完全卡死

由于render()次数比update()次数更少,就有可能出现render()发生在两次update()之间。

因此,需要在lag不为零lagMS_PER_UPDATE更小时,跳出循环进行渲染。 lag的剩余量就是到下一帧的时间。

render(lag / MS_PER_UPDATE);

决策

使用平台的事件循环:

1、简单
        不必担心编写和优化自己的游戏核心循环。

2、平台友好
        你不必明确地给平台一段时间让它处理它自己的事件,不必缓存事件,不必管理任何平台输入模型和你的不匹配之处。

3、失去了对时间的控制。

使用游戏引擎的循环:

1、不必自己编写
        编写游戏循环非常需要技巧。 由于是每帧都要执行的核心代码,小小的漏洞或者性能问题就对游戏有巨大的影响。 稳固的游戏循环是使用现有引擎的原因之一。

2、无法自己编写
        代价就是如果引擎无法满足真正的需求,开发者也没法获得控制权。

自己写:    

1、完全可控
        可以做任何想做的事情。可以为游戏的需求订制开发。

2、需要与平台交互
        应用框架和操作系统通常需要时间片去处理自己的事件和其他工作。 如果拥有应用的核心循环,平台就没有这些时间片了。 开发者得显式定期检查,保证框架没有挂起或者混乱。


http://www.kler.cn/a/559445.html

相关文章:

  • linux下软件安装、查找、卸载
  • ROS2 中 TF 变换发布与订阅:实现 base_link 和 test_link 实时可视化显示
  • Elasticsearch Open Inference API 增加了对 Jina AI 嵌入和 Rerank 模型的支持
  • Matplotlib,Streamlit,Django大致介绍
  • UniApp SelectorQuery 讲解
  • php重写上传图片成jpg图片
  • 【Linux】基于UDP/TCP服务器与客户端的实现
  • MATLAB中isspace函数用法
  • 霍格沃茨在线:开启你的魔法世界之旅
  • 《量子:开启未来的科技密码》:此文为AI自动生成
  • Linux虚拟机快照
  • 排查JVM的一些命令
  • 单链表相关操作(基于C语言)
  • element ui的select选择框
  • 详解 torch.triu:上三角矩阵的高效构造(中英双语)
  • Ubuntu cgroups v2切换cgroups v1
  • w803|联盛德|WM IoT SDK2.X测试|window11|TOML 文件|外设|TFT_LCD|测试任务|(5):TFT_LCD_LVGL示例
  • Zabbix 7.2实操指南:基于OpenEuler系统安装Zabbix 7.2
  • 音视频封装格式:多媒体世界的“容器”与“桥梁”
  • 达梦DTS数据迁移工具生产篇(MySQL->DM8)