news 2026/5/5 23:22:37

c++调用lua的方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
c++调用lua的方法

UE C++ 调用 Lua 的方法详解(基于 UnLua)


一、前置知识:C++ 为什么能调用 Lua?

回顾一下 UnLua 的核心架构:

┌──────────────┐ ┌──────────────┐ │ C++ 代码 │ │ Lua 脚本 │ │ │ │ │ │ 调用 UFunction ──→ UnLua 中转 ──→ 执行 Lua 函数 │ │ │ │ │ │ ← 返回值 ────── Lua 栈传回 ←──── push 返回值 │ └──────────────┘ └──────────────┘

核心原理:UnLua 在绑定时,将UFunction的执行指针替换为自己的中转函数。当 C++ 调用该UFunction时,中转函数会将参数通过Lua 栈传给 Lua 虚拟机,执行 Lua 函数,再把返回值通过栈传回 C++。

C++ 侧完全不知道 Lua 的存在——它只是调用了一个UFunction,至于这个函数最终由蓝图执行还是 Lua 执行,C++ 不关心。


二、方式一:BlueprintImplementableEvent(最推荐)

2.1 原理

C++ 声明一个"蓝图可实现事件",不提供 C++ 实现。Lua 覆写这个函数后,C++ 调用时自动走到 Lua。

2.2 代码示例

C++ 声明(.h 文件):

