c++和Unlua的交互
1. 背景
我们希望频繁变动的脚本是通过lua来完成的,因此我们引入了tx的UnLua(开源库)
我们会在c++中完成socket层面的处理,然后过滤出proto的字节流,这个字节流会从
c++传递给lua,然后在lua中完成proto消息的处理
2. 基本思路
1. 定义c++的静态函数
2. 在lua中访问该静态函数
2.1 (GlobalTable函数的交互)在c++定义暴露给UnLua的静态函数
UFUNCTION(BlueprintCallable, meta = (DisplayName = "CallLuaByGlobalTable", Category = "UnLua Tutorial"))
static void CallLuaByGlobalTable();
然后我们就可以在lua中调用该c++的函数了
在这里需要进修UnLua的入门教程
2.1.1 该函数的基本需求:
1. 我们在TestReceiveCpp.lua中定义了一个函数叫做:CallMe的函数
function TestReceiveCpp.CallMe(a, b)
local ret = a + b
local msg = string.format("c++ call a = %f,b = %f,ans = %f", a, b, ret)
Screen.Print(msg)
return
2. 我们希望在c++中去调用这个CallMe函数
2.1.2 该函数的基本实现思路
1. 拿到对应lua脚本的闭包,并用变量TRC保留该闭包的引用
UnLua::FLuaEnv Env;
const auto bSuccess = Env.DoString("TRC = require 'TestReceiveCpp'");
check(bSuccess);
显然,到这里,我们其实就是将定义的TestReceiveCpp的lua脚本加载到当前Env的内存当中
2. 调用该闭包的CallMe函数
const auto RetValues = UnLua::CallTableFunc(Env.GetMainState(), "TRC", "CallMe", 1.1f, 2.2f);
check(RetValues.Num() == 1);
3. 完整的函数
void AClient::CallLuaByGlobalTable()
{
UnLua::FLuaEnv Env;
const auto bSuccess = Env.DoString("TRC = require 'TestReceiveCpp'");
check(bSuccess);
const auto RetValues = UnLua::CallTableFunc(Env.GetMainState(), "TRC", "CallMe", 1.1f, 2.2f);
check(RetValues.Num() == 1);
}
2.2 (FLuaTable的交互)在c++定义暴露给UnLua的静态函数
2.2.1 该函数的基本需求
见2.1.1
2.2.2 该函数的实现思路
1. 利用UnLua自带的FLuaFuntion去加载require函数
const auto Require = UnLua::FLuaFunction(&Env, "_G", "require");
2. 利用requre函数获取TestReceiveCpp的闭包(加载到内存并返回引用)
const auto RetValues1 = Require.Call("TestReceiveCpp");
check(RetValues1.Num() == 2);
3. 获取函数Table
const auto RetValue = RetValues1[0];
const auto LuaTable = UnLua::FLuaTable(&Env, RetValue);
4. 调用CallMe函数
const auto RetValues2 = LuaTable.Call("CallMe", 3.3f, 4.4f);
check(RetValues2.Num() == 1);
5. 从lua函数的返回中解析目标值
const auto Msg = FString::Printf(TEXT("[C++]收到来自Lua的返回,结果=%f"), RetValues2[0].Value<float>());
PrintScreen(Msg);
3. 完整的c++代码
.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Client.generated.h"
UCLASS()
class LUAX_API AClient : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AClient();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintCallable, meta = (DisplayName = "CallLuaByGlobalTable",
Category = "UnLua Tutorial"))
static void CallLuaByGlobalTable();
UFUNCTION(BlueprintCallable, meta = (DisplayName = "CallLuaByFLuaTable", Category = "UnLua Tutorial"))
static void CallLuaByFLuaTable();
UFUNCTION(BlueprintCallable, meta = (DisplayName = "SetupCustomLoader", Category = "UnLua Tutorial"))
static void SetupCustomLoader(int Index);
UFUNCTION(BlueprintNativeEvent, Category = "Switch Functions")
void OnReceiveMsg(float val);
void OnReceiveMsg_Implementation(float val);
};
.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Client.h"
#include "UnLua.h"
#include "Kismet/KismetSystemLibrary.h"
//#include "UnLua/"
//#include "UnLua/"
// Sets default values
AClient::AClient()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
// Called every frame
void AClient::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
OnReceiveMsg(DeltaTime);
}
void AClient::OnReceiveMsg_Implementation(float val)
{
}
static void PrintScreen(const FString& Msg)
{
UKismetSystemLibrary::PrintString(nullptr, Msg, true, false, FLinearColor(0, 0.66, 1), 100);
}
void AClient::CallLuaByGlobalTable()
{
UnLua::FLuaEnv Env;
const auto bSuccess = Env.DoString("TRC = require 'TestReceiveCpp'");
check(bSuccess);
const auto RetValues = UnLua::CallTableFunc(Env.GetMainState(), "TRC", "CallMe", 1.1f, 2.2f);
check(RetValues.Num() == 1);
}
void AClient::CallLuaByFLuaTable()
{
PrintScreen(TEXT("[C++]CallLuaByFLuaTable 开始"));
UnLua::FLuaEnv Env;
const auto Require = UnLua::FLuaFunction(&Env, "_G", "require");
const auto RetValues1 = Require.Call("TestReceiveCpp");
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 结束"));
}
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;
}
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;
}
void AClient::SetupCustomLoader(int Index)
{
switch (Index)
{
case 0:
FUnLuaDelegates::CustomLoadLuaFile.Unbind();
break;
case 1:
FUnLuaDelegates::CustomLoadLuaFile.BindStatic(CustomLoader1);
break;
case 2:
FUnLuaDelegates::CustomLoadLuaFile.BindStatic(CustomLoader2);
break;
}
}
void AClient::BeginPlay()
{
Super::BeginPlay();
SetupCustomLoader(1);
}
4. 注意
4.1 添加Lua的搜索路径
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;
}
4.2 缺少Lua库
如果报以下错误:
这是因为缺少Lua库
可以在.Build.cs中
添加