什么是第一个虚幻编辑器


古典游戏的回顾展最近变得很流行,但很少回想起经典的开发工具。 我设法与Tim Sweeney讨论了Unreal Editor的第一个版本,即UnrealEd

BSP和谬误:“我们需要编写自己的编辑器”


大卫·莱特伯恩(David Lightbone):感谢您抽出宝贵时间与我交谈,蒂姆! 让我们从虚幻编辑器的早期开始。 我了解到Epic Pinball的创建者James Schmaltz向您展示了他从事的游戏,当您看到它时,我建议为其创建一个编辑器。 可以吗

蒂姆·斯威尼(Tim Sweeney):确实! 他的灵感来自游戏牛蛙魔术地毯 。 James是一位非常有才华的开发人员,但是他只用汇编语言编写代码,他不想学习C。[笑]因此,他用纯汇编语言编写了这个3D引擎,该引擎呈现了浮雕背景和游戏对象。 他不想创建编辑器,因此他手动制作了BSP树,并在此浮雕上放置了胶囊。 当我看到此消息时,我说:“不,不,不,詹姆斯,詹姆斯……您必须做一些完全不同的事情。” [笑]

詹姆斯·施马尔茨

魔术地毯,牛蛙

詹姆斯·史玛兹(James Schmalz)和牛蛙魔术地毯

我说过我会写一个编辑器,所以奇怪的是,我开始从Visual Basic中的UI布局创建Unreal Editor用户界面。 他有一个基于文本的命令界面,用于与渲染的C ++引擎进行通信。 然后,我编写了线框编辑器,并在此基础上继续进行开发。

也就是说,这是一个有趣的学习过程。 我以为我会写这个编辑器并将其集成到James的渲染器中。 我问他:“您能给我发密码吗? 我想了解如何集成渲染器。” 然后他给我寄了3万行汇编代码。 [笑]在3D渲染引擎中,有一些Epic Pinball元素和一些以前的汇编代码,他只是复制了这些代码。 我想:“天哪,这是什么混乱? 我不想碰这个!” [笑]

但是我告诉自己,在开始弄清楚之前,我只会编写一个小的纹理映射工具。 因此,我阅读了Michael Abrash关于纹理映射的文章,并研究了Billy Zelsnack共享的代码。 事实证明,纹理化是一个非常简单的主题,因此我决定:“我将尝试解决所有这些问题。”

在实现照明之类的东西之前,虚幻编辑器早期版本的魔力的重要部分是创建实时BSP树。 想法是,您可以更改画笔在3D空间中的位置,此后,生成BSP的所有工作都会实时进行完全更新。


这个功能所提供的机会并不适合我,只是为了展示其全部功能,我创建了这个工具来创建花托。 我联系了James,我记得他是手动构建他的BSP的,并告诉他“检查出来”。 我创建了两个相连的花托,并将它们从世界中减去。 他说:“哇,那不可能! 这太棒了。” 这是程序员固执的真实例子。

JL:据我所知,谈到BSP,约翰·卡马克(John Carmack)是最早在游戏引擎中使用BSP的人之一,与BSP合作的想法在当时的游戏行业中是相当新的。

TS: Carmack在NeXT上编写了非常强大的编辑器。 我阅读了有关他的所有信息,并看到了屏幕截图,但我从未使用过。 当时我想:“哇,卡马克写了一个实时BSP编辑器!” 我不明白的是,实际上它不能实时运行,需要进行预组装过程和其他操作。 我不知道这一点,以为他创建了一个可以完全实时工作的编辑器,所以他也写了同样的东西。 [笑]



NeXT和John Carmack上的QuakeEd

JL:(笑)您认为他是如此强大,因此您接受了挑战。

TS:是的! 虚幻的许多功能是由于我对他人所做的误解而出现的。

此外,一群前Future Crew演示制造商成立了一家设备公司,并发布了一些截图,这些截图带有在室内场景中令人难以置信的逼真的环绕照明。 周围有大球体的光源,周围的所有几何体显然都切断了体积照明。 它看起来在物理上非常准确。 我以为:“上帝,我从未见过这样的东西,我需要弄清楚这一点!”

