UnLua-UE4下的Lua脚本插件

Posted 虚幻引擎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UnLua-UE4下的Lua脚本插件相关的知识,希望对你有一定的参考价值。


简介
UnLua是一款UE4下特性丰富且高度优化的Lua脚本插件。它遵循UE4的编程模式,简单易上手,UE4程序员可以零学习成本使用。
该插件现已开源:
https://github.com/Tencent/UnLua
________________________________________________

特性
• 零胶水代码访问引擎反射体系内的所有UCLASS、UPROPERTY、UFUNCTION、USTRUCT、UENUM。
• 零辅助代码覆写(Override)所有'BlueprintEvent' (包括所有用'BlueprintImplementableEvent'或'BlueprintNativeEvent'标记的UFUNCTION和所有蓝图中定义的Event/Function)、RepNotify、AnimNotify、Input Event。
• 完备的静态导出方案,用于导出引擎反射系统之外的类(成员函数、成员变量)、全局函数、枚举。
• 高度优化的UFUNCTION调用,包括持久化参数缓存、优化的参数传递、优化的非常量引用和返回值处理。
• 高效的基础容器(TArray、TSet、TMap)访问,内存布局与引擎一致,无需在Lua Table和容器间转换。
• 高效的结构体创建、访问、GC。
• 支持自定义的碰撞检测相关枚举。
• 支持UFUNCTION(带BlueprintCallable或Exec标签)默认参数。
• 支持编辑器内Server/Client模拟。
• 支持Lua协程中执行Latent函数,同步写法完成异步逻辑。
• 支持根据Blueprint类型自动生成Lua模板代码。
________________________________________________


快速入门
• 创建新蓝图。
UnLua-UE4下的Lua脚本插件

• 新蓝图实现UnLuaInterface接口。
UnLua-UE4下的Lua脚本插件

  实现GetModuleName函数,返回一个Lua文件路径(相对于Content/Script目录)。
UnLua-UE4下的Lua脚本插件

• 生成Lua模板。
UnLua-UE4下的Lua脚本插件
 
UnLua-UE4下的Lua脚本插件

  向Lua模板加入逻辑代码。
UnLua-UE4下的Lua脚本插件

________________________________________________


代码一览
访问UClass
UnLua-UE4下的Lua脚本插件


访问UFunction
Widget:AddToViewport( 0 )
如果UFunction参数有默认值(UFunction必须包含'BlueprintCallable'或者'Exec'标签),Lua代码可以简单写成:
Widget:AddToViewport()

返回值处理
非常量引用参数
原子类型
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
或者
UnLua-UE4下的Lua脚本插件
非原子类型
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
或者
UnLua-UE4下的Lua脚本插件
前者类似C++调用,它比后者更为高效。

返回参数
原子类型
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
非原子类型
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
或者
UnLua-UE4下的Lua脚本插件
或者
UnLua-UE4下的Lua脚本插件
第一种方式最为直观,但是后两种方式更为高效,最后一种方式等价于:
UnLua-UE4下的Lua脚本插件

Latent函数
协程中执行Latent函数,同步写法完成异步逻辑:
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件


访问UStruct
UnLua-UE4下的Lua脚本插件

访问UProperty
UnLua-UE4下的Lua脚本插件

Delegate
UnLua-UE4下的Lua脚本插件

Multicast Delegate
UnLua-UE4下的Lua脚本插件

访问UEnum
UnLua-UE4下的Lua脚本插件


自定义碰撞检测枚举
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件

静态导出
UnLua-UE4下的Lua脚本插件


UnLua-UE4下的Lua脚本插件


全局函数
UnLua-UE4下的Lua脚本插件

枚举
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
________________________________________________

覆写(Override)'BlueprintEvent'
'BlueprintEvent'包括:
• 用BlueprintImplementableEvent标记的UFUNCTION
• 用BlueprintNativeEvent标记的UFUNCTION
• 所有蓝图中定义的Event/Function

