Lua讲解
概述
UnLua是一个功能丰富并且高效的UE4脚本编程解决方案。开发者可以使用Lua来开发游戏逻辑,并且它允许我们利用Lua的热加载功能来更快地更新游戏逻辑。这份文档将会介绍UnLua的主要功能以及最基础的编程模式。
Lua和引擎的绑定
UnLua提供了一种简单的方法将Lua和游戏引擎相互绑定,包括静态绑定和动态绑定:
静态绑定
C++
你的UCLASS只需要实现接口IUnLuaInterface,并在函数**GetModuleName_Implementation()**中返回一个Lua文件路径。
蓝图
你的蓝图只需要实现接口UnLuaInterface,并在函数**GetModuleName()**中返回一个Lua文件路径。
动态绑定
动态绑定适用于运行时创建AActor和UObject
Actor
local Proj = World:SpawnActor(ProjClass, Transform, ESpawnActorCollisionHandlingMethod.AlwaysSpawn, self, self.Instigator, "Weapon.BP_DefaultProjectile_C") |
“Weapon.BP_DefaultProjectile_C”是一个Lua文件路径.
Object
local ProxyObj = NewObject(ObjClass, nil, nil, "Objects.ProxyObject") |
“Objects.ProxyObject”是一个Lua文件路径.
Lua文件路径
不论是静态绑定还是动态绑定都需要一个Lua文件路径。它是项目目录**’Content/Script’的相对**路径。
Lua调用引擎
UnLua提供了两种从Lua端访问引擎的方法:
- 使用反射系统动态导出;
- 在反射系统外部静态导出类、成员变量、成员函数、全局函数和枚举。
使用反射系统动态导出
利用反射系统进行动态导出,使代码简洁、直观,消除了大量的胶水代码。
访问UCLASS
local Widget = UWidgetBlueprintLibrary.Create(self, UClass.Load("/Game/Core/UI/UMG_Main")) |
UWidgetBlueprintLibrary 是一个UCLASS。类在Lua里的名称必须是 PrefixCPP + ClassName + **[_C
]**,例如: AActor (原生类), ABP_PlayerCharacter_C(蓝图类)
访问UFUNCTION
Widget:AddToViewport(0) |
AddToViewport 是 UUserWidget 的一个UFUNCTION。 0 是函数的参数。如果(被标记为 ‘BlueprintCallable’ 或 **’Exec’**的)UFUNCTION的参数拥有默认值,那在Lua代码中可以忽略参数0:
Widget:AddToViewport() |
输出值处理
输出值包括 非常量引用参数 and 返回值参数。这些输出值分为 原生类型(bool, integer, number, string) 和 非原生类型 (用户自定义数据)。
非常量引用参数
原生类型
Lua代码:
local Level, Health, Name = self:GetPlayerBaseInfo() |
非原生类型
他们在Lua中有两种调用的方法:
local HitResult = FHitResult() |
或
local HitResult = self:GetHitResult() |
第一种方法和C++极为相似,当调用多次(比如在循环中)时,它比第二种方法效率高得多。
返回值参数
原生类型
Lua代码:
local MeleeDamage = self:GetMeleeDamage() |
非原生类型
他在Lua中有三种调用方式:
local Location = self:GetCurrentLocation() |
或者
local Location = FVector() |
以及
local Location = FVector() |
第一种方法最为直观,事实上,当调用多次(比如在循环中)时,后两种方法要比第一种方法效率高得多。最后一种方法等价于:
local Location = FVector() |
潜在功能
潜在功能允许开发人员使用同步调用风格开发异步逻辑。一个典型的潜在功能例子是Delay:
你可以在Lua协程中调用潜在功能:
coroutine.resume( |
优化
UnLua对UFUNCTION的调用进行了以下几点优化:
- 持久的缓冲参数
- 优化局部函数调用
- 优化参数传递
- 优化输出值处理
访问USTRUCT
local Position = FVector() |
FVector 是一个USTRUCT。
访问UPROPERTY
local Position = FVector() |
X 是 FVector 的一个UPROPERTY。
代理
绑定
FloatTrack.InterpFunc:Bind(self, BP_PlayerCharacter_C.OnZoomInOutUpdate)
InterpFunc* 是 FTimelineFloatTrack 的代理, ‘Bind’ 为 InterpFunc 绑定了一个回调函数 (BP_PlayerCharacter_C.OnZoomInOutUpdate)。
解除绑定
FloatTrack.InterpFunc:Unbind()
InterpFunc* 是 FTimelineFloatTrack 的代理, ‘Unbind’ 解除了 InterpFunc 所绑定的回调函数。
执行
FloatTrack.InterpFunc:Execute(0.5)
InterpFunc* 是 FTimelineFloatTrack 的代理, ‘Execute’ 调用了绑定到InterpFunc对象上的函数。
多播代理
添加
self.ExitButton.OnClicked:Add(self, UMG_Main_C.OnClicked_ExitButton)
OnClicked* 是 UButton 的一个多播代理,**’Add’** 为 OnClicked 添加了一个回调(UMG_Main_C.OnClicked_ExitButton)。
移除
self.ExitButton.OnClicked:Remove(self, UMG_Main_C.OnClicked_ExitButton)
OnClicked* 是 UButton 的一个多播代理,**’Remove’** 在 OnClicked 上移除了一个回调(UMG_Main_C.OnClicked_ExitButton)。
清除
self.ExitButton.OnClicked:Clear()
OnClicked* 是 UButton 的一个多播代理,**’Clear’** 清除了在 OnClicked 上的所有回调。
广播
self.ExitButton.OnClicked:Broadcast()
OnClicked* 是 UButton 的一个多播代理,**’Broadcast’** 调用了所有绑定在 OnClicked 对象上的函数。
访问UENUM
Weapon:K2_AttachToComponent(Point, nil, EAttachmentRule.SnapToTarget, EAttachmentRule.SnapToTarget, EAttachmentRule.SnapToTarget) |
EAttachmentRule 是一个枚举,SnapToTarget 是一个 EAttachmentRule 类型的枚举值。
自定义碰撞枚举
EObjectTypeQuery
local ObjectTypes = TArray(EObjectTypeQuery)
ObjectTypes:Add(EObjectTypeQuery.Player)
ObjectTypes:Add(EObjectTypeQuery.Enemy)
ObjectTypes:Add(EObjectTypeQuery.Projectile)
local bHit = UKismetSystemLibrary.LineTraceSingleForObjects(self, Start, End, ObjectTypes, false, nil, EDrawDebugTrace.None, HitResult, true)EObjectTypeQuery.Player,EObjectTypeQuery.Enemy* 以及 EObjectTypeQuery.Projectile 都是自定义对象通道。
ETraceTypeQuery
local bHit = UKismetSystemLibrary.LineTraceSingle(self, Start, End, ETraceTypeQuery.Weapon, false, nil, EDrawDebugTrace.None, HitResult, true)
ETraceTypeQuery.Weapon* 是一个自定义跟踪通道。
手动导出库
出于灵活性与性能考虑,UnLua在引擎中手动导出了几个重要的类,包括以下(详见代码):
基础类
- UObject
- UClass
- UWorld
常见容器
- TArray
- TSet
- TMap
示例
local Indices = TArray(0) |
local Vertices = TArray(FVector) |
数学库
- FVector
- FVector2D
- FVector4
- FQuat
- FRotator
- FTransform
- FColor
- FLinearColor
- FIntPoint
- FIntVector
静态导出
UnLua提供了一个简单的解决方案,可以在反射系统外部静态地导出类、成员变量、成员函数、全局函数和枚举。
类
没有反射的类
BEGIN_EXPORT_CLASS(ClassType, ...)
或者
BEGIN_EXPORT_NAMED_CLASS(ClassName, ClassType, ...)
‘…’* 表示构造函数中参数的类型列表。
有反射的类
BEGIN_EXPORT_REFLECTED_CLASS(UObjectType)
或者
BEGIN_EXPORT_REFLECTED_CLASS(NonUObjectType, ...)
‘…’* 表示构造函数中参数的类型列表。
成员变量
ADD_PROPERTY(Property) |
或者(用于bitfield的bool属性)
ADD_BITFIELD_BOOL_PROPERTY(Property) |
成员函数
非静态成员函数
紧凑型风格
ADD_FUNCTION(Function)
或者
ADD_NAMED_FUNCTION(Name, Function)
完全型风格
ADD_FUNCTION_EX(Name, RetType, Function, ...)
或者
ADD_CONST_FUNCTION_EX(Name, RetType, Function, ...)
‘…’* 为参数类型列表。
静态成员函数
ADD_STATIC_FUNCTION(Function) |
或者
ADD_STATIC_FUNCTION_EX(Name, RetType, Function, ...) |
‘…’ 为参数类型列表。
示例
struct Vec3 |
全局函数
EXPORT_FUNCTION(RetType, Function, ...) |
或者
EXPORT_FUNCTION_EX(Name, RetType, Function, ...) |
‘…’ 为参数类型列表。
示例
void GetEngineVersion(int32 &MajorVer, int32 &MinorVer, int32 &PatchVer) |
枚举
- 普通枚举
enum EHand |
- 作用域为类的枚举
enum class EEye |
可选“UE4”名称空间
UnLua提供了一个选项来添加一个命名空间**’UE4’到引擎中的所有类和枚举。你可以在UnLua.Build.cs**中找到这个选项。
如果这个选项被开启,你的Lua代码应该是这样子:
local Position = UE4.FVector() |
引擎调用Lua
UnLua提供了一个类似蓝图的解决方案来跨越C++/Script的边界。它允许C++/Blueprint代码调用在Lua代码中定义的函数。
覆写蓝图事件
你可以在Lua代码里覆写所有的蓝图事件。蓝图事件包括:
- 被标记为 ‘BlueprintImplementableEvent’ 的UFUNCTION
- 被标记为 ‘BlueprintNativeEvent’ 的UFUNCTION
- 所有定义在蓝图里的事件或函数
示例(没有返回值的蓝图事件)
你可以在Lua中这样覆写它:
function BP_PlayerController_C:ReceiveBeginPlay() |
示例(有返回值的蓝图事件)
在Lua中有两种覆写它的方式:
function BP_PlayerCharacter_C:GetCharacterInfo(HP, Position, Name) |
或者
function BP_PlayerCharacter_C:GetCharacterInfo(HP, Position, Name) |
推荐第一种方式。
覆写动画通知
动画通知:
Lua代码:
function ABP_PlayerCharacter_C:AnimNotify_NotifyPhysics() |
Lua的函数名称必须是 ‘AnimNotify_’ + 通知名称。
覆写输入事件
操作输入
function BP_PlayerController_C:Aim_Pressed() |
Lua的函数名称必须是 操作输入名称 + **’_Pressed’ 或者 ‘_Released’**。
轴输入
function BP_PlayerController_C:Turn(AxisValue) |
Lua的函数名称必须是与 轴输入名称 一模一样。
键盘输入
function BP_PlayerController_C:P_Pressed() |
Lua的函数名称必须是 按键名称 + **’_Pressed’ 或者 ‘_Released’**。
其他输入
你也可以在Lua中覆写 Touch/AxisKey/VectorAxis/Gesture Inputs。
覆写复制通知
如果你正在开发专用/侦听服务器&客户端游戏,你可以在Lua代码中覆写复制通知:
function BP_PlayerCharacter_C:OnRep_Health(...) |
调用被覆写的函数
如果你在Lua中覆写了一个蓝图事件、动画通知或者复制通知,你仍然可以访问原始函数实现。
function BP_PlayerController_C:ReceiveBeginPlay() |
self.Overridden.ReceiveBeginPlay(self) 将会调用蓝图实现的 ‘ReceiveBeginPlay’。
在C++中调用Lua函数
UnLua还提供了两种通用方法来调用全局Lua函数和C++代码中全局Lua表中的函数。
全局函数
template <typename... T>
FLuaRetValues Call(lua_State *L, const char *FuncName, T&&... Args);全局表中的函数
template <typename... T>
FLuaRetValues CallTableFunc(lua_State *L, const char *TableName, const char *FuncName, T&&... Args);
其他
- Lua模板文件导出
你可以在蓝图中生成Lua的模板文件
模板文件: