30 May 2016
版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)

自从13年开始做手游接触lua,始终是边写边学,缺乏对lua更加全面系统的学习,这几篇文章开始“重识lua”, 把欠下的帐还回来。这个系列侧重于总结lua作为扩展程序的用法,不会着重介绍lua的语法。

前言

前一篇文章总结了lua中C API的基本用法和常见的虚拟栈操作函数,并没有涉及到具体的lua代码,只有当这些API用来连接lua代码和C++代码的时候,才能发挥出它的最大威力。本文的主题在于使用C++来加载、调用lua代码。

本文使用的Lua版本还是5.1。

C++中加载lua脚本

就像打开其它类型的文件一样,比如打开一个txt文件读取成一个字符串,打开一个json文件用json解析库来解析出感兴趣的字段;对于lua脚本则有lua解析器去解释它,它不仅可以被当做普通的静态配置文件来使用,更牛的地方就在于,它,是可编程的!对,因为它是一门编程语言。尤其是当你为lua打开了它的翅膀(lua标准库)。

下面就给出一个简单的例子,演示一下,如何把lua脚本当做一种资源,载入到C++代码中。

下面这个lua文件里定义了两个全局变量一个是字符串类型的怪物类型,一个是怪物血量,我把它当做一个配置文件来用,在C++中读取它们的值

res/config.lua:

monster_type = "Ghost"
blood        = 99.9

main.cpp:

#include "lua_51/lua.hpp"
#include "include/inc.h"
#include <string>

using namespace elloop;
using namespace std;

int main() {

    string scriptName("res/config.lua");

    lua_State* lua = luaL_newstate();

    int luaError = luaL_loadfile(lua, scriptName.c_str());  // 加载lua文件
    if (luaError) {
        error(lua, "fail to load script: %s, %s", scriptName.c_str(), lua_tostring(lua, -1));
    }

    luaError = lua_pcall(lua, 0, 0, 0);     // 执行这个lua文件, 三个0依次表示:输入0个参数,返回0个结果,需要0个错误处理函数
    if (luaError) {
        error(lua, "fail to run script: %s", lua_tostring(lua, -1));
    }

    lua_getglobal(lua, "blood");                            // 获取全局变量blood
    lua_getglobal(lua, "monster_type");                     // 获取全局变量monster_type

    double blood     = lua_tonumber(lua, 1);                // 读取两个全局变量
    const char* type = lua_tostring(lua, 2);

    psln(blood);                                            // 打印 blood = 99.9
    psln(type);                                             // type = Ghost

    lua_close(lua);

    return 0;
}

输出:

blood = 99.9
type = Ghost

可以看到lua这种“配置”文件还是比较方便读取的,不用关心字符和数字等类型的处理细节,丢给lua解释器就行了,它会把值提取出来放到虚拟栈上等我来取, 只是我自己需要心里有数我想要的变量在栈的什么位置,数据类型是什么。这些都可以通过合理的封装做到更加易用和不易出错。

相对于txt, json, xml等配置文件,lua作为配置的优势还在于它拥有计算的能力,比如:

我可以在lua脚本里写一个复杂的公式,以此公式来计算一些血量值。

function f(var)
    return var * var + 2 * var + 100
end

blood1 = f(10)
blood2 = f(20)
blood3 = f(30)

-- .......

在C++中读取:

int main() {

    // 同上面的代码,省略...

    lua_pop(lua, 2);        // 弹出第一个例子中的两个值:blood和monster_type

    lua_getglobal(lua, "blood1");
    lua_getglobal(lua, "blood2");
    lua_getglobal(lua, "blood3");

    double blood1, blood2, blood3;

    psln(blood1 = lua_tonumber(lua, 1));
    psln(blood2 = lua_tonumber(lua, 2));
    psln(blood3 = lua_tonumber(lua, 3));

    lua_close(lua);

    return 0;
}

输出:

blood1 = lua_tonumber(lua, 1) = 220
blood2 = lua_tonumber(lua, 2) = 540
blood3 = lua_tonumber(lua, 3) = 1060

可以看到,这仅仅是用了lua本身的语法,还没用lua库功能情况下就已经比普通意义上的配置文件牛很多了。(想了想,其实别的编程语言不也是一种配置文件吗,他们是编译器(或解释器)才能解释的配置)

除了上面两个例子读取lua全局变量,lua中的table也很容易被C++读取:

我在lua里添加了一个table,

info = {
    blood        = 99.9,
    monster_type = "Ghost"
}

现在要在C++中读取这个table里的两个字段:

int main() {

    // 略去代码同上第二个例子......

    lua_pop(lua, 3);    // 弹出 blood1, blood2, blood3

    lua_getglobal(lua, "info");     // 获取table

    lua_pushstring(lua, "blood");   // 第一种读table的方法,先压入key
    lua_gettable(lua, -2);           // 获取key对应的value,-2代表table在栈中的索引, 现在-1位置的元素是刚刚压入的key("blood"), 获取value之后,key会自动弹出栈。

    double tableBlood;
    psln(tableBlood = lua_tonumber(lua, -1));       // 打印获取的blood字段

    lua_pop(lua, 1);                                // 弹出blood这个值, 现在栈里只剩下栈顶的table

    lua_getfield(lua, -1, "monster_type");      // 第二种获取table字段的方法,在Lua 5.1中存在,针对字符串类型key的一个特化方法, -1同样是table在栈中的索引。

    const char* tableMonster;
    psln(tableMonster = lua_tostring(lua, -1));     // 打印出获取的table的monster_type字段

    lua_close(lua);

    return 0;
}

输出:

tableBlood = lua_tonumber(lua, -1) = 99.9
tableMonster = lua_tostring(lua, -1) = Ghost

总结

好了,先说这么多,这篇演示了如何从C++加载lua脚本,并读取变量和表。

API 功能
lua_getglobal(lua_State* lua, const char* key) 获取key对应的全局变量,它其实是一个宏,详见lua.h
lua_gettable(lua_State* lua, int table_index) 获取table的一个字段,需通过table_index指明table在栈中的索引, key需要提前压入栈中
lua_getfield(lua_State* lua, int table_index, const char* key) 获取table key对应的value, 需通过table_index指明table在栈中的索引

以上三个API操作的结果都会被压入栈中,而不是由函数返回。

下一篇文章总结一下如何从C++调用lua的函数。


作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问



分享到