.为什么要使用脚本语言呢?对于资深从业人员或者业余开发者来说,这都是一个值得关注的问题。从游戏设计师的角度来说,使用脚本语言开发游戏可以很清楚地界定底层代码和游戏玩法代码。通常,在引入了脚本语言的项目中,底层模块交给像C++这样的核心语言,诸如界面交互、数据管理、人工智能和事件处理等,一般使用脚本语言实现。这种职责的划分可以让用户的游戏更加稳定,并且使得并行开发成为可能。
脚本语言还能使开发团队中的非技术成员参与到核心开发过程中。界面美术师不仅只制作界面素材,还能独立于程序员编写让界面运行在游戏中的脚本框架。要实现这些想法,设计师则可以不必麻烦程序员,而直接着手AI、数据处理或者创建场景脚本。
相对于C++这样的底层语言,脚本语言本身是易学易用的。由于Lua语言无须关心复杂的内存管理、对象渲染或者TCP/IP网络通信,所以十分容易上手并投入实际开发。学习脚本语言不需要花费很长时间,开发者可以在几个小时内就掌握它的语法和功能。
在本书中,我们将自下而上地学习如何使用Lua语言并利用C++的功能来创建一个完整的游戏。在这个过程中,会特别向读者展示脚本语言是如何优化开发过程的,不管是资深开发者还是业余爱好者,都将会有所收获。
脚本语言有很多,那我们为什么要选择Lua?纵观整个脚本语言领域,我们可以看到有Perl、Tcl、Ruby、Forth、Python、Java和Lua。尽管所有的脚本语言在特定领域都有自己的一席之地,但在游戏开发的世界中,Python和Lua是非常适合的(因为它们可以直接调用C++的功能)。
许多商业游戏已经成功地使用了Python和Lua,因为它们都有很强的兼容性,所以可以与编译后且基于C++技术的模块协同工作,而且还能扩展。如果读者有机会询问一下程序员对于这两种语言的看法,通常会有非常不同的观点,还可能有激烈的争论,就像体育中的“德比”赛事那样(想象一下芝加哥小熊对白袜,纽约喷气机对巨人3D动画,纽约大都会对洋基)。其实,这两种语言都是游戏开发领域中非常出色的工具。
1-3为什么使用Lua
对于游戏开发而言,Lua是较好的选择,其设计的核心目标是可扩展性,因此在最初设计时就考虑到要能够集成在大型应用中。因为有了这样的设计目标,所以非常容易在应用程序中加入Lua脚本。Lua的易集成的特性还使得Lua可以很方便地与父程序通信。游戏程序员都希望脚本语言能够简单地实现游戏设计,在这方面,Lua也能够胜任。
Lua免费、小巧、快速且易移植。所有的游戏开发者和游戏公司都喜欢“免费”的工具。通常讲,一分钱一分货,但是对于Lua来说,它完全超出你的预期。Lua采用了非常灵活的发布协议,它有极少的源代码,运行轨迹十分紧凑,在编译时间和运行时内存占用上都有很好的性能表现。
要说Lua最让人惊喜的地方,应该是它的执行速度。对于任何脚本语言的技术方案,游戏开发者的第一反应就是:“脚本语言太慢了,帧率一定不会很理想。”但这个说法对Lua是不成立的,事实上,我们还没有看到任何一个项目因为Lua的使用而造成瓶颈。最后,游戏开发界正在迎接一次新的硬件周期,我们将要学习如何使用一组新的平台。因为Lua的易移植性,当我们的技术储备转移到新的平台时,至少有一部分是不会过时的。
Lua是非常容易学习的语言。不需要了解很高级的编程概念(如对象和继承),大部分具有计算机学习背景的人都可以在短期内掌握它的基础知识并且马上投入正式的工作。如果团队成员熟悉其他的语言,那么Lua可以轻松上手,很适合那些非程序员背景的团队成员,它们也能对游戏功能和美术部分进行修改或创建。
在我们公司,最近刚刚发布了第13款使用Lua开发的游戏。我们的团队虽然很小,但是也有程序员、美术师和设计师的标准构成。当我们开始一个新的项目时,首先要确定项目的技术需求(什么是我们还没有而需要去实现的?)和设计需要的功能。程序员可以负责技术设计部分,专注于那些他们擅长的技术难题。同时,设计师和美术师可以马上开始着手界面流程和核心游戏功能的设计工作。通常,美术师(包括2D和3D)还会花一部分时间确定游戏的视觉需求。与此同时,3位熟悉Lua的设计师开始构建游戏基础、游戏数据及核心游戏系统。他们甚至都不需要等着程序员,如果需要什么功能一般就直接先用Lua代替。这样我们就可以进行非常高效率的游戏开发,因为团队的每个人都能从一开始就“热火朝天”干起来。
有一个项目要特别提起,在我们为2004年美国总统大选开发选举游戏模拟器的时候,我们已经可以用Lua开发出完整的游戏原型来验证AI和游戏流程,然后再回过头来重新用C++实现那些核心的部分。快速的原型开发可以让一个开发者就能够完成设计和开发环节最有价值的部分,这也是业界少有的高效率。
1-4本章小结
游戏开发是一个令人兴奋的产业,人们一旦有了灵感就想马上开始行动。Lua赋予了我们这样的能力,让我们可以快速实现游戏核心概念,设计并测试界面,并且方便地管理大量的运行时数据,而这一切都不需要读者有很深厚的技术背景。
在以后的章节中,我们将要学习如何在游戏项目中使用Lua,并学习语言本身的特性。然后,我们将开发一个完整的游戏,一起经历从开始到完工的整个过程。完成了这些,我们就可以很好地掌握这种灵活、小巧的开发语言,并切身感受到它给你的游戏和游戏开发体验所带来的不同。
第2章
脚 本 语 言
本章要点
脚本语言简介
Lua简介
虽然计算机可以做很多事情,从生成报表到模拟经营属于你自己的主题公园,但是计算机自己不会思考,它需要接受系统化的指令来工作。大部分用户通过像Word文字处理器或者电子表格工具这样的应用程序来为计算机指派任务。软件工程师则使用像C++这样的底层编程语言让计算机工作(通常是为普通用户开发应用程序)。而我们所说的脚本语言,存在于操作便捷的应用程序和开发软件的底层编程语言这二者之间。
2-1脚本语言简介
脚本语言可以方便地与计算机底层功能交互。这体现在它常常被当做批处理命令工具,即发送一系列重复的指令给命令处理器的工具。所以早期的脚本语言常常称做批处理语言或者作业控制语言。
一个熟悉的例子就是MSDOS时期的老的*-bat文件,这种批处理文件就是简单的文本文件,它包含一系列顺序执行的DOS命令。该语言本身就是DOS命令集合,通过进一步扩展成为一种伪脚本(参考下面的示例)。
计算机语言用于解决一些特定的问题,从系统控制级别的C和C++到人工智能处理语言(如LISP)。脚本语言通常拥有一些共同点,他们一般用在快速开发中(低成本、高效率),并采用接近自然语言的语法,对于非程序员背景的人更易于书写和阅读,这样有一定基础的用户就可以在没有程序员的帮助下编写和使用脚本语言。脚本语言在调用其他底层语言开发的模块方面十分出色。
脚本文件都是在载入时解释和编译(不是预编译,而是在调用时才处理)。以Lua为例,它只有在载入时才被编译成二进制形式并存在于内存中,直到被释放。
在软件开发(特别是游戏开发)领域,结合使用脚本语言和底层语言可以让开发者更好地控制运行环境,使得在开发过程中,在运行环境上的修改和测试都拥有更大的灵活性。
2-2Lua简介
Lua和传统的脚本语言不同,它是一种易整合语言(glue language)。一般的脚本语言用于控制执行重复的任务,而易整合语言可以让使用者把其他语言开发的功能整合在一起。这样就让脚本程序员有了更大的发挥空间,而不仅仅局限于执行命令。程序员可以使用这种脚本在底层语言开发的功能模块基础上创建新的命令。本书将探讨如何使用Lua来整合C++的与游戏相关的一些功能,如GUI、AI、数据等。
Lua本身是一种简单而又强大的编程语言,它可以让脚本程序员完成大量的处理。这种语言拥有很强大的字符处理和数学运算能力、灵活的数据类型(很快就可以熟悉),以及定义函数的功能。但是如果没有整合其他环境的组件的“魔力”,这些基础的特性也就丧失了。(没错,你可以在命令行下执行Lua脚本并查看运行结果,但除了学习语言本身游戏素材,对于游戏开发来说,命令行式的输出是没有实际意义的。)学习其他编程语言经典的第一课是如何输出“hello world”,Lua版本的方法参见代码清单2-1。
代码清单2-1用Lua编写的“hello world”程序
Lua非常适合作为更强大的底层编程语言的搭档,如C++。Lua能让游戏开发者快速建立游戏原型甚至是完整的游戏。游戏开发者可以在没有程序员帮忙的情况下构建整个图形界面。它还可以用来管理游戏进度文件的保存和载入,而且很容易阅读和调试。在游戏开发领域,Lua能帮助开发者构建一个高效并且方便验证游戏想法的环境。
按照开发Lua的团队的描述,Lua是一个可以集成在应用程序中的“语言引擎”。它本身是一种编程语言,并且还提供了很多可以和应用程序交换数据的API(应用编程接口)。另外,Lua还能够通过整合C++的模块来进行功能的扩展(这个就是我们之前所说的“整合”功能)。和程序开发语言(如C++)配合使用时,Lua也可以用来作为特定项目的框架语言。这种易扩展性使Lua非常适合作为游戏开发的环境。
作为独立的编程语言(在运行窗口中执行),Lua功能很有限,只能用做教学工具。(我们会在接下来的章节中使用控制台学习该语言。)Lua只有集成在其他语言中才能发挥它的价值。它的实现非常简单,仅仅通过一些LuaGlue函数就可以和底层语言通信,在用户自定义LuaGlue函数的基础上,它还可以进一步被扩展,甚至成为一种新的编程语言。
2-2-1Lua的历史
Lua在葡萄牙语中是“月亮”的意思,1993年由巴西的Pontifical Catholic University开发。该语言是由一个来自计算机图形技术组织(Tecgraf)的团队(Roberto Ierusalimschy、Waldemar Celes和Luiz Henrique de Figueiredo)开发,并作为自由软件发行。Lua开发小组的目标是开发一种小巧、高效并且能够很好地和C语言一起工作的编程语言。在脚本语言领域,Lua是最快、最高效的脚本语言之一c 游戏开发,因此它有资格作为游戏开发的备选方案。Lua的内核小于120KB(Python的内核大约860KB,Perl的内核大约1-1MB),当编译和集成到游戏开发系统中时非常小巧。Lua通常比Python这种流行的游戏开发脚本语言运行更快速,完整的性能测试报告可以在计算机编程语言实战性能测试网站中找到()。
计算机图形技术组织(Tecgraf)成立于1987年,致力于开发和维护用于技术和科技领域的计算机图形和用户界面。除了Lua,Tecgraf小组还开发了IUP(一种开发用户界面的系统)、CanvasDraw(跨平台的图形库)、TWF(一种用于Web页面的图形文件格式)和其他一些系统。读者可以访问相关网站获取更多信息。
2-2-2Lua授权
Lua是免费的开源软件,可以免费用于科研及商业应用。关于开源软件的更多信息可访问www-opensource-org。
对于游戏开发专业人员,授权费对于开发技术的选择影响很大。通常,对于一个项目,游戏引擎的预算会超过50万美元,中间件技术会花费5千美元~5万美元不等。因此,开源软件对于开发团队来说是很有吸引力的。
开源软件因为本身不盈利,所以它们的代码通常有很多bug,又由于没有太多注释,因此难以理解。另外,没人为技术支持付钱,所以也谈不上什么技术支持。
Lua则避免了这些问题,它小巧,实现简单(而且还在维护),代码简洁、清晰。Lua的开发团队是由具有计算机工程背景的专家组成,并且一直在关注着它的升级。和其他开源项目不同,设计Lua旨在项目中扩展功能,而不是在API级别,因此它的内核一直很稳定。
Lua授权的精神在于用户可以在任何情况下免费使用,并且不需要取得版权所有者的许可。如果用户想知道更多Lua授权的信息,那么可以访问www-lua-org/license-html。
完整的Lua 5-0授权如下(CDROM中也提供了该授权):
2-3本章小结
脚本语言最初只是一种简单的工具,目的是为了让资深用户在简单文本文件中能够批量执行常用命令。现在许多脚本语言,如Python、Ruby和Lua,可以提供像计算机编程语言一样强大而灵活的功能。最近几年,脚本语言已经“走”到了游戏开发业界的前端,作为一个可行的中间件产品,它可以有效地提升开发小组的工作效率以及游戏项目的性能。来自巴西的计算机图形技术组织(Tecgraf)的团队开发的Lua,由于它的小巧、高速以及与C和C++良好的兼容性,成为非常适合游戏开发的工具。Lua是免费的开源语言并且有良好的技术支持,再加上不断增长的忠实用户群,让它成为专业开发者及业余游戏开发爱好者的不错选择。
第3章
游戏开发世界的Lua语言
本章要点
脚本语言和游戏
游戏项目中的Lua
现实中的游戏开发常常面临两种互相矛盾的压力,一方面需要测试和验证新想法,另一方面又需要快速开发并且按时交付。如果适当地在项目中引入脚本语言,可以让资深工程师充分发挥他们的能力,开发出优秀的游戏系统和模块。
3-1脚本语言和游戏
脚本语言可以让美术师直接开始界面设计,让设计师和初级程序员(脚本语言是一种让新手快速进入游戏开发的很好的方式)立即着手游戏流程和逻辑的开发,让关卡设计师能迅速掌控游戏环境和游戏体验。
脚本语言不是非常高效——它们没有原生代码的运行效率,因此不适合作为开发高性能需求处理的工具。但易整合语言能够利用原生语言编写的模块扩展功能,比如Lua,可以作为控制机制来调用原生代码编写的高性能处理组件。(Lua是运行效率最高的脚本语言之一,因此大部分性能方面的问题都可以不用担心。)C函数可以利用自己高性能的特点,并且整合到Lua中,让脚本程序员可以利用这些功能。
这种处理的一个例子是在游戏世界中放置一个3D模型的功能。渲染系统完全由C++开发,但Lua可以调用C++来创建一个特定模型的实体对象,并且设置其在场景中的位置,然后Lua还可以为这个3D模型指定动画。Lua并不处理任何实时的复杂运算来改变该模型,而只是告诉底层渲染系统什么时候该做什么。在下面的例子中(参见代码清单3-1),AddEnvironmentObject()是一个LuaGlue函数——它可以直接调用C++方法并且让Lua能够控制底层的3D渲染功能。这种函数由C++程序员编写,把底层的功能提供给脚本程序员和设计师。
代码清单3-1使用Lua脚本在场景中放置3D模型
3-2游戏项目中的Lua
把脚本语言集成到游戏项目中可以提升团队的开发效率,并且可以很好地扩展原生编译语言的能力。Lua在游戏开发的许多基础领域中都表现得很出色。
在游戏开发团队中,可能有许多成员都使用Lua来完成他们的工作。程序员负责将Lua整合到游戏开发环境中,通常,他们会需要编写一些Lua代码。游戏设计师是脚本语言的主要使用者,因为他们和上层的游戏设计和数据直接打交道。美术师也会经常使用Lua,进行诸如界面布局、设计和3D场景中各种模型的摆放之类的工作。
Lua是非常强大的工具,可以用来完成下面这些工作:
编辑游戏的用户界面
定义、存储和管理基础游戏数据
管理实时游戏事件
创建和维护开发者友好的游戏存储和载入系统
编写游戏的人工智能系统
创建功能原型,可以之后用高性能语言移植
3-2-1游戏界面
游戏界面是玩家和你的游戏进行交互的媒介。游戏界面是一个游戏最基本的部分,因为它负责所有和玩家的交互工作。因为它的重要性,所以需要能够高品质、高性能地运行,并且能够被测试和持续改善,以最大化地提升用户体验。
Lua可以让界面设计师迅速创建所有主要的界面元素——进行界面布局、管理用户输入并且输出游戏数据。利用游戏程序员开发的一部分核心的界面控件和控制函数,界面设计师不仅能负责界面的美术观感还能控制游戏的交互。这不仅节省了程序员的时间,而且给了美术设计师更大的创作空间,同时还能为界面设计的测试节省出许多时间。代码清单3-2展示了使用Lua创建文本GUI控件的方法。
代码清单3-2使用Lua创建文本GUI控件
3-2-2管理游戏数据
管理游戏数据对于游戏开发者一直都是一种挑战。游戏数据定义了游戏世界中所有对象的参数和特性,如脉冲枪升级费用、气垫船的行驶速度等。对于数据密集型游戏来说,开发者常常利用电子表格工具来输入和保存数据,然后创建解析工具将其转换成游戏中可以使用的格式。这种方法一般用在数据密集型游戏中,如角色扮演类游戏(NPC或者非游戏玩家角色的信息以表格结构形式存储)或者策略类游戏(角色单位信息保存在数据表中)。
Lua可以让这个存储系统更为简单,它以Lua文件作为存储介质让程序使用相同的数据。通过创建一个简单的数据管理系统,变量和类型可以定义在Lua中,然后可以很容易地读取。因为不用关心整个数据处理过程,所以游戏设计师可以按照需要修改、增加和缩减游戏数据,而不需要程序员的协助。因为Lua语法的特性和在脚本中添加注释的能力,数据文件是易读的。如果需要转换工具,也非常容易开发,把Lua数据输出到Lua文件,然后在运行时载入。
Lua本身并没有可以直接访问外部数据库的能力,但可以用C++开发访问数据库的组件,然后再利用LuaGlue函数整合该组件来达到目的。
在代码清单3-3中,我们可以看到如何使用Lua示例文件来直接保存游戏数据。在这个例子中,通过使用LuaGlue函数,向由核心代码管理的数据结构中写入数据。代码清单3-3的输出可参照图3-1。
代码清单3-3使用Lua保存美国总统选举模拟游戏(《Frontrunner》)的数据
图3-1游戏《Frontrunner》的截图,Lua数据的实时显示
3-2-3事件处理
通常,游戏中的绝大部分重要的处理都是由事件驱动的,要么是来自游戏角色,要么是来自游戏中那些交互的代理。这些事件可以是简单的,如用户按下了键,也可以是复杂的,如两个游戏实体同时来到了某个地方。
事件驱动的编程,对于熟悉了Windows开发(事件是Windows GUI操作的基础)的用户来说不是什么陌生的技术。在C++精心开发的事件系统中,使用Lua来接收和处理这些事件,用户可以在游戏内部运行机制、高级Lua函数和用户输入之间创建清晰的反馈流程。一个简单的例子是,获取键盘输入然后向Lua事件处理器发送该事件,同时将该输入值显示出来。这个概念将一直贯穿于本书之中——事件会返回事件类型ID和驱动事件的物体ID,参照代码清单3-4。
代码清单3-4获取按键事件的例子
3-2-4保存和读取游戏状态
保存和读取玩家的数据是游戏开发项目中最具有挑战的事情之一。因为玩家有时需要暂时离开游戏,所以需要一种保存游戏进度的方法。玩家还需要在某种新的尝试和挑战前保存游戏状态,这样万一尝试失败了还可以恢复游戏进度。同样,在开发和测试过程中,开发者会经常需要读取特定的游戏状态来验证游戏功能或者确认bug是否已修复。
如果使用Lua保存用户的核心游戏数据,那么就可以用Lua作为保存和读取当前游戏状态的系统。在Lua中,游戏进度文件是简单的可执行的Lua代码的文本文件。程序员或者设计师可以通过进度文件来获取当前游戏的状态(也可以修改),读取游戏进度就和执行Lua脚本一样简单。利用Lua标准的输入/输出函数,编写一个函数来保存游戏数据到可执行的Lua脚本中是最直接的方法。这个系统的优点是可以让设计师在开发过程中,根据游戏数据的增长和删减来编辑并修改这个函数(不需要特别的读取函数)。在开发的最后阶段,还可以利用脚本编译函数来为游戏数据加密。
3-2-5人工智能
人工智能(AI)在如今的游戏中非常关键——玩家需要精明的、有挑战性的对手,感觉就像真人一样。游戏开发者明白真实世界的AI不是说完全模仿人如何玩和反应,而是为玩家创造这种感觉。多年来,开发者一直在争论计算机对手“作弊”(计算机对手可以比玩家访问更多的游戏数据)的优缺点。这种争论最好用在别的时间和地方。不过,几乎所有的开发者都同意,比起AI模拟,AI行为的玩家感知更为重要。
就开发AI判定来说,Lua是一种非常高效的工具。有许多人工智能组件,如路径寻找,最好留给底层语言来实现。路径寻找(计算机控制的物体在虚拟世界中的路径寻找)是一个数据运算量很大的工作,计算机需要反复测试可能的路径来寻找最短或者最直接的路径。(路径寻找最好整合到上层的LuaGlue函数中以便控制相关参数,但还是会在后面的章节中介绍一种Lua的实现。)另一个例子是用最大最小值方法实现的移动判定,一般被用在计算机象棋游戏中,预测之后几步的移动并尝试计算出最优的移动步骤。一般来说,“能思考的函数”都需要大量的数学计算,如导航树或者尝试错误法运算最好都留给底层代码。依赖有限的数据集合和参数的人工智能才更适合Lua的特点。Lua的优点是设计师可以编写简单的模型来试错,并快速验证和迭代想法而不需要麻烦程序员。想要利用Lua高效率实现AI,需要很仔细地设计函数(C函数)c 游戏开发,让Lua脚本可以访问和交互游戏数据。使用Lua作为事件管理系统同样可以让AI设计师能应对游戏开发中的变更,开发出灵活反应的AI系统。
在代码清单3-5中,用户可以看到使用Lua来评估可能的竞选旅行目的地,评估的依据是这个州的选举人票数量和这个州的支持率。
代码清单3-5评估虚拟的美国总统候选人应该去哪个州的Lua人工智能函数
3-2-6快速构建原型
商业化的游戏必须为玩家提供高性能的体验,掉帧和处理延迟的现象在如今竞争激烈的市场中是绝不允许的。程序概要分析(Profiling)是行之有效的确定性能瓶颈的方法,但必须在所有功能都开发完成并正确工作的前提下才能进行。原型开发和性能改善一般是不能同时进行的。
Lua是构建可移植的核心游戏功能原型的不错工具。因为Lua可以整合原生语言开发的组件,所以移植单独的Lua函数到C++,对于其他Lua函数来说是不需要变更的。它可以让设计师在构建原型时,如果碰到有高性能需求的函数就可以让程序员用底层语言来实现。因为程序的算法结构已经有了,所以C函数和LuaGlue调用都可以高性能、无缝地集成在项目中。
3-3本章小结
在游戏开发领域,Lua和C++是一个功能十分强大的组合。对于GUI开发、事件管理、数据保存和获取、游戏进度保存和载入以及人工智能这些游戏开发工作,需要能够快速地构建原型、测试和迭代设计,而这些正是Lua所擅长的。在核心底层语言开发的项目中,频繁的变更会导致系统的不稳定,并让开发者不能专注于更重要的开发任务。使用Lua环境分担这些工作,可以让设计师和程序员快速而安全地使用脚本来设计、测试和实现游戏功能。