Lua如何在游戏中用作脚本语言?


67

我对Lua到底是什么以及用C ++编程的游戏将如何使用它感到有些困惑。我主要是问它如何编译和运行。

例如,当您使用使用Lua脚本以C ++编写的程序时:Lua中的代码是否只是调用以C ++编写的主程序中的函数,并充当未编译的类,等待被编译并添加到C ++的内存堆中程序?

还是像Linux中的bash脚本那样工作,它只执行与主程序完全独立的程序?

Answers:


89

脚本是一种编程抽象,其中(概念上)您有一个程序(脚本)在另一个程序(主机)中运行。在大多数情况下,编写脚本的语言与编写主机的语言不同,但是任何程序内部程序抽象都可以视为脚本。

从概念上讲,启用脚本的常见步骤如下(我将对主机使用伪c,对脚本使用伪lua。这不是确切的步骤,但更像启用脚本的一般流程)

  1. 在主机程序中创建一个虚拟机:

    VM m_vm = createVM();
  2. 创建一个函数并将其公开给VM:

    void scriptPrintMessage(VM vm)
    {
        const char* message = getParameter(vm, 0); // first parameter
        printf(message);
    }
    
    //...
    
    createSymbol(m_vm, "print", scriptPrintMessage);
    

    请注意,公开函数(print)的名称不必与函数本身(scriptPrintMessage)的内部名称匹配。

  3. 运行一些使用该功能的脚本代码:

    const char* scriptCode = "print(\"Hello world!\")"; // Could also be loaded from a file though
    doText(m_vm, scriptCode);
    

这里的所有都是它的。然后,该程序以以下方式运行:

  1. 你打电话doText()。然后将控制权转移到虚拟机,该虚拟机将执行里面的文本scriptCode

  2. 脚本代码找到以前导出的符号print。然后它将控制权转移到该功能scriptPrintMessage()

  3. scriptPrintMessage()完成时,控制将转移回虚拟机。

  4. 当所有文本输入scriptCode执行doText()完毕后,将结束,并且控制将在后面的行中转移回您的程序doText()

因此,通常来说,您要做的就是在另一个程序中运行一个程序。从理论上讲,没有脚本是无法做的,但是如果没有脚本,脚本就无法做,但是这种抽象使您可以轻松地做一些有趣的事情。他们之中有一些是:

  • 关注点分离:一种常见的模式是使用C / C ++编写游戏引擎,然后使用诸如lua之类的脚本语言编写实际游戏。做对了,游戏代码可以完全独立于引擎本身进行开发。

  • 灵活性:脚本语言通常是解释性的,因此,对脚本进行更改并不一定需要重建整个项目。做对了,您甚至可以更改脚本并查看结果,而无需重新启动程序!

  • 稳定性和安全性:由于脚本在虚拟机中运行,因此如果执行正确,则有错误的脚本不会使主机程序崩溃。当允许用户向游戏编写自己的脚本时,这一点特别重要。请记住,您可以创建任意数量的独立虚拟机!(我曾经创建一个MMO服务器,其中每个匹配项都在单独的lua虚拟机上运行)

  • 语言功能:使用脚本语言时,根据您选择的宿主语言和脚本语言,您可以利用每种语言必须提供的最佳功能。特别是,lua的协程是一个非常有趣的功能,很难用C或C ++实现

脚本不是完美的。使用脚本有一些常见的缺点:

  • 调试变得非常困难:通常,常见的IDE中包含的调试器并非旨在调试脚本中的代码。因此,控制台跟踪调试比我想要的要普遍得多。

    一些脚本语言(例如lua)具有调试功能,可以在某些IDE(例如Eclipse)中加以利用。这样做非常困难,老实说,我从未见过脚本调试和本地调试一样有效。

    顺便说一句,Unity开发人员对此事极度缺乏兴趣是我对他们的游戏引擎的主要批评,也是我不再使用它的主要原因,但我离题了。

  • IDE集成:您的IDE不太可能知道从程序中导出了哪些功能,因此,诸如IntelliSense之类的功能不太可能与您的脚本一起使用。

  • 性能:脚本是通常解释的程序,适用于架构可能与实际硬件不同的抽象虚拟机,因此脚本执行的速度通常比本地代码慢。但是,某些虚拟机(例如luaJIT和V8)做得很好。如果您大量使用脚本,这可能会很明显。

    通常,上下文更改(主机到脚本和脚本到主机)非常昂贵,因此,如果遇到性能问题,可能需要将其最小化。

