UnLua是Tencent针对UE4的脚本解决方案,其目标是使用lua脚本来代替蓝图来编写业务逻辑,提升开发效率和降低维护成本。目前已在github上开源。
主要功能特性
1. 可在lua中通过UE4反射系统零胶水代码访问UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM
local UMG = UClass.Load("/Game/Global/UI/UMG/MessagePanel/HelloWorldUMG") local Widget = UE4.UWidgetBlueprintLibrary.Create(_G.GetCurrentWorld(), UMG) Widget:AddToViewport(50000) Widget.centerText:SetText("It's a test. "..UE4.ENetRole.ROLE_Authority) -- 打印ENetRole.ROLE_Authority枚举值 输出:It's a test. 3 local PkgInfo = UE4.FFullyLoadedPackagesInfo() --定义一个FFullyLoadedPackagesInfo结构体 PkgInfo.FullyLoadType = UE4.EFullyLoadPackageType.FULLYLOAD_Game_PostLoadClass --将枚举FULLYLOAD_Game_PostLoadClass赋给PkgInfo.FullyLoadType Widget.centerText:SetText("It's a test. "..PkgInfo.FullyLoadType) -- 输出:It's a test. 2
2. 可使用unlua提供的宏来静态导出反射体系外的类、成员函数、成员变量、全局函数和枚举
3. 可在lua中重写(Override )c++中带有BlueprintImplementableEvent、BlueprintNativeEvent修饰的成员函数
带有BlueprintImplementableEvent、BlueprintNativeEvent修饰的C++成员函数
/** Event when play begins for this actor. */ UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName = "BeginPlay")) void ReceiveBeginPlay(); /** On Jumped */ UFUNCTION(BlueprintNativeEvent, Category=Character) void OnJumped(); /** Get Character Info */ UFUNCTION(BlueprintImplementableEvent, Category="Character") bool GetCharacterInfo(int32 &HP, FVector &Position, FString &Name);
在绑定的lua中重写
function BP_PlayerCharacter_C:ReceiveBeginPlay() print("ReceiveBeginPlay in Lua!") self.Overridden.ReceiveBeginPlay(self) --通过Overridden来调用被覆盖的c++函数 end function BP_PlayerCharacter_C:OnJumped() print("OnJumped in Lua!") self.Overridden.OnJumped(self) --通过Overridden来调用被覆盖的c++函数 end function BP_PlayerCharacter_C:GetCharacterInfo(HP, Position, Name) -- 方式① 效率高 Position.X = 128.0 Position.Y = 128.0 Position.Z = 0.0 return 99, nil, "Marcus", true end function BP_PlayerCharacter_C:GetCharacterInfo(HP, Position, Name) -- 方式② return 99, FVector(128.0, 128.0, 0.0), "Marcus", true end
4. 可在lua中重写(Override )蓝图中的所有事件(Event)和函数(Function)
5. 可在lua中重写(Override )Replication Notify函数
6. 可在lua中重写(Override )Animation Notify函数
7. 可在lua中重写(Override )Input Event事件
8. lua中高效调用UE4引擎UFUCNTION(持久化参数缓存、参数传递、非常量引用参数和返回值处理)
-- UFUNCTION() void GetPlayerBaseInfo(int32& Level, float& Health, FString& Name) const; local Level, Health, Name = self:GetPlayerBaseInfo() -- UFUNCTION() void GetHitResult(FHitResult &HitResult) const; -- 方式① local HitResult = FHitResult() self:GetHitResult(HitResult) -- 更高效 -- 方式② local HitResult = self:GetHitResult() -- 会产生拷贝,效率低 -- 方式③ local HitResult = FHitResult() local HitResultCopy = self:GetHitResult(HitResult) -- 更高效 等价于self:GetHitResult(HitResult); local HitResultCopy = HitResult;
9. lua中高效使用容器(TArray、TSet、TMap)
TArray用法
local arr = UE4.TArray(0); arr:Add(1); arr:Add(2); local arr2 = UE4.TArray(0); arr2:Add(3); arr:Append(arr2); local len = arr:Length(); // len3为3 --为保持类似table访问的一致性,TArray的Index从1开始 for i = 1, len do arr:Get(i) end local t = arr:ToTable(); local arr3 = UE4.TArray(FVector); local idx1 = arr3:AddUnique(UE4.FVector()); local idx2 = arr3:AddUnique(UE4.FVector()); -- AddUnique失败。因为数组arr3中已经有一个UE4.FVector() local idx3 = arr3:AddUnique(UE4.FVector(128.0, 128.0, 0.0)); local v1 = FVector() v1.X = 256.0 local idx4 = arr3:AddUnique(v1); local len2 = arr3:Length(); // len2为3 local arr4 = TArray(UE4.AActor) local tac = UE4.UClass.Load("/Game/MyActor"); local a1 = _G.GetCurrentWorld():SpawnActor(tac, UE4.FTransform(), UE4.ESpawnActorCollisionHandlingMethod.AlwaysSpawn); arr4:Add(a1); local len3 = arr4:Length(); // len3为1
TSet用法
local set = UE4.TSet(0); set:Add(1); set:Add(2); set:Add(3); local len1 = set:Length(); -- len1为3 local ret1 = set:Remove(1); -- ret1为true local ret2 = set:Remove(10); -- ret2为false local len2 = set:Length(); -- len2为2 local arr = set:ToArray(); local t = set:ToTable(); set:Clear(); local len3 = set:Length(); -- len3为0
TMap用法
local map = UE4.TMap("",0); map:Add("a", 1); map:Add("b", 2); map:Add("c", 3); local len1 = map:Length(); -- len1为3 local arr1 = map:Keys(); local t1 = arr1:ToTable(); -- {"a", "b", "c"} local arr2 = map:Values(); local t2 = arr2:ToTable(); -- {1, 2, 3} local t = map:ToTable(); -- {1, 2, 3} local ret1 = map:Remove("a"); -- ret1为true local ret2 = map:Remove("d"); -- ret2为false local len2 = map:Length(); -- len2为2 local ret3 = map:Find("b"); -- ret3为2 local val = map:FindRef("b"); -- val为2 map:Clear(); local len3 = map:Length(); -- len3为0
10. lua中支持delegate的使用
动态代理(DECLARE_DYNAMIC_DELEGATE*)
local FloatTrack = UE4.FTimelineFloatTrack(); FloatTrack.InterpFunc:Bind(self, BP_PlayerCharacter_C.OnZoomInOutUpdate); FloatTrack.InterpFunc:Execute(0.5); FloatTrack.InterpFunc:Unbind();
动态多播代理(DECLARE_DYNAMIC_MULTICAST_DELEGATE_*)
self.ExitButton.OnClicked:Add(self, UMG_Main_C.OnClicked_ExitButton)
self.ExitButton.OnClicked:Broadcast()
self.ExitButton.OnClicked:Remove(self, UMG_Main_C.OnClicked_ExitButton)
self.ExitButton.OnClicked:Clear()
11. lua中高效访问结构体(struct)
12. 支持修饰符BlueprintCallable、BlueprintPure、Exec的UFUNCTION的缺省参数
13. 支持自定义碰撞(collision)枚举
Project Settings的Engine -- Collision标签下
Lua代码如下:
-- 自定义EObjectTypeQuery枚举碰撞类型 local ObjectTypes = TArray(EObjectTypeQuery) ObjectTypes:Add(EObjectTypeQuery.Player) ObjectTypes:Add(EObjectTypeQuery.Enemy) ObjectTypes:Add(EObjectTypeQuery.Projectile) local bHit1 = UKismetSystemLibrary.LineTraceSingleForObjects(self, Start, End, ObjectTypes, false, nil, EDrawDebugTrace.None, HitResult, true) -- 自定义ETraceTypeQuery枚举碰撞类型 local bHit2 = UKismetSystemLibrary.LineTraceSingle(self, Start, End, ETraceTypeQuery.Weapon, false, nil, EDrawDebugTrace.None, HitResult, true)
14. 支持编辑器server/clients模拟
15. 支持从蓝图生成Lua template模板lua文件
16. 支持协程(coroutine)实现的UE4的Latent函数,同步写法完成异步逻辑
--//============================================================================= --// Latent Actions -- --/** -- * Perform a latent action with a delay (specified in seconds). Calling again while it is counting down will be ignored. -- * -- * @param WorldContext World context. -- * @param Duration length of delay (in seconds). -- * @param LatentInfo The latent action. -- */ --UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep")) --static void Delay(const UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo ); -- 在lua中通过协程来实现Delay延迟函数 coroutine.resume(coroutine.create(function(GameMode, Duration) UKismetSystemLibrary.Delay(GameMode, Duration) end), self, 5.0)
C++访问lua
UnLua::Call // 调用全局函数
lua_State* L = UnLua::GetState(); if (L != NULLULL) { float DeltaTime = 2.0f; UnLua::Call(L, "G6AppTick", DeltaTime); // 调用在G表中的G6AppTick函数 }
UnLua::CallTableFunc // 调用表中函数
lua_State* L = UnLua::GetState(); if (NULL != L) { UnLua::CallTableFunc(L, "G6AppMain", "ApplicationWillEnterBackground"); // G6AppMain在G表中,调用G6AppMain表key为ApplicationWillEnterBackground的函数 UnLua::FLuaRetValues LuaRetValues = UnLua::Call(L, "require", "MyGame.Services.MFVoice"); // lua所在目录:Content/Script/MyGame/Services/MFVoice.lua if (LuaRetValues.IsValid()) { UnLua::FLuaTable LuaTable(LuaRetValues[0]); UnLua::CallTableFunc(L, LuaTable, "OnAndroidPermissionRequestReturn"); } }
模块
模块名 | 模块类型 | 说明 |
UnLua | Runtime | UnLua核心逻辑 |
UnLuaDefaultParamCollector | Program | 手机所有ue4中修饰符BlueprintCallable、BlueprintPure、Exec的UFUNCTION的缺省参数 |
UnLuaEditor | Editor |
① 蓝图生成Lua Template文件 ② UUnLuaIntelliSense静态导出(供IDE智能感知使用) ③ PIE或Standalone游戏运行时,按Ctrl+L执行Hotfix |
UnLuaInfiniteLoopWatcher | EditorNoCommandlet |
检查Lua死循环(infinite loop detection) |
UnLuaIntelliSense | Program | 编译代码时,UHT会生成ue4引擎反射lua文件(供IDE智能感知使用) |
UnLuaIntelliSenseBP | Editor | 执行编辑器菜单Window - Update BP IntelliSense,可导出项目所有蓝图的lua文件(供IDE智能感知使用) |
导出IDE用的IntelliSense(智能感知)lua文件
在vscode中,可通过快捷键ctrl+shift+p,然后执行reload window来加载最新的IntelliSense(智能感知)lua文件
① 开启UnLuaIntelliSense.Build.cs中的宏ENABLE_INTELLISENSE=1,在编译代码时,UHT会生成ue4引擎反射lua文件
注:文件UnLuaIntermediateIntelliSenseUE4.lua中会记录所有ue4反射类型
② 手动执行commandlet命令来生成静态导出的lua文件 %EngineDir%EngineBinariesWin64UE4Editor.exe %GameDir%MyGame.uproject -run=UnLuaIntelliSense
lua文件保存在UnLuaIntermediateIntelliSenseStaticallyExports目录中
③ 在编辑器中执行菜单Window - Update BP IntelliSense,可导出项目所有蓝图IntelliSense(智能感知)用的lua文件
注:文件UnLuaIntermediateIntelliSenseUE4_BP.lua中会记录所有蓝图类型
蓝图静态绑定lua
(1)创建一个蓝图
(2)为蓝图添加UnLuaInterface接口
(3)给GetModuleName函数返回一个lua文件(相对于项目的Content/Script目录)
(4)在蓝图文件的工具栏上点击“Lua Template”按钮
创建BP_Game_C.lua文件(注:会生成在项目的Content/Script目录中)
(5)在Lua Template文件中编写业务代码
多个BP不能绑定同一个lua
UnLua的设计是使用LUA脚本扩展BP,一个BP是一个逻辑模块,析构的时候也会释放LUA的Module,所以不支持多个BP绑定同一个LUA文件。
这个需求可以通过建立基类BP,绑定LUA,其他BP从基类BP继承来实现。
C++中UObject静态绑定lua
UObject从IUnLuaInterface接口上派生,然后重写GetModule_Implementation虚函数返回lua代码路径
注:TPSGameMode.lua在项目的Content/Script目录中
为生成的AActor、UObject动态绑定lua
-- ProjClass为要创建的Actor的UClass -- Transform为UE4.FTransform类型,为在世界空间的Location、Rotation和Scale3D -- ESpawnActorCollisionHandlingMethod.AlwaysSpawn表示不管出生点周围有没有碰撞都出生 -- self为其Owner,可以为nil -- self.Instigator为其Instigator,可以为nil -- Weapon.BP_DefaultProjectile_C为要绑定的lua文件。注:全路径为项目的Content/Script/Weapon/BP_DefaultProjectile_C.lua local Proj = _G.GetCurrentWorld():SpawnActor(ProjClass, Transform, ESpawnActorCollisionHandlingMethod.AlwaysSpawn, self, self.Instigator, "Weapon.BP_DefaultProjectile_C") -- ObjClass为要创建的Object的UClass -- 第2个参数为Outer,可以为nil -- 第3个参数为Object的Name,可以为nil -- Objects.ProxyObject为要绑定的lua文件。注:全路径为项目的Content/Script/Objects/ProxyObject.lua local ProxyObj = NewObject(ObjClass, nil, nil, "Objects.ProxyObject")
参考