无返回值
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件


带返回值
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
或者
UnLua-UE4下的Lua脚本插件


前者是更优选择。


覆写(Override)AnimNotify
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件

覆写(Override)输入事件
UnLua-UE4下的Lua脚本插件


Axis输入
UnLua-UE4下的Lua脚本插件

Action输入
UnLua-UE4下的Lua脚本插件

Key输入
UnLua-UE4下的Lua脚本插件

覆写(Override)RepNotify
UnLua-UE4下的Lua脚本插件
UnLua-UE4下的Lua脚本插件
________________________________________________

覆写(Override)是怎样做到的?
UnLua的显著特色就是可以无辅助代码覆写'BlueprintEvent'、AnimNotify、RepNotify、Input Event,这一特性由以下两种方法实现:

thunk函数替换
UnLua-UE4下的Lua脚本插件


'Func'是UFunction的thunk函数,下面是UFunction的执行:
 
UnLua-UE4下的Lua脚本插件
 
UnLua-UE4下的Lua脚本插件

由上面两图可以得到两个结论:
• UFunction是通过'Func'进行实际的执行;
• 通过UObject::ProcessEvent进行UFunction调用都会实际走到'Func'的执行。
所以如果将引擎默认设置的thunk函数替换成一个调用Lua函数的自定义thunk函数,那么我们就可以达到用Lua函数覆写UFunction的目的,而'BlueprintEvent'、AnimNotify、RepNotify以及Input Event恰好都是通过UObject::ProcessEvent进行调用的。

Opcode注入
对于非Native UFunction,除了UObject::ProcessEvent,还可以通过UObject::CallFunction调用:
UnLua-UE4下的Lua脚本插件
 
由上图(各版本的引擎会有区别)可以看到UObject::CallFunction并不像UObject::ProcessEvent一样通过UFunction::Invoke调用thunk函数,而是直接调用'ProcessInternal'(引擎为非Native UFunction设置的默认thunk函数,它会对Opcode进行解释执行),所以替换thunk函数并不能满足要求。 
这种情况下,我们可以通过向UFunction注入新的Opcode达到覆写的目的:
Opcode注册
UnLua-UE4下的Lua脚本插件
 
UnLua-UE4下的Lua脚本插件


注入
UnLua-UE4下的Lua脚本插件
________________________________________________



优化思路
UnLua的实现高度优化,尤其体现在以下几个方面。

结构体访问优化
当开发者通过
UnLua-UE4下的Lua脚本插件
在Lua中创建结构体实例时,其直观的实现往往类似(伪代码):
UnLua-UE4下的Lua脚本插件

而UnLua的实现如下(伪代码):
UnLua-UE4下的Lua脚本插件


两者比较可以看出后者节省了一次内存分配,同理对应Userdata进行GC时也会节省一次内存释放,而且结构体的访问更为缓存友好(cache friendly)。

内存布局
最终Userdata的内存布局为:
 
UnLua-UE4下的Lua脚本插件

其中Padding的引入是为了满足结构体的对齐要求。
UFunction调用优化
UnLua为UFunction的调用做了很多优化,使得其性能优于其他同类插件。
持久化参数缓存
每个UFunction的所有参数所占内存大小都是固定的,因此,相对于每次调用时分配,调用结束后释放,分配一块持久的参数缓存要有效的多。
 
UnLua-UE4下的Lua脚本插件


Native Local函数优化
对于Native Local(相对于RPC)函数,在持久化参数缓存基础之上,UnLua还提供了两个方面的优化:
• 为返回值参数预分配缓存
UnLua-UE4下的Lua脚本插件


• 提供快速调用路径
UnLua-UE4下的Lua脚本插件


这两方面的优化,避免了UObject::ProcessEvent每次执行都分配、复制参数缓存,以及分配返回值参数相关缓存的消耗。

