Home

Awesome

What's slua-unreal?

Slua-unreal is an unreal4 plugin, which allows you to use Lua language to develop game logic and hot fix your code. It gives you 3 ways to wrap your C++ interface to Lua, including reflection by blueprint, C++ template and static code generation. It also enables a two-way communication between blueprint and Lua. The advantage of Lua over C++ is that it requires no compilation for logic change, which significantly speeds up game development process.

slua-unreal 是什么

slua-unreal作为unreal引擎的插件,通过unreal自带蓝图接口的反射能力,结合libclang静态c++代码分析,自动化导出蓝图接口和静态c++接口,提供给lua语言,使得可以通过lua语言开发unreal游戏业务逻辑,方便游戏高效迭代开发,上线热更新,同时支持lua到c++双向,lua到蓝图双向调用,使用lua语言完美替代unreal的c++开发方式,修改业务逻辑不需要等待c++编译,大大提升开发速度。

目前该项目作为腾讯PUBG手游和潘多拉系统,该系统用于腾讯UE4游戏业务,帮助游戏业务构建周边系统、运营系统,上线质量稳定。

欢迎issue,pr,star,fork。

Showcases

icon_logologo

Slua-unreal is currently adopted in PUBG mobile game, one of Tencent’s most-played and highest-grossing mobile games, and Pandora system. This system is widely used in Tencent’s UE4 gaming business, helping the business build and maintain its commercial operation system.

What's new?

Release 1.3.3, fix a crash bug, more stable

Release 1.3.2, fix building error on UE 4.24

Release 1.3.1

Add a branch to support UE 4.25 or later

Features

slua-unreal 有什么功能

1 2

调试器支持

Debugger Support

我们开发了专门的vs code调试插件,支持真机调试,断点,查看变量值,代码智能提示等功能。调试器自动识别可以使用的UE UFunction蓝图函数和CppBinding导出的接口函数,不需要额外导出静态数据。

We developed a tool integrated with VsCode to support in-device debugging, breakpoint, variable inspection and code IntelliSense. Debugger will auto-generate data for UE UFunction and export C++ functions with CppBinding.

调试器支持 Debugger

使用方法简单范例

Usage at a glance

-- import blueprint class to use
local Button = import('Button');
local ButtonStyle = import('ButtonStyle');
local TextBlock = import('TextBlock');
local SluaTestCase=import('SluaTestCase');
-- call static function of uclass
SluaTestCase.StaticFunc()
-- create Button
local btn=Button();
local txt=TextBlock();
-- load panel of blueprint
local ui=slua.loadUI('/Game/Panel.Panel');
-- add to show
ui:AddToViewport(0);
-- find sub widget from the panel
local btn2=ui:FindWidget('Button1');
local index = 1
-- handle click event
btn2.OnClicked:Add(function() 
    index=index+1
    print('say helloworld',index) 
end);
-- handle text changed event
local edit=ui:FindWidget('TextBox_0');
local evt=edit.OnTextChanged:Add(function(txt) print('text changed',txt) end);

-- use FVector
local p = actor:K2_GetActorLocation()
local h = HitResult()
local v = FVector(math.sin(tt)*100,2,3)
local offset = FVector(0,math.cos(tt)*50,0)
-- support Operator
local ok,h=actor:K2_SetActorLocation(v+offset,true,h,true)
-- get referenced value
print("hit info",h)

在蓝图中调用lua

Calling Lua functions in blueprint

-- this function called by blueprint
function bpcall(a,b,c,d)
    print("call from bp",a,b,c,d)
end

Output is:

Slua: call from bp 1024 Hello World 3.1400001049042 UObject: 0x136486168

使用lua扩展Actor

Using Lua extend Actor

-- LuaActor.lua
local LuaActor={}

-- override event from blueprint
function LuaActor:BeginPlay()
    self.bCanEverTick = true
    print("LuaActor:BeginPlay")
end

function LuaActor:Tick(dt)
    print("LuaActor:Tick",self,dt)
    -- call LuaActor function
    local pos = self:K2_GetActorLocation()
    -- can pass self as Actor*
    local dist = self:GetHorizontalDistanceTo(self)
    print("LuaActor pos",pos,dist)
end

return Class(nil, nil, LuaActor)

Slua 2.0 特性介绍

Slua Override机制

-- LuaActor.lua
local LuaActor ={}

-- override event from blueprint
function LuaActor:ReceiveBeginPlay()
    self.bCanEverTick = true
    -- set bCanBeDamaged property in parent
    self.bCanBeDamaged = false
    print("actor:ReceiveBeginPlay")
end

-- override event from blueprint
function LuaActor:ReceiveEndPlay(reason)
    print("actor:ReceiveEndPlay")
end

return Class(nil, nil, LuaActor)

-- LuaBpActor.lua
local LuaBpActor = {}

-- override event from blueprint
function LuaBpActor:ReceiveBeginPlay()
    print("bpactor:ReceiveBeginPlay")
    -- call LuaActor super ReceiveBeginPlay
    LuaBpActor.__super.ReceiveBeginPlay(self)
    
    -- call blueprint super ReceiveBeginPlay
    self.Super:ReceiveBeginPlay()
