从2009年开始”非常规编程技巧”成为了开发者之间的一个热门话题,通过这个话题,我们得知了一些开发者为了按期发布或是通过测试而”发明”的非 常手段。在这里我们将为大家分享9个(带着硝烟味的)故事,包括(非编程的)其他开发行业的故事。阅读这篇文章,可以使你陶醉于同事的奇思妙想,并且长出 一起口气——因为你不是唯一一个在压力下掉节操的。 1. Crouching RSX,隐藏的纹理资源
Joe Valenzuela, Insomniac Games 这一招使用在了PS3上:当时在Insomniac的引擎团队,我们希望将一些纹理资源和我们的引擎/工具一起发布。这些资源类似于噪音纹理并且用 于全局滤镜特效。出于一些无关紧要的原因,我们没有直接发布这些资源,而是将它转为二进制数据并且编译到可执行文件之中。这样做有一个缺点,我们希望这些 资源在不同的内存块中(RSX可见),所以我们最终将它们复制出来但是浪费了一些内存。 PS3的链接工具有一个功能可以将一些段放在RSX内存中,但是需要占用至少1MB的内存,并且在我们的需求中会浪费700KB。所以最终我们向可 执行文件中添加了一个新的数据段别名为BSS(the “bss alias” or “balias” section)。在最终发布版中我们有3MB的BSS段,这样我们有足够的空间在保存我们的纹理资源。在CRT初始化目标内存之前,我们执行一段代码, 将BSS中的纹理数据拷出,然后重新将BSS段初始化为0。 不管你信不信,这段代码工作良好。为了适应BSS段的使用和工具的更新我们做了一些调整,不过总体来说还是很简单。
2. 这不是你正在寻找的错误(Bug) Brett Douville, LucasArts 在2002年,我们准备发布为Sony开发的游戏”星球大战:绝地战机”。一个琐碎的TCR bug仍然没有解决,在我们加载过关的过场动画时,这个bug会导致控制器的模拟摇杆功能关闭以及中心控制器的红灯熄灭。当我们更新为sony提供的库版 本时,这个bug被发现,而开发加载动画和IOP逻辑控制器功能的程序员已经离职了2个月。 为了在这些我不熟悉的代码中快速的定位问题,我在7处代码可能出错的地方插入了不同颜色的清屏代码,希望通过观察控制器关闭模拟功能前的屏幕颜色来缩小错误代码的范围(很常规的采用log排查bug,这个方法的价值比故事大)。但是当我再次测试时,bug没有重现。 有一个古老的编程谚语,如果你不知道确切的原因,你就不能说已经解决bug。但是现在我们离版本发布只剩下2-3天,而这个bug并没有什么大不了,所以我将所有的清屏颜色改为黑色,并将bug标志为已经解决,然后测试了一整天。之后我们如期发布,而TCR bug没有来搅局。 ![]()
3. (s)elf-exploitation Jonathan Garrett, Insomniac Games Ratchet and Clank: Up Your Arsenal是一款有着在线的名义但是很不幸缺乏在线升级功能的游戏。 每次游戏发布需要下载并显示一个最终用户许可协议。许可协议以ASCII字符串的形式存储在静态缓冲区,缓冲区被写入从服务器下载的数据,并且不会检查缓冲区的大小。 我们利用这个隐患,在EULA下载的时候导致静态缓冲区溢出并且覆盖了一个全局的变量。这个变量正好是处理网络数据的回调函数地址。一旦覆盖了这个变量,我们就可以发送数据,并且跳转到新的(被覆盖写入的)地址。这个地址指向了一些再下载EULA之前下载的可执行的代码。 有效的数据在EULA缓冲区尾部和被覆盖的全局变量之间,所以代码执行的首要工作是恢复被丢弃的数据。一旦数据恢复正常,就可以开始实际的修补工作。 一个复杂的问题是使用strcpy复制EULA文本。当strcpy发现一个值为0的字节(一般是字符串结尾)时会停止工作。而我们的字符串通常包含值为0的字节。因此我们对编译后的代码编码为不含值为0的字节的并且有一块精心制作的引导区进行解码。 最终,这个手段过程如下:
要点:发布的游戏包含在线升级和不要使用不安全的strcpy。Include patching code in your shipped game, and don’t use unbounded strcpy.
4. Jamming the cartridge 塞满磁带 Michael A. Carr-Robb-John, Monolith Productions 在1993年,我将Desert Strike从16位的主机移植移植到8位的主机。游戏所需要的磁带大小约为12K,并且更大容量的磁带超出了预算。现在12K听起来非常小,但是在当时 是笔不小的预算。在开发过程中,所有的图形和声音资源都被我压缩到了极限,唯一的可以压缩的只有部分代码。在那个时候,游戏一般采用的是汇编语言,我们采 用的是Z80汇编,所以我只有一个选择。我花了一周的时间查找了冗余代码,并且用内存占用更小的方式重写(一般会消耗更多CPU)。 当我完成之后,游戏安装到磁带只有98B的剩余空间!游戏被刻录成ROM经过几天的测试之后,提交到世嘉进行认证。不幸的是第一次测试没有通过,之后修复bug很快用完了这98B。当我们发布的时候,剩余空间只剩下6B。
5. The Dalton Allocator Jonathan Adamczewski, Insomniac Games 在一个快要结束的项目,我们发现在长时间游戏之后,触发过场动画无法正常播放影片。因为我们的内存分配器不能可靠分配足够内存,以供影片全屏播放。 我们需要找到一种方法以确保我们可以得到足够的内存。但是已经到了项目的晚期,我们没有预留足够的内存供影片播放使用,而且采用碎片整理堆的风险太大,从其他的模块得到剩余的内存也不切实际。 可以明确,影片播放的时候其他很多模块处于空闲状态,所以我们可以考虑从其他模块得到内存。然而这些潜在的内存来源要么太小,要么同样受困于碎片问题。GPU有大量的内存空间,但是因为一些原因影片播放器不能直接使用它做缓冲区,因此我们需要一些其它来源的地址。 转到另一个问题,我们游戏为主要资源分配内存的一个特性模式被发现了,当游戏开始的时候我们从磁盘载入资源,这些资源在内存中不会被修改。其中一些资源非常大。而影片播放的时候它们都不会被使用。 所以我们最终的做法。我们选择了游戏中的一个英雄(名为Dalton)最大的一套动画。过场动画触发时,在动画系统关闭之后将这套资源拷贝到GPU 的内存中,然后将资源占用的内存给影片播放系统使用。在影片播放结束后,动画资源被复制回原来的内存中,动画系统重新开始,游戏正常运行就像什么都没有发 生一样。实现的过程相当简单,虽然跨越了数帧以确保所涉及的系统的每一个步骤都安全的同步。 影片系统的内存被描述为从”the Dalton Allocator”分配,在动画资源的内存被强占之后。 (我发现这种事情在工作室已经有一些历史,在项目早期没有预留足够的空间,所以在GPU内存中随意塞入几个东西)
6. Certification headache Michael Carr-Robb-John, Monolith Productions 任何一个为游戏机开发过游戏的人都曾经为通过认证头疼过。大多数情况下,认证的标准一般只是一个惯例并且是良好的做法,但是总有一些标准会成为开发 商最头疼的问题。其中一个这样的标准就是,我们必须保证在用户选择开始游戏后4秒内显示第一帧画面。如果你的程序很大,在进入主菜单前至少要加载2-3 秒,而此后你还要加载一大堆的声音和资源文件。 我的游戏在用户开始游戏后需要26秒,所以这对我来说是个大问题。我的第一个优化是找出主菜单所必需的资源,其他资源都延后到需要时加载。出乎意 料,这样做优化了9秒的时间。在做了许多其它优化后,加载时间减少到10秒以内,但我没法再继续优化下去。在和另一个工程师讨论之后,我终于找到了最好的 办法。 这个游戏机要求在进入主菜单前必须播放2帧特定的画面,而且用户不可以跳过这2帧画面。这样就有了一个办法,我们最初只加载这2帧画面,在用户看这2帧画面时再加载主菜单所需要的资源,让我们来下……balabala……这次只画了5.5秒。 很不幸,这还是不能达到标准。所以最终我们不得不申请豁免,当然我们通过了(因为离标准已经很近了)。 ![]()
7. Painting sounds Edward J. Douglas, Flying Helmet Games 我长期为一个赛车系列游戏从事动画艺术工作。我们的游戏场景是一些网格和动作序列的组合。在我们的续作中,我们的野心越来越大,但是我们的技术更新比较慢。 我们的动画制作过程是,是从一个特效软件制作基础的动画,然后导入到三维动画程序中调整动作的时间和位置,接着重新导出到游戏的动作工具加入物理和 引擎的模拟信息,大量游戏数据被捕获,包括气体和来至控制器的信息,并保存在3D文件的元数据中,然后在游戏中再现。当时的想法是通过这个(读取对应3D 文件中的音效数据)驱动游戏的音效系统。 在几个续作之后问题又来了,在混合了手工捕捉的记录和汽车动作后,我们的动画变的非常复杂。以前老的通过使用3D文件的元数据来驱动我们汽车的音频 样本的做法行不通了——因为数据可能不存在。音效团队不可能像提交动画一样提交音频,因为在什么场景使用什么汽车取决于玩家的选择,所以音频需要能动态选 择。但是,我们游戏的音效已经赢得了无数奖项,特别是汽车发动机的声音,所以我们觉得继续使用它工作。 公测即将开始,事情变的疯狂起来,但是由我们的过场动画,音效,AI组成的疯狂而又才华横溢的团队找到了解决的办法。油门和刹车在3D场景中采用一 个浮动的立方体表示。如果一个艺术家加入,并且使用类似3D Max的编辑软件绘制一些曲线,他们可以画出期望的发动机声音曲线。音频团队的几个成员临时学习3D Max的使用,并且使用他们的直觉来判断什么转速模式采用什么样的声音曲线,然后赶在发布前,导入到所有场景。最终的音效听起来不错,但是最后采用的手段 让我们明白,我们需要更新技术保证续作能有更好的表现。 也是只有我是这么想的,在那个版本之后,我离开了工作室。几年以后,我遇到了一个做音效的家伙,在我之后加入了团队。不久后,我就意识到,他们并没有更新技术,他就是那个为最新续作绘制”发动机声音”的家伙。
8. And one for good luck Richard Morwood 我有一个背景纹理列表,在游戏滚动到对应场景时,我编码显示对应的背景。但是有一个背景图片被跳过,在花费了大量的时间调试之后,我仍然没有找到原因。离截止时间只剩下5天了,所以我插入了一个额外的纹理到列表中。Ta-da! 没有背景被跳过了:)
9. HR hacks Ben Burbank 当我在一家很大的公司工作时,有一个员工想通了,推动他职业发展的最佳方式就是尽可能多的给其他员工的绩效负面评价,这将提高他的绩效排名,并且进 而带来更高的奖金和股权分配。这样做并不容易,因为你需要确保被负面评价的是其他经理的下属,这样没有人可以识破你的诡计。我避免这种恶性循环的办法是, 去一个小地方工作,好棒。 Honorable mention: Nice save Chris Pruett, Robot Invader [Editor's note: This isn't, strictly speaking, a dirty game dev trick -- but we figured it's a handy way to use job skills for real-world problems. Also, it's a sweet story.(一个浪漫的故事)] 我的妻子很少会玩游戏,但是勇者斗巨龙系列,她在童年就开始着迷。几年前,她开始在我的老PlayStation上玩勇者斗巨龙VII,大约80小 时候(据我所知,已经完成主线任务的3/4)后,她发现,她的保存文件被损坏。在继续游戏的菜单中能出现,但是已经变灰无法选择。她变得绝望,生气,发誓 再也不玩游戏。 我发现一个设备DexDrive可以读取PS存储卡在PC上使用,在eBay上卖$15。我打算试试看能不能修复存档,但是我没有告诉我妻子,因为我不想给她希望再让她失望,因为我实际上觉得修复是不可能的。据推测,数据损坏时无法挽回的,但是另一方面,我猜它也许没有被损坏。 ![]()
使用DexDrive,我将被损坏的文件从PS存储卡读取到PC上,然后使用十六进制编辑器打开它。我最终把它打印出来,并且使用荧光笔标记了16 进制。尽管PS1保存在一个8KB的块中,但是8KB的16进制数打印了不少页。在一个PS1模拟器的作者写的非官方规范中,我了解了数据的主要结构:头 部,图标,最后数据本身。比较不幸,解码原始的游戏数据非常有挑战性,几天之后,我确定这比预计的工作量大不少。 最终,我把工作集中在数据头部。由于图标记录(这是在一个从文件顶部开始的偏移值,并且易于保存为像素数据)的位置,通过头部可以告诉程序开始和结 束的位置。如果继续游戏菜单显示存档被破坏,也许是头部的数据被损坏。我测试了一下使用我从网下下载的数据头部,然后将它覆盖到被损坏的存档中。然后我将 存档保存到存储卡,重新加载它。 不可思议,它开始重新正常工作。继续游戏菜单显示了其它的存档,但是一旦加载完,她的存档完全恢复。从订购DexDrive到修复完成,整个花开我 3周的时间。那天晚上,我启动游戏,并向她展示了她奇怪的存档命名。她加载完,很惊讶的发现,她丢失的进度,角色,等级全部都回来了。她非常的兴奋,但是 在我们讨论之前,她开始一个新的副本了。 |