UCLASS()classAMyCharacter:publicACharacter{GENERATED_BODY()public:// 声明一个蓝图可实现事件// C++ 不写实现体,由 Lua(或蓝图)来实现UFUNCTION(BlueprintImplementableEvent,Category="Skill")voidOnSkillActivated(int32 SkillId,floatCooldownTime);};

C++ 调用(.cpp 文件):

voidAMyCharacter::UseSkill(int32 SkillId){// 像调用普通函数一样调用,不需要任何 Lua 相关代码OnSkillActivated(SkillId,3.0f);}

Lua 覆写

-- Characters/MyCharacter.lualocalMyCharacter=UnLua.Class()functionMyCharacter:OnSkillActivated(SkillId,CooldownTime)print(string.format("技能 %d 激活,冷却 %.1f 秒",SkillId,CooldownTime))-- 在这里写 Lua 侧的逻辑self:StartCooldownTimer(CooldownTime)endreturnMyCharacter

2.3 调用链详解

C++ 调用 OnSkillActivated(1001, 3.0) │ ▼ UE 引擎执行 UFunction("OnSkillActivated") │ ▼ UFunction 的执行指针已被 UnLua 替换 → 进入 UnLua 中转函数 │ ├── 1. 通过 UObject 映射找到对应的 Lua Table ├── 2. 在 Table 中查找 "OnSkillActivated" 函数 ├── 3. 将 C++ 参数转换并 push 到 Lua 栈: │ push self (userdata) │ push 1001 (integer) │ push 3.0 (number) ├── 4. lua_pcall 调用 Lua 函数 └── 5. Lua 执行完毕,从栈上取返回值(如果有)传回 C++

2.4 带返回值的情况

// C++ 声明UFUNCTION(BlueprintImplementableEvent)floatCalculateDamageMultiplier(int32 SkillLevel);// C++ 使用返回值floatMultiplier=CalculateDamageMultiplier(5);floatFinalDamage=BaseDamage*Multiplier;
functionMyCharacter:CalculateDamageMultiplier(SkillLevel)return1.0+SkillLevel*0.2-- 等级5 → 2.0倍end

2.5 适用场景

  • 游戏逻辑事件:技能激活、角色死亡、任务完成等
  • UI 更新通知:血量变化、经验值变化等
  • 流程控制:关卡开始、关卡结束、存档等

三、方式二:BlueprintNativeEvent(有默认实现)

3.1 与方式一的区别

BlueprintImplementableEventBlueprintNativeEvent
C++ 默认实现_Implementation后缀)
Lua 未覆写时什么都不做执行 C++ 默认实现
Lua 覆写后执行 Lua执行 Lua(可选调用 C++ 默认实现)
适用场景纯 Lua 实现的逻辑有合理默认行为,Lua 可选择定制

3.2 代码示例

C++ 声明 + 默认实现

// .h 文件UFUNCTION(BlueprintNativeEvent,Category="Combat")floatCalculateDamage(floatBaseDamage,int32 ArmorLevel);// .cpp 文件 —— 注意函数名加 _Implementation 后缀floatAMyCharacter::CalculateDamage_Implementation(floatBaseDamage,int32 ArmorLevel){// C++ 默认实现:简单的护甲减伤floatArmorReduction=ArmorLevel*5.0f;returnFMath::Max(BaseDamage-ArmorReduction,0.0f);}

C++ 调用

voidAMyCharacter::ApplyDamage(floatBaseDamage){// 直接调用,不带 _Implementation 后缀floatFinalDamage=CalculateDamage(BaseDamage,CurrentArmorLevel);Health-=FinalDamage;}

Lua 覆写(可选)

functionMyCharacter:CalculateDamage(BaseDamage,ArmorLevel)-- 自定义伤害计算:百分比减伤localReduction=1.0-(ArmorLevel*0.05)returnBaseDamage*math.max(Reduction,0.1)end

3.3 在 Lua 中调用 C++ 默认实现

如果 Lua 想在自定义逻辑的基础上,也执行 C++ 的默认实现:

functionMyCharacter:CalculateDamage(BaseDamage,ArmorLevel)-- 先执行 C++ 默认实现localDefaultDamage=self.Overridden.CalculateDamage(self,BaseDamage,ArmorLevel)-- 在默认结果上做额外处理ifself:HasBuff("IronSkin")thenreturnDefaultDamage*0.5-- 铁皮 buff 再减半endreturnDefaultDamageend

self.Overridden.XXX是 UnLua 提供的语法,用于调用被覆写前的原始 C++ 实现。


四、方式三:直接操作 Lua C API(底层方式)

4.1 什么时候需要?

当你需要:

  • 调用 Lua 全局函数(不属于任何 UObject)
  • 调用指定 Lua 模块中的函数
  • 在非 UObject 上下文中与 Lua 交互

4.2 代码示例

C++ 侧

#include"lua.hpp"#include"UnLuaBase.h"voidAMyManager::CallLuaGlobalFunction(){// ========== 1. 获取 Lua 虚拟机 ==========lua_State*L=UnLua::GetState();if(!L)return;// ========== 2. 将要调用的函数压栈 ==========// lua_getglobal 会在 Lua 全局表中查找 "OnGameEvent" 函数// 找到后将其压入栈顶lua_getglobal(L,"OnGameEvent");// 检查栈顶是否是函数if(!lua_isfunction(L,-1)){lua_pop(L,1);// 不是函数,弹出并返回return;}// ========== 3. 压入参数 ==========// 参数按顺序压栈,先压的是第一个参数lua_pushstring(L,"PlayerDied");// 参数1: 事件名(string)lua_pushinteger(L,1001);// 参数2: 玩家ID(integer)lua_pushnumber(L,3.14);// 参数3: 某个数值(number)lua_pushboolean(L,true);// 参数4: 是否重生(boolean)// ========== 4. 调用函数 ==========// lua_pcall(L, 参数个数, 返回值个数, 错误处理函数索引)// 错误处理函数索引传 0 表示使用默认错误处理intResult=lua_pcall(L,4,1,0);if(Result!=LUA_OK){// 调用失败,栈顶是错误信息constchar*ErrorMsg=lua_tostring(L,-1);UE_LOG(LogTemp,Error,TEXT("Lua call failed: %s"),UTF8_TO_TCHAR(ErrorMsg));lua_pop(L,1);// 弹出错误信息return;}// ========== 5. 获取返回值 ==========// 调用成功后,返回值在栈顶if(lua_isinteger(L,-1)){intReturnValue=lua_tointeger(L,-1);UE_LOG(LogTemp,Log,TEXT("Lua returned: %d"),ReturnValue);}lua_pop(L,1);// 弹出返回值,清理栈}

Lua 侧

-- 全局函数functionOnGameEvent(EventName,PlayerId,Value,bRespawn)print(string.format("事件: %s, 玩家: %d, 值: %.2f, 重生: %s",EventName,PlayerId,Value,tostring(bRespawn)))return42-- 返回值end

4.3 Lua 栈操作图解

操作过程中 Lua 栈的变化: 初始状态: getglobal 后: push 参数后: pcall 后: ┌──────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ 空 │ │ OnGameEvent │ │ true │ │ 42 │ │ │ │ (function) │ │ 3.14 │ │ (返回值) │ │ │ │ │ │ 1001 │ └──────────┘ │ │ │ │ │ "PlayerDied" │ └──────┘ └──────────────┘ │ OnGameEvent │ 栈底 ──→ 栈顶 └──────────────┘ 栈底 ──→ 栈顶 lua_pcall 会消耗 函数和所有参数, 把返回值压到栈顶

4.4 调用 Lua 模块中的函数

如果目标函数不是全局函数,而是在某个模块的 table 中:

voidCallModuleFunction(lua_State*L){// 获取模块 tablelua_getglobal(L,"require");lua_pushstring(L,"GameLogic.EventSystem");lua_pcall(L,1,1,0);// require("GameLogic.EventSystem") → 栈顶是模块 table// 从 table 中获取函数lua_getfield(L,-1,"HandleEvent");// 栈顶是 HandleEvent 函数// 压入参数并调用lua_pushstring(L,"OnDamage");lua_pcall(L,1,0,0);lua_pop(L,1);// 弹出模块 table}

五、方式四:UnLua 辅助 API

UnLua 在底层 Lua C API 之上封装了一些便捷函数,减少手动操作 Lua 栈的工作:

5.1 调用绑定对象的 Lua 方法

#include"UnLuaBase.h"voidAMyActor::NotifyLuaSide(){lua_State*L=UnLua::GetState();if(!L)return;// 检查对象是否已绑定 Luaif(UnLua::IsUObjectBound(this)){// 调用绑定的 Lua 实例上的方法// 相当于 Lua 中的 self:OnCppNotify(100, "hello")UnLua::Call(L,this,"OnCppNotify",100,"hello");}}

5.2 调用 Lua table 的函数

这种是全局变量

// 调用指定模块中的函数UnLua::CallTableFunc(L,"Utils.MathHelper","Clamp",Value,MinVal,MaxVal);

六、各方式对比总结

推荐程度(高→低): BlueprintImplementableEvent ★★★★★ 零耦合,最规范 BlueprintNativeEvent ★★★★★ 有默认实现,灵活 UnLua 辅助 API ★★★☆☆ 特殊场景,中等耦合 Lua C API (lua_pcall) ★★☆☆☆ 底层操作,高耦合
维度BlueprintImplementableEventBlueprintNativeEventUnLua APILua C API
耦合度零(不知道 Lua 存在)中(依赖 UnLua)高(直接操作栈)
类型安全有(UE 反射)有(UE 反射)
有返回值
需要 UObject
可热更新
错误处理UE 自动处理UE 自动处理UnLua 处理手动处理
适用场景游戏逻辑接口有默认行为的接口特殊调用需求全局函数/工具调用

七、最佳实践

7.1 设计原则

C++ 负责: Lua 负责: ├── 引擎底层、性能敏感逻辑 ├── 游戏玩法逻辑 ├── 定义接口(UFUNCTION 声明) ├── 实现接口(覆写 BlueprintEvent) ├── 基础框架和系统 ├── 运营活动、可热更内容 └── 调用接口(不关心谁实现) └── UI 流程、技能配置

7.2 常见模式

模式一:事件通知(无返回值)

// C++ 在适当时机通知 LuaUFUNCTION(BlueprintImplementableEvent)voidOnLevelLoaded(constFString&LevelName);// C++ 内部voidAMyGameMode::HandleLevelLoaded(){// ... C++ 逻辑 ...OnLevelLoaded(CurrentLevelName);// 通知 Lua}

模式二:策略委托(有返回值)

// C++ 向 Lua 请求决策UFUNCTION(BlueprintNativeEvent)boolShouldAttackTarget(AActor*Target);// C++ 默认实现boolAMyAI::ShouldAttackTarget_Implementation(AActor*Target){returntrue;// 默认:见谁打谁}// C++ 使用if(ShouldAttackTarget(Enemy)){StartAttack(Enemy);}
-- Lua 可以实现更复杂的判断functionMyAI:ShouldAttackTarget(Target)ifTarget:HasBuff("Invisible")thenreturnfalse-- 隐身目标不打endifself:GetHP()<100thenreturnfalse-- 血量低不打endreturntrueend

模式三:数据获取

UFUNCTION(BlueprintImplementableEvent)TArray<FString>GetAvailableSkills();// C++ 调用TArray<FString>Skills=GetAvailableSkills();for(constFString&Skill:Skills){// 处理每个技能}

7.3 避免的做法

// ❌ 不要在 C++ 中直接 #include Lua 头文件来调用 Lua(除非必要)#include"lua.hpp"lua_getglobal(L,"SomeFunction");lua_pcall(L,0,0,0);// ✅ 应该通过 UE 反射系统间接调用UFUNCTION(BlueprintImplementableEvent)voidSomeFunction();
// ❌ 不要在 Tick 中频繁调用 Lua(性能问题)voidTick(floatDeltaTime){OnLuaTick(DeltaTime);// 每帧调 Lua,开销大}// ✅ 用事件驱动代替每帧轮询voidOnHealthChanged(floatNewHealth){OnHealthUpdate(NewHealth);// 只在变化时调用}

八、总结

C++ 调用 Lua = C++ 调用 UFunction → UnLua 拦截 → 转发到 Lua 虚拟机 核心选择: ┌─ 需要 Lua 提供完整实现 ──→ BlueprintImplementableEvent ├─ 需要 C++ 有默认行为 ──→ BlueprintNativeEvent ├─ 需要调用特定对象方法 ──→ UnLua::Call() └─ 需要调用全局 Lua 函数 ──→ lua_pcall()(尽量避免)

记住一个原则:让 C++ 不知道 Lua 的存在。通过 UE 反射系统做桥梁,C++ 定义接口、调用接口,Lua 负责实现接口。这样既解耦又支持热更新。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 23:17:57

大模型代码生成与代理任务评估框架及优化实践

1. 大型推理模型的技术背景与行业现状过去三年间&#xff0c;基于Transformer架构的大规模预训练模型在自然语言处理领域取得了突破性进展。这些参数量超过百亿的"大模型"展现出了惊人的上下文理解、逻辑推理和任务泛化能力。在代码生成与代理任务这两个特定领域&…

作者头像 李华
网站建设 2026/5/5 23:16:52

GLA与GDN注意力机制在长序列建模中的性能对比

1. 研究背景与核心问题在自然语言处理领域&#xff0c;模型架构的选择直接影响着训练效率和推理性能。近年来&#xff0c;GLA&#xff08;Gated Linear Attention&#xff09;和GDN&#xff08;Gated Dynamic Networks&#xff09;作为两种新型注意力机制变体&#xff0c;在长序…

作者头像 李华
网站建设 2026/5/5 23:16:12

超表面技术在水下定位系统中的应用与优化

1. 项目概述&#xff1a;超表面辅助的水下定位系统在水下环境中实现精确位置感知一直是极具挑战性的技术难题。传统的水下定位系统通常依赖于复杂的水听器阵列或高精度时钟同步&#xff0c;这些方案不仅硬件成本高昂&#xff0c;而且在实际部署中面临诸多限制。MetaBlue系统创新…

作者头像 李华
网站建设 2026/5/5 23:11:36

PaddleOCR VL-1.5技术解析与工程实践

1. 项目背景与核心价值在文档数字化和图像识别领域&#xff0c;OCR&#xff08;光学字符识别&#xff09;技术已经发展到一个新的高度。PaddleOCR作为国内领先的开源OCR工具库&#xff0c;其最新发布的VL-1.5版本在多项基准测试中展现了突破性的性能表现。这个版本特别针对复杂…

作者头像 李华
网站建设 2026/5/5 23:10:32

Sunshine自托管游戏串流服务器技术方案与实践指南

Sunshine自托管游戏串流服务器技术方案与实践指南 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款开源的自托管游戏串流服务器&#xff0c;专为Moonlight客户端设计…

作者头像 李华
网站建设 2026/5/5 23:07:27

视频字幕提取工具:本地化OCR识别,支持87种语言的字幕生成

视频字幕提取工具&#xff1a;本地化OCR识别&#xff0c;支持87种语言的字幕生成 【免费下载链接】video-subtitle-extractor 视频硬字幕提取&#xff0c;生成srt文件。无需申请第三方API&#xff0c;本地实现文本识别。基于深度学习的视频字幕提取框架&#xff0c;包含字幕区域…

作者头像 李华