找回密码
 立即注册
查看: 508|回复: 0

slua unreal分析(一)LuaActor概览

[复制链接]
发表于 2020-11-27 12:39 | 显示全部楼层 |阅读模式
简介

Slua unreal是腾讯提出的unreal引擎插件,支持使用lua语言进行unreal项目脚本开发,方便游戏高效迭代开发,上线热更新。slua可以用lua代码实现蓝图中的功能逻辑,lua相比蓝图在版本控制、热更新、多人协作上更有优势。
slua已经开源,可在https://github.com/Tencent/sluaunreal看到源码以及详细介绍。
下面是官方对其的介绍:
slua-unreal作为unreal引擎的插件,通过unreal自带蓝图接口的反射能力,结合libclang静态c++代码分析,自动化导出蓝图接口和静态c++接口,提供给lua语言,使得可以通过lua语言开发unreal游戏业务逻辑,方便游戏高效迭代开发,上线热更新,同时支持lua到c++双向,lua到蓝图双向调用,使用lua语言完美替代unreal的c++开发方式,修改业务逻辑不需要等待c++编译,大大提升开发速度。
由于slua unreal内容比较多,因此本文先对LuaActor做一个大致的解析。


相关文章:
LuaActror

LuaActor是slua中的一个重要概念,继承此类的Actor,可以用lua代码来实现其蓝图部分逻辑。通常,我们会将一些调用不频繁,但灵活性较高、改动频繁的逻辑放到蓝图中实现,我们可以把一些方法标记为BlueprintImplementableEvent、BlueprintNativeEvent,在蓝图中实现它们,然后在C++中调用,lua要替代的正是这些方法。
在看slua如何实现这个功能之前,我们先回顾一下C++如何调用蓝图实现方法的:
lua是如何把Blueprint runtime给换成lua runtime的呢?可以看下LuaActor.h文件,有如下代码:
#define LUABASE_BODY(NAME) \
protected: \
        virtual void BeginPlay() override { \
        if (!init(this, #NAME, LuaStateName, LuaFilePath)) return; \
                Super::BeginPlay(); \
                PrimaryActorTick.SetTickFunctionEnable(postInit("bCanEverTick")); \
        } \
        virtual void Tick(float DeltaTime) override { \
                tick(DeltaTime); \
        } \
public:        \
        virtual void ProcessEvent(UFunction* func, void* params) override { \
        if (luaImplemented(func, params))  \
                return; \
                Super::ProcessEvent(func, params); \
        } \
这个宏被置于LuaActor中,可以看到LuaActor覆写了ProcessEvent函数,它首先检查lua中是否已经实现了这个UFunction,如果有就调用lua的实现,否则使用默认的ProcessEvent流程。我们可以先不深究lua如何实现等细节,现在C++调用蓝图实现的方法流程变成了这样:
LuaActor C++对象与Lua表绑定

现在我们已经对LuaActor工作方式有了初步认识,接下来看一下LuaActor的实现细节。每个LuaActor实例在Lua中都有一张对应的表,这张lua表里面可以实现之前所说的蓝图方法,在C++中LuaActor创建之初,就会在Lua中同步的创建这张表。
不妨看下slua的官方demo例子,其中有个文件叫LuaGameMode.lua,内容如下:
local gamemode={}

function gamemode:ReceiveBeginPlay()
    -- call super ReceiveBeginPlay
    self:Super()
    print("gamemode:ReceiveBeginPlay")
end

return gamemode其创建了gamemode表,并覆写了ReceiveBeginPlay()方法,Super()即调用ReceiveBeginPlay()的蓝图实现,最后,返回了gamemode表。在所有LuaActor中,都会有属性LuaFilePath,在蓝图中把其设置为该LuaActor对应表所在的lua文件名,即可把LuaActor和表关联起来。LuaActor在BeginPlay()方法中安插了init()过程,每当其执行BeginPlay,就会创建对应的Lua表,init方法主要做了下面几件事情:
    得到指定Lua文件中的表
通过doFile方法可以执行某个文件中的lua代码,因此我们需要指定LuaFilePath。得到表后,会把表存储于LuaActor的luaSelfTable变量上,之后,LuaActor就可以通过这个变量索引到Lua中对应的表了。
2. 将LuaActor指针传递给lua表
lua中并不会直接使用LuaActor的指针。lua表上有一个键,名称为"__cppinst",类型为userdata,在C++中的定义如下:
template<class T>
struct UserData : public UDBase {
        static_assert(sizeof(T) == sizeof(void*), "Userdata type should size equal to sizeof(void*)");
        T ud;
};其ud属性是一个指针,指向了真正的LuaActor对象,因此lua表可以通过"__cppinst"来索引到真正的LuaActor对象,从而实现属性访问,函数调用等功能。这样一来,LuaActor和lua表就完成了双向关联。至于双向关联的具体细节会在之后详细分析。
3. 设置lua表的Super方法
通过这个步骤,lua表只要在函数中调用Super(),就会自动执行当前函数所覆写的蓝图方法。
4. 设置lua表的元表
创建一张元表,配置__index等方法,当lua表取actor上的属性时,就会通过__cppinst来取。


lua表的生命周期

lua中,gc是绕不开的话题,lua使用引用计数方式管理内存,在init过程中,slua把一张表从lua传递给了C++,那这张表在什么时候被销毁呢?这个就涉及到了LuaVar类,它管理了Lua变量,表也不例外,在此我们只看LuaVar如何影响表生命周期的。
LuaActor使用LuaVar变量来存储luaSelfTable和metatable,以gamemode表为例,我们从Lua中返回它时,会创建一个LuaVar,当LuaVar发现自己是一个lua表,就会通过RefRef来对该表添加引用,这样luagc时就不会销毁该表。
相应的,当我们的LuaActor销毁时,LuaVar也会进行析构,它再次通过RefRef来解除对该lua表的引用,当lua表没有其他引用时,就会被gc删除了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-25 22:11 , Processed in 0.111858 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表