引擎内嵌Lua脚本对象开发
您可以直接在编辑器内为应用对象实例绑定您的Lua逻辑脚本。现在可以支持的实例绑定对象有:世界、Actor、特定类型的节点。 引擎对象内嵌脚本对象可以解决如下情形:
- 场景已被创建,并且希望直接对场景内物体便捷地附加一些脚本运行逻辑。
- 对特定的场景对象(例如蒙太奇/过场动画实例对象)中的指定节点实例附加一些脚本运行逻辑。
编辑器会将lua脚本通过xscript资产文件管理的方式, 保存到磁盘上。 读入内存时, 每个脚本节点会为编辑器lua脚本生成一个code_entity的lua表对象,该表对象会在运行时被压入Lua栈中,并自动执行绑定对象的相应方法。例如OnHolderEnterance
, OnHolderRelease
, OnHolderTick
, OnHolderRender
为这个绑定Actor的脚本对象生成时、释放时、Actor在Tick时、Actor在Render时的方法代理。
特别地, 在OnHolderEnterance
中,场景中的(其他)Actor对象可能还没有创建到场景中,因此,如果您希望在入口函数中查找场景中的某个Actor时,您需要在OnHolderTick
中查找, 并通过变量设置只查找一次。
现在我们尝试为这个绑定的Actor绕Y轴进行旋转:
--this function will be called each tick after the ticking of the holder.
function code_entity:onHolderTick(actor, interval)
-- rotate speed is pi/s
local rotateSpeed = math.pi * interval / 1000.0
actor:RotateY(rotateSpeed)
end
在OnHolderTick
中查找一次场景中的其他Actor, 并保存入self.actors表中:
--this function will be called each tick after the ticking of the holder.
function code_entity:onHolderTick(actor, interval)
if not self.__first_find_actor_done then
self.__first_find_actor_done = true
local level = actor:GetLevelOwner()
self.actors = {
level:FindActor(ActorName1ThatYouWant),
level:FindActor(ActorName2ThatYouWant),
...
}
end
end
这里我们尝试对这个绑定的Actor进行绕Y轴旋转操作,旋转角速度为pi每秒。输入的interval单位是毫秒,因此这里我们需要除以1000转化为秒。 点击主面板中的“播放”按钮,即可查看运行结果。打开”视图->显示->Lua日志“,即可查看Lua虚拟机运行您的绑定脚本时所输出的日志。如下: こんにちは、じゃ、まだね。为您好、再见的意思。输出这句话时,表明您绑定的Lua脚本代码已成功运行。
1. 绑定对象规范
1.1. 明确需要绑定的脚本对象
您可以通过为世界、Actor、特定类型的节点创建并绑定您的内嵌脚本。 一般地, 如果场景业务包含多个子场景(LevelScene), 我们建议您在游戏场景中对Actor进行绑定。游戏场景可以直接被游戏世界以Level的形式加载,所有Actor会存在在Level中。此时,您不能在游戏场景中绑定世界, 目前尚不支持直接将世界中的脚本绑定转换为对应Level的绑定。
绑定Actor时, holder为Actor, OnHolderEnterance
, OnHolderRelease
, OnHolderTick
, OnHolderRender
的接口方法定义为
-- actor.entrance
local code_entity = {}
-- this function will be called once when the binding holder is ready to work.
function code_entity:onHolderEntrance(script_ins, actor)
end
-- this function will be called once when the binding holder is ready to release.
function code_entity:onHolderRelease(actor)
end
-- this function will be called each tick after the ticking of the holder.
function code_entity:onHolderTick(actor, interval)
end
-- this function will be called each tick after the rendering of the holder.
function code_entity:onHolderRender(actor, viewport)
end
您可以通过actor来获取工作场景Level, 并在Level中创建您需要创建的Actor:
function code_entity:onHolderEntrance(script_ins, actor)
local level = actor:GetLevelOwner()
local newActor = level:CreateActor(SomeActorType, SomeActorName)
print("The level is", level:GetLevelName())
end
当您需要新创建一个Actor时,我们建议您在具体的Level中创建,而不是在World中。在World中创建Actor, Actor将会被World的默认Level管理,这可能会导致您想控制的Level并不包含您新创建的Actor,从而无法通过您的Level进行管理(例如删除您的Level并不能删除掉您直接使用World创建的Actor)
总结
- 1.游戏场景可以以"Level"形式加载到World中,作为World中的一个子场景, 此时建议您绑定Actor。
- 2.新创建一个Actor时,我们建议您在具体的Level中创建, 而不是在World中。
绑定World时, holder为World, OnHolderEnterance
, OnHolderRelease
, OnHolderTick
, OnHolderRender
的接口方法定义为
-- world.entrance
local code_entity = {}
-- this function will be called once when the binding holder is ready to work.
function code_entity:onHolderEntrance(script_ins, world)
end
-- this function will be called once when the binding holder is ready to release.
function code_entity:onHolderRelease(world)
end
-- this function will be called each tick after the ticking of the holder.
function code_entity:onHolderTick(world, interval)
end
-- this function will be called each tick after the rendering of the holder.
function code_entity:onHolderRender(world, viewport)
end
总结
- 1.当世界中不存在需要专门处理的Actor,或者您想全局处理世界中的Actor时,可以通过绑定World来进行脚本绑定。
- 2.如果您的场景有可能作为子场景以Level的形式加载时,当前对World的绑定将会丢失。此时您应考虑通过绑定Actor的形式进行脚本绑定。
1.2. 使用编辑器的脚本编辑器进行脚本对象绑定
1.2.1. 为Actor绑定脚本
点击"Actor"的属性选项卡-> 点击想要绑定的"Actor" -> 点击Actor的属性数据面板中的"编辑绑定脚本"。
1.2.2. 为世界绑定脚本
点击"World"的属性选项卡-> 点击World的属性数据面板中的"打开和绑定脚本"。
2. 内嵌脚本资产的文件位置放置规范
2.1. xscript 对象
xscript对象管理了world/actor/usernode所绑定的1个或者多个lua脚本。绑定后,xscript/lua文件对象请不要再挪动到其他位置,否则将会找不到。因此,您需要考虑好这些脚本对象资产应该放在哪里。 点击绑定脚本按钮即可打开 或者 创建 一个xscript对象,并放置到您所设置的位置上。
2.2. lua代码文件
您需要创建一个脚本节点,然后点击脚本节点属性面板中的"创建新的代码文件”,或者选择一个已经存在的Lua脚本进行脚本关联。如下:
在lua代码中,您可以require其他lua脚本文件,同理,您也应该考虑这些代码文件应该放到您的工程的什么位置。 lua代码文件可以被多个xscript绑定对象复用。 如果您在编辑器中进行调试,您可以点击“运行世界”按钮,这些Lua绑定脚本只会在运行时世界中生效,如下:
require使用的Lua文件由于lua的require机制,当lua代码文件已经加载时,并不会复生加载,因此,您在编辑器中对require的lua代码文件进行修改后,您需要删除Lua中放置require的表数据,才能重新加载您修改后的lua文件。您可以参考如下方法删除 require表数据:
--[[
Lua的require(modelname)把一个lua文件加载存放到package.loaded[modelname]中,
重复require同一个模块实际还是沿用第一次加载的chunk。
在复用一个lua实例栈的环境下,如果想要修改代码文件,又不想重启Lua实例,可强制去掉require方法中的模块索引, 请在Lua实例启动入口处调用该方法
注意:本方法如需要热更新,只能重启Lua实例了
链接:https://www.jianshu.com/p/015c41bfb7d0
]]
--代码热更方法处理, 本方法不可热更
local hotFix = {}
--构造函数
function hotFix:clean(package_contain_path)
print("ready to construct hot_fix! current packages:")
local pcp = package_contain_path or "Asset"
for k,v in pairs(package.loaded) do
print(k,v)
end
while true do
local found = false
for k,v in pairs(package.loaded) do
if type(k) == "string" then
if string.find( k,pcp) and v ~= self then
print("require cache ".." wtih:" .. pcp .. " fixed =>", k, v)
package.loaded[k] = nil
found = true
break
end
end
end
if not found then break end--not any more
end
end
--类型字符串
function hotFix:__tostring()
return "hot_fix"
end
return hotFix
3. 项目资源目录定义相关建议
编辑器的基本资源目录结构如下:
Root
|---Asset
|----fx
|----GameAssets
|---YourProject.project
|---YourScene.xscene
|---SequenceFrameDefaultDir(2D序列帧图片文件夹)
3.1. 引擎实例单一游戏项目
对于单一引擎实例游戏项目,资源管理相对简单,我们建议您就以基本资源目录结构
进行组织和管理即可。如果有多场景,请以/Asset/
为相对路径进行放置,即将场景及相关游戏资源放到Asset目录下。资源文件一旦定下来后如果需要挪动位置,请在编辑器内挪动。请尽量不要更改资源的位置。
3.2. 引擎实例直播多场景项目
在引擎的直播业务上,资源加载需求较为复杂。现在,一个引擎直播效果对应一个完整的引擎资源包,并且存在同时加载多个引擎资源包的需求。建议您在使用MagicCubeEditor制作素材资源的时候,遵循以下文件目录结构:
Root
|---Asset
|----YourProjectName
|----fx
|----GameAssets
|---YourProject.project
|---YourScene.xscene
|---SequenceFrameDefaultDir(2D序列帧图片文件夹)
即在Asset下多一层以项目名称命名的目录YourProjectName, 然后将素材资源,包括Lua代码资源放到此目录下。这样可以保证在多个引擎资源搜索路径下,素材按项目加载时的正确性,否则可能会命中其他相同的相对路径,从而导致效果加载错误或者失败。