UE4 Unlua的快速使用

目录

Unlua的使用

前言

整理一下Unlua的整个学习流程

下载Unlua插件

我们此处使用的是腾讯的Unlua插件,打开官方的Github链接,下载对应的版本
官方链接GitHub
Wiki文档

插件安装

把下载好的插件放在自己新建项目的Plugins文件夹下,编译启动

快速入门

萌新看的图文教学
老手看的文档

点击Create的时候,会根据填写的模块名字生成路径

语法汇总

模块导入

与路径对应即可

bash 复制代码
local Common = require "Core.Common"

多行字符串

官方静态方法调用

c++ 复制代码
UE.类名.静态方法名字

nil代表无效值空值未定义的值

蓝图方法调用

bash 复制代码
self:XXXFunction() 


输出结果:这是我的蓝图测试

重载蓝图中的方法

bash 复制代码
function M:XXXFunction())
end
c++ 复制代码
输出结果:M:TestFunction

主动调用被重载的蓝图方法

cpp 复制代码
self.Overridden.XXXFunction()
c++ 复制代码
输出结果:
这是我的蓝图测试
M:TestFunction

输入绑定

cpp 复制代码
function M:LeftMouseButton_Pressed()
end

实例:绑定按键并打印它的名字

cpp 复制代码
local M = UnLua.Class()

local PrintString = UE.UKismetSystemLibrary.PrintString

local function Print(text)
    PrintString(nil, text, true, false, UE.FLinearColor(1, 1, 1, 1), 100)
end

function M:ReceiveBeginPlay()
    local msg =
        [[
    来试试以下输入吧:
    字母、数字、小键盘、方向键、鼠标
    ]]
    Print(msg)
end

local function SetupKeyBindings()
    local key_names = 
    {
        -- 字母
        "A", "B", --[["C",]] "D", "E","F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", --[["V", ]] "W", "X", "Y", "Z",
        -- 数字
        "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine",
        -- 小键盘
        "NumPadOne", "NumPadTwo", "NumPadThree", "NumPadFour", "NumPadFive", "NumPadSix", "NumPadSeven", "NumPadEight", "NumPadNine",
        -- 方向键
        "Up", "Down", "Left", "Right",
        -- ProjectSettings -> Engine - Input -> Action Mappings
        "Fire", "Aim",
    }
    
    for _, key_name in ipairs(key_names) do
        M[key_name .. "_Pressed"] = function(self, key)
            Print(string.format("按下了%s", key.KeyName))
        end
    end
end

local function SetupAxisBindings()
    local axis_names = {
        "MoveForward", "MoveRight", "Turn", "LookUp", "LookUpRate", "TurnRate"
    }
    for _, axis_name in ipairs(axis_names) do
        M[axis_name] = function(self, value)
            if value ~= 0 then
                Print(string.format("%s(%f)", axis_name, value))
            end
        end
    end
end

SetupKeyBindings() -- 在require的时候会执行
SetupAxisBindings()

local BindKey = UnLua.Input.BindKey

BindKey(M, "C", "Pressed", function(self, Key)
    Print("按下了C")
end)

BindKey(M, "C", "Pressed", function(self, Key)
    Print("复制")
end, { Ctrl = true })

BindKey(M, "V", "Pressed", function(self, Key)
    Print("按下了V")
end)

BindKey(M, "V", "Pressed", function(self, Key)
    Print("粘贴")
end, { Ctrl = true })

return M

动态绑定Lua脚本

Actor:

cpp 复制代码
    local World = self:GetWorld()
    local SpawnClass = self.SpawnClass
    local Transform = self.SpawnPointActor:GetTransform()
    local AlwaysSpawn = UE.ESpawnActorCollisionHandlingMethod.AlwaysSpawn
    World:SpawnActor(SpawnClass, Transform, AlwaysSpawn, self, self, "XXX.XXX")

Object:

cpp 复制代码
local WidgetClass = self.WidgetClass
local img = NewObject(WidgetClass, self, nil, "XXX.XXX")
img:AddToViewport()
img:RandomPosition()

委托

cpp 复制代码
local FLinearColor = UE.FLinearColor

local M = UnLua.Class()

function M:Construct()
	-- Bind
    self.ClickMeButton.OnClicked:Add(self, self.OnButtonClicked)
    self.ClickMeCheckBox.OnCheckStateChanged:Add(self, self.OnCheckBoxToggled)
    -- SetTimerByEvent
    self.TimerHandle = UE.UKismetSystemLibrary.K2_SetTimerDelegate({ self, self.OnTimer }, 1, true)
end

function M:OnButtonClicked()
    local r = math.random()
    local g = math.random()
    local b = math.random()

    self.ClickMeButton:SetBackgroundColor(FLinearColor(r, g, b, 1))
end

