UE4熱更新:基於UnLua的Lua編程指南

UE4熱更新:基於UnLua的Lua編程指南

https://imzlp.me/posts/36659/

https://imzlp.me/posts/36659/

Z's Blog

 

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提供了對引擎內反射符號的導出,也可以自己靜態導出非反射類的符號,並且提供了一個CommandletUUnLuaIntelliSenseCommandlet導出這些符號。

下面簡單介紹一下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對靜態導出的函數有參數個數檢查。

而且,使用引用作爲返回參數的的使用方式需要考慮到下面兩種情況:

  1. 非const引用作爲純輸出
void GetPlayerBaseInfo(int32 &Level, float &Health, FString &Name)
{
    Level = 7;
    Health = 77;
    Name = "Marcus";
}

這種情況下返回值和傳入值是沒有任何關係的。在lua中可以這麼使用:

local level,heath,name = self:GetPlayerBaseInfo(0,0,"");
  1. 非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;
};

通過對象調用函數

  1. 可以通過拿到實現接口的對象然後通過該對象調用函數(使用lua的:操作符):
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
local findRet1,findRet2 = GameInstance:FindSubsystem("TouchController")

指定類和函數名調用

  1. 也可以直接通過指定實現該接口的類型名字來調用,就像函數指針,需要把調用該函數的對象傳遞進去:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
local findRet1,findRet2 = UE4.UNetGameInstance.FindSubsystem(GameInstance,"TouchController")

指定接口的類和函數名調用

  1. 以及通過接口的類型調用(因爲接口也是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時也需要對其調用OnComponentCreatedRegisterComponent,這兩個函數不是UFUNCTION,需要手動導出。

導出ActorComponent中OnComponentCreatedRegisterComponent等函數:

// 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控件事件

如果要綁定類似UButtonOnPressed/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
轉載請保留原文鏈接及作者信息,謝謝!

您的捐贈將鼓勵我繼續創作!

# UE4 # 熱更新 # Lua # UnLua

UEC++與標準C++的區別與聯繫

ModularFeature:爲UE4集成ZSTD壓縮算法

 

選擇語言​▼

  • 文章目錄
  • 站點概覽

 

 

  1. 1. UnLua注意事項
  2. 2. Lua代碼提示
    1. 2.1. 導出符號
    2. 2.2. VSCode中使用
  3. 3. 調用父類函數
  4. 4. 調用被覆寫的方法
  5. 5. 調用UE的C++函數
  6. 6. 覆寫多返回值的函數
    1. 6.1. 藍圖
    2. 6.2. C++
    3. 6.3. 非const引用參數作爲返回值需要注意的問題
  7. 7. 檢測是否繼承接口
  8. 8. 獲取TScriptInterface接口對象
  9. 9. 調用成員函數
    1. 9.1. 通過對象調用函數
    2. 9.2. 指定類和函數名調用
    3. 9.3. 指定接口的類和函數名調用
  10. 10. 獲取UClass
  11. 11. LoadObject
  12. 12. 創建對象
    1. 12.1. SpawnActor
    2. 12.2. NewObject
    3. 12.3. Component
    4. 12.4. UMG
  13. 13. 綁定代理
    1. 13.1. 動態多播代理
    2. 13.2. 動態代理
    3. 13.3. 不支持非Dynamic Delegate
  14. 14. 使用異步事件
    1. 14.1. Delay
    2. 14.2. AsyncLoadPrimaryAsset
    3. 14.3. SetTimer
  15. 15. 綁定UMG控件事件
  16. 16. UE4.TArray
  17. 17. Cast
  18. 18. lua使用藍圖結構

 

4%

 

© 2014 - 2020 | 418.9k

Github Pages & Hexo Deploy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章