Lua,游戏状态和游戏循环

时间:2010-04-21 18:41:40

标签: c++ lua game-loop lua-c++-connection

  1. 在每个游戏循环迭代中调用main.lua脚本 - 它的设计是好还是坏?它如何影响性能(相对)?

  2. 维持 a 的游戏状态。 C ++主机程序或 b 。来自Lua脚本或 c 。来自两者并同步它们?

  3. (关于该主题的上一个问题: Lua and C++: separation of duties

    (我投票给每个答案。最佳答案将被接受。)

9 个答案:

答案 0 :(得分:25)

我对lua的基本规则是 - 或游戏中的任何脚本语言 -

  • 每一帧上发生的任何事情:c ++
  • 异步事件 - 用户输入 - lua
  • 同步游戏引擎事件 - lua

基本上,任何以> 33-100Hz(取决于帧速率)调用的代码都是C ++ 我尝试调用脚本引擎< 10Hz。

基于任何实际指标?并不是的。但它确实在设计中设置了一条分界线,c ++和lua任务明确划分 - 没有预先描绘每帧lua任务将增长,直到他们每帧进行模糊处理 - 然后没有明确的指导修剪什么。

答案 1 :(得分:9)

关于lua的最好的事情是它有一个轻量级的VM,并且在块之后进行预编译,在VM中运行它们实际上非常快,但仍然没有C ++代码那么快,我不认为每次渲染帧都会调用lua是一个好主意。

我将游戏状态放在C ++中,并在lua中添加可以到达的函数,并修改状态。基于事件的方法几乎更好,其中事件注册应该在lua中完成(最好只在游戏开始时或在特定游戏事件中,但每分钟不超过几次),但实际事件应该由C ++代码。用户输入也是事件,并且它们通常不会在每个帧发生(除了可能是MouseMove,但由于这个原因应该小心使用)。处理用户输入事件的方式(无论你是否处理lua中的所有内容(例如按下哪个键等),或者键盘上的每个键是否都有单独的事件(在极端情况下)取决于你的游戏'重新尝试制作(基于转弯的游戏可能只有一个事件处理程序用于所有事件,RTS应该有更多的事件,并且应该小心处理FPS(主要是因为移动鼠标将在每一帧发生)。一般来说你拥有的各种事件,你在lua中编码的次数越少(这将提高性能),但如果你需要处理的“真实事件”实际上是由更多单独的“编程级别事件”触发,则会越难获得(这实际上可能会降低性能,因为lua代码需要更复杂。)

或者,如果性能非常重要,你可以通过向它添加新的操作码来实际改进lua VM(我见过一些公司这样做,但主要是为了使编译的lua块的反编译更加困难),实际上并不是一件难事。如果你有一些lua代码需要做很多次的事情(比如事件注册,事件运行或者改变游戏状态)你可能想在lua VM中实现它们,所以代替多个{{1}和getglobal操作码它们只需要一个或两个(例如你可以用一个0-255和一个0-65535参数制作一个SETSTATE操作码,其中第一个参数描述要修改的状态,第二个参数desribes状态的新值。当然,这仅在最多255个事件,最多2 ^ 16个值时才有效,但在某些情况下可能就足够了。事实上,这只需要一个操作码意味着代码运行得更快)。如果你打算隐藏你的lua代码,这也会使反编译变得更加困难(虽然对知道lua内部工作原理的人来说并不多)。每帧运行几个操作码(大约30-40个顶部)不会严重影响您的性能。但是如果你需要做一些非常复杂的事情,那么lua VM中的30-40个操作码将无法让你走得更远(一个简单的if-then-else可能需要多达10-20个或更多的操作码,具体取决于表达式)。

答案 2 :(得分:6)

我在一直在努力的游戏中第一次使用Lua。我的应用程序的C ++端实际上持有指向每个游戏状态实例的指针。一些游戏状态是用C ++实现的,有些是在Lua中实现的(比如“游戏”状态)。

更新和主应用程序循环存在于C ++方面。我公开了一些函数,允许Lua VM在运行时向应用程序添加新的游戏状态。

即使在资源有限的硬件上运行(带有集成视频的Atom处理器),我还没有遇到任何慢速问题。 每帧调用Lua函数。我的应用程序中最昂贵的(在时间上)操作是呈现

在Lua中完全创建新状态的能力是我在项目上做出的最佳决策之一,因为它允许我自由添加部分游戏而无需重新编译整个事物。

编辑:我正在使用Luabind,我读过它的速度通常比其他绑定框架慢,当然还有Lua C API。

答案 3 :(得分:5)

恕我直言Lua脚本是针对特定行为的,如果你每秒调用一次Lua脚本,它肯定会损害性能。

Lua脚本通常用于分隔行为和游戏引擎逻辑中的特定事件(GUI,项目,对话,游戏引擎事件等)。例如Lua的一个很好的用法就是当触发爆炸(粒子FX)时,如果游戏角色走到某处,那么在你的引擎中对该事件的输出进行硬编码将是一个非常难看的选择。但是,让引擎触发正确的脚本将是一个更好的选择,将特定行为与引擎分离。

我建议,尝试将游戏状态保持在一个部分,而不是升级在两个地方保持状态同步的复杂程度(Lua和Engine),添加线程,你最终会有一个非常难看的混乱。把事情简单化。 (在我的设计中,我主要用C ++保存游戏状态)

祝你好运!

答案 4 :(得分:5)

我不喜欢C ++。但我确实喜欢游戏。

我的方法可能有点不典型:我在Lua中尽我所能,而且只有C ++中的绝对最小值。游戏循环,实体等都在Lua中完成。我甚至在Lua中完成了QuadTree实现。 C ++处理图形和文件系统的东西,以及与外部库的接口。

这不是基于机器的决定,而是基于程序员的决定;我在Lua中比在C ++中输出代码要快得多。因此,我将程序员周期用于新功能而不是节省计算机周期。我的目标机器(过去3年的任何笔记本电脑)都能够轻松应对这一数量的Lua。

Lua的占地空间非常低(如果您不知道,请查看luaJIT。)

这就是说,如果我找到了瓶颈(我还没有),我会对游戏进行分析以找到缓慢的部分,我会将那部分翻译成C ++ ......只有我能够'用Lua找到解决办法。

答案 5 :(得分:5)

  1. 您可能不希望在每次迭代时执行整个Lua脚本,因为任何足够复杂的游戏都会有多个具有自己行为的游戏对象。换句话说,除非你有多个小脚本来处理较大游戏行为的特定部分,否则Lua的优点就会丢失。您可以使用lua_call函数在脚本中调用任何适当的lua例程,而不仅仅是整个文件。

  2. 这里没有理想的答案,但绝大多数游戏状态传统上都存储在游戏引擎中(即C ++)。你向Lua透露了足够让Lua做出你已经分配给Lua的决策。

  3. 您需要考虑哪种语言适合哪种行为。 Lua对于高级控件和决策非常有用,而C ++对于面向性能的代码非常有用。 Lua对于游戏中需要调整而无需重新编译的部分特别有用。例如,所有魔术常数和变量都可以进入Lua。不要试图将Lua归属于不属于的地方,即图形或音频渲染。

答案 6 :(得分:4)

关于1的性能:如果main.lua未更改,请使用lua_loadfileloadfile加载一次,保存对返回函数的引用,然后在需要时调用它。

答案 7 :(得分:2)

通过Lua和C ++之间的绑定,大部分性能都会丢失。函数调用实际上需要被包装,并重新包装,并且通常需要几次。纯Lua或纯C ++代码通常比混合代码 (对于小型操作)。

话虽如此,我个人并没有看到任何强大的性能在每个框架运行Lua脚本

通常脚本编写好高级。 Lua已经在着名游戏中用于 Bots Quake 3 )和用户界面魔兽世界 >)。在高级别使用Lua微线程非常方便:协同程序可以节省很多(与真实线程相比)。例如,仅在一段时间内运行一些Lua代码。

答案 8 :(得分:2)

我想投入两分钱,因为我坚信在这里给出了一些不正确的建议。对于上下文,我在一个涉及密集3D渲染和密集游戏逻辑模拟的大型游戏中使用Lua。我已经比我更喜欢Lua和表演了......

请注意,我将特别谈谈LuaJIT,因为您将要使用LuaJIT。它是即插即用的,所以如果你可以嵌入Lua,你可以嵌入LuaJIT。你想要它,如果不是为了额外的速度,那么对于自动化的外部功能接口模块(需要' ffi'),它将允许你直接从Lua调用你的本机代码,而不必触摸Lua C API(95%+案例)。

  1. 完全正常以60hz调用Lua(我在VR中称之为90hz)。问题是,你必须小心谨慎地做到这一点。正如其他人提到的那样,仅加载脚本一次至关重要。然后,您可以使用C API访问您在该脚本中定义的函数,或者将脚本本身作为函数运行。我推荐前者:对于一个相对简单的游戏,你可以使用onUpdate(dt),onRender(),onKeyPressed(key),onMouseMoved(dx,dy)等定义函数。你可以在适当的时候调用它们来自C ++中的主循环。或者,您实际上可以将整个主循环放在Lua中,而是调用C ++代码来执行性能关键的例程(我这样做)。使用LuaJIT FFI特别容易。

  2. 这是一个非常难的问题。这取决于您的需求。你能轻易地击败游戏状态吗?很棒,把它放在C ++端并从LuaJIT FFI访问。不确定游戏中的所有状态/喜欢能够快速原型化吗?将它保存在Lua中没有错。也就是说,直到你开始谈论一个包含1000个对象的复杂游戏,每个对象都包含非平凡的状态。在这种情况下,混合是要走的路,但是弄清楚如何在C ++和Lua之间分离状态,以及如何在两者之间进行军事状态(特别是在执行关键的例程中)是一件艺术。如果你想出一个防弹技术让我知道:)与其他一切一样,一般的经验法则是:经过性能关键路径的数据需要在本机方面。例如,如果您的实体具有更新每个帧的位置和速度,并且您有数千个所述实体,则需要在C ++中执行此操作。但是,您可以通过分层“库存”来实现目标。使用Lua在这些实体之上(库存不需要每帧更新)。

  3. 现在,还有一些笔记我想把它们作为一般的FYI抛弃,并回应其他一些答案。

    1. 基于事件的方法通常对任何游戏的性能都至关重要,但对于用Lua编写的系统来说,这是双倍的。我说打电话给Lua @ 60hz完全没问题。但是,在所述Lua的每一帧中,对于许多游戏对象运行紧密循环, 你可能会在C ++中浪费地调用Universe中所有内容的update()(虽然你不应该这样做),但是在Lua中这样做会很快开始吃掉那些宝贵的毫秒。相反,正如其他人所提到的,你需要将Lua逻辑视为“反应性”和“反应性”。 - 通常,这意味着处理一个事件。例如,不要检查Lua中每个帧的一个实体是否在另一个实体的范围内(我的意思是,对于一个实体来说这是好的,但一般来说,当你缩小游戏规模时,你需要不去思考像这样)。相反,告诉您的C ++引擎在两个实体彼此相距一定距离时通知您。通过这种方式,Lua成为游戏逻辑的高级控制器,调度高级命令并响应高级事件,而不是执行琐碎游戏逻辑的低级数学研究。

    2. 警惕混合代码的建议'是慢的。 Lua C API轻巧而快速。在最糟糕的情况下,与Lua一起使用的包装函数非常容易(如果您需要一段时间来了解Lua如何与C和虚拟堆栈等接口,您会注意到它是专门为最小化调用开销而设计的),以及充其量只是微不足道的(不需要包装)和100%的本地调用(谢谢,LuaJIT FFI!)在大多数情况下,我发现Lua和C(通过LJ FFI)的精心混合优于纯Lua。即使是矢量操作,我相信LuaJIT手册也提到了代码应该保存到Lua的例子,当我通过Lua> C调用执行时,我发现它更快。最终,当涉及对性能敏感的代码时,不要相信任何人或任何事情,除了你自己的(谨慎)性能测量。

    3. 大多数小型游戏都可以通过游戏状态,核心循环,输入处理等完全摆脱Lua并在LuaJIT下运行。如果您要构建一个小型游戏,请考虑"根据需要转移到C ++"方法(懒惰的编译!)写在Lua中,当你发现一个明确的瓶颈(并测量它以确保它是罪魁祸首)时,将该位移到C ++并继续前进。如果您正在编写一个包含复杂逻辑,高级渲染等1000个复杂对象的大型游戏,那么您将从更多的前期设计中受益。

    4. 祝你好运:)