传送门:[UnrealCircle]腾讯 罗谦 | UnLua-UE4下的Lua脚本插件_哔哩哔哩_bilibili
参考PPT:UnrealCircle921北京PPT_免费高速下载|百度网盘-分享无限制
一. UnLua 基础
1.1 概念
- UnLua 是一个脚本插件
- UnLua 不是蓝图的替代,而是一种补充
- 没有 Asset 预览
- 不支持 nativization
- 无法保持内容的引用
- UnLua 为使用 Lua 编写游戏逻辑提供了支持
1.2 主要特性
- 无需胶水代码访问 UCLASS、UPROPETY、UFUNCTON、USTRUCT、UENUM
- 无辅助代码覆写(Overide)BlueprintEvent
- 无辅助代码覆写(Overmide) AnimNottfy
- 无辅助代码覆写(Overide) RepNottfy
- 无辅助代码覆写(Overide) Input Event
- 简单完备的静态导出方案
- 高度优化的 UFUNCTION 调用
- 高度优化的容器(TAray、TSet、TMap)访问
- 高度优化的结构体访问
- 支持 UFUNCTON(带'BlueprintCallable' 或 'Exec' 标签)默认参数
- 支持自定义的碰撞检测相关枚举
- 支持编辑器内 Server/Cllent 模拟
- 支持 Lua 协程中执行 Lateni 函数,同步写法完成异步逻辑
- 支持根据 Blueprint 类型自动生成 Lua 模板代码
二. 使用原理
2.1 引擎和 Lua 绑定
- 静态绑定
- 仅一个 Interface 和一个纯虚函数
- 同时支持蓝图类和 C++ 类
- 动态绑定
- 动态创建的 Object
- 动态生成的 Actor
2.2 Lua 访问引擎
- 无巨量的胶水代码
- 支持反射体系内的所有数据
- 手动导出一些最基础的函数
- UObject.Load、UObject.GetName、UObject.GetClass...
- UClass.Load、UClass.lsChildOf...
- UWord.SpawnActor...
- 手动导出数学库
- FVector、FVector2D、FVector4、FQuat、FRotator、FTransform...
- 访问 UClass
- 访问 UStruct:
- 访问 UEnum
- 访问自定义的碰撞检测相关的 UEnum
- 访问 UProperty
- 访问 Delegate/Multi-cast Delegate/Sparse Delegate
- 访问 UFunction
- UFunction 非常量引用参数处理(原子类型)
- UFunction 非常量引用参数处理(非原子类型)
- 第一种写法在性能上远远高于第二种
- UFunction 返回值参数处理(原子类型)
- UFunction 返回值参数处理(非原子类型)
- 在有性能要求的场合,建议使用下面第二、三种写法
- 访问 Latent Function
- 支持在 Lua 的协程中做 Latent Function 的调用(用同步的写法完成异步的逻辑)
- 访问基础容器(TArray、TMap、TSet)
- Lua 语言本身不支持模板,UnLua 进行特殊处理后把 C++ 里面的类型信息用 Lua 中的参数代替
TArray(0)
对应 C++代码中实例TArray<int 32>
TArray(FVector)
对应 C++代码中实例TArray<FVector>
- 在 Lua 中创建的这些容器实例,在 memory layout 内存的布局上,是和引擎一致的,没比要再进行容器到 Lua Table 间的转换
- 静态导出反射体系外的数据
- 均为程序员手动操作控制,如果修改了类(如:修改了函数签名,函数原型发生了变化)又正好要导出,则需要修改两处
2.3 引擎访问 Lua
- 使用 UnLua 插件
- UnLua 插件区别于其他同类 Lua 插件的最大地方:可以覆写
- Override覆写所有 BlueprintEvent:
- 用 BlueprintImplementableEvent 标记的 UFunction
- 用 BlueprintNativeEvent 标记的 UFunction
- 所有蓝图中定义的 Events/Functions
- 以上三种类型的 UFunction,都可以直接在 Lua 中写同名函数来覆写,而没有任何辅助代码
- 覆写 BlueprintEvent:
- 覆写不带返回值的 BlueprintEvent
- 覆写带返回值的 BlueprintEvent
- 覆写 AnimNotify
- 覆写 RepNotify
- 覆写 Input Events
三. 重要特性
3.1 覆写的实现
- Thunk Function 函数
- 在 4.23 引擎版本下的 Thunk Function 函数:
FNativeFuncPtr Func
- 在 4.19 引擎版本前,Thunk Function 函数指针的原型历史:
- Thunk 函数的调用
- UFunction 的执行大概率会走到
ProcessEvent
函数,并最终通过 Thunk Function 的调用实现(如果将 Thunk Function 指针替换为自定义的可调用 Lua 的函数,则可以实现覆写)
- Thunk 函数替换
- 一个 public 函数
- Thunk 函数替换不适合所有场景
- 对于在蓝图中定义且在蓝图中被调用的函数或 Events,则走
CallFunction
函数(不会走到ProcessEvent
函数),不通过 Thunk 函数的调用,引擎为所有非 native UFunction 定义的默认的 Thunk Function,被写死了,不管换成什么都没用
- 解决此问题的一些“野路子”:
- 由于
ProcessInternal
函数是脚本语言,可以对编译好的字节码进行解释执行,在覆写时,动态插入到字节码序列中 - 自定义新的 Opcode 注册
- Opcode 注入(引擎处理时,会检测 return、nothing)
- 返回值处理
- 非 const 引用参数、return 返回值
3.2 优化的思路
- 结构体访问优化
- 结构体创建
- 直观实现(伪代码)
- 在内存的操作上:两个 malloc、一个构造函数
- UnLua 实现(伪代码)
- 在内存的操作上:一个 malloc、一个构造函数
- 创建时节省一次内存分配,GC时节省一次内存释放
- Userdata 内存布局:为了满足引擎对一些数据结构的结构体边界对齐要求,使用 Padding 填充,缓存友好(不需要二级指针之类的常规处理),但 Padding 会造成一定的内存浪费,影响不大
- UFunction 调用优化
- 持久化参数缓存
- 第一次调用时就被分配好了,最后才被释放掉,(避免频繁的 malloc 和 free)
- 为 Native Local 函数返回值参数,预分配缓存(Local 对应 RPC 函数)
- 为 Native Local 函数提供快速调用路径
- 传参优化
- UFunction 带常量引用参数
- 直观实现(伪代码)
- UnLua实现(伪代码)
- Memcpy 浅拷贝,经过大量实践有正确性的保证,且对于复杂数据结构(如:含有大量元素的容器),浅拷贝的性能优势巨大
- 非常量引用参数优化
- UFunction 带非常量引用参数
- 和 C++ 类似的 Lua 调用方式
- 两次浅拷贝(传参和值返回各一次)
- 返回值参数优化
- UFunction返回非原子类型
- 直观实现:新创建 Userdata 并将其压入 Lua 栈顶
- UnLua 实现
- 先创建 Userdata 并将其作为参数传入函数
- 利用了传参优化
- 多次调用(例如循环)情况下,避免了大量的 Userdata 创建和 GC,性能优势明显
3.3 智能语法提示
- UnLua 插件中,内置了智能语法提示功能
- 符号信息(反射体系内)
- 导出单独的 UnLuaIntelliSense 模块
- 和 UHT 一同工作,在编译时就生成了所有反射体系内数据的符号信息,为智能语法提示信息提供基础,
- 符号信息位于ProjectDir/Plugins/UnLua/Intermediate/IntelliSense
- 符号信息(反射体系外)
- UnLuaIntelliSense Commandlet
- 符号信息位于ProjectDir/Plugins/UnLua/Intermediate/IntelliSense/StaticallyExports
- IDE 中的智能语法提示 API(Lua 语法提示插件:EmmyLua)
3.4 调试
- 调试工具:Debug Any Where
标签:罗谦,插件,函数,Thunk,覆写,UFunction,Lua,UnLua From: https://www.cnblogs.com/ZWJ-zwj/p/18342070