function M:OnCheckBoxToggled(on)
    if on then
        self.CheckBoxText:SetText("已选中")
    else
        self.CheckBoxText:SetText("未选中")
    end
end

function M:OnTimer())
end

function M:Destruct()
    -- Unbind
    self.ClickMeButton.OnClicked:Remove(self, self.OnButtonClicked)
    self.ClickMeCheckBox.OnCheckStateChanged:Remove(self, self.OnCheckBoxToggled)
    -- ClearTimer
    UE.UKismetSystemLibrary.K2_ClearAndInvalidateTimerHandle(self, self.TimerHandle)
end

return M

容器使用

创建原生容器时通常需要指定参数类型,来确定容器内存放的数据类型

参数类型 示例 实际类型
boolean true Boolean
number 0 Interge
string "" String
table FVector Vector
userdata FVector(1,1,1) Vector

例:

cpp 复制代码
local array = TArray({ElementType})
local set = TSet({ElementType})
local map = TMap({KeyType}, {ValueType})
cpp 复制代码
local array = UE.TArray(0)
local set = UE.TSet(0)
local map = UE.TMap(0, true)

延迟与协程的使用

local M = UnLua.Class()

cpp 复制代码
local Latent = UE.UKismetSystemLibrary.XXXLatentFunction 

-- 定义一个协程函数
function M:StartCoroutine()
    local co = coroutine.create(function()
        print("开始等待...")
        Latent.Wait(2.0) 
        print("等待结束")
    end)
    coroutine.resume(co)
end

function M:ReceiveBeginPlay()
    self:StartCoroutine()
end

return M
cpp 复制代码
local M = UnLua.Class()

local PrintString = UE.UKismetSystemLibrary.PrintString

local function Print(text)
    PrintString(nil, text, true, false, UE.FLinearColor(1, 1, 1, 1), 100)
end

local function run(self, name)
   Print(string.format("协程%s:启动", name))
    for i = 1, 5 do
        UE.UKismetSystemLibrary.Delay(self, 1)
       Print(string.format("协程%s:%d", name, i))
    end
   Print(string.format("协程%s:结束", name))
end

function M:ReceiveBeginPlay()
    local msg = [[
    ------ ReceiveBeginPlay"
    ]]
    Print(msg)

    coroutine.resume(coroutine.create(run), self, "A")
    coroutine.resume(coroutine.create(run), self, "B")
end

return M

C++ 调用Lua

C++部分

cpp 复制代码
void UTutorialBlueprintFunctionLibrary::CallLuaByFLuaTable()
{
    PrintScreen(TEXT("[C++]CallLuaByFLuaTable 开始"));
    UnLua::FLuaEnv Env;

    const auto Require = UnLua::FLuaFunction(&Env, "_G", "require");
    const auto RetValues1 = Require.Call("Tutorials.08_CppCallLua");
    check(RetValues1.Num() == 2);

    const auto RetValue = RetValues1[0];
    const auto LuaTable = UnLua::FLuaTable(&Env, RetValue);
    const auto RetValues2 = LuaTable.Call("CallMe", 3.3f, 4.4f);
    check(RetValues2.Num() == 1);

    const auto Msg = FString::Printf(TEXT("[C++]收到来自Lua的返回,结果=%f"), RetValues2[0].Value<float>());
    PrintScreen(Msg);
    PrintScreen(TEXT("[C++]CallLuaByFLuaTable 结束"));
}

void UTutorialBlueprintFunctionLibrary::CallLuaByGlobalTable()
{
    PrintScreen(TEXT("[C++]CallLuaByGlobalTable 开始"));

    UnLua::FLuaEnv Env;
    const auto bSuccess = Env.DoString("G_08_CppCallLua = require 'Tutorials.08_CppCallLua'");
    check(bSuccess);

    const auto RetValues = UnLua::CallTableFunc(Env.GetMainState(), "G_08_CppCallLua", "CallMe", 1.1f, 2.2f);
    check(RetValues.Num() == 1);

    const auto Msg = FString::Printf(TEXT("[C++]收到来自Lua的返回,结果=%f"), RetValues[0].Value<float>());
    PrintScreen(Msg);
    PrintScreen(TEXT("[C++]CallLuaByGlobalTable 结束"));
}

static void PrintScreen(const FString& Msg)
{
    UKismetSystemLibrary::PrintString(nullptr, Msg, true, false, FLinearColor(0, 0.66, 1), 100);
}

lua 部分

cpp 复制代码
local M = UnLua.Class()

local PrintString = UE.UKismetSystemLibrary.PrintString

local function Print(text)
    PrintString(nil, text, true, false, UE.FLinearColor(1, 1, 1, 1), 100)
end

