游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

开发者回顾《星际争霸》艰难的开发之路

发布时间:2013-11-07 08:51:34 Tags:,,,,

作者:Patrick Wyatt

我已经描写过《魔兽争霸》的早期开发,但是最近看过的一篇博客给了我很大的灵感,并最终写出了这篇有关《星际争霸》的开发,包含3部分,20多页内容的文章,同时还有我对于编写更多可靠的游戏代码的看法。

《星际争霸》的开始

在《星际争霸》开发期间,即2年半的艰苦工作与1年多的关键时间,游戏就像是一个布满虫子的蚁巢。尽管其前辈(《魔兽争霸1》和《魔兽争霸2》)相比当时产业中的其它游戏可靠许多,但是《星际争霸》却频繁地崩溃,其游戏测试也是直到发行前才完成的,游戏在发行后还需要持续的修补工作。

为什么会出现这种情况?这有许多原因。

太空兽人

开发者关于《星际争霸》最初的设想是一款带有适度目标,即能够符合1年开发过程的游戏,在1996年的圣诞节期间发行。

该项目的领导团队是创造了《Shattered Nations》(游戏邦注:一款基于《X-COM》的回合制策略游戏,暴雪最初于1995年5月公开了这款游戏,但是在几个月后又放弃了它)的原班人马。

团队成员经过重组去创造出一些能够快速问世的内容,从而帮助暴雪避免游戏发行间隔过长的缺口。

1994年第4季度——《魔兽争霸》

1995年第4季度——《魔兽争霸2》

1996年第4季度——《星际争霸》的预期发行时间

1998年第2季度——《星际争霸》的真正发行时间

回想起来,加快游戏开发的决定似乎很荒唐,但是该公司的总裁Allen Adham却经受着提高收益的压力。尽管暴雪早前的游戏获得了超过预期的成功,但是这也会提升人们对于其未来发展的期待值。

考虑到较短的时间安排以及有限的工作人员,《星际争霸》团队的目标是创造出一款适度的游戏——被戏称为“太空兽人”。1996年的Q2上一张来自E3游戏展上的图片呈现了该游戏团队最初所选择的方向:

Starcraft-orcs-in-space(from codeofhonor)

Starcraft-orcs-in-space(from codeofhonor)

1996年6月在电子娱乐展会上出现的《星际争霸》。

但是一个更高级的项目遮盖了《星际争霸》的光彩,并一个又一个第抢走了它的开发者。即Condor Studios在加州雷德伍德城所开发的一款角色扮演游戏《暗黑破坏神》需要额外的帮助。由Dave Brevik,Max Schaefer和他的兄弟Erich Schaefer所组成的公司只有120万美元的预算(这在当时真的是少得离谱了)。

Condor团队没有希望创造他们渴望创造的游戏,但是他们创造了一些具有创造性的内容,让暴雪有了收购Condor的理由,并将其重新命名为暴雪北方,然后开始为其提供游戏所需要的预算和人员。

最初我和《星际争霸》的一名程序员Collin Murray飞到了雷德伍德城帮助他们,而暴雪总部的其它开发者则待在加州欧文市致力于battle.net,调制解调器和局域网游戏以及执行角色创造的用户界面屏幕,游戏参与以及其它元游戏功能中。

随着《暗黑破坏神》的不断发展,最终暴雪总部的所有成员,包括美术人员,程序员,设计师,音效工程师以及测试人员都开始致力于该游戏中,直至最后已经没人在负责《星际争霸》这一项目了。甚至连项目领导也被指派去完成我写了一半但却没时间继续做下去的游戏安装器。

在1996年末《暗黑破坏神》发行后,《星际争霸》的开发工作才再次开启,每个人都有机会重新审视游戏之前的前进方向,并发现那其实并不合适。游戏已经过时了,很难留给人们深刻的印象,特别是与《Dominion Storm》等项目进行比较时更加明显,尽管它在六个月前E3上展示的演示版本看起来很棒。

《暗黑破坏神》的巨大成功重置了人们对于暴雪的期望值:《星际争霸》变成了定义暴雪策略的游戏,即在真正准备完成时才面向市场发行。但是在证明这一策略的合理性途中也出现了许多问题。

需要证实的事

当所有人更挑剔地看待《星际争霸》时,很明显该项目需要比我们之前的开创性努力(即基于前两款《魔兽争霸》游戏定义实时策略游戏类型的未来)拥有更大的野心。

