引言
鉴于# C++ 与 Lua 交互全链路解析:基于Lua5.4.8的源码剖析文章篇幅过长,在此展示代码示例。
完整示例演示
示例结构
lua-cpp-game/
├── main.cpp # C++主程序
├── game_logic.lua # Lua游戏逻辑
├── skills.lua # 技能配置表
├── characters.lua # 角色配置表
└── CMakeLists.txt # 构建配置文件(可选)
示例代码
main.cpp:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <chrono>
#include <thread>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
// 角色基类
class Character {
public:
Character(const std::string& name, int health, int mana, int attack)
: name(name), maxHealth(health), health(health), maxMana(mana), mana(mana), attack(attack), shield(0) {}
std::string getName() const { return name; }
int getHealth() const { return health; }
void setHealth(int value) {
// 应用护盾
if (shield > 0) {
int damage = std::max(0, health - value); // 计算实际伤害
if (damage > 0) {
if (shield >= damage) {
shield -= damage;
damage = 0;
} else {
damage -= shield;
shield = 0;
}
value = health - damage;
}
}
health = std::max(0, std::min(maxHealth, value));
if (health == 0) {
onDeath();
}
}
int getMana() const { return mana; }
void setMana(int value) { mana = std::max(0, std::min(maxMana, value)); }
int getAttack() const { return attack; }
int getShield() const { return shield; }
void setShield(int value) { shield = std::max(0, value); }
virtual void onDeath() {
std::cout << name << " has been defeated!" << std::endl;
}
virtual void basicAttack(Character* target) {
int damage = attack;
target->setHealth(target->getHealth() - damage);
std::cout << name << " attacks " << target->getName() << " for " << damage << " damage!" << std::endl;
}
// 从Lua表加载角色状态
void loadState(lua_State* L, int index) {
// 假设栈顶是角色状态表
lua_getfield(L, index, "health");
if (lua_isnumber(L, -1)) {
health = lua_tointeger(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, index, "mana");
if (lua_isnumber(L, -1)) {
mana = lua_tointeger(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, index, "maxHealth");
if (lua_isnumber(L, -1)) {
maxHealth = lua_tointeger(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, index, "maxMana");
if (lua_isnumber(L, -1)) {
maxMana = lua_tointeger(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, index, "attack");
if (lua_isnumber(L, -1)) {
attack = lua_tointeger(L, -1);
}
lua_pop(L, 1);
lua_getfield(L, index, "shield");
if (lua_isnumber(L, -1)) {
shield = lua_tointeger(L, -1);
}
lua_pop(L, 1);
}
// 保存角色状态到Lua表
int saveState(lua_State* L) {
// 创建新表
lua_newtable(L);
// 设置字段
lua_pushinteger(L, health);
lua_setfield(L, -2, "health");
lua_pushinteger(L, mana);
lua_setfield(L, -2, "mana");
lua_pushinteger(L, maxHealth);
lua_setfield(L, -2, "maxHealth");
lua_pushinteger(L, maxMana);
lua_setfield(L, -2, "maxMana");
lua_pushinteger(L, attack);
lua_setfield(L, -2, "attack");
lua_pushinteger(L, shield);
lua_setfield(L, -2, "shield");
// 返回表在栈中的索引
return lua_gettop(L);
}
protected:
std::string name;
int maxHealth;
int health;
int maxMana;
int mana;
int attack;
int shield; // 新增护盾属性
};
// C++角色管理器
class CharacterManager {
public:
static CharacterManager& getInstance() {
static CharacterManager instance;
return instance;
}
Character* createCharacter(const std::string& name, int health, int mana, int attack) {
characters.push_back(std::make_unique<Character>(name, health, mana, attack));
return characters.back().get();
}
void removeCharacter(Character* character) {
for (auto it = characters.begin(); it != characters.end(); ++it) {
if (it->get() == character) {
characters.erase(it);
break;
}
}
}
private:
CharacterManager() = default;
std::vector<std::unique_ptr<Character>> characters;
};
// Lua与C++交互函数
static int lua_createCharacter(lua_State* L) {
const char* name = luaL_checkstring(L, 1);
int health = luaL_checkinteger(L, 2);
int mana = luaL_checkinteger(L, 3);
int attack = luaL_checkinteger(L, 4);
Character* character = CharacterManager::getInstance().createCharacter(name, health, mana, attack);
// 将C++对象指针存储为Lua轻量用户数据
lua_pushlightuserdata(L, character);
return 1;
}
static int lua_getCharacterName(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
lua_pushstring(L, character->getName().c_str());
return 1;
}
static int lua_getCharacterHealth(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
lua_pushinteger(L, character->getHealth());
return 1;
}
static int lua_setCharacterHealth(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
int health = luaL_checkinteger(L, 2);
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
character->setHealth(health);
return 0;
}
static int lua_getCharacterMana(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
lua_pushinteger(L, character->getMana());
return 1;
}
static int lua_setCharacterMana(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
int mana = luaL_checkinteger(L, 2);
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
character->setMana(mana);
return 0;
}
static int lua_getCharacterShield(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
lua_pushinteger(L, character->getShield());
return 1;
}
static int lua_setCharacterShield(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
int shield = luaL_checkinteger(L, 2);
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
character->setShield(shield);
return 0;
}
static int lua_characterBasicAttack(lua_State* L) {
Character* attacker = static_cast<Character*>(lua_touserdata(L, 1));
Character* target = static_cast<Character*>(lua_touserdata(L, 2));
if (!attacker || !target) {
luaL_error(L, "Invalid character userdata");
return 0;
}
attacker->basicAttack(target);
return 0;
}
// 加载角色状态
static int lua_loadCharacterState(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
// 第二个参数应为表
if (!lua_istable(L, 2)) {
luaL_error(L, "Argument #2 must be a table");
return 0;
}
character->loadState(L, 2);
return 0;
}
// 保存角色状态
static int lua_saveCharacterState(lua_State* L) {
Character* character = static_cast<Character*>(lua_touserdata(L, 1));
if (!character) {
luaL_error(L, "Invalid character userdata");
return 0;
}
// 保存状态到新表并返回
int tableIndex = character->saveState(L);
return 1;
}
// 从Lua表加载技能配置
static int lua_loadSkillConfig(lua_State* L) {
// 假设栈顶是技能配置表
if (!lua_istable(L, -1)) {
luaL_error(L, "Top of stack must be a table");
return 0;
}
// 遍历表中的所有技能
lua_pushnil(L); // 初始键
while (lua_next(L, -2) != 0) {
// 键在索引-2,值在索引-1
if (lua_istable(L, -1)) {
const char* skillName = lua_tostring(L, -2);
std::cout << "Loading skill: " << skillName << std::endl;
// 获取技能属性
lua_getfield(L, -1, "damage");
int damage = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "manaCost");
int manaCost = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "duration");
int duration = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "type");
const char* type = lua_tostring(L, -1);
lua_pop(L, 1);
// 打印技能信息
std::cout << " Type: " << type << ", Damage: " << damage
<< ", Mana Cost: " << manaCost
<< ", Duration: " << duration << std::endl;
}
// 弹出值,保留键用于下次迭代
lua_pop(L, 1);
}
return 0;
}
// 从Lua表加载角色配置
static int lua_loadCharacterConfig(lua_State* L) {
// 假设栈顶是角色配置表
if (!lua_istable(L, -1)) {
luaL_error(L, "Top of stack must be a table");
return 0;
}
// 遍历表中的所有角色
lua_pushnil(L); // 初始键
while (lua_next(L, -2) != 0) {
// 键在索引-2,值在索引-1
if (lua_istable(L, -1)) {
const char* characterName = lua_tostring(L, -2);
std::cout << "Loading character: " << characterName << std::endl;
// 获取角色属性
lua_getfield(L, -1, "health");
int health = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "mana");
int mana = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "attack");
int attack = lua_tointeger(L, -1);
lua_pop(L, 1);
// 创建角色
Character* character = CharacterManager::getInstance()
.createCharacter(characterName, health, mana, attack);
// 将角色指针存储在注册表中
lua_pushlightuserdata(L, character);
lua_setfield(L, LUA_REGISTRYINDEX, characterName);
std::cout << " Health: " << health << ", Mana: "
<< mana << ", Attack: " << attack << std::endl;
}
// 弹出值,保留键用于下次迭代
lua_pop(L, 1);
}
return 0;
}
// 改进的wait函数实现
static int lua_wait(lua_State* L) {
double seconds = luaL_checknumber(L, 1);
// 仅在调试时打印等待信息
if (seconds >= 0.5) { // 只对较长的等待打印信息
std::cout << "Waiting for " << seconds << " seconds..." << std::endl;
}
// 实际游戏中应该使用计时器系统,这里简化处理
lua_yield(L, 0); // 挂起协程
return 0;
}
// 从文件加载并执行Lua脚本
bool executeLuaFile(lua_State* L, const std::string& filePath) {
// 加载Lua文件
if (luaL_loadfile(L, filePath.c_str()) != LUA_OK) {
std::cerr << "Error loading Lua script: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1); // 弹出错误消息
return false;
}
// 执行Lua代码
if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) {
std::cerr << "Error executing Lua script: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1); // 弹出错误消息
return false;
}
return true;
}
int main() {
// 创建Lua状态机
lua_State* L = luaL_newstate();
if (!L) {
std::cerr << "Failed to create Lua state" << std::endl;
return 1;
}
// 打开标准库
luaL_openlibs(L);
// 注册C++函数
lua_register(L, "createCharacter", lua_createCharacter);
lua_register(L, "getCharacterName", lua_getCharacterName);
lua_register(L, "getCharacterHealth", lua_getCharacterHealth);
lua_register(L, "setCharacterHealth", lua_setCharacterHealth);
lua_register(L, "getCharacterMana", lua_getCharacterMana);
lua_register(L, "setCharacterMana", lua_setCharacterMana);
lua_register(L, "getCharacterShield", lua_getCharacterShield);
lua_register(L, "setCharacterShield", lua_setCharacterShield);
lua_register(L, "characterBasicAttack", lua_characterBasicAttack);
lua_register(L, "loadCharacterState", lua_loadCharacterState);
lua_register(L, "saveCharacterState", lua_saveCharacterState);
lua_register(L, "loadSkillConfig", lua_loadSkillConfig);
lua_register(L, "loadCharacterConfig", lua_loadCharacterConfig);
// 注册改进的wait函数
lua_register(L, "wait", lua_wait);
// 从文件加载角色配置
std::cout << "Loading character configurations..." << std::endl;
if (!executeLuaFile(L, "characters.lua")) {
lua_close(L);
return 1;
}
// 从文件加载技能配置
std::cout << "Loading skill configurations..." << std::endl;
if (!executeLuaFile(L, "skills.lua")) {
lua_close(L);
return 1;
}
// 从文件加载主游戏逻辑
std::cout << "Loading main game logic..." << std::endl;
if (!executeLuaFile(L, "game_logic.lua")) {
lua_close(L);
return 1;
}
// 清理Lua状态机
lua_close(L);
return 0;
}
game_logic.lua:
-- 从注册表获取角色
local Hero = getmetatable(_G).__index.Hero
local Goblin = getmetatable(_G).__index.Goblin
local Wizard = getmetatable(_G).__index.Wizard
local Knight = getmetatable(_G).__index.Knight
local Dragon = getmetatable(_G).__index.Dragon
-- 游戏状态
local gameState = {
combatants = {Hero, Goblin},
currentTurn = 1,
gameOver = false
}
-- 战斗系统
local CombatSystem = {}
-- 使用技能
function CombatSystem.useSkill(caster, target, skillName)
local skill = Skills[skillName]
if not skill then
print("技能不存在: " .. skillName)
return false
end
-- 检查魔法值
if caster:getMana() < skill.manaCost then
print(caster:getName() .. " 魔法值不足,无法使用 " .. skillName)
return false
end
-- 消耗魔法值
caster:setMana(caster:getMana() - skill.manaCost)
-- 根据技能类型执行不同效果
if skill.type == "offensive" then
-- 攻击技能
print(caster:getName() .. " 施放 " .. skillName .. " 对 " .. target:getName() .. " 造成 " .. skill.damage .. " 点伤害!")
target:setHealth(target:getHealth() - skill.damage)
-- 处理持续性效果(如燃烧)
if skill.duration > 0 then
print(target:getName() .. " 受到 " .. skill.damage/3 .. " 点持续伤害!")
for i = 1, skill.duration do
wait(1)
if target:getHealth() > 0 then
local burnDamage = math.floor(skill.damage / 3)
print(target:getName() .. " 受到 " .. burnDamage .. " 点燃烧伤害!")
target:setHealth(target:getHealth() - burnDamage)
end
end
end
elseif skill.type == "healing" then
-- 治疗技能
local healAmount = -skill.damage -- 伤害值为负表示治疗
print(caster:getName() .. " 施放 " .. skillName .. " 恢复了 " .. healAmount .. " 点生命值!")
caster:setHealth(caster:getHealth() + healAmount)
elseif skill.type == "defensive" then
-- 防御技能(护盾)
print(caster:getName() .. " 施放 " .. skillName .. " 获得了 " .. skill.damage .. " 点护盾!")
caster:setShield(skill.damage)
-- 护盾持续时间
if skill.duration > 0 then
wait(skill.duration)
if caster:getShield() > 0 then
print(caster:getName() .. " 的护盾消失了!")
caster:setShield(0)
end
end
end
return true
end
-- 简单的AI决策
function CombatSystem.aiDecision(aiCharacter, targets)
-- 随机选择一个目标
local targetIndex = math.random(1, #targets)
local target = targets[targetIndex]
-- 根据角色类型决定使用的技能
if aiCharacter:getName() == "Goblin" then
-- 哥布林总是进行普通攻击
wait(0.5)
characterBasicAttack(aiCharacter, target)
elseif aiCharacter:getName() == "Wizard" then
-- 法师优先使用魔法
wait(0.5)
if aiCharacter:getMana() >= Skills.Fireball.manaCost then
CombatSystem.useSkill(aiCharacter, target, "Fireball")
else
characterBasicAttack(aiCharacter, target)
end
elseif aiCharacter:getName() == "Knight" then
-- 骑士优先使用护盾
wait(0.5)
if aiCharacter:getMana() >= Skills.Shield.manaCost and aiCharacter:getShield() < 20 then
CombatSystem.useSkill(aiCharacter, aiCharacter, "Shield")
else
characterBasicAttack(aiCharacter, target)
end
elseif aiCharacter:getName() == "Dragon" then
-- 龙随机使用技能或普通攻击
wait(0.5)
local rand = math.random(1, 3)
if rand == 1 and aiCharacter:getMana() >= Skills.Fireball.manaCost then
CombatSystem.useSkill(aiCharacter, target, "Fireball")
elseif rand == 2 and aiCharacter:getMana() >= Skills.Lightning.manaCost then
CombatSystem.useSkill(aiCharacter, target, "Lightning")
else
characterBasicAttack(aiCharacter, target)
end
end
end
-- 战斗回合
function CombatSystem.executeTurn()
local attacker = gameState.combatants[gameState.currentTurn]
if attacker:getHealth() <= 0 then
-- 如果攻击者已死亡,跳过此回合
print(attacker:getName() .. " 已死亡,跳过回合")
gameState.currentTurn = gameState.currentTurn % #gameState.combatants + 1
return
end
print("\n=== " .. attacker:getName() .. "'s 回合 ===")
-- 确定目标列表
local targets = {}
for i, combatant in ipairs(gameState.combatants) do
if combatant ~= attacker and combatant:getHealth() > 0 then
table.insert(targets, combatant)
end
end
-- 玩家控制的角色
if attacker:getName() == "Hero" then
-- 简单模拟玩家输入
wait(0.5)
-- 随机选择一个技能或普通攻击
local rand = math.random(1, 4)
if rand == 1 and attacker:getMana() >= Skills.Fireball.manaCost then
local targetIndex = math.random(1, #targets)
CombatSystem.useSkill(attacker, targets[targetIndex], "Fireball")
elseif rand == 2 and attacker:getMana() >= Skills.Heal.manaCost and attacker:getHealth() < attacker:getHealth() * 0.5 then
CombatSystem.useSkill(attacker, attacker, "Heal")
elseif rand == 3 and attacker:getMana() >= Skills.Shield.manaCost and attacker:getShield() < 30 then
CombatSystem.useSkill(attacker, attacker, "Shield")
else
local targetIndex = math.random(1, #targets)
characterBasicAttack(attacker, targets[targetIndex])
end
else
-- AI控制的角色
CombatSystem.aiDecision(attacker, targets)
end
-- 检查战斗是否结束
local allEnemiesDead = true
for i, combatant in ipairs(gameState.combatants) do
if combatant ~= Hero and combatant:getHealth() > 0 then
allEnemiesDead = false
break
end
end
if allEnemiesDead then
print("\n=== 战斗胜利! ===")
gameState.gameOver = true
return
end
local heroDead = Hero:getHealth() <= 0
if heroDead then
print("\n=== 战斗失败! ===")
gameState.gameOver = true
return
end
-- 切换到下一回合
gameState.currentTurn = gameState.currentTurn % #gameState.combatants + 1
end
-- 游戏主循环
function runGame()
print("\n=== 游戏开始 ===")
-- 展示初始状态
print("\n=== 初始状态 ===")
for i, combatant in ipairs(gameState.combatants) do
print(combatant:getName() .. ": 生命值=" .. combatant:getHealth() .. ", 魔法值=" .. combatant:getMana())
end
-- 战斗循环
while not gameState.gameOver do
wait(1)
CombatSystem.executeTurn()
-- 展示当前状态
print("\n=== 当前状态 ===")
for i, combatant in ipairs(gameState.combatants) do
local status = combatant:getHealth() > 0 and "存活" or "死亡"
print(combatant:getName() .. ": 生命值=" .. combatant:getHealth() .. ", 魔法值=" .. combatant:getMana() .. ", 护盾=" .. combatant:getShield() .. ", 状态=" .. status)
end
end
print("\n=== 游戏结束 ===")
end
-- 状态管理示例
function testStateManagement()
print("\n=== 状态管理测试 ===")
-- 保存初始状态
local heroState = {}
heroState = saveCharacterState(Hero)
local enemyState = {}
enemyState = saveCharacterState(Goblin)
print("\n=== 保存的状态 ===")
print("Hero Health: " .. heroState.health)
print("Hero Mana: " .. heroState.mana)
print("Enemy Health: " .. enemyState.health)
-- 进行一些操作
print("\n=== 进行战斗操作 ===")
characterBasicAttack(Hero, Goblin)
wait(0.5)
CombatSystem.useSkill(Hero, Goblin, "Fireball")
wait(0.5)
-- 展示当前状态
print("\n=== 操作后的状态 ===")
print("Hero Health: " .. Hero:getHealth())
print("Hero Mana: " .. Hero:getMana())
print("Enemy Health: " .. Goblin:getHealth())
-- 恢复状态
print("\n=== 恢复到保存的状态 ===")
loadCharacterState(Hero, heroState)
loadCharacterState(Goblin, enemyState)
-- 展示恢复后的状态
print("\n=== 恢复后的状态 ===")
print("Hero Health: " .. Hero:getHealth())
print("Hero Mana: " .. Hero:getMana())
print("Enemy Health: " .. Goblin:getHealth())
end
-- 测试不同的战斗场景
function testMultipleScenarios()
print("\n=== 多场景测试 ===")
-- 测试英雄 vs 哥布林
print("\n=== Hero vs Goblin ===")
gameState.combatants = {Hero, Goblin}
gameState.currentTurn = 1
gameState.gameOver = false
runGame()
wait(2)
-- 测试英雄 vs 法师
print("\n=== Hero vs Wizard ===")
Hero:setHealth(100)
Hero:setMana(50)
Wizard:setHealth(50)
Wizard:setMana(100)
gameState.combatants = {Hero, Wizard}
gameState.currentTurn = 1
gameState.gameOver = false
runGame()
wait(2)
-- 测试英雄 vs 骑士
print("\n=== Hero vs Knight ===")
Hero:setHealth(100)
Hero:setMana(50)
Knight:setHealth(150)
Knight:setMana(30)
gameState.combatants = {Hero, Knight}
gameState.currentTurn = 1
gameState.gameOver = false
runGame()
wait(2)
-- 测试英雄 vs 龙 (可能会失败)
print("\n=== Hero vs Dragon (挑战!) ===")
Hero:setHealth(100)
Hero:setMana(50)
Dragon:setHealth(300)
Dragon:setMana(150)
gameState.combatants = {Hero, Dragon}
gameState.currentTurn = 1
gameState.gameOver = false
runGame()
}
-- 运行测试
testStateManagement()
wait(2)
testMultipleScenarios()
print("\n=== 所有测试完成 ===")
skills.lua:
-- 技能配置表
Skills = {
Fireball = {
damage = 30,
manaCost = 20,
duration = 3,
type = "offensive"
},
Heal = {
damage = -40, -- 负数表示治疗
manaCost = 15,
duration = 0,
type = "healing"
},
Lightning = {
damage = 45,
manaCost = 30,
duration = 0,
type = "offensive"
},
Shield = {
damage = 50, -- 护盾值
manaCost = 25,
duration = 10,
type = "defensive"
},
Poison = {
damage = 10,
manaCost = 15,
duration = 5,
type = "offensive"
}
}
-- 加载技能配置
loadSkillConfig(Skills)
characters.lua:
-- 角色配置表
Characters = {
Hero = {
health = 100,
mana = 50,
attack = 15
},
Goblin = {
health = 70,
mana = 0,
attack = 10
},
Wizard = {
health = 50,
mana = 100,
attack = 8
},
Knight = {
health = 150,
mana = 30,
attack = 20
},
Dragon = {
health = 300,
mana = 150,
attack = 25
}
}
-- 加载角色配置
loadCharacterConfig(Characters)
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(LuaCppGame)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
# 查找Lua库
find_package(Lua REQUIRED)
include_directories(${LUA_INCLUDE_DIR})
# 添加可执行文件
add_executable(lua_game main.cpp)
# 链接Lua库
target_link_libraries(lua_game ${LUA_LIBRARIES})
# 复制Lua脚本到构建目录
file(COPY characters.lua skills.lua game_logic.lua DESTINATION ${CMAKE_BINARY_DIR})