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

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