程序响应性

【如果你不能在游戏中控制你的行动,是否应该责怪这个游戏呢?在这篇技术文章中,Neversoft的合伙人Mick West会阐述游戏中造成响应延迟(Lag)的问题,并提出一些必要的解决方案。】

响应性是一种可以在第一时间成就或毁坏一个游戏的东西。在一些游戏的杂志评论中,可以明显体会到响应性差的游戏会被形容为“迟钝的”,“没反应的”,“浮躁的”或“懒散的”。而好的游戏会被评价为“紧凑的”或“有响应的”。

响应性可以从几个方面来理解,本文主要是从一个程序员的角度来阐述,提供一些可以提高游戏响应性的方法。

响应延迟

响应延迟是指从玩家触发一个事件到玩家收到事件发生的反馈(通常是视觉上的)之间的时间迟滞。如果时间迟滞过长,游戏会显得没有响应。有几个因素决定了响应延迟时间长度。

如果你的游戏是没有响应的,这很可能是4或5个不同因素造成的累积效应。只是单独调整其中一个因素,是不能起到明显作用的。但是找出所有的因素,并调整它们,会带来显而易见的改进。

玩家,有时甚至是游戏设计师,都不能把他们在游戏中操作觉得不适的地方用语言表达出来。通常他们会试着做一些需要同步操作的事情,但是失败了,他们不会告诉你“这个事件在我输入后的0.10秒才发生作用”,而是会说这个游戏有些“迟缓”,“不紧凑”或者“难度高”。

或者他们根本不告诉你一些重点,只是单纯说这个游戏很烂,但不知道为什么很烂。

设计师和程序员需要时刻注意响应延迟以及它对游戏的负面作用,甚至在测试玩家没有直接汇报这一点时。

为什么发生延迟

要了解延迟发生的原因,你需要理解事件的发生序列:从用户按下一个按钮,到结果显示在屏幕上。为了理解这一点,我们需要看一下游戏的主循环结构。主循环进行了两个基本任务:逻辑和渲染。

主循环的逻辑部分更新了游戏的状态(游戏对象和环境的内部实现),渲染部分则创建了需要显示在电视上的一帧画面。

在主循环的一些阶段,通常是开始时,我们会接受到用户的输入,作为主循环的第三项任务,但也通常把它作为逻辑任务的一部分。在这里我把输入单独提出来,是为了看清楚事件发生的顺序。

有几种方式来描述主循环结构。最简单的一种如表1所示,交替调用逻辑和渲染的代码。我们假设游戏运行在固定帧率,通常是60fps或30fps的NTSC制式家用机游戏,而一些帧的同步发生在调用Rendering()中。

1:简单的主循环

while (1) {
Input();
Logic();
Rendering();
}

在这里主循环只展现了故事的一半。对Rendering()的调用是在CPU端处理渲染任务,这些任务包括场景、物体、剔除、动画、排序、设置变换、构造一个供GPU处理的显示列表。这是一个迭代的过程。

实际上GPU渲染是在CPU渲染之后进行的,通常是异步的。所以当主循环开始进行到下一帧时,GPU仍然在渲染前一帧。

那么延迟在什么时候发生?要理解造成延迟的原因,你需要理解从用户按键输入到接受反馈之间的事件序列。

在最顶层,用户按下一个按键;游戏逻辑读取这个按键输入,更新游戏状态;CPU渲染函数准备好渲染这个新状态的一帧,然后GPU将其渲染;最后这个新的一帧被显示在屏幕上。

1:当玩家按下一个键,游戏要占用3帧(最理想情况)来创建一个视觉反馈,程序问题会引入多余的帧造成延迟。实际延迟的时间是每一帧时间长度的乘积。

图1显示了图形化的序列内容。在第一帧的某一时刻,玩家按下一个键来开枪。输入过程完成后,这个输入会被读取到第二帧。第二帧更新了基于按键输入的逻辑状态(开火)。

仍然在第二帧,渲染的CPU端开始执行这个新的逻辑状态。然后在第三帧,GPU执行了这个新逻辑状态的实际渲染。最后在第四帧的开始,新渲染出来的一帧画面从帧缓冲中呈现给玩家。

那么这个延迟有多长时间?这要看一帧有多长时间(这里“一帧”是指主循环的一个完整迭代过程)。从用户输入到转换成视觉反馈,需要占用3帧。

如果我们的游戏在30fps下运行,延迟就是3/30,即十分之一秒。如果游戏在60fps下运行,那这个延迟会是3/60,即二十分之一秒。

这个计算说明了一个关于60fps和30fps之间的差异的判断错误。因为这两个帧率之间的差异是1/60秒,人们推断出响应性之间的差异也是1/60。

但实际上,从60到30并没有给延迟增加一个垂直同步,它的效果是乘积形式的,加倍了延迟响应的过程管线。在我们图1中的理想示例中,增加了3/60秒,并不是1/60。如果事件管线再长一些,这个结果会变得更多,这是极有可能发生的情况。

实际上图1展示的是事件序列的最佳状态。按键输入通过最短路径转换为视觉反馈。我们可以在事件序列中清楚的看到这一点。

作为一个程序员,熟悉这些事件发生的顺序,是理解游戏中事物运作原理的重要环节。如果对事件发生的顺序不加注意的话,很容易造成多余帧的延迟(意味着一个1/60或1/30秒的延迟)。

举一个简单的例子,如果我们把主循环中的Logic()和Rendering()交换一下,想想会发生什么。来看一下图1的第二帧:这里GPU逻辑(渲染)发生在CPU逻辑之后,所以在第二帧开始时输入会影响CPU逻辑,之后是同一帧里的GPU逻辑。

然而如果GPU逻辑在前面进行,那么输入就得到下一帧才能影响到GPU逻辑,因此造成了一个多余帧的延迟。虽然这只是一个初学者犯得错误,但程序员仍要牢记不要让它发生。

造成延迟的多余帧可能在一些微小的行为中产生,但结果会影响游戏逻辑的整个序列。在我们的例子中,是开枪的过程。

假如现在我们的引擎用一个物理引擎来设置场景中物体的位置信息,并且需要处理的事件在更新中增加了(例如碰撞事件)。这种情况下,输入的序列或逻辑如表2所示。

2:更新物理之后是事件处理

void Logic() {
HandleInput();
UpdatePhysics();
HandleEvents();
}

基于消息的事件处理(Event Handling)是用于低耦合系统的一个不错的手段,程序员也通常会使用它来控制事件。要使枪开火,HandleInput()函数会发出一个事件,告诉枪去开火。

HandleEvents()函数会处理这个事件,并使枪实际开火。但是在这一帧里,物理更新已经发生了,这个效果直到下一帧才能同步,这样就产生了一个多余帧的延迟。

更多延迟的原因

低层的行为顺序会造成更多的延迟。例如,考虑一个跳跃。反馈内容是角色实际的移动。要使游戏中的物体移动,你可以直接设置速度,或者给物体加一个作用力,例如加速度,或者瞬间的推力。

这个情景会产生一个问题,就是如果你的物理引擎在速度改变之前更新位置信息——这是很多游戏编程入门教程中的常见情况。

尽管在处理输入事件后,物体跳跃的速度变化已经在同一帧里更新了,但是物体直到下一帧才能开始改变位置,这样就产生了一个多余帧的延迟。

要记住,问题都是累积起来的,并且很难单独去辨识,这些组合的效果会使你的游戏操作变得很艰难。

假设你同时犯了以上三个错误:你在逻辑之前进行渲染,你在物理状态进行之后处理逻辑事件,并且你在速度变化之前更新位置信息。这是从玩家输入到接受反馈的三个主循环的完整迭代,在这三帧之上,你至少创建了6帧的延迟。

在60fps的游戏中,这会是1/10秒,已经足够糟糕了。但如果你的游戏是在30fps下,这个延迟会加倍,变为无法忍受的1/5秒,即200毫秒。

综合以上的问题,一些其他因素也可以导致延迟。动作可以由动画来驱动,由动画特定时间点的速度变化来实现。例如,如果一个动画师为了让动画与视觉更匹配,在动画中设置了一个不到一秒的跳跃速度变化,这可能看起来挺好,但实际上感觉却很糟。