end

local CLuaActor = require("LuaActor")
-- CLuaActor is base class
return Class(CLuaActor, nil, LuaBpActor)
-- LuaActor.lua
local LuaActor = 
{
    ServerRPC = {},     --C2S类RPC列表,类似UFUNCTION宏中的Server
    ClientRPC = {},     --S2C类RPC列表,类似UFUNCTION宏中的Client
    MulticastRPC = {},  --多播类RPC列表,类似UFUNCTION宏中的NetMulticast
}

local EPropertyClass = import("EPropertyClass")

LuaActor.ServerRPC.TestServerRPC = {
    -- 是否可靠RPC
    Reliable = true, 
    -- 定义参数列表
    Params = 
    { 
        EPropertyClass.Int, 
        EPropertyClass.Str, 
        EPropertyClass.bool, 
    }
}

LuaActor.ClientRPC.TestClientRPC = {
    -- 是否可靠RPC
    Reliable = true, 
    -- 定义参数列表
    Params = 
    { 
        EPropertyClass.Int, 
        EPropertyClass.Str, 
        EPropertyClass.bool, 
    }
}

LuaActor.MulticastRPC.TestMulticastRPC = {
    -- 是否可靠RPC
    Reliable = true, 
    -- 定义参数列表
    Params = 
    { 
        EPropertyClass.Int, 
        EPropertyClass.Str, 
        EPropertyClass.bool, 
    }
}

function LuaActor:TestServerRPC(ArgInt, ArgStr, ArgBool)
    
end

function LuaActor:TestClientRPC(ArgInt, ArgStr, ArgBool)
    
end

function LuaActor:TestMulticastRPC(ArgInt, ArgStr, ArgBool)
    
end

-- LuaActor.lua
local LuaActor ={}

function LuaActor:GetLifetimeReplicatedProps()
    local ELifetimeCondition = import("ELifetimeCondition")
    local FVectorType = import("Vector")
    return {
        { "Name", ELifetimeCondition.COND_InitialOnly, EPropertyClass.Str},
        { "HP", ELifetimeCondition.COND_OwnerOnly, EPropertyClass.Float},
        { "Position", ELifetimeCondition.COND_SimulatedOnly, FVectorType},
        { "TeamateNameList", ELifetimeCondition.COND_None, EPropertyClass.Array, EPropertyClass.Str},
        { "TeamatePositions", ELifetimeCondition.COND_None, EPropertyClass.Array, FVectorType},
    }
end

return Class(nil, nil, LuaActor)

-- MyActor.cpp
class MyActor : public Actor, public ILuaOverriderInterface
{
public:
    void CallDemoFunction()
    {
        CallLuaFunction<bool>(TEXT("IsTestEnable"), Arg1, Arg2);
    }
}

-- MyActor.lua

local MyActor ={}

function MyActor:IsTestEnable(Arg1, Args)
    return true
end

return Class(nil, nil, MyActor)

机制修改

--- 以修改一个Vector类型字段为例

--- 老代码
local Position = Actor.Position
Position.X = 1
Actor.Position = Position

--- 新代码
Actor.Position.X = 1

Slua交互性能优化

稳定性更加强大

支撑PUBG Mobile 线上业务开发,稳定性得到充分验证。

版本支持

性能

slua-unreal提供3种技术绑定lua接口,包括:

1)蓝图反射方法

2)静态代码生成

3)CppBinding(模板展开)

其中方法2和3运行原理上没差别,只是方法2是基于libclang自动化生成代码,方法3是手写代码,所以下面的统计上仅针对1和3来对比,可以认为2和3的性能是等价的。

100万次函数调用时间统计(秒),测试用例可以参考附带的TestPerf.lua文件。

测试机器 MacOSX,Unreal 4.18 dev版(非release,release应该会稍微快一点),CPU i7 4GHz。

Performance

unit in second, 1,000,000 calls to C++ interface from Lua, compared reflection and cppbinding, (both reflection and cppbinding are supported by slua-unreal).

Test on MacOSX, Unreal 4.18 develop building, CPU i7 4GHz, test cases can be found in TestPerf.lua

Without the time spent on gc alloc, the blueprint reflection-based approach is twice as fast as the one using static code generation, while CppBinding is an order of magnitude faster than reflection.

蓝图反射方法(Reflection)CppBinding方法(CppBinding)
空函数调用(empty function call)0.5415070.090571
函数返回int(function return int)0.5600520.090419
传入int函数返回int(function return int and pass int)0.5871150.097639
传入Fstring函数返回int(function return FString and pass int)0.9300420.223207

与slua unity版本相比,因为unreal的蓝图反射更高效,没有gc alloc开销,基于蓝图反射的方法的性能比slua unity的静态代码生成还要快1倍,而cppbinding则快一个数量级。

相关参考

slua-unreal依赖dot-clang做c++静态代码生成的工具稍后开源,目前常用FVector等常用类的静态生成代码已经附带。

使用帮助(Document in Chinese)

更完整的demo

QQ技术支持群:15647305,需要提交具体问题issue后申请入群,谢绝hr和非技术人员进入。