根据《计算机游戏世界》(当时发行量最大的游戏杂志)的主编Johnny Wilson,在《星际争霸》再次起航之时,共有80多款RTS游戏处于开发过程中。面对着如此多的竞争对手,还包括了Westwood Studios(游戏邦注:即创造了现代RTS游戏风格的公司),我们需要采取一些具有创造性的方法。

我们不再是失败者;伴随着《魔兽争霸》的成功以及《暗黑破坏神》的加入,我们不会再被玩家或游戏媒体所忽视了。在游戏世界中,你的上一款游戏的成败将很大程度地影响着你的现状。我们需要超越之前所做的一切,而这就需要我们敢于冒险。

新面孔

《魔兽争霸2》只拥有6名核心程序员和2名支持程序员,这对于《星际争霸》的大规模来说太少了,所以开发团队便开始吸收一些全新且未经测试的游戏程序员,并要求他们在没有任何指导的前提下学会编写游戏代码。

我们的编程领导能力较弱:我们还不清楚在项目早期提供给没有经验的开发者相关指导的重要性,如此才能让他们在游戏发行前学到更多必要的经验教训。而出现这一问题的一大原因便是我们基础的薄弱性——每个程序员都迫切地想到实现目标,没有时间进行回顾,检查或培训。

团队中不仅有毫无经验的新人,连《星际争霸》编程工作的负责人也从未创造过一个真正发行过的游戏引擎。Bob Fitch虽然已经从事游戏编程工作多年并取得不错的结果,但是他之前都是致力于游戏端口,即是基于现有引擎进行创造,而在为《魔兽争霸1》和《魔兽争霸2》编写程序的时候,这两款游戏又不需要太大规模的引擎设计。尽管他曾作为《Shattered Nations》的技术总监,但是该项目最后被取消了,因此没有任何案例能够证实他的架构决策是可行的。

该团队投入了前所未有的精力于该项目中,同时也忽视了个人的健康与家庭生活。我所参加过的项目的工作成员从未像他们那样刻苦工作。但是项目中的一些关键编码决策却一直困扰着编程团队。

starcraft(from polycat.net)

starcraft(from polycat.net)

一些情况发生了改变

在历经几个月时间并最终发行了《暗黑破坏神》之后,我们又花了几个月时间进行整理与修补工作,最终我也回到了《星际争霸》的重置工作中。我并不期望投入另一项漏洞修补工作,但我所面对的现实就像如此。

我认为回到原先的项目会比较轻松,因为我非常了解《星际争霸》的代码—-我将准确地瞄准每一个组件。但事实上我却惊讶地发现自己所熟悉的许多引擎组件已经被抛弃或部分得到了重写。

那时候游戏的单位类正在进行重新编写,而单位调度程序已经被抛弃了。这一调度程序是我为了确保每个游戏单位能够拥有足够时间去计划想做的事而创造的机制。每个单位定期会问:“我完成了当前的工作了,那现在该做什么?”“我是否应该重新评估方向以明确接下来该怎么走?”“是否存在更好的攻击单位不是那些我现在所瞄准的对象?”“用户是否给了我新的命令?”“我死了,我该怎么收拾自己?”等等。

重新编写代码有许多理由,但删除之前的代码也存在一定的风险。Joel Spolsky在《http://www.codeofhonor.com/blog/tough-times-on-the-road-to-starcraft》中说道:

你必须记得,当自己重头开始创造时,并不存在任何原因能够证明你现在所做的工作比最初做的来得出色。首先,你可能不具有同样的程序团队,所以你将缺少“更多经验”。你将可能重蹈之前的问题,并引进一些之前版本未遇到的新问题。

《星际争霸》的引擎花了几个月的编程时间才最终确定下来,同时我们也还需要修改一些新的游戏功能,而现在新的编程团队将投入大量时间重新学习有关引擎一开始的架构方式的经验教训。

游戏引擎架构

我使用了Watcom Compiler并基于C语言面向微软DOS编写了最初的《星际争霸》引擎。当过度到微软Windows时,Bob选择使用Visual Studio编辑器并基于C++语言重新建造了游戏引擎架构。这都是合理的选择,但实际上,我们团队中很少有开发者接触过该语言,所以很容易掉进各种陷阱中。

尽管C++具有一些优势,但是我们却很容易滥用它们。就像C++语言之父Bjarne Stroustrup所说的:“C语言会导致你搬起石头砸自己的脚,而C++能够避免这种情况,但是如果你不小心将石头砸向了自己的脚,你的脚就会废了。”