动画师可以修正这个问题,只要确保速度变化是在动画的第一帧发生,玩家可以迅速得到反馈。但又有一个问题产生了,就是如何触发一个动画,并转换为实际的动作?看起来动画的更新是由Render()函数来处理的。但任何以动画形式触发的事件,都需要在增加一帧后的循环中才能处理。

此外,触发一个动画可能在下一帧才会发生作用,又造成一帧的延迟。我们的延迟会从6帧增加到8帧。即使在每秒60帧的情况下,这也就几乎不能玩了。

这些也并不是全部。还有很多方式使多余的延迟帧潜入一个游戏。你也许会把你的物理部分单独辟出一个线程(或者一个物理处理单元)。

如果你使用三倍缓冲来使你的帧率更平滑呢? 你也许用抽象事件通过系统的并行过程分解成真实事件。你也许在用一种脚本语言让等待事件时增加额外的一帧。

你可以通过组合各种关于时间和事件的概念,使你的游戏逻辑更灵活,这是非常必要的。但是在实现它们的时候,程序员可能会忽视发生在表面之下的事情,使造成延迟的多余帧悄悄潜入。

响应性,不是反应时间

有一个关于响应性的极大的误解,和人类的反应时间相关。人类不能通过视觉刺激做出身体上的反应,并且在0.1秒内移动他们的手指。

游戏玩家的顶峰反应时间分布在0.15秒至0.30秒,这取决于他有多“高桥名人”(形容按键反应迅速)。像这些可以计量的信息,通常在讨论游戏响应性时会提到。但它们之间是没有联系的。

不是玩家对游戏的反应有多块,而是游戏对玩家的反应有多快。问题并不在于反应时间,而是在于同步性。

用吉他英雄来举例,这个游戏中会有一些符号出现,玩家需要在恰当的时间按下相应的按键(当目标物体在特定区域范围时)。你是在预判断事件,这里不涉及任何反应时间。

缺乏响应性的问题一般是游戏没有及时反馈玩家的操作,并且事件发生时目标物体已经超出目标范围了。

如果你在正确时间按下键,你绝不希望物体会在爆炸前再多移动几个像素。但是物体通常在每帧会移动几个像素,有几帧的延迟使物体滑过目标。

有许多基于预测和按键输入的游戏。在一个滑板游戏中,你想要在到达轨道终点之前跳跃。在一个第一人称射击游戏中,你朝一个前方快速跑动的敌人开枪。

再提一次,响应性并不是反应时间。你通常会在射击的半秒前看见你的目标,或许更长时间,你会移动你的枪,或者等待目标跑到你的准星上。

对响应性问题是不能靠直觉来解决的,程序员全面了解问题本身才是非常重要的。最重要的一点,是要清楚按键触发行为任务到创建视觉反馈这个过程中逻辑和渲染的逐帧过程。一旦你理解了这个过程,你就能够优化它,使它工作在最佳状态。

时间: 2025-01-20 11:08:30

程序响应性的相关文章

打造可靠的Ajax应用程序: 第1部分:构建前端

简介:如今,Ajax 仍然是业界的热门字眼,越来越多的应用程序都采用 Ajax 技术构建.然而,构建一个好的 应用程序并不容易.本文将着重讨论如果构建 直 观易用的受 Ajax 驱动的应用程序. Ajax 并不只是一种技术.大多数开发人员却认为它是,并试图借助诸如 XML 和 JavaScript 这类语言证明其观点.但这种观点非常局限,并且,忽视了付钱 给您的人:客户,不管是咨询代理还是您的老板(只有您建立了令人满意的用户 群,他们才能获得收入). 客户并不关心技术:他们关心的是应用程序的外在

.NET程序的性能要领和优化建议

前几天在老赵的博客上看到,Bill Chiles (Roslyn 编译器的Program Manager)写了一篇文章叫做<Essential Performance Facts and .NET Framework Tips>.这篇文章是一个14页的pdf,当时我是在地铁上在Lumia手机上看的,觉得很是不错,这里也建议大家直接下载阅读原文,我这里试着翻译一下,以加深自己印象,后面也有一些思考,以下是原文内容: ----------------------------------------

.NET开发中性能的基本要领及优化建议