附:同品类性能对比

系统Win10 64位 CPU: AMD Ryzen 7 4700G with Radeon Graphics 3.60 GHZ

10万次/秒SluaUnLuaUnlua/Slua
TestStaticCall0.018940.026671.41
TestEmptyCall0.01830.033511.83
TestSetBoolCall0.025410.042061.66
TestGetBoolCall0.021340.048932.29
TestSetIntCall0.023810.042221.77
TestGetIntCall0.020850.042392.03
TestSetFloatCall0.022650.040311.78
TestGetFloatCall0.021670.037011.71
TestSetFStringCall0.049860.088011.77
TestGetFStringCall0.030320.071632.36
TestSetVectorCall0.033390.072082.16
TestGetVectorCall0.056190.08781.56
TestSetStructCall0.03760.079822.12
TestGetStructCall0.081370.088711.09
TestGetObjectCall0.030540.047091.54
TestSetIntVar0.013050.017451.34
TestGetIntVar0.013960.015531.11
TestSetStrVar0.025730.033271.29
TestGetStrVar0.017430.025551.47
TestSetBoolVar0.013620.015591.14
TestGetBoolVar0.014060.014351.02
TestSetFloatVar0.013360.015571.17
TestGetFloatVar0.013810.015851.15
TestSetVectorVar0.01940.017730.91
TestGetVectorVar0.011090.023582.13
TestSetStructVar0.019180.021971.15
TestGetStructVar0.010850.024082.22
TestGetObjectVar0.021110.023221.10
TestAddArrayElement0.052160.072071.38
TestGetArrayElement0.041150.082812.01
TestAddSetElement0.080380.098141.22
TestGetSetElement0.02821
TestAddMapElement0.107570.166731.55
TestGetMapElement0.090390.142661.58
TestBPEmptyCall0.063350.075481.19
TestBPSetIntCall0.107590.132051.23
TestBPGetIntCall0.105750.133381.26
TestBPSetBoolCall0.109510.132011.21
TestBPGetBoolCall0.105720.131931.25
TestBPSetStringCall0.130690.180151.38
TestBPGetStringCall0.120130.159761.33
TestBPSetFloatCall0.106260.129821.22
TestBPGetFloatCall0.104860.12911.23
TestBPSetVectorCall0.12040.164781.37
TestBPGetVectorCall0.171940.180611.05
TestBPSetStructCall0.124530.172911.39
TestBPGetStructCall0.175260.182161.04
TestBPSetObjectCall0.111120.14451.30
TestBPGetObjectCall0.119790.146631.22
TestSetBPIntVar0.012880.016961.32
TestGetBPIntVar0.014320.016541.16
TestSetBPStrVar0.024810.034471.39
TestGetBPStrVar0.017010.025151.48
TestSetBPBoolVar0.013180.018011.37
TestGetBPBoolVar0.01310.01631.24
TestSetBPFloatVar0.011940.016461.38
TestGetBPFloatVar0.013520.015541.15
TestSetBPVectorVar0.020130.017380.86
TestGetBPVectorVar0.011270.022892.03
TestSetBPStructVar0.020060.019870.99
TestGetBPStructVar0.011540.022521.95
TestSetBPObjectVar0.0180.018981.05
TestGetBPObjectVar0.018850.022161.18
TestAddBPArrayElement0.049940.072661.45
TestGetBPArrayElement0.041170.089022.16
TestAddBPSetElement0.077190.101071.31
TestGetBPSetElement0.08785
TestAddBPMapElement0.106850.155371.45
TestGetBPMapElement0.086070.145641.69
TestOverrideSetIntVar0.012530.016911.35
TestOverrideGetIntVar0.013150.015261.16
TestOverrideSetStrVar0.025690.032611.27
TestOverrideGetStrVar0.016370.027551.68
TestOverrideSetBoolVar0.013570.017311.28
TestOverrideGetBoolVar0.013120.015981.22
TestOverrideSetFloatVar0.012980.01681.29
TestOverrideGetFloatVar0.013250.016131.22
TestOverrideSetVectorVar0.021410.017950.84
TestOverrideGetVectorVar0.01030.022972.23
TestOverrideSetStructVar0.019060.019021.00
TestOverrideGetStructVar0.011350.023132.04
TestOverrideSetObjectVar0.020020.020911.04
TestOverrideGetObjectVar0.018710.02261.21
TestOverrideAddArrayElement0.049590.070261.42
TestOverrideGetArrayElement0.040330.086862.15
TestOverrideAddSetElement0.076040.099881.31
TestOverrideGetSetElement0.08807
TestOverrideAddMapElement0.10350.159051.54
TestOverrideGetMapElement0.085640.144751.69
TestOverrideReceiveBeginPlay0.001350.0395629.30
TestOverrideTestFunc0.013130.096337.34
TestIndexStaticCall0.001250.005354.28
TestIndexEmptyCall0.001260.005594.44
TestIndexSetBoolCall0.001340.00584.33
TestIndexGetBoolCall0.001420.005724.03
TestIndexOverrideTestFunc0.001090.000690.63