您如何使用脚本取决于您自己。我已经看到用于执行任务的脚本很简单,例如加载设置,以及使用非常薄的游戏引擎以脚本语言制作整个游戏一样复杂。我什至曾经看到过一个非常奇特的游戏引擎,它混合了lua和JavaScript(通过V8)脚本。

脚本编写只是一种工具。如何使用它制作出色的游戏完全取决于您。


我真的很陌生,但是我使用Unity。您提到了有关Unity的一些事情,您能详细说明一下吗?我应该使用其他东西吗?
Tokamocha 2014年

1
我不会printf在您的示例中使用(或至少使用printf("%s", message);
棘轮怪胎

1
@ratchetfreak:消息结尾的分号和括号对我眨了眨眼
Panda Pajama

1
我在这个网站上看到的最好的答案之一;它值得得到它的每一个支持。做得非常好。
Steven Stadnicki 2014年

1
Lua在游戏中的代言人是《魔兽世界》,其中大部分用户界面都是用Lua编写的,并且大多数都可以由玩家代替。我很惊讶您没有提到它。
迈克尔·汉普顿

7

通常,您可以将某些本机函数绑定或公开给Lua(通常可以使用实用程序库来实现,尽管您可以手工完成)。这样,当您的游戏执行Lua代码时,Lua代码就可以调用您的本机C ++代码。从这个意义上讲,您假设Lua代码只是调用本机代码是正确的(尽管Lua有其自己的标准功能库;您无需为所有内容都调用本机代码)。

Lua代码本身由Lua运行时解释,它是您通常在自己的程序中链接为库的C代码。您可以在Lua主页上了解有关Lua如何工作的更多信息。特别地,Lua并不是您所猜测的“未编译的类”,尤其是在考虑C ++类的情况下,因为在实践中C ++几乎不会动态编译。但是,您的游戏运行的Lua运行时和Lua脚本创建的对象确实会占用游戏系统内存池中的空间。


5

像Lua这样的脚本语言可以以多种方式使用。正如您所说的,您可以使用Lua调用主程序中的函数,但也可以根据需要从C ++端调用Lua函数。通常,您会建立一个界面,以使您所选择的脚本语言具有一定的灵活性,因此您可以在一系列场景中使用该脚本语言。像Lua这样的脚本语言的妙处在于它们是解释而不是编译的,因此您可以即时修改Lua脚本,因此您不必坐等您的游戏进行编译,只需要您重新编译即可。做的不适合您的口味。

Lua命令仅在C ++程序要执行时才被调用。这样,仅在调用代码时才解释该代码。我想您可以将Lua视为与主程序分开执行的bash脚本。

通常,您想对可能需要更新或迭代的内容使用脚本语言。我已经看到很多公司将其用于GUI,因此它为界面提供了很多可定制性。如果您知道他们是如何创建Lua界面的,则还可以自己修改GUI。但是您可以使用Lua进行其他多种选择,例如AI逻辑,有关武器的信息,角色对话等。


3

大多数脚本语言(包括Lua)都在虚拟机(VM)上运行,该虚拟机基本上是一个将脚本指令映射到“真实” CPU指令或函数调用的系统。Lua VM通常以与主应用程序相同的过程运行。对于使用它的游戏来说尤其如此。Lua API为您提供了一些功能,您可以在本机应用程序中调用这些功能来加载和编译脚本文件。例如luaL_dofile(),将给定脚本编译成Lua 字节码,然后运行它。然后,该字节码将由运行在API内的VM映射到本机机器指令和函数调用中。

将诸如C ++之类的本地语言与脚本语言连接的过程称为binding。在Lua情况下,其API提供的功能可帮助您向脚本代码公开本机功能。因此,您可以例如定义一个C ++函数say_hello()并使该函数可从Lua脚本中调用。Lua API还提供了通过C ++代码创建变量和表的方法,这些变量和表在脚本运行时将可见。通过组合这些功能,可以将整个C ++类公开给Lua。相反也可能,Lua API允许用户修改Lua变量并从本地C ++代码调用Lua函数。

大多数(如果不是全部)脚本语言都提供API,以促进脚本代码与本机代码的绑定。大多数代码也被编译成字节码并在VM中运行,但有些可能逐行解释。

希望这有助于澄清您的一些问题。


3

既然没有人提及,我将在这里为感兴趣的人添加。有一本关于游戏脚本精通的完整书籍。这是一段很久以前写的很棒的文本,但是今天仍然完全相关。

本书不仅向您展示脚本语言如何适合本机代码,还教您如何实现自己的脚本语言。尽管这对于99%的用户来说是过大的杀伤力,但没有比实际实现更好的理解方法的方法了(即使是非常基本的形式)。

如果您想自己编写游戏引擎(或仅使用渲染引擎),那么此文本对于理解如何将脚本语言最佳地集成到您的引擎/游戏中非常有用。

而且,如果您想创建自己的脚本语言,这是最好的起点之一(据我所知)。


2

首先,脚本语言通常不进行编译。这是通常将它们定义为脚本语言的很大一部分。相反,它们经常被“解释”。这本质上的含义是,存在另一种语言(通常是一种编译的语言)正在实时读取文本并逐行执行操作。

该语言与其他语言的区别在于脚本语言趋向于更简单(通常称为“高级”)。但是,它们也趋向于变慢一些,因为编译器倾向于优化编码“人为因素”带来的许多问题,并且对于机器而言,生成的二进制文件往往更小且读取速度更快。另外,使用已编译的程序,从运行另一个程序来读取正在运行的代码的开销也较小。

现在,您可能会想:“好吧,我觉得这要容易一些,但是为什么有人为了某种额外的易用性而放弃所有性能呢?”

在这种假设下,您可能不会孤单,但是脚本语言的轻松程度(取决于您对它们的处理方式)很值得在性能上做出牺牲。

基本上: 如果开发速度比正在运行的程序的速度更重要,请使用脚本语言。 游戏开发中存在很多这样的情况。特别是在处理诸如高级事件处理之类的琐碎事情时。

编辑:lua在游戏开发中趋于流行的原因是因为它可以说是世界上最快(如果不是最快)的公开脚本语言之一。但是,以这种额外的速度,它牺牲了一些便利性。话虽如此,它仍然可以说比直接使用C或C ++更方便。

重要修改: 经过进一步研究,我发现关于脚本语言的定义还有很多争议(请参阅Ousterhout的二分法)。将语言定义为“脚本语言”的主要批评是,它对所解释或编译的语言的语法或语义均不重要。

尽管通常被认为是“脚本语言”的语言通常是按传统方式进行解释而不是编译,但其定义为“脚本语言”的长短实际上取决于人们如何看待它们以及其创建者如何定义它们。

一般而言,如果一种语言符合以下条件(根据上面的链接文章),就可以轻松地将其视为脚本语言(假设您同意Ousterhout的二分法):

  • 它们是动态键入的
  • 他们很少或根本没有提供复杂的数据结构
  • 其中的程序(脚本)被解释

此外,如果一种语言被设计为与另一种编程语言(通常被认为是脚本语言)互动和起作用,那么它通常是一种脚本语言。


1
在您编写“脚本语言未编译”的第一行,我会不同意您的看法。实际上,在通过JIT编译器执行之前,Lua被编译为中间字节码。当然,有些是逐行解释的,但不是全部。
glampert 2014年

我不同意您对“脚本语言”的定义。存在具有双重用途的语言(例如Lua或C#),这些语言在其编译形式而非脚本形式中相对较常用(反之亦然,在C#中就是这样)。将一种语言用作脚本语言时,严格地将其定义为一种解释的语言,而不是编译的语言。
Gurgadurgen

公平地说,您的定义与Wikipedia更加内联:“脚本语言或脚本语言是一种编程语言,支持脚本,为特殊运行时环境编写的程序,这些程序可以解释(而不是编译)...”,没关系那我的评论。
glampert 2014年

1
甚至常规的Lua都可以完全编译为字节码,JIT编译器甚至可以生成本地二进制文件。因此,不对Lua进行解释。
Oleg V. Volkov

第一部分是不确定的,我很想拒绝投票。他是对的,因为它最终会被解释并且@ OlegV.Volkov JIT编译不会使某些内容被编译。编译是由Lua拥有的编译时间定义的,而Lua字节码则没有(JIT或没有JIT)。让我们不要混淆我们的任期。
亚历克·蒂尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.