历史告诉我们,程序员会在第一个项目中尝试新语言的每个功能,所以在《星际争霸》中会发现类的继承。有经验的程序员在看到针对游戏单位所设计的继承链时将会颤抖:

CUnit < CDoodad < CFlingy < CThingy

CThingy对象是会出现在游戏地图上任何地方的精灵,但却不能移动或带有行动,而CFlingys则是用于创造粒子;当爆炸出现时,它们便会基于随机方向分离。CDoodad(在经过14年后我想这应该是类的名字)是一种已经创建了密钥的类,并带有重要行为要求派生类的正常运行。CUnit是基于此进行分层。单位的行为将贯穿这些不同的模块分散,而只有了解每个类才有可能完成任何创造。

除了类分层,CUnit类本身也非常乱,即是在多个头文件中进行定义:

class CUnit … {
#include “header_1.h”
#include “header_2.h”
#include “header_3.h”
#include “header_4.h”
};

每个头文件都有上百行内容,并通向一个完整的类定义—-至少能够说是有趣的。

直到几年后,“支持架构胜过继承”的咒语获得程序员们的信任时,这种情况才成真,但是那些致力于《星际争霸》的程序员们更早体会到了这点。

我们离发行只有2个月时间了

面对着充满困境的早期历史,在顶着压力完成了开发团队的重启后,项目日程表最终显示的结果是开发团队必须在2个月后发行游戏。

考虑到需要添加的游戏单位和行为的数量,该团队必须从自上而下的架构转向等距视图,添加一个全新的地图编辑器以及基于battle.net的网络游戏功能,最终游戏竟然真的能在规定时间内问世,甚至连图像团队,设计师,音效工程师,游戏平衡人员,测试者都能够按时完成自己的工作。但是编程团队在致力于2个月内发行游戏的工作后仍将在今后的14个月内继续忙碌工作。

整个团队都是长时间地工作着,Bob甚至连续40个小时,42个小时甚至48个小时都在编写着游戏程序。不过当我回想起来时发现,别人并未像他这样付出这么疯狂的努力,尽管所有人都承受着紧迫的时间压力。

我在开发《星际争霸》的经历——经常熬夜编写代码,以及之后参与《暗黑破坏神》项目的经历,连续好几周每天都要编写14个小时以上的代码,都让我意识到通宵工作是很难真正做好一些事。通宵编写的一些代码最终只会落得重新编写的下场。

长时间的工作会让人们变得无力,这将不利于执行那些要求大量创造性的知识型任务,所以我们才会遇到种种的问题与漏洞。

顺便一提的是,这种加班加点的工作情况并不是强制性的—-这只是成员们自己的选择,因为我们想要创造出真正优秀的游戏。现在回想起来真觉得那时候的我们太过愚蠢,因为如果是基于更合理的努力,我们肯定能够更出色地完成工作。

我最骄傲的成就便是在未让开发团队加班至深夜的前提下于2年时间内发行了4个《激战》中的战役。

《星际争霸》崩溃的最常见原因

当我在《星际争霸》中执行一些重要功能,包括“战争迷雾”,“视距”,“飞行单位路径控制”,“语音聊天”,“AI加固点”等等,我的主要工作便会受到一些固定漏洞的影响。

等等,你说语音聊天!在1998年?!没错,我在1997年12月便做到了这点。我使用了第三方声音音素压缩机,并编写代码通过网络发送音素,压缩它们,然后将其送回其它玩家的计算机上。

但是我们办公室中每个单一的声卡要求一个驱动器升级才能保证它的运行,如果声卡能够全双工传输声音(游戏邦注:同时记录并播放声音),我便会懊悔放弃这一理念。技术支持压力真的太大了,我们花在游戏支持上的费用比从游戏身上赚到的钱还要多很多。

不过不管怎样我解决了许多漏洞。有些是我们自己造成的漏洞,但大多数还是其它程序员所编写的难以捉摸的漏洞。我在几个月前收到了一个最棒的赞美,即在我所遇到的最棒的程序员之一的Brian Fitzgerald提到《星际争霸》的代码评价时,他赞扬了我在面对整个代码基础所做出的改变与修正。如此让我欣慰于自己的努力得到了肯定。

考虑到团队所面对的所有问题,你可能觉得明确一个单一的漏洞源会非常困难,但是基于我的经历,《星际争霸》的最大问题与双向链接列表的使用息息相关。

