top*_*dev 22 c++ lua game-loop lua-c++-connection
在每个游戏循环迭代中调用main.lua脚本 - 是好还是坏设计?它如何影响性能(相对)?
从维护游戏状态一个.C++主机程序或b.来自Lua脚本或c.来自两者并同步它们?
(关于该主题的上一个问题:Lua和C++:职责分离)
(我投票给每个答案.最好的答案将被接受.)
Chr*_*cke 25
我对lua的基本规则是 - 或者游戏中的任何脚本语言 -
基本上,任何调用> 33-100Hz(取决于帧速率)的代码都是C++我尝试调用脚本引擎<10Hz.
基于任何类型的实际指标?并不是的.但它确实在设计中设置了一条分界线,c ++和lua任务明确划定 - 没有前面描述每帧lua任务将增长,直到他们每帧进行模糊处理 - 然后没有明确的指导修剪什么.
关于lua的最好的事情是它有一个轻量级的VM,并且在块被预编译后在VM中运行它们实际上非常快,但仍然没有C++代码那么快,我不认为每次调用lua渲染帧是个好主意.
我将游戏状态放在C++中,并在可以到达的lua中添加函数,并修改状态.基于事件的方法几乎更好,其中事件注册应该在lua中完成(最好仅在游戏开始时或在特定游戏事件中,但每分钟不超过几次),但实际事件应该由C++代码.用户输入也是事件,并且它们通常不会在每个帧发生(除了可能是MouseMove,但由于这个原因应该小心使用).您处理用户输入事件的方式(无论您是否处理lua中的所有内容(例如按下哪个键等),或者键盘上的每个键是否都有单独的事件(在极端情况下)取决于您的游戏重新尝试制作(基于转弯的游戏可能只有一个事件处理程序用于所有事件,RTS应该有更多的事件,并且应该小心处理FPS(主要是因为移动鼠标将在每一帧发生).一般来说你拥有的各种事件,你在lua中编码的次数越少(这将提高性能),但如果你需要处理的"真实事件"实际上是由更多单独的"编程级别事件"触发,则会越难获得(这实际上可能会降低性能,因为lua代码需要更复杂).
或者,如果性能非常重要,你可以通过向它添加新的操作码来实际改进lua VM(我已经看到一些公司要这样做,但主要是为了使编译的lua块的反编译更加困难),实际上并非如此一件难事.如果你有一些lua代码需要做很多次的事情(比如事件注册,事件运行或者改变游戏状态)你可能想在lua VM中实现它们,那么它们将代替多个getglobal和setglobal操作码而不是只需要一个或两个(例如,您可以使用0-255和0-65535参数创建一个SETSTATE操作码,其中第一个参数描述要修改的状态,第二个参数获取状态的新值.当然这只有当你最多有255个事件,最多有2 ^ 16个值时才有效,但在某些情况下它可能就足够了.事实上,这只需要一个操作码意味着代码运行得更快).如果你打算隐藏你的lua代码,这也会使反编译变得更加困难(虽然对知道lua内部工作原理的人来说并不多).每帧运行几个操作码(大约30-40个顶部)不会严重影响您的性能.但是,如果你需要做一些非常复杂的事情,那么lua VM中的30-40个操作码将无法让你走得更远(一个简单的if-then-else可能需要多达10-20个或更多的操作码,具体取决于表达式).
我在一个我一直在努力的游戏中第一次使用Lua.我的应用程序的C++端实际上持有指向每个游戏状态实例的指针.一些游戏状态是用C++实现的,有些是在Lua中实现的(比如"游戏"状态).
更新和主应用程序循环存在于C++方面.我公开了一些函数,允许Lua VM在运行时向应用程序添加新的游戏状态.
即使在资源有限的硬件上运行(带集成视频的Atom处理器),我还没有遇到任何慢速问题.每帧都调用Lua函数.我的应用程序中最昂贵的(在时间上)操作是渲染.
在Lua中完全创建新状态的能力是我在项目上做出的最好的决定之一,因为它允许我自由地添加部分游戏而无需重新编译整个事物.
编辑:我正在使用Luabind,我读过它比其他绑定框架执行速度慢,当然还有Lua C API.
恕我直言Lua脚本是针对特定行为的,如果你每秒调用一次Lua脚本60次,肯定会损害性能.
Lua脚本通常用于分离行为和游戏引擎逻辑中的特定事件(GUI,项目,对话框,游戏引擎事件等).例如Lua的一个很好的用法就是当触发爆炸(粒子FX)时,如果游戏角色走到某处,那么在你的引擎中对该事件的输出进行硬编码将是一个非常难看的选择.但是,使引擎触发正确的脚本将是更好的选择,从而将特定行为与引擎分离.
我建议,尝试将游戏状态保持在一个部分,而不是升级在两个地方保持状态同步的复杂程度(Lua和Engine),添加线程,你最终会有一个非常丑陋的混乱.把事情简单化.(在我的设计中,我主要用C++保存游戏状态)
祝你的游戏好运!
我不喜欢C++.但我喜欢游戏.
我的方法可能有点不典型:我在Lua中尽我所能,而且只有C++中的绝对最小值.游戏循环,实体等都在Lua中完成.我甚至在Lua中完成了QuadTree实现.C++处理图形和文件系统的东西,以及与外部库的接口.
这不是基于机器的决策,而是基于程序员的决策; 我在Lua中比在C++中输出代码要快得多.因此,我将程序员周期用于新功能而不是节省计算机周期.我的目标机器(过去3年的任何笔记本电脑)都能够轻松应对这一数量的Lua.
Lua的占地空间非常小(如果你不知道的话,请看看luaJIT).
这就是说,如果我找到了瓶颈(我还没有),我会描述游戏以找到缓慢的部分,我会将那部分翻译成C++ ...只有我找不到一个用Lua绕过它.
您可能不希望在每次迭代时执行整个Lua脚本,因为任何足够复杂的游戏都会有多个具有自己行为的游戏对象.换句话说,除非你有多个小脚本来处理较大游戏行为的特定部分,否则Lua的优点就会丢失.您可以使用lua_call函数在脚本中调用任何适当的lua例程,而不仅仅是整个文件.
这里没有理想的答案,但绝大多数游戏状态传统上都存储在游戏引擎中(即C++).你向Lua透露了足够让Lua做出你已经分配给Lua的决策.
您需要考虑哪种语言适合哪种行为.Lua对于高级控件和决策非常有用,而C++对于面向性能的代码非常有用.Lua对于游戏中需要调整而无需重新编译的部分特别有用.例如,所有魔术常数和变量都可以进入Lua.不要试图将Lua放在不属于的地方,即图形或音频渲染.
我想投入我的两分钱,因为我坚信这里给出了一些不正确的建议。就上下文而言,我在一个大型游戏中使用 Lua,该游戏既涉及密集的 3D 渲染,又涉及密集的游戏逻辑模拟。我对 Lua 和性能的熟悉程度超出了我的预期......
请注意,我将专门讨论 LuaJIT,因为您将想要使用 LuaJIT。它确实是即插即用的,所以如果你可以嵌入 Lua,你就可以嵌入 LuaJIT。如果不是为了额外的速度,您会需要它,然后是自动外部函数接口模块(需要“ffi”),它允许您直接从 Lua 调用本机代码,而无需接触 Lua C API(95 %+ 的案例)。
以 60hz 调用 Lua是完全可以的(我在 VR 中以 90hz 调用它..)。问题是你必须小心才能正确地做到这一点。正如其他人提到的,仅加载脚本一次至关重要。然后,您可以使用 C API 来访问您在该脚本中定义的函数,或者将脚本本身作为函数运行。我推荐前者:对于一个相对简单的游戏,你可以定义像onUpdate(dt),onRender(),onKeyPressed(key),onMouseMoved(dx,dy)等函数,你可以在适当的时候调用这些函数从 C++ 的主循环中。或者,您实际上可以将整个主循环放在 Lua 中,然后调用 C++ 代码来执行性能关键的例程(我就是这样做的)。使用 LuaJIT FFI 可以非常轻松地做到这一点。
这是一个非常难的问题。这将取决于您的需求。你能轻松地确定游戏状态吗?太棒了,把它放在 C++ 端并从 LuaJIT FFI 访问。不确定游戏状态/喜欢什么才能快速制作原型?把它保存在 Lua 中并没有什么问题。也就是说,直到您开始谈论一个包含数千个对象的复杂游戏,每个对象都包含重要的状态。在这种情况下,混合是可行的方法,但准确地弄清楚如何在 C++ 和 Lua 之间分割状态,以及如何在两者之间管理所述状态(特别是在性能关键的例程中)是一门艺术。如果您想出一种万无一失的技术,请告诉我:) 与其他所有事情一样,一般的经验法则是:通过性能关键路径的数据需要位于本机端。例如,如果您的实体具有每帧更新的位置和速度,并且您有数千个所述实体,则需要在 C++ 中执行此操作。但是,您可以使用 Lua 在这些实体之上分层“库存”(库存不需要每帧更新)。
现在,我想抛出更多注释,作为一般性仅供参考,并作为对其他一些答案的回应。
一般来说,基于事件的方法对于任何游戏的性能都至关重要,但对于用 Lua 编写的系统来说更是如此。我说以 60hz 调用 Lua 是完全可以的。但在 Lua 中的每一帧中对大量游戏对象运行紧密循环并不完全正确。在 C++ 中,你可能会浪费地对宇宙中的所有东西调用 update()(尽管你不应该这样做),但在 Lua 中这样做会开始太快地消耗掉那些宝贵的毫秒。相反,正如其他人提到的,您需要将 Lua 逻辑视为“反应式”——通常,这意味着处理事件。例如,不要在 Lua 中的每一帧中检查一个实体是否在另一个实体的范围内(我的意思是,这对于一个实体来说很好,但一般来说,当您扩展游戏时,您不需要这样思考)。相反,告诉您的 C++ 引擎在两个实体彼此达到一定距离时通知您。这样,Lua 就成为了游戏逻辑的高级控制器,调度高级命令并响应高级事件,而不是执行琐碎游戏逻辑的低级数学苦差事。
请警惕“混合代码”速度慢的建议。Lua C API 轻量级且快速。在最坏的情况下,包装与 Lua 一起使用的函数非常简单(如果您花一些时间来了解 Lua 如何与 C 交互虚拟堆栈等,您会注意到它是专门为最小化调用开销而设计的),并且最好的情况下,微不足道(不需要包装)并且 100% 与本机调用一样性能(感谢 LuaJIT FFI!)在大多数情况下,我发现 Lua 和 C 的仔细混合(通过 LJ FFI)优于纯 Lua。即使是矢量操作,我相信 LuaJIT 手册在某处提到了代码应保留在 Lua 中的示例,我发现当我通过 Lua->C 调用执行时速度更快。最终,当涉及到性能敏感的代码片段时,除了您自己(仔细的)性能测量之外,不要相信任何人或任何东西。
大多数小型游戏都可以很好地完成游戏状态、核心循环、输入处理等,完全用 Lua 完成并在 LuaJIT 下运行。如果您正在构建一个小型游戏,请考虑“根据需要转移到 C++”方法(延迟编译!)用 Lua 编写,当您发现明显的瓶颈(并对其进行测量以确保它是罪魁祸首)时,请转移学习一下 C++,然后就可以上路了。如果您正在编写一个包含数千个复杂对象、复杂逻辑、高级渲染等的大型游戏,那么您将受益于更多的前期设计。
祝你好运:)