传参和返回值优化
UnLua对传参和返回值处理做了细致的优化,在UFunction带有复杂数据结构(例如结构体、 TArray等容器)引用参数情况下,其调用性能大幅度领先于其他同类插件。
• 传参
UnLua-UE4下的Lua脚本插件


上图中的UFunction带有一个常量引用参数,其调用的直观实现往往类似(伪代码):
UnLua-UE4下的Lua脚本插件


而UnLua的实现如下(伪代码):
UnLua-UE4下的Lua脚本插件

两者对比可以看出,UnLua使用的是浅拷贝,因为浅拷贝已经能够保证正确性了,对于容器这样的复杂数据结构,尤其容器中含有大量元素的情况下,浅拷贝的性能优势是巨大的。
• 非常量引用参数
UnLua-UE4下的Lua脚本插件


上图中的UFunction带有一个非常量引用参数,和传参利用浅拷贝类似,UnLua对它的调用实现也是基于两次浅拷贝(传参和值返回各一次)完成的。开发者可以使用和C++类似的方式在Lua中调用:
UnLua-UE4下的Lua脚本插件


• 返回值
UnLua-UE4下的Lua脚本插件

上图中的UFunction返回一个常量FVector引用,直观的实现是每次调用它时,新创建一个Userdata并把它压入Lua栈顶,调用方式如下:
UnLua-UE4下的Lua脚本插件

除了支持这种实现,UnLua还提供了一种更高效的实现,即先创建Userdata,再将它作为参数传入函数。调用方式和带非常量引用参数的UFunction类似:
UnLua-UE4下的Lua脚本插件


这种方式虽然不如常规方式直观,但在多次调用(例如循环)的情况下,却有明显的性能优势,因为这种方式不仅利用了传参优化,并且避免了大量的Userdata创建和GC。
________________________________________________


周边工具支持
智能语法提示
UnLua提供了两种方式用于导出智能语法提示相关的符号信息。
• 反射体系内符号UnLuaIntelliSense模块与UHT一同工作,在编译时自动导出所有反射体系内数据的符号信息(位于ProjectDir/Plugins/UnLua/Intermediate/IntelliSense)。
 
UnLua-UE4下的Lua脚本插件


• 反射体系外符号
UnLuaIntelliSenseCommandlet用于通过命令行导出反射体系外静态导出数据的符号信息(位于ProjectDir/Plugins/UnLua/Intermediate/IntelliSense/StaticallyExports)。
UnLua-UE4下的Lua脚本插件


IDE
基于VSCode我们开发了内部的集成开发环境G6IDE,它为开发者提供了完善的智能语法提示、脚本语法检查等功能,使Lua代码编写更加简单高效。
不久后G6IDE将会开放给开发者使用!
调试
G6IDE还提供了Lua脚本的调试功能(DebugAnyWhere),它是一个调试云服务,可以调试远程手机和服务器上的脚本代码。

G6 Team : G6是腾讯CROS体系下,致力于游戏研发(中间件)技术平台建设的团队。

UnLua-UE4下的Lua脚本插件
近期焦点






如需获得更多虚幻引擎4的授权合作方式和技术支持,请发送邮件至
EGC-Business@epicgames.com咨询;
如果你想来 Epic 工作,扫描下方二维码关注我们后点击菜单栏按钮“更多”并选择“招聘”,即可了解我们的最新招聘信息。Epic Games 欢迎你的加入!
长按屏幕选择“识别二维码”关注虚幻引擎
“虚幻引擎”微信公众账号是 Epic Games 旗下 Unreal Engine 的中文官方微信频道,在这里我们与大家一起分享关于虚幻引擎的开发经验与最新活动。

以上是关于UnLua-UE4下的Lua脚本插件的主要内容,如果未能解决你的问题,请参考以下文章

lua能做像TC或按键精灵那样的脚本吗?

Redis与Lua脚本

Xposed插件dump Cocos2d-x应用的lua脚本

wireshark插件开发 - Lua插件解析

利用Lua脚本语言制作魔兽WOW插件

Lua脚本语言简单学习