链接列表被广泛地用于引擎中追踪带有共享行为的单位。因为《星际争霸》的单位数量(最大达到1600个单位)是《魔兽争霸2》(800)的2倍,所以优化特定类型的单位搜索就变得更加重要了,即确保它们能够在列表中联系在一起。

回想起来,那时候我们为每个玩家的单位和建筑设置了列表,为每个玩家的“能量生成”建筑设置了列表,也为每个载体的铁骑设置了列表,可以说是设置了许许多多不同的列表。

所有的这些列表都是双向链接,让它能够在固定时间内往列表添加并删除某些元素,并且无需贯穿列表去寻找需要删除的元素。

不幸的是所有的这些列表都需要“手动完成”,并不存在哪些公共模块能够从这些列表中链接并分离元素;程序员只能在需要的时候手动链接并揭开某些行为。而比起简单使用已经调试过的程序,手动代码更容易出现漏洞。

一些链接字段被共享在多个列表中,所以为了保证安全的分离,我们就必须清楚对象是链接到哪个列表中。甚至为了减少内存的时候,有些链接字段还与其它数据类型被储存在C组合中。

所有游戏随时都有可能崩溃。

但是为什么你们要这么做呢?

悲剧的是,这些链接链表问题并没有存在的必要。Mike O’ Brien(他和我与Jeff Strain一起创建了ArenaNet)编写了名为Storm.DLL的程序库,并用于《暗黑破坏神》中。在它的众多功能中,storm使用C++模版包含了一个非常出色的双向链接列表执行。

在《星际争霸》最初的开发阶段,开发团队使用了该程序库。但是在开发早期,团队撇开了代码并手动处理链接列表,希望能够更轻松地编写游戏储存文件。

让我明确第说说有关游戏储存的情况。

储存游戏

在开发《魔兽争霸》前我玩过的许多游戏都带有蹩脚的游戏储存功能。玩过基于Origin所创造的游戏的玩家都应该记得它们需要很长很长一段时间去编辑游戏储存文件。也就是它们基于较慢的微处理器而编写到硬盘上(即根据今天的标准),即不同于三轮脚踏车和赛车。但没有理由能够让它们堕落下去,我也决定不让《魔兽争霸》出现这些问题。

所以在《魔兽争霸》中我们通过使用了某些技巧去编写较大容量的内存块,将储存文件记录在一个组块上而不是贯穿内存记录在不同地方。整个单位数组可以通过编写而储存在一个组块中。所有不是基于指示器的全局变量以及每种游戏地形和战争迷雾地图都可以同样被编写在一个组块中。

但是说来奇怪,这种编写单位并记录在一个组块中的能力对于提高游戏储存文件的编写速度来说并非绝对必要,尽管这能够彻底简化代码。但从根本上看来它是有效的,因为《魔兽争霸》的单位并未包含“指示器”数据。

而就像之前所提到的,《星际争霸》的单位包含了许多指示器,所以这是完全不同的情况。我们必须修正所有链接指示器(尤其关注于联合指示器领域),从而让所有的1600个单位能够同时进行编写。然后修正链接指示器而保持游戏的继续。

回到过去!

所以在修改了许多链接列表漏洞后,我坚决主张我们可以回去使用Storm的链接列表了,即使这会让游戏储存代码变得更加复杂。当我说“坚决主张”时,这应该是我们在暴雪中进行辩论的唯一一种方式——这是年轻人的莽撞性格和狂妄自大的心态的表现,我们的每一次辩论都是如此激烈与坚定,除非只是关于讨论中午该吃什么。

我并未赢得辩论。因为我们离发行只有“2个月时间”,改变引擎的提议经常被应对现有的问题所掩盖而变成次等解决方法,这让我痛苦了好几个月,甚至影响了我的编码工作,我会在第二部分中讨论这一问题。

更多解决方法:在《星际争霸》中搜索路径

我想要提及更多有关修改漏洞而不是解决潜在问题的例子:当《星际争霸》从自上而下的视角准许等距视图,背景砖块图像渲染了引擎,一切便会到了我在1993年4月所编写的代码中。

使用方形砖块引擎渲染等距视图砖块并不难,尽管使用地图编辑器等内容存在一定的难度,因为将一个地图砖块放置在另一个砖块上要求许多“边缘修正”—-因为地图编辑器一直尝试着在方形砖块上绘制多边形图像。

尽管渲染并不糟糕,但是在方形砖块上基于等距视角搜索路径非常困难。比起较大的对角线砖块(32×32像素),地图必须被分解成较小的8×8像素砖块—-将路径数量乘以16并为不能压缩成较下载路径的大型单位创造难度。