因此,我开始理解并意识到,我需要计算从摄像机到屏幕上每个点的线性积分。 在大学里,我学习了数学分析,所以对自己说:“我必须成功。” 因此,我为此导出了一个带有复杂的三角函数的公式。 我用代码实现了它,但是它比必需的慢了一百倍。 然后我意识到:“停下来,我可以在光照贴图的空间中进行这些计算,”因为光照贴图是将几何体离散化为小片段。 我将计算结果转移到光照图上,并实时实施了所有操作。



基于光照贴图的虚幻中的光照示例

我拍摄了一张屏幕截图,并将其邮寄给了来自该设备公司的芬兰朋友。 他回答:“哇,这太神奇了! 但是我们的图片只是3D Studio Max的渲染,因为我们无法实时了解如何做到这一点。” [笑]

JL: [笑]是的!

[作者注:Tim谈论的公司是Bit Boys ]

TS:也就是说,我认为虚幻引擎是世界上第一个体积照明引擎,这是基于我的谬误。

由于3D才刚刚开始成为可能,所以这在游戏行业的早期历史上是一段令人称奇的时刻。 已经编写了多个软件渲染器,但没有人能够解决大规模问题:如何使照明在大世界中工作,如何使实时几何在大世界中工作。 这个开发过程发生了巨大的飞跃:Carmack实施了新的疯狂想法,我意识到了新的疯狂想法,并且我们不断发布自己能力的屏幕截图。

如果现在来看,仅用四年,我们就重新创建了约20年的1980年代和1990年代的渲染研究,以前只有通过初步计算才能实现,而不能实时进行。

JL:是的,BSP的想法是基于1969年写的一篇文章。

TS:是的 ,在我出生之前!



TurboPascal和Maya:UnrealEd灵感


JL:您使用了哪些灵感来源来开发Unreal Editor设计理念?

TS:只有少数灵感来源。

如果您看一下1991年发布的ZZT游戏,您会看到虚幻引擎功能的基本集合。 本质上,它是一个内置了游戏的游戏引擎。 该游戏具有一种小型脚本语言,尽管简单,却是一种完全脚本化的语言,可让您编写小型游戏脚本。

[作者注:ZZT的详细信息可以 Boss Fight Books出版的Anna Anthropy的书中找到]

该游戏具有用于创建关卡的交互式所见即所得实时编辑器。 通过按几个键,您可以四处移动,添加关卡并在游戏中检查它们,然后进行处理。 这种交互式工作流程是游戏的关键功能。



ZZT,游戏模式和编辑器模式

另一个灵感来自Turbo Pascal ,它是我真正喜欢的第一个开发工具。 这是一个非常易于使用的编辑器,用于创建代码和进行编译。 您刚刚输入了代码,几秒钟后它就已经编译并运行了。 与我当时所习惯的相比,创建程序的迭代过程非常了不起。 如果您看一下ZZT实现,它实际上看起来像是虚幻引擎的文本版本。 整个虚幻模型都受到了它的启发。

还有另一个令人鼓舞的灵感来源,导致了引擎的许多设计元素的创建:Visual Basic,类似于Microsoft的Delphi,即用于Windows的Object Pascal版本,可以在Borland的可视界面中进行编辑。 但是我从来没有使用过Delphi,我只在Visual Basic中工作。

想法是,用户拥有一个表单编辑器,他绘制表单元素,字段以及其中的所有内容,然后单击它们并打开代码。 该代码会立即出现,用户只需继续输入它即可继续工作。



Borland的TurboPascal

我将这些原理转移到了虚幻引擎:您可以将一个对象拖到某个级别,双击该对象以打开脚本编辑器,然后输入脚本并保存。 要开始编写代码,创建三维对象并实时进行所有操作,只需单击几下鼠标就足够了。

虚幻引擎开发中的另一个重要方面是Epic购买了多个启动了Maya第一版的Silicon Graphics工作站。 Maya当时已经完全过时了,但是存在一种具有蓝红黑黑色背景空间的交互式3D模式,可以在其上实时绘制对象和线框。 PC上的任何程序都无法做到这一点。 它们都停留在旧版代码和旧版UI模式中。



因此,当我开始使用虚幻编辑器时,我要做的第一件事就是通过复制Maya创建带有蓝色和红色线条的黑色背景。 我编写了绘制线条的过程,并创建了所有对象的三维线框轮廓。 那是启发我的例子,证明了这是可能的。

从Visual Basic到Slate:UnrealEd接口的演变


