这是CSDN网站的一篇文章,怕以后这篇文章会被删除,因此保存起来。
StarCraft开发的荆棘之路
StarCraft开发启动
开发StarCraft,我们经历了两年半的艰苦岁月包括发布前一年的关键时期,这款游戏曾像一个蚁穴一样,到处都是bug。
它的“前辈”们(Warcraft I and II)远比同类游戏稳定,但StarCraft 却经常崩溃,在测试初期简直是一团糟,而即使在游戏发布后,它同样还需要不停地打补丁。
这究竟是为什么?原因有很多很多。
宇宙中的Orcs(兽人)
StarCraft原本设定为开发周期一年的普通游戏,这样就能赶在1996年圣诞节之前发布。其领导层也是Shattered Nations的领导层。
为了快速发布一款游戏,Blizzard重组了开发团队,大家在两款游戏中间并没有足够的间隙(休息)。
现在想想,当初打算快速开发这款游戏真是个荒唐的决定。但公司的董事长Allen Adham受到了增加收入的压力,因为Blizzard早期的游戏太过成功,导致了对其成长的预期过高。
由于时间表太过紧凑,而且员工也有限,StarCraft团队打算开发一款普通的游戏。当时这款游戏被描述为“宇宙中的兽人”,从1996第二季度的E3游戏展中的一幅图片上,我们可以看到当时开发团队的大致路线。
1996年5月,StarCraft在E3上的首次露面
但这时另一个高优先权的游戏出现了——Diablo,这款由加利福尼亚红杉城(Redwood,California)的Condor Studios(秃鹫工作室,后来被称为“北暴雪”)开发的游戏,正需要额外的帮助,而StarCraft开发人员就这样一个接一个地被夺走了。
Condor团队并没有能力制作自己想做的游戏,但其独创的效果非常有趣,这也是Blizzard需要他们的原因。后来Condor被重新命名为Blizzard North(北暴雪),并开始逐渐获得自己应有的投入和员工。
刚开始,我和Collin Murray,一个StarCraft的程序员,飞往Redwood城去帮忙。其它Blizzard“总部”开发人员正在加利福尼亚州Irvine城从事battle.net(战网)网络“供应商”、调制解调器以及局域网游戏,同时还有用户界面屏幕(Blizzard内部称作“glue screens”,用来执行角色创建、加入游戏以及其它游戏功能元素)相关工作。
随着Diablo规模的扩大,Blizzard“总部”所有员工——艺术家、程序员、设计师、音效工程师以及测试人员都转移到了Diablo上,而StarCraft变得无人问津,甚至其团队领导都被指派去处理我没时间完成的游戏安装程序。
在Diablo 1996年发布后,StarCraft又复活了,所有人都有机会了解游戏的发展方向,但它明显前途暗淡。这款游戏已经过时了,给人一种古老的印象,特别当和6个月前在E3展览中大放异彩的Dominion Storm这样的项目相比。
Diablo的巨大成功重塑了Blizzard的目标:StarCraft明确了Blizzard“游戏在完成前不会发布”的战略(这就是“跳票之王的由来”)。但为了证明这一策略,我们遭受了巨大痛苦。
我们需要证明自己
每个人都在用批评性的眼光看待StarCraft,这注定了它比我们之前定义了RTS时代的两款Warcraft还要雄心勃勃!
在StarCraft项目重启时,根据Johnny Wilson——当时最大的游戏杂志Chief of Computer Gaming World的编辑——统计,总共有超过80!!款RTS游戏正在开发中,其中包括现代RTS始祖Westwood Studios。所以我们需要做出一些惊天动地的事!
我们不再是弃儿,因为Warcraft和Diablo的极大成功,我们不可能从游戏和玩家压力下那里得到松懈的机会。在游戏的世界里,你的实力等同你最后一次发挥出来的水准,所以我们需要远远超过之前的辉煌,但这也带来了风险。
新面貌
Warcraft II只有6个核心程序员和两个支持程序员,但对于StarCraft这样的大工程这显然太少了,所以开发团队引入了一些不需要太多的指导就可以编写游戏代码的新人,他们并没有受到足够的训练。
我们的编程领导层也不够可靠:我们并没有意识到给经验不足的开发者足够的指导有多么重要,这样他们可以在项目启动前掌握一些必备的知识,所以这些新学者决定了游戏开发的成败。问题的一大原因在于每个人都疯狂地编程,而没有留下时间去做代码复查、审查以及新人训练。
团队中不仅有很多无经验的成员,StarCraft开发团队的领导也从未给发布了的游戏设计过引擎架构。Bob Fitch有多年的游戏开发经验,并取得了优异的成果,但他一直以来都是在做游戏移植、使用已经完成的引擎,以及为Warcraft I、II做一些特性编程,这都不需要考虑大规模的游戏引擎设计。当他在Shattered Nations做技术领导时,项目又被终止了,所以无法证明他在架构设计上的能力。
开发团队在这个项目上的投入无与伦比,大家牺牲了个人健康和家庭生活来全力以赴。我从未见过在哪个项目上每个人都是这么的热血沸腾地工作着!但项目中的几个关键编程决定给之后的工作带来了很多遗留问题,这也是我将要重点讲述的。
人是物非
在发布了Diablo以及后续清理、打补丁完成后,我开始重新帮助StarCraft项目启动。我没想到面对的将是一个bug的集合体,但事实上这就是我所遇到的。
我本以为能够很容易地返回项目开发中,因为我是如此熟悉其中的代码——其中每个组件我都亲自参与了编码。相反,我震惊地发现引擎的很多组件都被丢弃或者重写了。
游戏的单元类被重头编写了一遍,而单元调度程序被丢弃了。调度器是我编写的每个游戏单元正常工作的保障程序!每个单元都会定期询问:“我把现在的工作完成后应该继续做些什么?”“我应该重新评估路径来获得我将要去的路径吗?”“我已经结束了,应该怎样去清理我自己?”等等。
重写代码会带来很多好处,但删除旧的代码同时也会带来风险。Joel Spolsky在他的文章《你不应该做的事情:一》里说得特别好:
永远记住:当你打算重头开始时,你并没有理由保证能做得比之前更好。首先,你的团队不一定还是当时的团队,所以你们并不一定更有经验。大多数情况,你们只是再一次重蹈覆辙,甚至还可能会带来一些新的问题。
Warcraft的游戏引擎是我们几个月努力的结果,为了增加更多的特性来满足新游戏的需求,一个不成熟的编程团队正打算花很多时间来学习游戏引擎的架构应当如何设计以及为什么。
引擎架构
我使用C语言及Watcom编译器,为Microsoft DOS编写了最初版的Warcraft引擎。为了保证在Microsoft Windows上运行,Bob选择Visual Studio编译器并且用C++重新设计了游戏引擎的架构。这都是可以理解的,但他忽略了一个事实——当时,团队里大多数开发者并不熟悉这门语言,非常容易陷入其陷阱中。
尽管C++很强大,但它很容易被人误用。正如 Bjarne Stroustrup(C++之父)所说,“使用C语言(不当)容易伤到自己的脚,但是C++会让你失去整条腿。”
历史的教训告诉我们,程序员总是会强迫自己在第一个项目中使用新语言的每一个特性,这也就是StarCraft里使用类继承的原因。有经验的程序员在见到游戏单元使用继承链后不可能有什么乐观的想法:
CUnit < CDoodad < CFlingy < CThingy
CThingy对象是可能在游戏地图任何地方出现的图元,但不会移动也没有动作;CFlingys的作用则是创建微粒,当发生爆炸时,会出现多个CFlingys向随机的方向飞去;CDoodad,我记得14年前用的应该是这个名字,是一个未实例化的的类,但它是派生类正常运行的重要保障;而CUnit则是基于CDoodad之上。这些单元的行为在多个模块中非常混乱,而且你想做一件事就必须了解所有的类。
比类层级的混乱更糟糕的是,CUnit类本身是一个定义得非常糟糕的类,使用了多个头文件:
每一个头文件都有数百行,这简直好笑!
很多年后,“优先使用对象组合的”理念才在程序员中得到认可,但StarCraft的的程序员很早就在实践中得到了这一结论。
离发布只剩两个月
因为早期的多难之秋,开发团队重新启动后承受着快速完成的压力,新的时间表显示离产品发布只有两个月。
需要增加的游戏单元和行为,还需要从鸟瞰图(top-down)变成等角图(isometric artwork)、一个全新的地图编辑器、通过battle.net在网上联机等功能,所以即使假设其他开发人员(艺术家、音效师游戏平衡性控制者、测试人员)都没问题,让我们在那么短的时间里完成游戏也是不可能的。但编程部门在接下来的14个礼拜内一直以在两个月内发布游戏为目标!整个团队和Bob一起不停地在增加工作时间:(每周)40小时、42小时、48小时。虽然每个人都投入了巨大的、荒谬的时间,但没有人是真的想自讨苦吃。
我以前经常彻夜编码地开发Warcraft,也有连续一周每天超过14个小时编程完成Diablo的经历,这一切都告诉我彻夜工作没有任何意义。任何代码提交[哈哈,多么乐观的一个词啊]都会在之后发现巨大的问题,然后又需要更多的时间去重写代码。
这么长时间地工作让人变得毫无精神,特别是在做非常需要想象力的工作时,这额外糟糕!所以出现那么多的错误、丢失特性以及极其明显的bug都不足为奇。
顺便提一下,这些疯狂的加班工作并不是强制的——只是因为我们的员工非常想完成杰出的作品。现在想想还真是愚蠢,如果我们能合理地安排时间,一定能完成更好的作品。
目前最令我骄傲的成就是在两年内发布了4个版本的Guild Wars(激战),我也并没有为此带领开发团队夜以继日地工作。
StarCraft游戏崩溃最常见的原因
当我在实现StarCraft的某些重要功能包括战争迷雾、视线、飞行单位路径重叠、语言聊天、AI(人工智能,在游戏中指计算机控制的人工智能单位)增强等等之后,我的主要工作被迫变成了修复bug。等等,语音聊天!在1998年?对,我早在1997年就完成了这一功能!其中使用了第三方的语言转换器,并成功完成了通过网络传递音位、解压,并在其他七位玩家的计算机上播放。
但我们办公室的每一个声卡都需要更新驱动才能保证这一功能真正起作用,而且声卡支持全双工传输(full-duplex)音效(同时录制和播放声音),所以我很遗憾地放弃了这一想法。技术支持的任务会因此变得非常繁重,我们在游戏支持上的投入可能甚至超过从游戏中获得的收入。
无论如何,我修复了很多bug,有的是来自我自己的代码,但更多难懂的问题来自那些疲惫的程序员。几个月前,我收到一封来自Brian Fitzgerald——我有机会共同工作的最优秀的两个程序员之一——的问候,谈到StarCraft的一次代码复查,他们记不清我在整个编程过程中到底做了多少修改以及问题修复,但至少我为此受收到了不少赞扬。
面对整个团队的问题,你可能觉得很难处理一大堆问题。但根据我的经验,StarCraft的最个问题在于双向链接的链表。
链表在新引擎中被广泛用作跟踪行为共享的单位。单位的数目是它前辈的两倍——StarCraft的最大限制是1600,而Warcraft II是800——于是通过将它们在链表中链接在一起,优化特定类型单位搜索是非常重要的。
每个玩家的单位和建筑都有专门的列表。
所有这些列表都被双向链接了,来保证可以经常增加和删除列表中的元素—O(1)—而不必去遍历列表来寻找需要删除的元素—O(N)。可惜的是,每个列表都需要“手动维护”——列表间没有共享链接和解链函数;程序员只能在需要的地方手动内联或者解链。而手动控制代码比使用经过调试的程序更容易出错。
某些链接域(link field)被多个列表共享,所以有必要明确了解对象是链接到哪个列表来保证安全的解链。某些链接域甚至和其它数据类型存储在C union中,将内存使用率降到最低。
因此游戏会一直崩溃,一直崩溃!
为什么要那样做?
悲剧的是,这些链表问题本是可以避免的。和我、Jeff Strain一起创办ArenaNet的Mike O’Brien,写了一个Storm.DLL,Diablo中运行的就是它,使用C++模板实现了双向链表。
在StarCraft开发初期使用的正是那个库,但很快它就被新的开发团队丢掉了,他们开始手动控制链表,只是为了让保存游戏存档变得更简单。
可能这里会让大家有点不明白,我来稍微解释下吧:
保存游戏
在Warcraft之前我玩过的那些游戏的存档功能都很糟糕,相信玩过Origin这款游戏的玩家都会记得存档所花费的时间。当然,当时的硬件跟现在比起来确实要慢得多,但这并不是它并不是存档功能如此糟糕的借口,我决定让Warcraft中不再有同样的问题。
因此,Warcraft使用了一些小技巧将大的内存块一起写入磁盘中,整个单元阵列(600个单元,而每个单元数百字节)会作为一整块写入到磁盘中。所有的非指标(non-pointer-based)全局变量可以一次写入磁盘,包括每一个游戏地形和战争迷雾地图都没有问题。
但奇怪的是,尽管它彻底地简化了代码,但这种整体地保存单位的功能并不是提高游戏保存速度的关键。根本上的原因在于Warcraft中的单位不包括“pointer”数据。
StarCraft的单位,正如之前所提到的,在链表区域中包含很多指针,是完全不同的问题——要保证1600个单位一次写入就必须固定所有链指针(还特别要注意联合指针域),然后再解除所有链指针来继续游戏。Yuck!
给我改回来!
在修复很多很多链表之后,我激烈地讨论要改回去——继续使用Storm的链表,即使这会导致保存游戏的代码变得更为复杂。这里我应该多少提提在Blizzard“讨论”的意思——因为我们年轻、莽撞而又傲慢,所以我们的讨论没有不激烈的,除非已经到了午饭时间,只有这时,才没有人会希望继续讨论下去。
我并没有赢得讨论,因为我们离发布只剩下“两个月”,所以经常对现存的拼凑的系统打补丁,当然也包括修缮引擎。这的确不是什么好方法,也使得我们在接下来的几个月里饱受煎熬,同样深深影响(提高)了我之后写代码的方式,这我将在第二篇里讨论。
其它修缮:StarCraft里的路径搜索(path-finding)
这里我还要再提一个给bug的补丁的故事,这不是在修复潜在问题:StarCraft从鸟瞰图变成了等角图,而背景拼图绘制引擎(background tile-graphics rendering engine),还是用的我在1993/4年所写的,没有做任何改变。
使用方块拼图引擎(square tile engine)绘制等角图并不难,但要保证地图编辑器正常运行可不容易,因为将拼图一块一块贴在一起需要做很多边缘固定,因为地图编辑器会将对角形状的图片绘制在方块中。
图形绘制还不是那么糟糕,在方块拼图上做等角路径搜索却是非常困难。地图没有使用大对角拼图(32×32像素),采用的是8×8的小拼图——这增加了16倍的难度。
那时Brian Fitzgerald当时还不是主程序员,路径搜索问题导致游戏发布变得遥遥无期。路径问题最终成为游戏发布前最后解决的一批问题。我打算就StarCraft路径搜索中众多技术和设计细节单独写一些文章。
结束语
所以你听到我抱怨StarCraft的开发究竟有多难,主要因为公司对于这款游戏方向、开发以及设计等各个层次上的糟糕决定。
我们有幸成为鲁莽但又勇敢的船员,通过自己的才智走过了那些艰苦的日子。最终,我们“处理好了问题”,也停止向其中新增功能并发布这款游戏,玩家永远无法看到下面恐怖的代码。也许这也是编译语言相比JavaScript这样的脚本语言的好处——终端用户永远看不到底层的问题。
在下一篇文章中,我将继续深入技术,并讲述为什么大部分程序员使用错了链表,并提出一个Diablo、battle.net以及Guild War中所使用的替代方案。
如果你不需要链表,我的的解决方案同样适用于更复杂的数据结构,例如哈希表、B-trees以及优先队列。我相信,这些基本思路适用于任何编程。但记住不要试图染指能力外的范围,我将在另一篇文章里讲述为什么。
原文链接: http://www.csdn.net/article/2012-09-13/2809882-tough-times-on-the-road-to-starcraft/1