如果Brian Fitzgerald并未成为游戏的主要程序员,搜索路径问题便会影响游戏最终发行。路径控制是在项目最后解决的问题之一。因为有关路径控制还有许多有趣的技术和设计问题,所以我打算写更多有关《星际争霸》路径搜索的内容。

小结

你应该感受到我对创造《星际争霸》的种种难处的抱怨,这主要是源于每个阶段公司对于游戏方向,技术和设计所做出的糟糕选择。

我们很幸运能够作为鲁莽但却勇敢的成员,并且我们的洞察力最终也获取了胜利。最后我们开始倾尽全力并停止添加不必要的功能而按时发行了游戏,玩家并不会看到任何潜在的问题。也许这是基于脚本的编辑语言(如JavaScript)的另一大好处——终端用户永远都不会看到“失事的列车”。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Tough times on the road to Starcraft

by Patrick Wyatt

I’ve been writing about the early development of Warcraft, but a recent blog post I read prompted me to start scribbling furiously, and the result is this three-part, twenty-plus page article about the development of StarCraft, along with my thoughts about writing more reliable game code. I’ll be posting the latter parts over the next several days.

This post: Why StarCraft crashed frequently during development

Part 2: How we could have fixed the most common causes

Part 3: Explaining the implementation details of the fix

The beginnings of StarCraft

During the development of StarCraft, a two and a half year slog with over a year of crunch time prior to launch, the game was as buggy as a termite nest. While its predecessors (Warcraft I and II) were far more reliable games than their industry peers, StarCraft crashed frequently enough that play-testing was difficult right up until release, and the game continued to require ongoing patching efforts post-launch.

Why? There were sooooo many reasons.

Orcs in space

StarCraft was originally envisioned as a game with modest goals that could fit into a one-year development cycle so that it could be released for Christmas, 1996.

The project leadership was comprised of the same folks who had started Shattered Nations (video), a turn-based strategy game along the lines of X-COM that Blizzard announced in May 1995 but canceled some months later.

The team members were regrouped to build something that could reach market quickly so Blizzard wouldn’t have a long gap between game launches.

Q4 1994 – Warcraft
Q4 1995 – Warcraft II
Q4 1996 – planned ship date for StarCraft
Q2 1998 – actual ship date for StarCraft

The decision to rush the game’s development seems ludicrous in retrospect, but Allen Adham, the company’s president, was under pressure to grow revenue. While Blizzard’s early games had been far more successful than expected, that just raised expectations for future growth.

Given a short timeframe and limited staff, the StarCraft team’s goal was to implement a modest game — something that could best be described as “Orcs in space”. A picture from around the time of the E3 game show in Q2 1996 shows the path the game team originally chose:

StarCraft as it appeared in June 1996 at the Electronic Entertainment Expo. Yeah — I wouldn’t play it either.

But a higher priority project overshadowed StarCraft and stole its developers one by one. Diablo, a role-playing game being developed by Condor Studios in Redwood City California, was in need of additional help. Condor, a company formed by Dave Brevik along with Max Schaefer and his brother Erich Schaefer, was given a budget of only $1.2 million — ridiculously small even in those days.

The Condor team had no hope of making the game they aspired to build, but they did such ground-breaking work in developing something fun that it made sense for Blizzard to acquire Condor, rename it Blizzard North, and start pouring in the money and staff the game really deserved.

Initially Collin Murray, a programmer on StarCraft, and I flew to Redwood City to help, while other developers at Blizzard “HQ” in Irvine California worked on network “providers” for battle.net, modem and LAN games as well as the user-interface screens (known as “glue screens” at Blizzard) that performed character creation, game joining, and other meta-game functions.

As Diablo grew in scope eventually everyone at Blizzard HQ — artists, programmers, designers, sound engineers, testers — worked on the game until StarCraft had no one left working on the project. Even the project lead was co-opted to finish the game installer that I had half-written but was too busy to complete.

After the launch of Diablo at the end of 1996, StarCraft development was restarted, and everyone got a chance to see where the game was headed, and it wasn’t pretty. The game was dated, and not even remotely impressive, particularly compared to projects like Dominion Storm, which looked great in demos at E3 six months before.

The massive success of Diablo reset expectations about what Blizzard should strive for: StarCraft became the game that defined Blizzard’s strategy of not releasing games until they were ready. But a lot of pain had to occur along the way to prove out this strategy.

Something to prove