[作者注:在采访开始时,我在笔记本电脑上的虚拟机中启动了UnrealEd1。我给了蒂姆一个鼠标,以便他可以在其中工作。]

TS:哇,太好了,您已经发布了!

JL:是的! 我介绍了我们在这里看到的版本是Visual Basic。

TS:是的,是的!

[作者注:蒂姆很高兴从头开始创建关卡。 从外面看,好像是很久没见面的朋友聚会了。

JL:是什么促使您使用Visual Basic来开发UnrealEd的第一个版本的界面,还有什么其他选择?



艾伦·库珀和Visual Basic

TS:那是在1995年。 那时,C ++的用户界面框架简直糟透了。 有Microsoft基础类,这是您可以想象的最惨的API。 用户开始绘制窗口控件,并且该框架创建了大量C ++代码,并带有大量注释,例如:“这里我们为您创建了控件!” 如果用户移动了对象,则框架会更新部分代码,而不是其他部分,并且会不断崩溃,因此我对自己说:“我不会再碰这个了。”

Visual Basic是用于开发用户界面的绝佳工具,您可以在其中非常高效地放置所有控件,菜单项和UI的各个部分。 它是我见过的最高效的UI工具包,主要是因为它是一个非常清晰,相互关联的程序:您绘制了一个UI,单击了它,并添加了用于交互的简单代码。 我意识到,创建UI,然后通过命令行界面将其传递给C ++,来回发送文本作为序列化数据的方法要容易得多。 我认为情况并没有改变大约十二年,直到2000年代初期,诸如Qt之类的C ++的体面UI工具包开始出现。

[作者注:Visual Basic的第一个版本是由Alan Cooper开发的,通常被称为“ Visual Basic之父 ”。 他还是可用性和用户体验领域的重要人物]



UnrealEd 3,其中存在wxWidgets元素

DL :我认为随着时间的流逝,编辑器的某些部分开始被其他部分所取代。 这种演变是如何发生的?

TS:完成Unreal Editor 1之后,我冷静了下来,进行了许多新一代研究,这些研究通常直到沃伦·马歇尔(Warren Marshall)出现后才见效,后者使用wxWidgets用C ++重写了虚幻编辑器中的Visual Basic部分。 wxWidgets当时是最好的工具包。 这成为了虚幻编辑器2中UI框架的基础。

在虚幻引擎2开发过程的中间,Visual Basic代码从引擎中完全消失了。 我们有一个更方便,更干净的C ++框架。 因此,我们获得了几乎相同的UI,但没有语言困难。 但是真正的问题是wxWidgets并未开发,并且其他UI工具包也出现了,因此我们继续将它们集成为特殊工具。 因此,到虚幻引擎4开发周期开始时,我们有了五个不同的UI工具包...

JL:这经常发生...

TS:例如 ,包括用C#编写并集成到Unreal Engine 4中的WPF疯狂片段,例如在Mac上不起作用。 因此,那时我们的代码中出现了巨大的混乱。

同时,Nick Atamas用C ++创建了新UI层的原型,随着时间的流逝,我们决定使用它。 于是他变成了板岩。 因此,我们重写了100%的Unreal Engine用户界面,摆脱了所有插件UI工具包,并以相同的样式对其进行了重新制作。 这使我们能够扩大编辑器的规模,并达到今天的水平。


Slate UI的UnrealEd屏幕截图

但是我们仍然无法实现Visual Basic时代存在的便利。 甚至C#用户界面框架也只是一大堆XML和其他不必要的混乱。 似乎每个新一代都以越来越复杂的方式实现UI,并且情况越来越糟。

屏幕截图和XCopy:许可的重要性


DL:哪些公司最先使用Unreal Editor?

TS:在发布的前两年,我们已经有两个许可证购买者:Unreal Engine使用Microprose,然后Legend Entertainment将其用于《时轮》,我们为他们提供了支持。



传奇娱乐时光之轮

JL:他们也帮助了你,对吗? 合作是这种关系的一部分。

TS:是的,在虚幻开发过程中,他们的许可证付款使Epic持续运转。

当时,我非常重视许可,因为它允许我们付款,这导致我们使用了引擎的许可模式,该模式与Id模式完全不同。 当时有个玩笑,说从Id获得引擎许可就好比以一百万美元的价格购买XCopy:您支付一百万美元的费用,然后他们输入DOS XCopy命令为您提供源代码的副本……就是这样。 [笑]

JL :(笑)好吧,那么导致Unreal Engine许可证出售给Microprose和Legend Entertainment的原因是什至在Unreal 1发布之前?

TS:我想是因为我们仍处于1995年前期的早期阶段,发布了惊人的屏幕截图,不仅包括我们的游戏,还包括我们编辑器的截图。 这使公司与我们联系。 他们从Microprose打电话给我们,说:“我们想为您的引擎许可!”,我们就像:“引擎? 哪个引擎? 啊,我们的引擎! 这将使您付出巨大的代价。” (笑)这就是谈话的样子。



虚幻编辑器和虚幻引擎的第一批屏幕截图之一

恐龙和蜥蜴:虚幻的术语和图像


DL:谈到屏幕截图:这是90年代后期在Blue's News中发布的其中一个截图。 与我的虚拟机中运行的版本有明显的细微差异:例如,在左上角有“播放”,“帮助”和“史诗”按钮,这些按钮不在最终版本中。

你能谈谈吗?


1998年在Blues News上发布的UnrealEd屏幕快照

TS:绝对是1998年左右的虚幻引擎1,因为它具有Steve Polge代码,该代码当时协调了多人游戏。

“史诗”按钮只是一个指向网页的链接,每个人都觉得这很烦,因为他们不断点击并感到烦恼:“啊,浏览器再次打开!” [笑]

JL: [笑]好吧,您能谈谈肖像学吗?

TS:对于UI的所有元素,我绘制了绝对可怕的图标,然后将它们发送给了丹·库克(Dan Cook),他是我们的早期射击者Tyrian从事图形处理的艺术家。

他需要为Pawn元素绘制一个图标,因此他创建了一个棋子。 我说:“不,不,不,”他回答:“那么,请告诉我什么是典当。” 我说那像是一个怪物,非常酷,所以他画出了一个难以理解的生物的头:他们说这是恐龙,蜥蜴或其他东西......但是这些标志仍然存在了我们大约十年。



Daniel Cook及其为UnrealEd创建的图标。 “添加典当”图标位于底部,右侧第三处。

DL:我们已经提到过“典当”一词。 是我们自己提出来的,还是在某个地方看到了?

TS:我认为是Steve Polge或James Schmalz提出的。

JL: “演员”呢?

TS:卡马克提出了自己的术语,他称物体为“实体”,而我想,“不,我们需要一些独特的东西。”

JL: [笑]

TS:我们决定将对象指定为演员,因为在1980年代,我当时很喜欢的SmallTalk提出了基于演员模型的编程原则 。 该模型是面向对象的,对我们来说似乎是一个不错的开始。 因此,我们提出了典当和煽动者的思想,该思想决定了一系列事件和所有其他术语的开始。

schmalcism和大脑伏都教病毒:创建UnrealScript


JL:告诉我们更多有关James和Steve如何使用和创建UnrealScript的贡献。

TS: James Schmalz是一位独一无二的钻石,勤杂工。 他是团队中最好的艺术家,一个了不起的设计师,并且还可以使用UnrealScript和汇编程序进行编程。

DL:虚幻学分中,他的名字出现在一对完全不同的类别中。

TS:在整个游戏行业中,只有极少数的人才具有这种水平,他值得获得所有成功。

但是他从汇编程序转到了UnrealScript,并编写了疯狂的UnrealScript代码,在这些代码中,他们只是在继续工作的过程中反复敲击,晚上我走近了他,简化了他的代码。他有多行表达,例如“煽动者等等……”,而我用……“自我”代替了它们。 [笑]

JL: [笑]

TS:我们称此代码为“ Schmalcism”。Polge在他的代码中具有神奇的数字,例如“步行速度=奔跑速度x 3.072”。我问他:“我确实不知道物理学中是否存在恒定的3.072?” [笑]



TS: UnrealScript受Java启发;那时,这种语言似乎是C ++的继承者。该语言的创建者复制了许多建设性的解决方案,并在顶部添加了许多新概念。在UnrealScript中,存在基本容器的雏形,只有几代人后来出现在Java中。

我一直以为开发C#语言时,作者遵循UnrealScript,因为我看到了C#中浮现的UnrealScript的某些功能。我一直希望他们借用其中一些想法。

但是我越是沉迷于面向对象的程序设计和SmallTalk中,研究了有关元类的最高级研究,我就越发意识到这是一种没有真正理论基础的脑伏都教病毒。反过来,如果我们研究HaskellCurry-Howard的对应关系以及其他现代编程原理,我们将看到启发的来源和结构。

SoftDisk,Id和数百万美元的支票


JL: Jay Wilbur和Mark Rein,以他们的业务定位和共享软件的经验,是否影响了他们提供的引擎,工具,编辑器或资源?

TS:在早期,Epic的工作是因为我从事技术方面,而Mark从事业务。马克飞到世界各地,进行了疯狂的交易,为我们带来了现金。非常重要,如果不是他的话,我们将无法在这些早期阶段幸免。



马克·雷恩(Mark Rain)和杰伊·威尔伯(Jay Wilbur)

在某个时候,我们搁浅了,我们所有的支出都由马克的美国运通卡提供资金,该卡因此被取消。

JL: [笑]

TS:然后他飞往TG Interactive开会,从那儿回来了,并带了一百万美元的支票。它救了我们。因此,这在我们的历史上曾多次重复。有优秀的商人可以谈判非常重要。他是Id的第一任总裁,并且在Mark Jay成为Id的第一任首席执行官之后。

DL:在那之前,他和Carmack都在SoftDisk,对吗?

TS:对!这很有趣,因为实际上我将我的第一个ZZT游戏出售给了SoftDisk。是Jay Wilbur处理了我与SoftDisk的合同。这些谈判的结果是,我从SoftDisk那里收到了三千美元,所以我认识Jay很长时间了。


Id软件的早期。Jay Wilbur在右边。

从一开始就与Id的家伙一起工作启发了我。我参加了一个愚蠢的共享软件会议,Id出现在那儿。当时他们是业界的最爱,因为他们发布了Wolfenstein 3D,这是共享软件历史上最大的成功。他们与我们聊天,然后邀请他们共进晚餐。看到行业巨星只是谦虚的家伙真是太好了。约翰·罗梅罗(John Romero)是世界上最可爱的游戏开发商。

[作者注:我同意。约翰·罗梅罗(John Romero)在我们的TEd采访中花费了大量时间。]

所见即所得和易用性:最重要-工具观点


DL:因此,在1998年11月,Maximum PC版本出现了,您在接受采访时还谈到了当时存在的各种技术。这篇文章说:“ [Unreal Editor]在简单性方面领先于所有人”,“使用Quake技术很难。”

它还说:“ [Quake III:竞技场]技术确实令人印象深刻”和“ Prey and Trespasser的外观和效果比虚幻效果更好。但是,如果事实证明与他们合作更加困难,那么开发人员将继续使用Unreal。”

也就是说,您是否从一开始就制定了一个目标,即要创建一种具有易于使用的竞争优势的工具?



Maximum PC,1998年11月

TS:是的,是的。您知道,这一直是我最重要的事情:编写一个允许关卡设计师创建令人惊叹的作品的编辑器。从一开始就很明显,其中最重要的方面是实时工作和超快速的设计迭代,即“所见即所得”的全面实施(“所见即所得”,所见即所得) 。这样,您将仅受您的想象力和提出新想法的能力的限制。对于我们来说,Epic一直都有非常重要的工具。

JL: Epic使用了什么流程来引起广泛关注,在简化工具的使用上花费了时间和精力?

TS:Epic中的开发过程是引擎的开发团队,创建系统,功能和工具以及游戏团队的结合,他们花费了所有这些来创建游戏。当引擎开发团队创建新想法,然后与游戏团队分享它们时,我们会使用一个迭代过程,并不断获得有关什么可行和什么无效的反馈。这就是我们的技术流程的创建方式:工具开发人员应该帮助游戏开发人员这一事实使他们变得诚实。

我们不仅要创建易于使用的工具,而且要确保它们提供了正确的折衷方案,这些折衷方案实际上使您可以创建发布游戏所需的内容。

这是易用性的另一面。不可能将易用性视为一个单独的概念。当工具非常易于使用且开始创建游戏时,这是很不好的,但是由于它们会限制工作流程或功能,因此很难完成游戏的创建。


如果您查看引擎发展的最后五到六年,那么Epic和Unity之间的竞争取决于最初的易用性,而Unity在其中具有优势。 同时,在游戏发布的开发周期中,就不同平台上的性能而言,虚幻具有优势。 这是因为我们专注于发布史诗级游戏,即我们所做的游戏。 实际上,这比简化三个人快速发布简单内容的工作要复杂得多。

今天,虚幻引擎的代码大小比虚幻引擎1的代码大20倍,工具变得复杂了十倍,这是有原因的。 人们启动虚幻引擎并迷路了,因为菜单选项太多了。 然后他们切换到Unity,看到一切都很可爱和简单。 然后它们到达了您需要发布产品的阶段,事实证明您需要购买源代码的许可证才能向引擎添加菜单上没有的功能。 有这样的二分法。

灵感和传承的来源:虚幻教育的影响


JL: UnrealEd启发了包括我在内的一些游戏开发人员,不仅开始创建游戏,而且编写了自己的工具。 您如何看待UnrealEd对行业的影响?

TS:我认为每个现有的游戏编辑者都从UnrealEd那里借了些东西。 这是第一批编辑者之一,我们做出了许多正确的基本决策,例如,用户应如何使用2D网格,放置对象并在世界范围内移动。 我认为您可以跟踪从第一个Doom编辑器传递给Quake编辑器,再传递给Unreal的继承。 今天,一切都在某种程度上基于此。


维基百科的FPS引擎的历史记录图。 单击以打开较大的版本。

有些方面受一般原则的影响,例如Maya,但有些方面与Unreal紧密相关,Unreal是一种构造类层次结构,实施撤消系统以及所有其他严重游戏开发问题的方式。 在2000年代初期进入该行业的每个人通常都会经历Unreal或Quake。 尽管Quake是一个更大的游戏,但在我看来,大多数设计师都是通过UnrealEd来的,因为它的工具非常方便。

生产力乘法和除法:游戏开发人员的提示


JL: 2011年,您对Kotaku进行了采访。 在我看来,我将阅读一些与我们的主题有关的引文:

“我们一直在工具方面着手开发游戏。 我们创建了所需的工具,创建了一套用户友好的工具,并继续使用它。”

“我们史诗般的想法遥遥领先。 我们就像英特尔。 我们考虑在五到十年内将要做的事情,并选择适当的发展方向,而对于大多数公司而言,计划的极限是发布下一款游戏。 他们将所有资源投入其中,然后进行下一场比赛。”

“大型游戏公司(例如EA或Activision)不投资工具,他们没有像我们这样的长期规划,也意识到我们需要使游戏开发流程尽可能高效。”



在我对约翰·罗梅罗的采访中,他很好地理解了这一点,他说:“工具的寿命比游戏更​​长。”

您可以给游戏开发人员什么建议,以使他们可以避免此错误并了解投资工具的长期价值?

TS:好吧……您不必“似乎”创建引擎。 要么建造引擎,要么不做。 现在,如此众多的公司削弱了他们的生产流程,使用了无用的工具来创建引擎。 它是杀死人的工具。

查看公司内部创建的所有这些引擎...例如,Frostbite具有比我们的引擎更高级的渲染功能,并且在许多情况下,它比我们的引擎绘制更漂亮的像素,但是虚幻的开发人员可以更高效地创建内容,大约生产率提高30-50%。 就是说,团队可以用一半的能力来创建相同质量的游戏。 与使用质量较低的工具相比,它可以执行更多的迭代并更好地磨练游戏。 因此,每个人都需要做出明智的决定-要么全力投资于创建出色的内部使用工具,要么使用第三方引擎。

DL:因为您认为开发人员正在遭受一半的考验?

TS:是的。 这些公司内部有些地方的会计师非常愚蠢,他们的想法是这样的:“哦,通过限制开发工具的支出,我们可以节省百分之二的预算。” 结果,这导致预算增加了50%,因为在创建游戏上必须投入更多的时间和精力。 因此,它会造成如此疯狂的后果,不能证明节省成本是合理的。

我认为每个公司都应该做出决定-要么在工具上投入更多,要么根本不做。


这适用于一切。 不仅是用于创建关卡的3D编辑器,还包括装配系统,编程语言,开发技术,DCC工具等等。

工具应该提高生产率,如果事实证明它们降低了生产率,那么您就需要摆脱它们。

JL:太好了。 感谢您抽出宝贵的时间与我聊天。

Source: https://habr.com/ru/post/zh-CN409969/


All Articles