UE4熱更新:基於UnLua的Lua編程指南
UE4熱更新:基於UnLua的Lua編程指南
發表於 2020-03-24 10:50 | 更新於 2020-05-19 16:57 | 分類於 UE4 , Lua | 字數統計 4.2k
UE使用的是C++這種編譯型語言,在編譯之後就成了二進制,只有通過玩家重新安裝才能打到更新遊戲的目的。但是對於遊戲業務而言,對於需求調整和bug修復時間要求非常迫切,頻繁地讓玩家更新App是不能接受的,遊戲項目一般使用Lua作爲遊戲業務的腳本語言,是爲了把運行時不可變的C++代碼變成運行時可更新的Lua代碼。
UE官方沒有提供Lua的支持,但是騰訊開源了UnLua,在我當前的項目使用了,這兩天我梳理了一下UnLua的資料(主要是官方文檔、issus、宣講PPT),加上自己測試UnLua寫了一個小Demo的感悟,形成了本篇UE結合UnLua的編程指南,主要是總結使用UnLua來寫業務的一些基本方法和坑,方便查看,本篇文章會持續更新。
另外,Lua文件打包成Pak可以用我之前開源的工具:hxhb/HotPatcher,而且我基於UnLua自己修改了一個版本,添加了一些額外的優化,源碼集成了Luasocket/Luapanda/
lpeg
/Sproto
/Luacrypt
庫,可以直接使用LuaPanda調試,Github地址爲:hxhb/debugable-unlua.
參考資料:
UnLua注意事項
這些是UnLua官方倉庫裏我摘錄出來的一些可能有坑的地方。
- 並非所有的UFUNCTION都支持,只支持
BlueprintNativeEvent
/BlueprintImplementationEvent
/Replication Notify
/Animation Notify
/Input Event
。 - UnLua不支持多State,所以在PIE模式下運行多個Client會有問題。issus/78
- 不要在Lua裏訪問藍圖定義的結構體。issues/119 / issus/40
- UnLua裏使用
self.Super
不會遞歸向下遍歷繼承層次中的所有基類。issus/131 - 非dynamic delegate不支持。issus/128
- 不可以直接導出類的static成員,但可以爲它封裝一個static方法。issus/22
- 不支持綁定lua腳本到對象實例,NewObject指定的腳本是綁定到UCLASS的。issus/134
- Unlua裏使用
UE4.TArray
的下標規則是從1開始的,與引擎中TArray
以0開始不同.issues/41 - 注意導出給Lua的函數很多與藍圖中的名字不同,如藍圖中的
GetActorTransform
其實在lua裏要用GetTransform
,在lua裏使用的名字都是C++真實定義的函數名字,而不是DisplayName
的名字。
Lua代碼提示
導出符號
UnLua提供了對引擎內反射符號的導出,也可以自己靜態導出非反射類的符號,並且提供了一個Commandlet
類UUnLuaIntelliSenseCommandlet
導出這些符號。
下面簡單介紹一下Commandlet
的調用方法:
UE4Editor-cmd.exe PROJECT_PATH.uproject -run=COMMANDLET |
那麼UnLua的Commandlet用法爲:
D:\UnrealEngine\Epic\UE_4.23\Engine\Binaries\Win64\UE4Editor-cmd.exe C:\Users\imzlp\Documents\UnrealProjectSSD\MicroEnd_423\MicroEnd_423.uproject -run=UnLuaIntelliSense |
執行完畢之後,會生成UnLua/Interamate/IntelliSense
這個目錄,裏面有引擎導出到lua的符號。
VSCode中使用
在VSCode中安裝Emmylua插件,安裝之後把上一步生成的IntelliSense
目錄添加到vcode的工作區即可。
調用父類函數
在UEC++中,當我們重寫了一個父類的虛函數,可以通過調用Super::
來指定調用父類實現,但是在Lua中不同。
self.Super.ReceiveBeginPlay(self) |
在Lua使用self.Super
來調用父類的函數。
注意:UnLua裏的Super只是簡單模擬了“繼承”語義,在繼承多層的情況下會有問題,Super不會主動向下遍歷Super。“父類”不是Class()返回的表的元表,只設置在Super這個field上(元表在插件的c++代碼裏定義了,這個過程已經固化)。
對於基類的基類
的Super調用:
a.lua local a = Class() function a:test() end return a -------------------------------------- b.lua local b = Class("a") -------------------------------------- c.lua local c = Class("b") function a:test() c.Super.Super.test(self) end |
該問題摘錄於UnLua的issus:Class內的Super的使用問題
調用被覆寫的方法
注意:Lua和原來的類並不是繼承關係,而是依附關係,lua依賴於藍圖或者C++的類。Lua覆寫的類的函數,相當於給當前類的函數換了一個實現。
當在Lua中重寫了一個附屬類的UFUNCTION函數時,可以通過下列方法調用,有點類似於Super
但要寫成Overridden
:
function BP_Game_C:ReceiveBeginPlay() self.Overridden.ReceiveBeginPlay(self) end |
注意一定要傳self
進去,不然Unlua調用的時候執行的參數檢查會Crash,在UnLua中調用UE函數有點類似於拿到成員函數的原生指針,必須要手動傳this進去。
調用UE的C++函數
UnLua在編譯時可以開啓是否啓用UE的namespace(也就是UE的函數都需要加UE4前綴)。
調用方法爲:
UE4.UKismetSystemLibrary.PrintString(self,"HelloWorld") |
參數與C++調用的相匹配(具有默認參數的同樣可以不寫):
UKismetSystemLibrary::PrintString(this,TEXT("Hello")) |
覆寫多返回值的函數
藍圖
覆寫的lua代碼:
function LoadingMap_C:GetName(InString) UE4.UKismetSystemLibrary.PrintString(self,InString) return true,"helloworld" end |
C++
因爲C++的多返回值是通過傳遞引用參數進去實現的,所以在Lua中這個不太一樣。
如下面的C++函數:
// .h UFUNCTION(BlueprintCallable, Category = "GameCore|Flib|GameFrameworkStatics", meta = (CallableWithoutWorldContext, WorldContext = "WorldContextObject")) static bool LuaGetMultiReturnExample(UObject* WorldContextObject, FString& OutString, int32& OutInt, UObject*& OutGameInstance); // .cpp bool UFlibGameFrameworkStatics::LuaGetMultiReturnExample(UObject* WorldContextObject, FString& OutString, int32& OutInt, UObject*& OutGameInstance) { bool bStatus = false; if (WorldContextObject) { OutString = TEXT("HelloWorld"); OutInt = 1111; OutGameInstance = UGameplayStatics::GetGameInstance(WorldContextObject); bStatus = true; } return bStatus; } |
這個函數接收WorldContextObject
的參數,並接收FString
/int32
/UObject*
這三個類型的引用類型,並返回一個bool,那麼這個函數該怎麼在lua中接收這些參數值的?
local ret1,ret2,ret3,ret4 = UE4.UFlibGameFrameworkStatics.LuaGetMultiReturnExample(self,nil,nil,nil) |
這種local ret1,ret2=func()
的這種寫法是Lua裏的規則,可以看Programming in Lua,4th的第六章。
注意:接收引用參數的返回值和真正函數返回值的順序是:ret1,ret2,ret3都是引用參數,最終函數的返回bool是最後一個ret4。
非const引用參數作爲返回值需要注意的問題
注意:當調用UFUNCTION函數時,非const引用參數可以忽略,但是非UFUNCTION而是靜態導出的函數則不行,因爲UnLua對靜態導出的函數有參數個數檢查。
而且,使用引用作爲返回參數的的使用方式需要考慮到下面兩種情況:
- 非const引用作爲純輸出
void GetPlayerBaseInfo(int32 &Level, float &Health, FString &Name) { Level = 7; Health = 77; Name = "Marcus"; } |
這種情況下返回值和傳入值是沒有任何關係的。在lua中可以這麼使用:
local level,heath,name = self:GetPlayerBaseInfo(0,0,""); |
- 非const引用參數既作爲輸入又作爲輸出
void GetPlayerBaseInfo(int32 &Level, float &Health, FString &Name) { Level += 7; Health += 77; Name += "Marcus"; } |
在這種情況下,返回值和輸入是有直接關係的,所以不能像情況1中那樣使用:
local level,heath,name level,heath,name = self:GetPlayerBaseInfo(level,heath,name); |
在這種情況下,在lua裏調用傳入的參數和返回的參數是都必須要傳遞和接收的,這樣纔會有正常的行爲,如果不接收返回值:
local level,heath,name self:GetPlayerBaseInfo(level,heath,name); |
level,heath,name
這些傳進去的對象的值並不會像C++中傳遞的引用那樣值會改變。
所以函數怎麼調用還是要看函數裏是怎麼寫的。
這個在UnLua的issus裏有提到:issus/25
檢測是否繼承接口
C++裏使用UKismetSystemLibrary::DoesImplementInterface
,Lua裏也一樣,不過區別是接口類型的傳入:
local CubeClass = UE4.UClass.Load("/Game/Cube_Blueprint.Cube_Blueprint") local World = self:GetWorld() local Cube_Ins = World:SpawnActor(CubeClass,self:GetTransform(),UE4.ESpawnActorCollisionHandlingMethod.AlwaysSpawn, self, self) if UE4.UKismetSystemLibrary.DoesImplementInterface(Cube_Ins,UE4.UMyInterface) then print(fmt("{1} inheritanced {2}",CubeClass,UE4.UMyInterface)) end |
要使用UE4.U*Interface
這種形式。
獲取TScriptInterface接口對象
當我們在C++中獲得一個接口時,獲得的類型是TScriptInterface<>
類型,本來以爲還要自己導出TScriptInterface
纔可以拿到接口,但是發現並不是這樣,UnLua裏可以直接拿到TScriptInterface<>
就像普通的UObject對象:
如下面這樣一個函數:
UFUNCTION(BlueprintCallable, Category = "GameCore|Flib|GameFrameworkStatics", meta = (CallableWithoutWorldContext,WorldContext="WorldContextObject")) static TScriptInterface<IINetGameInstance> GetNetGameInstance(UObject* WorldContextObject); |
在Lua裏調用:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self); |
這個得到的類型在C++裏是TScriptInterface<>
但在Lua裏得到的就是該接口的UObject對象。
調用成員函數
上面講了怎麼得到TScriptInterface
的接口(在lua裏得到的其實就是該接口的UObject),那麼怎麼通過它來調用接口的函數呢?有三種方法。
// This class does not need to be modified. UINTERFACE(BlueprintType,MinimalAPI) class UINetGameInstance : public UIBaseEntityInterface { GENERATED_BODY() }; class GWORLD_API IINetGameInstance { GENERATED_BODY() public: UFUNCTION(Category = "GameCore|GamePlayFramework") virtual bool FindSubsystem(const FString& InSysName,TScriptInterface<IISubsystem>& OutSubsystem)=0; }; |
通過對象調用函數
- 可以通過拿到實現接口的對象然後通過該對象調用函數(使用lua的
:
操作符):
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self); local findRet1,findRet2 = GameInstance:FindSubsystem("TouchController") |
指定類和函數名調用
- 也可以直接通過指定實現該接口的類型名字來調用,就像函數指針,需要把調用該函數的對象傳遞進去:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self); local findRet1,findRet2 = UE4.UNetGameInstance.FindSubsystem(GameInstance,"TouchController") |
指定接口的類和函數名調用
- 以及通過接口的類型調用(因爲接口也是UClass,接口中的函數也都標記了UFUNCTION):
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self); local findRet1,findRet2 = UE4.UINetGameInstance.FindSubsystem(GameInstance,"TouchController") |
獲取UClass
lua中獲取uclass可以用於創建對象,其方法爲:
local uclass = UE4.UClass.Load("/Game/Core/Blueprints/AI/BP_AICharacter.BP_AICharacter_C") |
Load的路徑是該類的PackagePath
。
如,在lua中加載UMG的類然後創建並添加至視口:
function LoadingMap_C:ReceiveBeginPlay() local UMG_C = UE4.UClass.Load("/Game/Test/BPUI_TestMain.BPUI_TestMain_C") local UMG_TestMain_Ins = UE4.UWidgetBlueprintLibrary.Create(self,UMG_C) UMG_TestMain_Ins:AddToViewport() end |
注意:UnLua官方的UE4.UClass.Load
實現是默認只能創建藍圖類的,具體實現看LuaLib_Class.cpp
中的UClass_Load
的實現。
可以修改一下支持C++的類:
int32 UClass_Load(lua_State *L) { int32 NumParams = lua_gettop(L); if (NumParams != 1) { UE_LOG(LogUnLua, Log, TEXT("%s: Invalid parameters!"), ANSI_TO_TCHAR(__FUNCTION__)); return 0; } const char *ClassName = lua_tostring(L, 1); if (!ClassName) { UE_LOG(LogUnLua, Log, TEXT("%s: Invalid class name!"), ANSI_TO_TCHAR(__FUNCTION__)); return 0; } FString ClassPath(ClassName); bool IsCppClass = ClassPath.StartsWith(TEXT("/Script")); if (!IsCppClass) { const TCHAR *Suffix = TEXT("_C"); int32 Index = INDEX_NONE; ClassPath.FindChar(TCHAR('.'), Index); if (Index == INDEX_NONE) { ClassPath.FindLastChar(TCHAR('/'), Index); if (Index != INDEX_NONE) { const FString Name = ClassPath.Mid(Index + 1); ClassPath += TCHAR('.'); ClassPath += Name; ClassPath.AppendChars(Suffix, 2); } } else { if (ClassPath.Right(2) != TEXT("_C")) { ClassPath.AppendChars(TEXT("_C"), 2); } } } FClassDesc *ClassDesc = RegisterClass(L, TCHAR_TO_ANSI(*ClassPath)); if (ClassDesc && ClassDesc->AsClass()) { UnLua::PushUObject(L, ClassDesc->AsClass()); } else { lua_pushnil(L); } return 1; } |
C++類的路徑爲:
/Script/GWorld.GWorldGameEngine |
以/Script
開頭,其後是該C++類所屬的模塊名,.
之後的是類名(不包含U
/A
之類的開頭)。
LoadObject
把資源加載到內存:
local Object = LoadObject("/Game/Core/Blueprints/AI/BT_Enemy") |
比如加載某個材質球給模型:
function Cube3_Blueprint_C:ReceiveBeginPlay() local MatIns = LoadObject("/Game/TEST/Cube_Mat_Ins") UE4.UPrimitiveComponent.SetMaterial(self.StaticMeshComponent,0,MatIns) end |
注:LuaObject在UnLua裏對應的是LoadObject<Object>
:
int32 UObject_Load(lua_State *L) { // ... UObject *Object = LoadObject<UObject>(nullptr, *ObjectPath); // ... } |
創建對象
注意:Lua中創建的對象使用動態綁定是綁定到該類的UCLASS上,並不是綁定到該New出來的實例。
UnLua中對NewObject
處理的代碼爲Global_NewObject
:
FScopedLuaDynamicBinding Binding(L, Class, ANSI_TO_TCHAR(ModuleName), TableRef); UObject *Object = StaticConstructObject_Internal(Class, Outer, Name); |
SpawnActor
Lua中SpawnActor以及動態綁定:
local World = self:GetWorld() local WeaponClass = UE4.UClass.Load("/Game/Core/Blueprints/Weapon/BP_DefaultWeapon.BP_DefaultWeapon") local NewWeapon = World:SpawnActor(WeaponClass, self:GetTransform(), UE4.ESpawnActorCollisionHandlingMethod.AlwaysSpawn, self, self, "Weapon.BP_DefaultWeapon_C") |
NewObject
lua中調用NewObject以及動態綁定:
local ProxyObj = NewObject(ObjClass, self, nil, "Objects.ProxyObject") |
UnLua中的NewObject
可以接收四個參數,依次是:創建的UClass、Outer、Name,以及動態綁定的Lua腳本。
Component
注意:原版UnLua只可以加載BP的UClass,這個需要做改動(修改
LuaLib_Class.cpp
中的UClass_Load
函數,檢測傳入是C++類時把添加_C
後綴的邏輯去掉), 而且創建Component時也需要對其調用OnComponentCreated
和RegisterComponent
,這兩個函數不是UFUNCTION,需要手動導出。
導出ActorComponent中OnComponentCreated
和RegisterComponent
等函數:
// Export Actor Component BEGIN_EXPORT_REFLECTED_CLASS(UActorComponent) ADD_FUNCTION(RegisterComponent) ADD_FUNCTION(OnComponentCreated) ADD_FUNCTION(UnregisterComponent) ADD_CONST_FUNCTION_EX("IsRegistered",bool, IsRegistered) ADD_CONST_FUNCTION_EX("HasBeenCreated",bool, HasBeenCreated) END_EXPORT_CLASS() IMPLEMENT_EXPORTED_CLASS(UActorComponent) |
則使用時與C++的使用方法一致:
local StaticMeshClass = UE4.UClass.Load("/Script/Engine.StaticMeshComponent") local MeshObject = LoadObject("/Engine/VREditor/LaserPointer/CursorPointer") local StaticMeshComponent= NewObject(StaticMeshClass,self,"StaticMesh") StaticMeshComponent:SetStaticMesh(MeshObject) StaticMeshComponent:RegisterComponent() StaticMeshComponent:OnComponentCreated() self:ReceiveStaticMeshComponent(StaticMeshComponent) -- StaticMeshComponent:K2_AttachToComponent(self.StaticMeshComponent,"",EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget) UE4.UStaticMeshComponent.K2_AttachToComponent(StaticMeshComponent,self.StaticMeshComponent,"",EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget) |
UMG
創建UMG首先需要獲取到UI的UClass,然後使用UWidgetBlueprintLibrary::Create
來創建,與C++一致:
local UMG_C = UE4.UClass.Load("/Game/Test/BPUI_TestMain.BPUI_TestMain_C") local UMG_TestMain_Ins = UE4.UWidgetBlueprintLibrary.Create(self,UMG_C) UMG_TestMain_Ins:AddToViewport() |
綁定代理
動態多播代理
在C++代碼中寫了一個動態多播代理:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGameInstanceDyDlg, const FString&,InString); // in class UPROPERTY() FGameInstanceDyDlg GameInstanceDyDlg; |
在lua中綁定,可以綁定到lua的函數:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self); GameInstance.GameInstanceDyMultiDlg:Add(self,LoadingMap_C.BindGameInstanceDyMultiDlg) -- test bind dynamic multicast delegate lua func function LoadingMap_C:BindGameInstanceDyMultiDlg(InString) UE4.UKismetSystemLibrary.PrintString(self,InString) end |
同樣也可以對該代理進行調用、清理、移除:
-- remove GameInstance.GameInstanceDyMultiDlg:Remove(self,LoadingMap_C.BindGameInstanceDyDlg) -- Clear GameInstance.GameInstanceDyMultiDlg:Clear() -- broadcast GameInstance.GameInstanceDyMultiDlg:Broadcast("66666666") |
動態代理
C++中有如下動態代理聲明:
DECLARE_DYNAMIC_DELEGATE_OneParam(FGameInstanceDyDlg,const FString&,InString); // in class UPROPERTY() FGameInstanceDyDlg GameInstanceDyDlg; |
在lua中綁定:
GameInstance.GameInstanceDyDlg:Bind(self,LoadingMap_C.BindGameInstanceDyMultiDlg) -- test bind dynamic delegate lua func function LoadingMap_C:BindGameInstanceDyDlg(InString) UE4.UKismetSystemLibrary.PrintString(self,InString) end |
動態代理支持Bind
/Unbind
/Execute
操作:
-- bind GameInstance.GameInstanceDyDlg:Bind(self,LoadingMap_C.BindGameInstanceDyMultiDlg) -- UnBind GameInstance.GameInstanceDyDlg:Unbind() -- Execute GameInstance.GameInstanceDyDlg:Execute("GameInstanceDyMultiDlg") |
不支持非Dynamic Delegate
因爲BindStatic
/BindRaw
/BindUFunction
這些都是模板函數,UnLua的靜態導出方案不支持將他們導出。
官方issus:如何正確靜態導出繼承自FScriptDelegate的普通委託
使用異步事件
Delay
如果想要使用類似Delay的函數:
/** * Perform a latent action with a delay (specified in seconds). Calling again while it is counting down will be ignored. * * @param WorldContextWorld 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 voidDelay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo ); |
在lua中可以通過協程(coroutine)
來實現:
function LoadingMap_C:DelayFunc(Induration) coroutine.resume(coroutine.create( function(WorldContectObject,duration) UE4.UKismetSystemLibrary.Delay(WorldContectObject,duration) UE4.UKismetSystemLibrary.PrintString(WorldContectObject,"Helloworld") end ), self,Induration) end |
就是通過coroutine.create
綁定上一個函數,可以直接在coroutine.create
裏寫,或者綁定上一個已有的函數:
function LoadingMap_C:DelayFunc(Induration) coroutine.resume(coroutine.create(LoadingMap_C.DoDelay),self,self,Induration) end function LoadingMap_C:DoDelay(WorldContectObject,duration) UE4.UKismetSystemLibrary.Delay(WorldContectObject,duration) UE4.UKismetSystemLibrary.PrintString(WorldContectObject,"Helloworld") end |
但是要注意一點:在綁定已有的lua函數時,傳遞的參數需要多一個self
,標識調用指定函數的調用者。
coroutine.resume(coroutine.create(LoadingMap_C.DoDelay),self,self,Induration) |
這裏的第一個self
,就是在通過self
調用LoadingMap_C.DoDelay
,後面的兩個參數才作爲傳遞給協程函數的參數。
調用代碼爲:
function LoadingMap_C:ReceiveBeginPlay() -- 5s後輸出HelloWorld self:DelayFunc(5.0) end |
注意:對於直接是UFUNCTION但是帶有
FLatentActionInfo
的函數可以直接使用上面的方法,但是對於UE封裝的異步節點,不是函數而是一個類的節點需要自己導出。
AsyncLoadPrimaryAsset
在C++裏可以使用UAsyncActionLoadPrimaryAsset::AsyncLoadPrimaryAsset
來異步加載資源:
/** * Load a primary asset into memory. The completed delegate will go off when the load succeeds or fails, you should cast the Loaded object to verify it is the correct type. * If LoadBundles is specified, those bundles are loaded along with the asset */ UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", Category = "AssetManager", AutoCreateRefTerm = "LoadBundles", WorldContext = "WorldContextObject")) static UAsyncActionLoadPrimaryAsset* AsyncLoadPrimaryAsset(UObject* WorldContextObject, FPrimaryAssetId PrimaryAsset, const TArray<FName>& LoadBundles); |
想要在Lua中使用的話需要把FPrimaryAssetId
這個結構導出:
#include "UnLuaEx.h" #include "LuaCore.h" #include "UObject/PrimaryAssetId.h" BEGIN_EXPORT_CLASS(FPrimaryAssetId,const FString&) ADD_FUNCTION_EX("ToString",FString, ToString) ADD_STATIC_FUNCTION_EX("FromString",FPrimaryAssetId, FromString,const FString&) ADD_FUNCTION_EX("IsValid", bool, IsValid) END_EXPORT_CLASS() IMPLEMENT_EXPORTED_CLASS(FPrimaryAssetId) |
然後就可以在Lua中使用了,如異步加載關卡資源,加載完成後打開:
function Cube_Blueprint_C:ReceiveBeginPlay() local Map = UE4.FPrimaryAssetId("Map:/Game/Test/LoadingMap") local AsyncActionLoadPrimaryAsset = UE4.UAsyncActionLoadPrimaryAsset.AsyncLoadPrimaryAsset(self,Map,nil) AsyncActionLoadPrimaryAsset.Completed:Add(self,Cube_Blueprint_C.ReceiveLoadedMap) AsyncActionLoadPrimaryAsset:Activate() end function Cube_Blueprint_C:ReceiveLoadedMap(Object) UE4.UGameplayStatics.OpenLevel(self,"/Game/Test/LoadingMap",true) end |
SetTimer
有些需求需要Timer循環調用,在C++裏可以使用UKismetSystemLibrary::K2_SetTimerDelegate
,在藍圖中對應的是SetTimerByEvent
,因爲它是UFUNCTION
的函數,所以在lua中也可以調用。
綁定代理和清理操作:
function LoadingMap_C:ReceiveBeginPlay() UpdateUILoopCount = 0; UpdateUITimerHandle = UE4.UKismetSystemLibrary.K2_SetTimerDelegate({self,LoadingMap_C.UpdateUI},0.3,true) end function LoadingMap_C:UpdateUI() if UpdateUILoopCount < 10 then print("HelloWorld") UpdateUILoopCount = UpdateUILoopCount + 1 else UE4.UKismetSystemLibrary.K2_ClearAndInvalidateTimerHandle(self,UpdateUITimerHandle) end end |
{self,FUNCTION}會創建出來一個Delegate,本來還以爲要自己導出一個創建Dynamic Delegate的方法,其實不用。
綁定UMG控件事件
如果要綁定類似UButton
的OnPressed
/OnClicked
/OnReleased
/OnHovered
/OnUnhovered
等事件,它們都是多播代理,所以要用Add
來添加:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClickedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonPressedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonReleasedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonHoverEvent); |
在lua中綁定:
function UMG_LoadingMap_C:Construct() self.ButtonItem.OnPressed:Add(self,UMG_LoadingMap_C.OnButtonItemPressed) end function UMG_LoadingMap_C:OnButtonItemPressed() print("On Button Item Pressed") end |
UE4.TArray
Unlua裏使用UE4.TArray
的下標規則是從1開始的,而不是與引擎中相同的0.issues/41
Cast
UnLua裏的類型轉換語法爲:
Obj:Cast(UE4.AActor) |
如:
local Character = Pawn:Cast(UE4.ABP_CharacterBase_C) |
lua使用藍圖結構
在bp裏創建一個藍圖結構:
在UnLua裏可以通過下列方式訪問:
local bp_struct_ins = UE4.FBPStruct() bp_struct_ins.string = "123456" bp_struct_ins.int32 = 12345 bp_struct_ins.float = 123.456 print(fmt("string: {},int32: {},float: {}",bp_struct_ins.string,bp_struct_ins.int32,bp_struct_ins.float)) |
可以像C++的結構那樣使用,需要注意的地方爲,需要在藍圖結構類型的名字前加F
,如上面的例子裏,在藍圖中的名字爲BPStruct
,在UnLua中訪問時則爲FBPStruct
.
未完待續,歡迎指出問題和交流意見。
掃描二維碼,分享此文章
本文標題:UE4熱更新:基於UnLua的Lua編程指南
文章作者:ZhaLiPeng
發佈時間:2020年03月24日 10時50分
更新時間:2020年05月19日 16時57分
本文字數:本文一共有4.2k字
原始鏈接:https://imzlp.me/posts/36659/
專欄鏈接:https://zhuanlan.zhihu.com/p/117236078
許可協議: CC BY-NC-SA 4.0
捐贈BTC:1CbUgUDkMdy6YRmjPJyq1hzfcpf2n36avm
轉載請保留原文鏈接及作者信息,謝謝!
您的捐贈將鼓勵我繼續創作!
- 文章目錄
- 站點概覽
- 1. UnLua注意事項
- 2. Lua代碼提示
- 3. 調用父類函數
- 4. 調用被覆寫的方法
- 5. 調用UE的C++函數
- 6. 覆寫多返回值的函數
- 7. 檢測是否繼承接口
- 8. 獲取TScriptInterface接口對象
- 9. 調用成員函數
- 10. 獲取UClass
- 11. LoadObject
- 12. 創建對象
- 13. 綁定代理
- 14. 使用異步事件
- 15. 綁定UMG控件事件
- 16. UE4.TArray
- 17. Cast
- 18. lua使用藍圖結構
4%
© 2014 - 2020 | 418.9k
Github Pages & Hexo Deploy