With everyone looking critically at StarCraft, it was clear that the project needed to be vastly more ambitious than our previous ground-breaking efforts in defining the future of the real-time strategy (RTS) genre with the first two Warcraft games.

At the time of the StarCraft reboot, according to Johnny Wilson, then Editor in Chief of Computer Gaming World, the largest-distribution gaming magazine of that time, there were over eighty (80!!) RTS games in development. With so many competitors on our heels, including Westwood Studios, the company that originated the modern RTS play-style, we needed to make something that kicked ass.

And we were no longer an underdog; with the successes of Warcraft and Diablo continuing to fill the news we sure wouldn’t be getting any slack from players or the gaming press. In the gaming world you’re only ever as good as your last game. We needed to go far beyond what we’d done previously, and that required taking risks.

New faces

Warcraft II had only six core programmers and two support programmers; that was too few for the larger scope of StarCraft, so the dev team grew to include a cadre of new and untested game programmers who needed to learn how to write game code without much mentoring.

Our programming leadership was weak: we hadn’t yet learned how essential it is to provide guidance to less experienced developers early in the project so they learn much-needed lessons before the game launches, so it was very much a sink-or-swim proposition for new Padawans. A big part of the problem was just how thin we were on the ground — every programmer was coding like mad to meet goals, with no time for reviews, code-audits, or training.

And not only were there inexperienced junior members on the team, the leader of the StarCraft programming effort had never architected a shipping game engine. Bob Fitch had been programming games for several years with great results but his previous efforts were game ports, where he worked within an existing engine, and feature programming for Warcraft I and II, which didn’t require large-scale engine design. And while he had experience as the tech lead for Shattered Nations, that project was canceled, therefore no validation of its architectural decisions was possible.

The team was incredibly invested in the project, and put in unheard of efforts to complete the project while sacrificing personal health and family life. I’ve never been on a project where every member worked so fiercely. But several key coding decisions in the project, which I’ll detail presently, would haunt the programming team for the remainder of the project.

Some things have changed

After spending months working to launch Diablo, and further months of cleanup effort and patching afterwards, I returned to help with the reboot of StarCraft. I wasn’t looking forward to diving into another bug-fest, but that’s exactly what happened.

I thought it would be easy to jump back into the project because I knew the Warcraft code so well — I’d literally worked on every component. I was instead terrified to discover that many components of the engine had been thrown away and partially rewritten.

The game’s unit classes were in the process of being rewritten from scratch, and the unit dispatcher had been thrown out. The dispatcher is the mechanism I created to ensure that each game unit gets time to plan what it wants to do. Each unit periodically asks: “what should I do now that I finished my current behavior?”, “should I re-evaluate the path to get where I’m going?”, “is there a better unit to attack instead of the one that I’m targeting now?”, “did the user give me a new command?”, “I’m dead, how do I clean up after myself?”, and so forth.

There are good reasons code needs to be rewritten, but excising old code comes with risks as well. Joel Spolsky said it most eloquently in Things You Should Never Do, Part I:

It’s important to remember that when you start from scratch there is absolutely no reason to believe that you are going to do a better job than you did the first time. First of all, you probably don’t even have the same programming team that worked on version one, so you don’t actually have “more experience”. You’re just going to make most of the old mistakes again, and introduce some new problems that weren’t in the original version.

The Warcraft engine had taken months of programming effort to get right, and while it needed rework for new gameplay features, a fresh programming team was now going to spend a great deal of time relearning lessons about how and why the engine was architected the way it was in the first place.

Game engine architecture

I wrote the original Warcraft engine for Microsoft DOS in C using the Watcom Compiler. With the switch to releasing on Microsoft Windows, Bob chose to use the Visual Studio compiler and re-architected the game engine in C++. Both were reasonable choices but for the fact that — at that point — few developers on the team had experience with the language and more especially with its many pitfalls.

Though C++ has strengths it is easy to misuse. As Bjarne Stroustrup, the language’s creator, so famously said: “C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”

History tells us that programmers feel compelled to try every feature of their new language during the first project, and so it was with class inheritance in StarCraft. Experienced programmers will shudder when seeing the inheritance chain that was designed for the game’s units:

CUnit < CDoodad < CFlingy < CThingy

CThingy objects were sprites that could appear anywhere on the game map, but didn’t move or have behaviors, while CFlingys were used for creating particles; when an explosion occurred several of them would spin off in random directions. CDoodad — after 14 years I think this is the class name — was an uninstantiated class that nevertheless had important behaviors required for proper functioning of derived classes. And CUnit was layered on top of that. The behavior of units was scattered all throughout these various modules, and it required an understanding of each class to be able to accomplish anything.

