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

embedded/2025/2/24 6:43:03/

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

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

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

这个概念就是:循环

循环的意图

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

事件循环

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

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

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.ppmy.cn/embedded/164770.html

相关文章

AI工作流+专业知识库+系统API的全流程任务自动化

我有点悲观,甚至很沮丧,因为AI留给普通人的机会不多了,这既是人类之间权力的斗争,也是硅基生命和碳基生命的斗争。AI自动化是无法避免的趋势,如果人类不能平权,那就只能跪下接受审判。 通过整合AI工作流、专…

【Java高级篇】——第16篇:高性能Java应用优化与调优

第16篇:高性能Java应用优化与调优 Java应用的性能优化是构建高并发、低延迟系统的关键挑战。本文将从 性能分析工具、JVM调优、代码级优化、并发模型优化、数据库优化 等维度,结合线上事故案例与实战调优经验,系统梳理Java性能优化的完整方法…

[数据结构]栈详解

目录 一、栈的概念及其结构 二、栈的实现 1.栈的初始化 void STInit(ST* ps); 2.栈的插入 void STPush(ST* ps, STDataType x); 3.栈的删除 void STPop(ST* ps); 4.栈的大小计算 int STSize(ST* ps); 5.判断栈是否为空 bool STEmpty(ST* ps); 6.栈的销毁 void STDestro…

【PX4日志解析报错】pyulog工具解析日志出错

px4的日志文件一直通过pyulog做解析 但是发现有的时候会出现错误,解析不了,使用网站解析也会失败,但是这把数据很重要所以想办法必须解决,报错如下 zzhzzh-System-Product-Name:~/Desktop/log/log223/log42$ ulog_info -m MESSAG…

朗致集团面试-Java架构师

总结 三轮面试,第一轮是逻辑测试性格测试,第二轮是技术面试(面试官-刘老师),第三轮是CTO面试(面试官-屠老师)。第三轮Coding做完之后共享屏幕讲一个你自己负责过的项目(请提前准备好…

【FAQ】HarmonyOS SDK 闭源开放能力 —Live View Kit (1)

1.问题描述: 客户端创建实况窗后,通过Push kit更新实况窗内容,这个过程是自动更新的还是客户端解析push消息数据后填充数据更新?客户端除了接入Push kit和创建实况窗还需要做什么工作? 解决方案: 通过Pu…

【部署优化篇三】《DeepSeek边缘计算实战:把目标检测模型塞进树莓派,让AI在巴掌大的设备上“开天眼“》

“谁说只有超级计算机才能跑AI?今天咱们就要在树莓派上玩转DeepSeek目标检测,让这个巴掌大的小盒子变成会‘看’世界的智能终端!” 本文手把手教你从零开始,把最潮的目标检测模型塞进树莓派。全程高能预警,建议准备好你的树莓派4B/5和散热风扇,咱们这就开启边缘计算的魔法…

第二周补充:Go语言中取地址符与fmt函数详解

第二周补充:Go语言中&取地址符与fmt函数详解 在Go语言中,& 是取地址符,用于获取变量的内存地址。具体到代码 fmt.Scanln(&celsius) 中: 作用:&celsius 获取变量 celsius 的内存地址,使得 …