function M:ReceiveBeginPlay()
    local msg =
        [[
    ------ ReceiveBeginPlay
    ]]
    Print(msg)
    UE.UTutorialBlueprintFunctionLibrary.CallLuaByGlobalTable()
    Print("=================")
    UE.UTutorialBlueprintFunctionLibrary.CallLuaByFLuaTable()
end

function M.CallMe(a, b)
    local ret = a + b
    local msg = string.format("[Lua]收到来自C++的调用,a=%f b=%f,返回%f", a, b, ret)
    Print(msg)
    return ret
end

return M

静态导出自定义类型到Lua使用

C++.h

cpp 复制代码
#pragma once

#include "CoreMinimal.h"

struct FTutorialObject
{
protected:
	FString Name;

public:
	FTutorialObject();

	explicit FTutorialObject(const FString& Name)
		:Name(Name)
	{
	}

	FString GetTitle() const
	{
		return FString::Printf(TEXT("《%s》"), *Name);
	}

	FString ToString() const
	{
		return GetTitle();
	}
};

c++ cpp

cpp 复制代码
#include "TutorialObject.h"

#include "LuaCore.h"
#include "UnLua.h"
#include "UnLuaEx.h"

FTutorialObject::FTutorialObject()
{
}

static int32 FTutorialObject_New(lua_State* L)
{
	const auto NumParams = lua_gettop(L);
    if (NumParams != 2)
    {
        UNLUA_LOGERROR(L, LogUnLua, Log, TEXT("%s: Invalid parameters!"), ANSI_TO_TCHAR(__FUNCTION__));
        return 0;
    }

    const char* NameChars = lua_tostring(L, 2);
    if (!NameChars)
    {
        UE_LOG(LogUnLua, Log, TEXT("%s: Invalid tutorial name!"), ANSI_TO_TCHAR(__FUNCTION__));
        return 0;
    }

    const auto UserData = NewUserdataWithPadding(L, sizeof(FTutorialObject), "FTutorialObject");
	new(UserData) FTutorialObject(UTF8_TO_TCHAR(NameChars));
    return 1;
}

static const luaL_Reg FTutorialObjectLib[] =
{
    { "__call", FTutorialObject_New },
    { nullptr, nullptr }
};

BEGIN_EXPORT_CLASS(FTutorialObject)
ADD_FUNCTION(GetTitle)
ADD_LIB(FTutorialObjectLib)
END_EXPORT_CLASS()
IMPLEMENT_EXPORTED_CLASS(FTutorialObject)

lua部分

c 复制代码
local M = UnLua.Class()
local PrintString = UE.UKismetSystemLibrary.PrintString

local function Print(text)
    PrintString(nil, text, true, false, UE.FLinearColor(1, 1, 1, 1), 100)
end

function M:ReceiveBeginPlay()
    local msg =
        [[
    -- ReceiveBeginPlay
    ]]
    Print(msg)
    
    local tutorial = UE.FTutorialObject("教程")
    msg = string.format("tutorial -> %s\n\ntutorial:GetTitle() -> %s", tostring(tutorial), tutorial:GetTitle())
   Print(msg)
end

网络

使用 {函数名}RPC 可以覆盖蓝图中RPC函数的实现
使用 OnRep
{变量名} 可以覆盖蓝图中变量同步消息的处理

蓝图里面添加多人联机复制广播之类的

lua里写真正调用的方法

UMG资源释放

这部分直接附着官方内容

UMG:

cpp 复制代码
---@type ReleaseUMG_Root_C
local M = UnLua.Class()

function M:Construct()
    print("Root Construct")
    self.Button_AddNew.OnClicked:Add(self, self.OnAddNew)
    self.Button_ReleaseAll.OnClicked:Add(self, self.OnReleaseAll)
end

function M:OnAddNew()
    print("Root Add New")
    local widget_class = UE.UClass.Load("/Game/Tutorials/11_ReleaseUMG/ReleaseUMG_ItemParent.ReleaseUMG_ItemParent_C")
    local widget = NewObject(widget_class, self)
    self.VerticalBox_Panel:AddChildToVerticalBox(widget)
end

function M:OnReleaseAll()
    self:RemoveFromViewport()
end

function M:Destruct()
    print("Root Destruct")
    self:Release()
end

return M

测试部分:

cpp 复制代码
--[[
    说明:

    UMG对象的释放流程:
    1、调用self:Release(),解除LuaTable在C++侧的引用
    2、确保LuaTable在Lua侧没有其他引用,触发LuaGC
    3、C++侧收到UObject_Delete回调,解除UMG在C++侧的引用
    4、确保UMG在C++侧没有其他引用,触发UEGC

    小提示:

    使用控制台命令查看对象和类的引用情况:
    
    查看指定类的引用列表:Obj List Class=ReleaseUMG_Root_C
    查看指定对象的引用链:Obj Refs Name=ReleaseUMG_Root_C_0
]] --

local Screen = require "Tutorials.Screen"

local M = UnLua.Class()

local function print_intro()
    local msg =
        [[
使用以下按键进行一次强制垃圾回收:

C:强制 C++ GC
L:强制 Lua GC

------ 本示例来自 "Content/Script/Tutorials.11_ReleaseUMG.lua"
]]
    Screen.Print(msg)
end

function M:ReceiveBeginPlay()
    local widget_class = UE.UClass.Load("/Game/Tutorials/11_ReleaseUMG/ReleaseUMG_Root.ReleaseUMG_Root_C")
    local widget_root = NewObject(widget_class, self)
    widget_root:AddToViewport()

    print_intro()
end

function M:L_Pressed()
    collectgarbage("collect")
    Screen.Print('collectgarbage("collect")')
end

function M:C_Pressed()
    UE.UKismetSystemLibrary.CollectGarbage()
    Screen.Print("UKismetSystemLibrary.CollectGarbage()")
end

return M

自定义加载器

复制代码
说明:通过绑定 FUnLuaDelegates::CustomLoadLuaFile 可以实现自定义Lua加载器
方式1:查找路径固定,性能更好
方式2:通过package.path查找,更加灵活

lua部分:

cpp 复制代码
UE.UTutorialBlueprintFunctionLibrary.SetupCustomLoader(1)
Screen.Print(string.format("FromCustomLoader1:%s", require("Tutorials")))

package.loaded["Tutorials"] = nil

package.path = package.path .. ";./?/Index.lua"
UE.UTutorialBlueprintFunctionLibrary.SetupCustomLoader(2)
Screen.Print(string.format("FromCustomLoader2:%s", require("Tutorials")))

UE.UTutorialBlueprintFunctionLibrary.SetupCustomLoader(0)

C++部分

cpp 复制代码
bool CustomLoader1(UnLua::FLuaEnv& Env, const FString& RelativePath, TArray<uint8>& Data, FString& FullPath)
{
    const auto SlashedRelativePath = RelativePath.Replace(TEXT("."), TEXT("/"));
    FullPath = FString::Printf(TEXT("%s%s.lua"), *GLuaSrcFullPath, *SlashedRelativePath);

    if (FFileHelper::LoadFileToArray(Data, *FullPath, FILEREAD_Silent))
        return true;

    FullPath.ReplaceInline(TEXT(".lua"), TEXT("/Index.lua"));
    if (FFileHelper::LoadFileToArray(Data, *FullPath, FILEREAD_Silent))
        return true;

    return false;
}
cpp 复制代码
bool CustomLoader2(UnLua::FLuaEnv& Env, const FString& RelativePath, TArray<uint8>& Data, FString& FullPath)
{
    const auto SlashedRelativePath = RelativePath.Replace(TEXT("."), TEXT("/"));
    const auto L = Env.GetMainState();
    lua_getglobal(L, "package");
    lua_getfield(L, -1, "path");
    const char* Path = lua_tostring(L, -1);
    lua_pop(L, 2);
    if (!Path)
        return false;

    TArray<FString> Parts;
    FString(Path).ParseIntoArray(Parts, TEXT(";"), false);
    for (auto& Part : Parts)
    {
        Part.ReplaceInline(TEXT("?"), *SlashedRelativePath);
        FPaths::CollapseRelativeDirectories(Part);
        
        if (FPaths::IsRelative(Part))
            FullPath = FPaths::ConvertRelativePathToFull(GLuaSrcFullPath, Part);
        else
            FullPath = Part;

        if (FFileHelper::LoadFileToArray(Data, *FullPath, FILEREAD_Silent))
            return true;
    }

    return false;
}

动画通知

cpp 复制代码
local M = UnLua.Class()

function M:Received_Notify(MeshComp, Animation)
    return true
end

return M
相关推荐
_风华ts10 小时前
创建并使用AimOffset
ue5·动画·虚幻·虚幻引擎·aimoffset
AI视觉网奇12 小时前
ue 角色驱动衣服 绑定衣服
笔记·学习·ue5
LYOBOYI12317 小时前
vscode界面美化
ide·vscode·编辑器
浔川python社17 小时前
关于浔川代码编辑器 v5.0 网页版上线时间的通知
编辑器
浔川python社21 小时前
浔川代码编辑器 v5.0 上线时间公布
编辑器
山峰哥1 天前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
Doro再努力1 天前
Vim 快速上手实操手册:从入门到生产环境实战
linux·编辑器·vim
Doro再努力1 天前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
kun200310291 天前
如何用Obsidian+VSCode生成文案排版并发布到公众号
ide·vscode·编辑器
AI视觉网奇2 天前
3d数字人 ue blender 绑定衣服对齐 2026
学习·ue5