And beyond the horror of the class hierarchy, the CUnit class itself was an unholy mess defined across multiple header files:

class CUnit … {
#include “header_1.h”
#include “header_2.h”
#include “header_3.h”
#include “header_4.h”
};

Each of those headers was several hundred lines, leading to an overall class definition that could at best be called amusing.

It wasn’t until many years later that the mantra “favor composition over inheritance” gained credence among programmer-kind, but those who worked on StarCraft learned the hard way much earlier.

We’re only two months from launch

With its troubled early history, after the reboot the development team was pressured to finish up, and so schedules were bandied about that showed the game could be launched in two months.

Given the number of game units and behaviors that needed to be added, the changes necessary to switch from top-down to isometric artwork, a completely new map editor, and the addition of Internet play over battle.net, it was inconceivable that the game actually could ship in that time, even assuming that the art team, designers, sound engineers, game-balancers and testers could finish their end of the bargain. But the programming team continually worked towards shipping in only two months for the next fourteen months!

The entire team worked long hours, with Bob working stretches of 40 hours, 42 hours, even 48 hours programming. As I recall no one else attempted these sorts of masochistic endeavors, though everyone was putting in massive, ridiculous hours.

My experiences developing Warcraft, with frequent all-nighters coding, and later Diablo, where I coded fourteen-plus hour days seven days a week for weeks at a time, suffered me to learn that there wasn’t any point in all-nighters. Any code submissions [ha! what an appropriate word] written after a certain point in the evening would only be regretted and rewritten in the clear light of following days.

Working these long hours made people groggy, and that’s bad when trying to accomplish knowledge-based tasks requiring an excess of creativity, so there should have been no surprises about the number of mistakes, misfeatures and outright bugs.

Incidentally, these sorts of crazy hours weren’t mandated — it was just the kind of stuff we did because we wanted to make great games. In retrospect it was foolish — we could have done better work with more reasonable efforts.

One of my proudest accomplishments was to ship four Guild Wars campaigns in a two-year window without leading the development team down that dark path.

The most common cause of StarCraft game crashes

While I implemented some important features in StarCraft, including fog-of-war, line-of-sight, flying unit pathing-repulsion, voice-chat, AI reinforcement points, and others, my primary job gravitated to fixing bugs.

Wait: voice-chat! In 1998?!? Yeah: I had it all working in December 1997. I used a 3rd-party voice-to-phoneme compressor, and wrote the code to send the phonemes across the network, decompress them, and then play them back on the other seven players’ computers.

But every single sound-card in our offices required a driver upgrade to make it work, if the sound card was even capable of full-duplex sound (simultaneous recording and playback of sounds), so I regretfully made the recommendation to scrap the idea. The tech-support burden would have been so high that we would have spent more money on game support than we would have made selling the game.

So anyway I fixed lots of bugs. Some of my own, sure, but mostly the elusive bugs written by other tired programmers. One of the best compliments I’ve received came just a few months ago, when Brian Fitzgerald, one of two best programmers I’ve had occasion to work with, mentioned a code-review of StarCraft; they were blown away by how many changes and fixes I had made over the entire code-base. At least I got some credit for the effort, if only well after the fact!

Given all the issues working against the team, you might think it was hard to identify a single large source of bugs, but based on my experiences the biggest problems in StarCraft related to the use of doubly-linked linked lists.

Linked lists were used extensively in the engine to track units with shared behavior. With twice the number of units of its predecessor — StarCraft had a maximum of 1600, up from 800 in Warcraft 2 — it became essential to optimize the search for units of specific types by keeping them linked together in lists.

Recalling from distant memory, there were lists for each player’s units and buildings, lists for each player’s “power-generating” buildings, a list for each Carrier’s fighter drones, and many many others.

All of these lists were doubly-linked to make it possible to add and remove elements from the list in constant time — O(1) — without the necessity to traverse the list looking for the element to remove — O(N).

Unfortunately, each list was “hand-maintained” — there were no shared functions to link and unlink elements from these lists; programmers just manually inlined the link and unlink behavior anywhere it was required. And hand-rolled code is far more error-prone than simply using a routine that’s already been debugged.

Some of the link fields were shared among several lists, so it was necessary to know exactly which list an object was linked into in order to safely unlink. And some link fields were even stored in C unions with other data types to keep memory utilization to a minimum.