老赵的.NET程序性能的基本要领 说起Roslyn大家肯定都已经有所耳闻了,这是下一代C#和VB.NET的编译器实现.Roslyn使用纯托管代码开发,但性能超过之前使用C++编写的原生实现.Bill Chiles是Roslyn的PM(程序经理,Program Manager),他最近写了一篇文章叫做<Essential Performance Facts and .NET Framework Tips>,其中总结了几条经验,目前是个CodePlex上的PDF文件,以后可能会发布在MSDN上.

大数据处理驱使云存储的需求日益增多

企业的IT策略正迫于压力做出改变,云计算在修正方案中变得越来越重要.IDC分析家希望企业IT商店可以增加私有云方案的实施.这些云方案中的核心部分往往是存储. 预计2010年至2015年,私有云存储的花销将达到28.9%的年均增长率.照此速度,私有云存储在2015年前将翻三倍.这比当前年均增长为4%的内部存储要巨大.报告指出,虽然未来四年会看到云服务收入的巨大增长,但是要求公共云和私有云供应商花费的资金也同样巨大.未来四年,私有云与公共云供应商预期成为IT产品花钱最大方的两大巨头. IDC存储与I

如何使用AJAX技术构建优秀的Web应用程序

一. 简介 异步JavaScript+XML(即Ajax),是一种创建交互式web应用程序的Web开发技术.这种程序使用JavaScript和XML从客户端提交服务器请求,且整个过程中仅需要交换少量的数据而不必提交整个web页面.因此,这样的程序将更快和更具响应性,并将成为新一代客户机-服务器系统的重要基础技术之一.你可以在站点http://www.google.com/webhp?complete=1&hl=en处看到一种良好的AJAX实践技术展示.在此页面中,如果你把任何字母输入到文本框内,

使用AJAX技术开发新一代Web应用程序(3)

ajax|web|程序 这并不是在诋毁Amazon,在非常有限的限定内它工作得相当优秀.但是与工作表相比,它所依赖的交互模型毫无疑问相当有限. 那么,为什么在现代web应用程序中存在这么多的限制呢?目前,存在很多技术上的原因.因此,现在让我们作进一步分析.三.网络的潜力 互联网时代的伟大就在于世界各地所有的计算机互相联系,就象在一个非常大的计算资源之中.远程和本地过程调用变得很难区分,并且发行者已经不再清醒地了解它们在哪些物理机器上工作.  不幸的是,远程和本地过程调用是根本不相同的技术. 在网

精通J2EE应用程序开发之交叉分析J2EE

j2ee|程序 在不久前的一段时间内,Java 开发人员在准备一个新的企业 Java 开发项目时,事先就知道将要使用的工具.当时,一切都很简单:J2EE 是新的,HTML 浏览器是公认的用户界面标准,而复杂性(至少从推测的角度而言)已成为过去的事情.而如今,事情变得如此复杂. "开发人员面对的选择令人眼花缭乱." 开发人员面对的选择令人眼花缭乱,从"轻型容器"(如 Spring.NanoContainer 或 HiveMind)到"web 框架"

ASP.NET+Atlas创建客户端Web应用程序

asp.net|web|程序|创建|客户端 提要 本文介绍了Atlas框架,并探讨它的客户端和服务器端类库及其编程模型.另外,本文还详细剖析了一个支持Atlas功能的示例Web应用程序. 一. 开发环境说明 本文中所提供的信息适用于下列技术:Asp.net 2.0,Asp.net Atlas CTP,Visual Studio Professional 2005和Visual Web Developer 2005. 二. 简介 Atlas是一个框架的代号,该框架对于客户端Web应用程序的开发方面

使用AJAX技术构建更优秀的Web应用程序

ajax|web|程序 一. 简介 异步JavaScript+XML(即Ajax),是一种创建交互式web应用程序的Web开发技术.这种程序使用JavaScript和XML从客户端提交服务器请求,且整个过程中仅需要交换少量的数据而不必提交整个web页面.因此,这样的程序将更快和更具响应性,并将成为新一代客户机-服务器系统的重要基础技术之一.你可以在站点http://www.google.com/webhp?complete=1&hl=en处看到一种良好的AJAX实践技术展示.在此页面中,如果你把