Urho3D中的Lua脚本有其专用的LuaScript子系统,必须先实例化该子系统,然后才能使用脚本功能。默认情况下,Lua支持不会在中编译,但必须通过CMake构建选项-DURHO3D_Lua=1启用。有关详细信息,请参阅构建选项。实例化子系统的过程如下:
context_->RegisterSubsystem(new LuaScript(context_));
与AngelScript一样,Lua脚本支持立即编译和执行单个脚本行、加载脚本文件并从中执行过程函数,以及使用LuaScriptInstance组件将脚本对象实例化到场景节点。
使用ExecuteString()编译并运行一行Lua脚本。这不应用于性能关键型操作。
与AngelScript模块不同,AngelScript模块作为单独的实体存在,除非明确标记为共享,否则不会共享函数或变量,在Lua子系统中,所有的东西都是在一个Lua状态下加载和执行的,因此脚本自然可以访问到目前为止加载的所有东西。要加载并执行Lua脚本文件,请调用ExecuteFile()。
之后,脚本文件中的函数可供调用。使用GetFunction()按名称获取Lua函数。这将返回一个LuaFunction对象,您应该首先调用BeginCall(),然后推送函数参数(如果有),最后使用EndCall()执行函数。
在应用程序中嵌入Lua脚本的调试可以在将客户端首次注入应用程序之后,通过附加到远程调试器来完成(例如,请参阅eclipseLDT的远程调试器)。
然而,Urho3D中的Lua脚本文件通过Urho3D的资源缓存加载到解释器中,该资源缓存在将缓冲区传递给解释器之前将脚本文件加载到内存缓冲区中。这有利于性能和跨平台兼容性,但意味着调试器无法使用源文件,因此,例如,断点可能不起作用,代码无法有意义地单步执行。
对于您希望单步执行的单个脚本,可以使用ExecuteRawFile(),它将脚本从文件系统直接加载到Lua解释器中,使依赖它的调试器可以使用源代码:
要绕过第二个警告并避免更改方法调用,请使用URHO3D_LUA_RAW_SCRIPT_LOADER构建选项。这将迫使Urho3D在返回资源缓存之前,默认情况下尝试从文件系统加载脚本。然后,可以如上所述使用ExecuteFile(),并根据需要禁用CMake选项用于生产。
通过使用LuaScriptInstance组件,可以将Lua脚本对象添加到场景节点。创建组件后,有两种方法可以指定要实例化的对象:指定脚本文件名和对象类名,在这种情况下,首先加载并执行脚本文件,或者只指定类名,在那种情况下,必须已经执行了包含类定义的Lua代码。在C++中从LuaIntegration示例创建脚本对象的示例,其中名为Rotator的类是从脚本文件Rotator.lua实例化的:
LuaScriptInstance* instance = node->CreateComponent<LuaScriptInstance>();
instance->CreateObject("LuaScripts/Utilities/Rotator.lua", "Rotator");
实例化后,使用GetScriptObjectFunction()按名称获取对象的函数;呼叫的发生方式如上所述。
与AngelScript类似,脚本对象类可以定义LuaScriptInstance自动调用的函数,用于初始化、场景更新或加载/保存等操作。以下列出了这些功能。有关详细信息,请参阅AngelScript脚本页面。
与AngelScript一样,支持过程和对象事件处理。在过程事件处理中,LuaScript子系统充当C++端的事件接收器,并将事件转发给Lua函数。使用SubscribeToEvent并提供事件名称和用作处理程序的函数。可选地,可以将特定的发送方对象作为第一个参数。例如,订阅应用程序范围的Update事件,并在事件处理程序函数中获取其timestep参数。
SubscribeToEvent("Update", "HandleUpdate")
...
function HandleUpdate(eventType, eventData)
local timeStep = eventData["TimeStep"]:GetFloat()
...
end
订阅脚本对象以接收事件时,请改用形式self:SubscribeToEvent()。用作处理程序的函数被指定为“ClassName:FunctionName”。例如,订阅NodeCollision物理事件,并在处理程序函数中获取参与的其他场景节点和接触点VectorBuffer。注意,在Lua中,从VariantMap检索对象指针需要将对象类型作为第一个参数:
CollisionDetector = ScriptObject()
function CollisionDetector:Start()
self:SubscribeToEvent(self.node, "NodeCollision", "CollisionDetector:HandleNodeCollision")
end
function CollisionDetector:HandleNodeCollision(eventType, eventData)
local otherNode = eventData["OtherNode"]:GetPtr("Node")
local contacts = eventData["Contacts"]:GetBuffer()
...
end
Urho3D C++类的绑定是通过tolua++库完成的,它在很大程度上绑定了与C++完全相同的函数参数。与AngelScript API相比,类的Get/Set函数总是可用的,但除此之外,还存在方便的财产。
从上面的事件处理示例中可以看出,VariantMap处理与C++和AngelScript都类似。要从映射中获取变量对象,请按字符串键对映射进行索引。当映射的键不存在时,返回nil值。然后使用一个变量getter方法返回存储在变量对象中的实际Lua对象。这些getter方法通常不接受任何参数,除了GetPtr()和GetVoidPtr((),它们接受表示Lua用户类型的字符串参数,该方法将用于将返回对象强制转换为该类型。GetPtr()用于获取引用计数对象,而GetVoidPtr(()用于获得POD值对象。
您还可以使用VariantMap作为伪Lua表来存储脚本中的任何变量值对象。VariantMap类将尽力将任何Lua对象转换为变量对象,并使用提供的键作为索引存储变量对象。密钥可以是字符串或无符号整数,甚至可以是StringHash对象。当还不支持特定的数据类型转换时,将存储一个空的变量对象。因此,如果您正在使用此功能,请务必小心。您还可以使用Variant类构造函数之一在将Variant对象分配给VariantMap之前先构造Variant对象,但此操作将比直接转换慢。以这种方式使用VariantMap的目的是促进Lua和C++之间的对象传递,如上面的事件处理机制所示。在Lua端创建对象时,您必须确保它们不会被Lua垃圾收集,而在C++端仍有引用指向它们,特别是当对象未被引用计数时。
local myMap = VariantMap()
myMap[1] = Spline(LINEAR_CURVE) -- LINEAR_CURVE = 2
print(myMap[1].typeName, myMap[1]:GetVoidPtr("Spline").interpolationMode)
-- output: VoidPtr 2
myMap["I am a table"] = { 100, 200, 255 }
print(myMap["I am a table"].typeName, myMap["I am a table"]:GetBuffer():ReadByte())
-- output: Buffer 100
print(myMap["I am a table"]:GetRawBuffer()[3], myMap["I am a table"]:GetRawBuffer()[2])
-- output: 255 200
local hash = StringHash("secret key")
myMap[hash] = Vector2(3, 4)
print(myMap[hash].typeName, myMap[hash]:GetVector2():Length())
-- output: Vector2 5
如上面的示例所示,您可以使用GetRawBuffer()或GetBuffer(()获取存储在变量对象中的无符号字符数组。它还表明VariantMap能够将包含无符号字符数组的Lua表转换为存储为缓冲区的变量对象。您可能想知道,它能够将包含变量对象数组或字符串对象数组的Lua表转换为VariantVector和StringVector。它还转换任何Lua原始数据类型和所有暴露给Lua的Urho3D类,如所有数学类、引用计数类、POD类、资源引用类。等
与C++和AngelScript内联,在Lua中,您必须调用Variant的getter方法之一来“打开”Variant对象中存储的实际对象。然而,特别是在Lua中,有一个通用的Get()方法利用了Lua是无类型的,因此该方法可以取消Variant对象的框,并将存储的对象作为无类型的Lua对象返回。它接受一个可选的字符串参数,表示该方法将用于将返回对象强制转换为的Lua用户类型。该参数用于从存储void指针或refcount指针的Variant对象返回对象时需要类型转换的情况。类型转换也可以是可选的,例如请求为存储无符号字符缓冲区的Variant对象返回VectorBuffer,或者请求为存储整数值的Variant返回无符号或StringHash。对于所有其他情况,将忽略该参数。接下来使用上面的相同示例,我们可以索引地图并访问存储的对象,如下所示:
print(myMap[1]:Get("Spline").interpolationMode)
print(myMap["I am a table"]:Get("VectorBuffer"):ReadByte())
print(myMap["I am a table"]:Get()[2])
print(myMap[hash]:Get():Length())
Variant类还有一个通用的Set()方法来处理Lua。Lua不支持赋值运算符重载。Set()方法接受一个参数,该参数可以是Lua中可以转换为Variant的任何参数,包括存储为空Variant的nil值。当需要向现有Variant对象分配值时,请使用此方法。
关于其余的函数和类,请参阅生成的Lua脚本API参考。此外,请查看bin/Data/LuaScripts目录中示例应用程序的Lua副本,并将它们与C++和AngelScript版本进行比较,以熟悉Lua端的操作方式。
关于Lua脚本实现,还有一点需要注意的是它在C++集合容器(Vector和PODVector)和Lua数组(分别是非POD表和POD对象表)之间的双向转换。当集合跨越C++/Lua边界时,转换将自动完成。生成的Lua脚本API参考页没有正确反映这一事实。当使用Lua脚本API获取对象集合时,应将其视为一个Lua表,尽管文档页面中声明要返回Vector或PODVector用户类型。
有两种方法可以在Lua脚本中分配C++对象,它们的行为与Lua的自动垃圾收集不同:
1) 调用类构造函数:
local scene = Scene()
tolua++将用垃圾收集注册这个C++对象,Lua将最终收集它。如果要将对象添加到在C++端使用SharedPtr保持活动的对象层次结构中,例如子场景节点或UI子元素,请不要使用此表单。否则,对象将被双重删除,导致崩溃。
注意,以这种方式调用类构造函数相当于调用类new_local()函数。
2) 调用class new()函数:
local text = Text:new()
使用此表单时,Lua不会收集对象,因此可以安全地传递到C++对象层次结构中。否则,为了防止内存泄漏,需要通过调用其上的delete函数手动删除:
text:delete()
当您从Lua调用ResourceCache的GetFile()函数时,您收到的文件在处理完后也必须手动删除,如上所述。