So the game would blow up all the time. All the time.

But why did you do it that way?

Tragically, there was no need for these linked-list problems to exist. Mike O’Brien, who, along with Jeff Strain, cofounded ArenaNet with me, wrote a library called Storm.DLL, which shipped with Diablo. Among its many features, storm contained an excellent implementation of doubly-linked lists using templates in C++.

During the initial development of StarCraft, that library was used. But early in the development the team ripped out the code and hand-rolled the linked-lists, specifically to make writing save-game files easier.

Let me talk about save games for a second to make this all clearer.

Save games

Many games that I played before developing Warcraft had crappy save-game functionality. Gamers who played any game created by Origin will remember how looooooong it took to write save-game files. I mean sure, they were written by slow microprocessors onto hard-drives that — by today’s standards — are as different as tricycles and race cars. But there was no reason for them to suck, and I was determined that Warcraft wouldn’t have those problems.

So Warcraft did some tricks to enable it to write large memory blocks to disk in one chunk instead of meandering through memory writing a bit here and there. The entire unit array (600 units times a few hundred bytes per unit) could be written to disk in one chunk. And all non-pointer-based global variables could similarly be written in one chunk, as could each of the game-terrain and fog-of-war maps.

But oddly enough, this ability to write the units to disk in one chunk wasn’t essential to the speed of writing save game files, though it did drastically simplify the code. But it worked primarily because Warcraft units didn’t contain “pointer” data.

StarCraft units, which as mentioned previously contained scads of pointers in the fields for linked lists, was an entirely different beast. It was necessary to fixup all the link pointers (taking special care of unioned pointer fields) so that all 1600 units could be written at once. And then unfixup the link pointers to keep playing. Yuck.

Change back!

So after fixing many, many linked list bugs, I argued vehemently that we should switch back to using Storm’s linked lists, even if that made the save-game code more complicated. When I say “argued vehemently”, I should mention that was more or less the only way we knew how to argue at Blizzard — with our youthful brashness and arrogant hubris, there was no argument that wasn’t vehement unless it was what was for lunch that day, which no one much wanted to decide.

I didn’t win that argument. Since we were only “two months” from shipping, making changes to the engine for the better was regularly passed over for band-aiding existing but sub-optimal solutions, which led to many months of suffering, so much that it affected my approach to coding (for the better) ever since, which is what I’ll discuss in part two of this article.

More Band-Aids: path-finding in StarCraft

I wanted to mention one more example of patching over bugs instead of fixing the underlying problem: when StarCraft switched from top-down artwork to isometric artwork, the background tile-graphics rendering engine, which dated back to code I had written in 1993/4, was left unchanged.

Rendering isometric-looking tiles using a square tile engine isn’t hard, though there are difficulties in getting things like map-editors to work properly because laying down one map tile on another requires many “edge fixups” since the map editor is trying to place diagonally-shaped images drawn in square tiles.

While rendering isn’t so bad, isometric path-finding on square tiles was very difficult. Instead of large (32×32 pixel) diagonal tiles that were either passable or impassable, the map had to be broken into tiny 8×8 pixel tiles — multiplying the amount of path-searching by a factor of 16 as well as creating difficulties for larger units that couldn’t squeeze down a narrow path.

Had Brian Fitzgerald not been a stellar programmer, the path-finding problem would have prevented the game from launching indefinitely. As it was pathing was one of the problems that was only finalized at the end of the project. I plan to write more about path-finding in StarCraft because there are lots interesting technical and design bits.

End of part 1

So you’ve heard me whine a bit about how difficult it was to make StarCraft, largely through poor choices made at every level of the company about the game’s direction, technology and design.

We were fortunate to be a foolhardy but valiant crew, and our perspicacity carried the day. In the end we buckled down and stopped adding features long enough to ship the game, and players never saw the horror show underneath. Perhaps that’s another benefit of compiled languages over scripted ones like JavaScript — end users never see the train wreck!

In part two of this article I’m going to get even more technical and talk about why most programmers get linked lists wrong, then propose an alternative solution that was used successfully for Diablo, battle.net and Guild Wars.

And even if you don’t use linked-lists, the same solutions carry over to more complex data structures like hash tables, B-trees and priority queues. Moreover, I believe the underlying ideas generalize well to all programming. But let’s not get ahead of ourselves; that’s another article.

Thanks for reading this far, and sorry I haven’t yet discovered how to write concisely.(source:codeofhonor)


上一篇:

下一篇: