使用脚本语言可以让您更快地开发游戏逻辑,而不必担心 C++ 程序员粗心的后果。使用现有的脚本语言可以节省开发新的自定义语言的时间和费用,而且这些语言通常比您自己创建的语言更强大。
对于游戏脚本语言来说是一个不错的选择。它功能强大,易于嵌入和使用,可以与C/C++无缝扩展,包含脚本语言的许多高级功能,并且可以用于自动化流程。 [TR1:]。此外,书籍、开发工具和库也很丰富,可以很容易地从其他开发者那里受益。
我们来谈谈我们在娱乐公司整合新游戏引擎方面的一些经验。解释我们为什么选择、我们获得的好处、我们遇到的问题以及我们如何解决这些问题。
为什么使用脚本语言
C++ 是一种功能强大的语言,比 C 有了巨大的改进,但它并不是所有任务的最佳选择。 C++ 非常重视运行时性能[]。例如,如果某个语言特性使得程序运行速度变慢,那么这个特性就不会被添加到C++语言中。 C++程序员也背负着许多限制和担忧。
以下是 C++ 程序员经常遇到但很少注意到的一些限制:
C++是静态的,而脚本语言是动态的。简单地说,C++ 程序运行速度很快,但脚本语言可以让你编码得更快。
因此,C++ 只能用在您希望优化运行时性能的地方。今天的计算机速度足够快,对于大多数代码来说性能不是问题。如果您用 C++ 开发可以用脚本语言实现的程序,那么您正在针对错误的事情进行优化。
SCUMM 的问题
该公司已经使用SCUMM(for)制作了超过50款游戏。 SCUMM是一种功能强大的冒险游戏开发语言,但它有一些局限性。 SCUMM 是十多年前编写的,它缺乏现代语言的一些特征。
尽管 SCUMM 不断得到修补和维护,但它并不像其他语言那样健壮或功能齐全。
为什么选择
我们曾想过创建一种新的、现代的私人语言,但最终明智地放弃了它。我们的工作是玩游戏,而不是文字。
当我们每年花费大量资金维护一组专有工具时,我们确实想使用现有的脚本语言,而不是重新发明一种脚本语言。使用现有语言的速度更快,开销更少,并且通常比我们创建的语言更好,即使我们不使用它,以后也会变得更好。
一旦我们决定使用现有的脚本语言,我们就需要选择一种。我们需要一种支持面向对象编程并且可以嵌入到我们的游戏中而不会出现任何技术或许可问题的语言。
我们考虑了 Lua [Lua01] 和 [Lua],这两种语言已经在一些游戏中使用。
Lua 更小,更容易嵌入到应用程序中,并且具有一些很棒的语言结构。然而,当时我们发现Lua的文档有点粗略,可能是因为Lua是一种较新的语言。
比Lua有更多的扩展模块,更多的参考书,并且[]非常适合为对象AI创建微线程[TR3:微]。最终我们没有选择的版本,而是开始编写自动生成的脚本,这给了我们继续使用它的动力。了解了它之后,我们喜欢它的语法,最终选择了它。
自从我们做出决定以来,两种语言都得到了改进:Lua 已经成为,并且通过生成器,这一种语言提供了一些类似的功能。现在无论是哪一种都是安全的选择。
谁在游戏中使用过
它已被用于许多游戏中,包括:
还有很多其他游戏,但我们很难确认。例如,至少一款 PS2 游戏使用它。
它还用于至少两个游戏引擎:
构建脚本示例
下面是一个简单构建脚本的代码示例,该脚本递归地生成所有 VC++ 工作区。它只有以下几行:
源代码打印?
通过更多代码,您可以让此脚本[]分析输出,然后通过电子邮件将结果报告发送给团队中的每个人。与其他一些脚本语言不同,上面的代码非常具有可读性。用它来编写构建脚本和游戏脚本将节省大量的学习时间。
这个构建脚本示例还显示了一些令新手头疼的问题。流程的控制通过缩进来指示,而不使用开始/结束语句或大括号。
我花了很短的时间来适应这个规则,最后我发现它非常有效。我不止一次讨论过C/C++中的花括号应该写在哪里。我认为程序员的工作效率更高,因为他们不必花时间争论 K&R 和其他缩进样式 [TR4:样式]。因为代码块是通过缩进定义的,所以如果没有任何违反编译器规则的缩进,就不能编写代码块(因为这会导致程序出错)。
请注意,混合使用 TAB 和空格进行缩进时可能会出现问题。大多数程序员使用 3 或 4 个空格的 TAB 缩进宽度,但编译器内部使用 8 个空格的缩进,混合使用 TAB 和空格可能会导致语法错误。如果您专门使用空格或 TAB 进行缩进,并且使用对混合空格和 TAB 缩进发出警告的 IDE,那么就没有问题。
游戏脚本示例
下面的示例是我们的第一个/C++ 游戏的一些代码。这段代码是一个正在执行的主循环,它调用其他模块,这些模块甚至可以用其他语言编写:
源代码打印?
因此,我们的游戏首先在需要时调用 C++ 程序。
它是如何运作的
程序由模块组成,当一个源文件中定义的函数在另一个源文件中使用时,需要导入该文件。例如,如果 .py 有一个函数,则可以在其他源文件中调用它,如下所示:
源代码打印?
游戏程序员能想到的一个伟大的事情是,如果()很慢,他们可以用C++重写它。为此,.py 中的函数和类型需要用 C++ 实现,并使用原始模块名称进行注册。之后,消费者可以继续导入并使用该模块,无需进行任何更改。
因此,模块可以帮助您轻松构建整个游戏框架,并在适当的情况下使用 C++ 代码。
胶水代码
如果你手写让C++代码协同工作的胶水代码,那将是一项繁琐乏味的任务【TR5:胶水代码】。生成粘合代码的系统框架很重要。
Swig、Boost、CXX 等[]可以帮助您更轻松地生成代码并与 C++ 结合。还有Fubi[],它是一个将C++函数和类映射到脚本语言的通用框架。
早期,这些胶水代码框架大多依赖分析 C++ 头文件来工作。因此,它们受到它们公开的 C++ 头文件的限制,并且某些框架不支持从 C++ 类派生类。从那时起,这些框架已经有所改进,因此现在仍然值得考虑。
我们决定制作自己的解决方案,它可以根据类或导出函数的 IDL 描述生成粘合代码。它的代码称为 Yaga,这是一个递归术语,意思是 Yaga 是一个游戏。
典型的Yaga IDL代码如下:
它可以生成以下粘合代码等:
源代码打印?
使用此框架,您可以轻松导出类和函数、从 C++ 类派生类、映射 C++ 数组和序列类型等等。
内存分配
里面的一切都是对象,对象都分配了内存。由于所有对象都是引用计数的,因此您不必担心释放内存。但是,如果您正在编写游戏,尤其是控制台游戏,您需要了解这些内存是从哪里分配的以及分配过程的碎片有多严重。性别。
为了控制这个性能问题,您需要将其隔离,使其拥有自己的内存分配场。您需要将所有内存分配操作重定向到自定义分配器,该分配器从固定大小的分配场分配内存。只要你保留足够大小的缓冲区,大于最大历史分配(原文:上面的leave),你应该能够避免内存碎片问题。
另一个内存问题是未释放的块。这在 Java 中通常不是问题,因为每个对象都有一个引用计数。当变量离开作用域或被显式删除时,其引用计数将减一。当计数达到0时,对象被释放,对象的生命结束。
想象一下这样一种情况,一个被遗忘的变量与一堆其他对象相关联,这将阻止这些对象的释放,所以你应该警惕清理对象。然而,更糟糕的是循环引用问题,例如:对象A包含对象B,但对象B有一个回调指针指向对象A,那么这两个对象将永远不会被删除。开发人员意识到了这个问题,并在最近的版本中添加了垃圾收集器,它会搜索无法访问的对象并将其全部清除。
垃圾收集器对于游戏来说是可怕的,因为它们是不可预测的,并且可以运行很长时间,从而降低游戏的帧速率。因此,游戏程序需要禁用垃圾收集器,这很容易做到,然后在每个游戏关卡后显式调用它。垃圾收集器还可以告诉您仍然分配了多少无法访问的对象。这可以帮助您跟踪参考循环情况,以便您可以手动解决它们。这相当于内存泄漏检查。
表现
如果您正在进行一些繁重的浮点计算,与 C++ 相比,其性能将令人失望。是一种慢速语言,每个对象引用都意味着哈希表查找,每个函数调用也是如此。这根本无法与 C++ 的性能相比,C++ 中变量位置和函数调用地址是在编译时确定的。
但这并不意味着它不适合游戏编程,只是你需要在正确的地方使用它。如果比较字符串操作或者C++ STL的set和map类型操作,那么代码可能会更快。字符串操作函数是用C编写的,引用计数对象模型可以避免一些C++类的字符串复制过程。 Set和Map的大多数操作的复杂度是O(log n),而哈希表的复杂度是O(1)。
你一定认为最好不要编写场景图遍历或BSP冲突检测代码。但如果你用 C++ 编写它们,然后导出它们以在 C++ 中使用,你可以更快地编写 AI 代码。
记住90/10原则,这意味着对于90%的代码,你不必太担心它的运行时性能,但代码的清晰度和编码的效率才是关键。
控制台游戏
内存和性能问题在游戏机游戏平台上尤其重要。当没有虚拟内存可以让你随意分配内存时,确保内存分配在单独的内存分配场中就显得尤为重要。另外,更明智地使用垃圾收集器(就像使用 )。
控制台平台没有键盘、鼠标和多个显示器,因此在控制台平台上运行调试器非常不方便。远程调试是关键,它可以让你知道代码是如何运行的。幸运的是,使用免费的[]可以轻松设置远程调试环境。
它是用 C 语言编写的,并已移植到各种编译环境和平台,包括 PDA。因此,它可能在某个主机游戏平台下得到很好的发展。
它会消耗少量与主机游戏无关的内存,但在新一代游戏平台上你不必担心这一点。它们都至少有 24M 内存。
法律问题
推出新语言对于我们公司来说是一个重大决定,我想在做出之前一定得到了公司律师的祝福。
律师了解法律,但他们通常不太了解编程。大多数程序员在引入开源代码之前不会咨询公司律师,当你询问他们时,他们认为你在问一些奇怪的、不合时宜的问题。他们的第一反应是认为这是一个有风险、没有保障的计划。
如果你和一位专门从事知识产权的律师进行了长时间的交谈,他会不断地告诉你,使用开源软件会让你痛苦不堪。在某些情况下,当“免费分发”源代码包含专利或受版权保护的内容时,会出现严重的法律问题。当您从商业软件供应商那里获得许可代码时,他们会保护您免受法律责任,但对于开源软件,则没有人可以追究您的责任。
然而,开源社区始终对知识产权法保持警惕。例如,JPEG 已从其开发库中删除了 LZW 算法代码以避免专利问题 [IJG]。负责任的程序员关心许可问题,并且通常熟悉 GPL 和 LPGL [FSF01] 及其差异。
将开源代码引入商业产品存在许多风险。这些风险应该认真对待,但不应妨碍开源代码的使用。游戏开发中使用了很多开源开发库,确实没有理由不使用它们。
缺点
多语言开发增加了额外的复杂性。同时调试两种语言的代码很困难,并且必须花费时间维护绑定两种语言的粘合代码。
类似的动态语言没有编译时类型检查。这种情况一开始可能看起来很可怕,但它实际上意味着,与 C++ 相比,你会遇到各种不同的运行时错误,而且它们通常很容易解决。
不同类型的换行符
UNIX (LF)、Mac OS (CR) 和 (CR LF) 对于如何结束文本文件中的行有不同的约定,这确实很糟糕。
上的C/C++库(译注:指API和VC运行时库)会做换行转换,所以上可以读取UNIX文件,并且可以像UNIX文件一样操作文件。 UNIX 和文本文件的共同点甚至更少,并且转换依赖于给定平台上的文件仅在该平台上创建的假设。这种假设在当今的网络环境中是站不住脚的,并且深受其害。到目前为止,用 编写的代码可能无法在 下编译,反之亦然。
解决这个问题的办法是,在运行代码之前,先通过文件过滤器(开发可以用?)来运行源文件。另一种方法是以编译后的字节码形式发布代码。然而,这两种方法都有缺点。理想的情况是标准化计算机行业中的文本文件格式,或者让所有文件 IO 库实现读取任何类型文本文件的能力。
这个问题在苹果的操作系统上更有趣,这会导致一个系统上出现两个不同的换行符,甚至不需要重新启动。
2 的版本最近通过在打开文件时检查换行符并对每个文件进行调整来解决了这个问题。将所有换行符指定为 UNIX 是一种可行的方法,并且适用于所有平台,但请注意此问题。
调试器问题
许多程序员认为自动化测试和打印语句是他们唯一需要的调试工具,并且使用调试器会影响编码效率。也许对他们来说确实如此,但我习惯了进行源代码级调试,不会轻易放弃。
它是一个底层调试器和 IDE(很酷,对吧?)。它是免费的并且有一些不错的功能,但它也有一些缺点,例如:它只能在 10下运行并且无法调试带有自己的消息循环的程序。
在 ,我们开发 和 iOS 游戏以及主机游戏。我们需要一个适用于所有三个平台的调试器,最好的解决方案是使用远程调试器。它的架构使得编写调试器变得很容易。与其他一些免费组件一起,我们开发了自己的调试器。我认为它更有效并且具有远程调试功能。被调试的客户端需要运行一些额外的代码。调试界面为ASCII 文本。此外,我们还没有考虑将调试器客户端移植到更多其他平台。
因为我们想专注于开发游戏本身,而不是语言工具,所以我们决定再次借用开源的力量。我们将 HAP 调试器作为开源项目发布到社区[]。这是回馈社区并让我们摆脱维护这个调试工具的负担的绝佳机会。
我们尚未解决的一个问题是调试器性能。大多数编译语言通过用导致CPU异常的指令替换常规指令来实现调试断点,例如x86处理器上的int 3中断。这允许程序全速执行,直到触发断点。不支持异常继续执行,因此不能使用断点异常方法。调试器处理断点的方式是单步执行代码,不断地问自己“这一行有断点吗?”
这种性能影响的后果可能很严重。我们目前减少这种影响的方法是确保开发机器比目标机器快得多。此外,所有重量级计算均作为 C++ 扩展实现,因此即使代码减慢了调试器的速度,也不会过多减慢整个游戏的速度。这是一个可以解决的问题,只是主要开发者还没有考虑到。
代码安全和游戏作弊
C++ 程序员有时开玩笑说删除注释和缩短变量名可以优化代码。然而,事实确实如此。
代码在运行时被编译成字节码并缓存以供后续运行,因此删除注释不会产生优化程序的效果,但缩短变量名称是另一回事。大多数脚本语言在运行时按名称定位变量。这就是脚本语言强大的原因之一,因为它们可以突破C++编译时绑定带来的许多限制。不过,这也意味着变量名会一直和代码一起存在(译注:C/C++等传统编译语言不同,优化编译后的C/C++程序中没有变量名的概念,只有地址) )。
游戏程序中语义明确的变量名()会被认为是笑话。更严重的问题是,如果该脚本用于多人游戏,作弊者反编译程序后会得到完整的变量和函数名称,这比反编译C++程序破解游戏更容易。
优点
编程很有趣。易于学习,效率更高,并迫使您以不同的方式思考编程。学习编程使我成为一名更好的 C++ 程序员。
快乐的程序员学习效率更高、生产力更高,而且他们往往会创造出更好的游戏。公司里开发游戏的团队是整个公司工作士气最高的。
游戏编程系统(译注:开发工具、框架、类库等)生产力很高,并且仍在发展中。通过使用它们,我们节省了很多钱。 (原文:已经有了游戏,甚至还在做。显然,我们会因此节省很多钱。)
用C++开发用户界面可能会花费很长时间,但是可以通过一些创新的方式来实现。文本文件通常用于定义 GUI 元素和关联图形资源的位置,然后定义菜单。在C++中,硬编码的函数和控制对象用于挂钩GUI元素;在C++中,函数和对象名称可以放入文本文件并在运行时扫描。 ()的动态性和内省性特征使得做这些事情变得非常自然。 (译注:C++也可以使用读取文本配置方式自动生成菜单,但使用反射特性更自然)
我们最初担心的许多语言限制现在已成为过去。开发人员不断改进语言,有时就好像他们不断满足我们对功能的渴望。
游戏保存和加载
C++ 程序员花费大量时间来解决脚本语言中不会出现的难题。例如,使用C++存储和读取游戏状态是一个很麻烦的问题,往往需要编写大量代码。这种方法通常会导致保存文件仅适用于特定版本的游戏程序。在 中,这个问题可以使用模块轻松解决,模块可以存储和读取任何复杂的数据结构。
下面的例子声明了一个对象,通常是一个用户定义的类对象,其中包含需要存储的各种状态的句柄,但为了简单起见,这里只将其做成一个列表。最初列表包含数字 0 和一个字符串,然后列表的第一个元素被分配给另一个列表。此过程可以继续包含具有任意复杂嵌套级别的对象,包括循环引用。
源代码打印?
接下来我们保存它,这需要两行代码。一行导入模块,另一行打开文件并以二进制格式保存对象。开发时,保存为文本格式很有用,只需省略 dump() 的最后一个参数即可。
源代码打印?
然后加载文件数据,同样需要两行代码。一行导入模块,另一行重建对象,以及包含的子对象、列表、成员变量等。第三行打印出对象,可以看到嵌套列表已经正确恢复。
源代码打印?
C++ 基本功能中不存在此功能。
生成器:游戏 AI 的微线程
微线程将对象状态信息放入局部变量(这是正确的位置),极大地简化了 AI 和对象更新代码[]。可以使用汇编语言技巧将微线程放入 C++ 中,但这很混乱。在最新版本中,微线程内置于该语言中。现在使用微线程就可以正常工作了。
它们被称为生成器(),它们用于编写函数。当函数产生一定的结果后,控制权返回到主程序。主程序稍后可以重新唤醒它们并从中断的地方继续运行,并保留原始的局部变量值。以下示例代码显示了创建对象并将其移动到屏幕上的过程。这个简单的示例并没有体现微线程/生成器的实际好处,它只是基本上展示了如何使用它们来简化人工智能和对象更新代码。
源代码打印?
即使不使用生成器,用 C++ 实现 AI 更新方法也比使用 C++ 更干净。因为如果你的AI代码的一部分需要一些额外的临时状态,你可以将其添加到对象中,然后在不需要时将其删除。由于其静态性质,C++ 无法在运行时添加新的成员变量,因此您的对象必须随时包含所有所需的状态。
开始使用
如果您开始使用,首先要做的就是访问官方网站下载适合您平台的版本。
文档就在那里,还有一个已编译的 HTML 版本 (CHM),以便于检索。
开发人员可以使用各种Win32扩展
调试器
您可以下载并编译源代码并构建您自己的调试和版本。 2.2 源码下载 ftp:///pub//2.2/-2.2.tgz
最后,您可以阅读有关手动创建扩展的更多信息,然后选择一个粘合包来为您执行此操作,请参阅
扫一扫在手机端查看
-
Tags : 游戏脚本谁写的好
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。