Cocos2d-x Lua 读取Csv文件,更方便的使用数据

笨木头  2014-07-31 22:00   Cocos Code IDE,Cocos2d-x Lua   阅读(14,593)   15条评论

 

我的书上或者是我曾经出售的源码里,都有Csv文件的影子。

也许是先入为主吧,我工作那会用的最久的配置文件就是Csv,所以我在很多游戏里都会情不自禁地优先选择它。

 

Csv文件,格式很简单,就是一行一条数据,字段之间用逗号分隔,策划也可以方便地使用Excel进行编辑。

Csv格式的文件,解析起来也很简单,所以自己动手写写很快~(小若:我就喜欢拿来主义,你怎么着)

 

最近在用Lua写游戏,对于技能、怪物等配置,我还是选择用Csv~

不得不说,Lua等脚本语言,在某些方面是C++没法比的,这次我就用Csv来表达这种心情~

 

笨木头花心贡献,哈?花心?不,是用心~

转载请注明,原文地址:http://www.benmutou.com/archives/1634

文章来源:笨木头与游戏开发

 

 

一份Csv配置文件

我们来看看这样一份Csv文件,如图:

ID,Name,Des,Model
ID,名字,描述,图片模型
1,广告,www.benmutou.com,csv.png
2,否认,呵呵,广告什么的,我才不会加,good.png

(小若:这是图吗?是哪里是图啊!)

 

当然,这样好像不够清晰,我们来这样看看:

mutouCsv

这是很简单的一个Csv文件:

第一行是英文名字,很重要,因为在代码里我们要通过这个名字来获取对应的字段内容。

第二行是中文名字,没什么作用,主要是给那些小白看的,比如旁白什么的。(小若:你的比如能不能不要这么有针对性?)

第三和第四行才是真正的内容,通常我都会给第一个字段设定为ID,这样可以根据ID轻松获取到对应行的内容。

 

Cocos Code IDE

OK,在读取Csv文件之前,稍微提一下这个IDE,它是Cocos2d-x官方出的一个IDE,专门针对Cocos2d-x+Lua、Cocos2d-x+JS,但不支持Cocos2d-x+C++。

虽然目前这个IDE还是RC版本,我是比较喜欢尝鲜的人,Cocos2d-x3.0版本我也是从Alpha玩起的~

就那句什么,“有坑才有成长”,我是这么认为的~

本文也是基于Cocos Code IDE来讲解,所以稍微提一下,IDE可以到官网下载,随便看看入门指引,几分钟的事情。

然后创建一个项目。

 

字符串分割

好了,项目创建完之后,默认有个main.lua和GameScene.lua,我们只需要在main.lua里做做测试就可以了

Csv是用逗号分割的,所以,字符串分割功能,必不可少,Lua似乎没有提供这个功能,幸好,这很简单。

 

在mian.lua里main函数前面添加一个函数:

 

1
2
3
4
5
function split(str, reps)
    local resultStrsList = {};
    string.gsub(str, '[^' .. reps ..']+', function(w) table.insert(resultStrsList, w) end );
    return resultStrsList;
end

split有两个参数,str当然就是准备被分割的字符串了,reps又当然是分隔符了~

这里用到了string库的gsub函数,共三个参数:

1. str ,待分割的字符串

2.'[^’ .. reps ..’]+’,这一看就是那什么,正则表达式,这货我很少用,所以每次用的时候就翻文档,用完就忘。

这里还比较简单,比如reps是逗号,那么,就变成 ‘[^,]+’  ,这个表达式的意思是:查找非逗号字符,并且多次匹配~

比如“heab,”,前面的heab都不是逗号,所有都匹配了,直到发现了第五个字符,它是逗号,停止匹配,于是最终匹配的字符串是“heab”。

(来自2014.09.17的说明:这里其实不是正则表达式,而是Lua里的“模式”,跟正则表达式不完全一样,但这里的解释仍然是行得通的,所以不用担心。)

3.每次分割完的字符串都能通过回调函数获取到,w参数就是分割后的一个子字符串,把它保存到一个table

OK,就这么简单,这样就能得到一个table,table里保存了所有被分割的子字符串。

 

测试一下,把默认的main函数的代码都删了,改成下面这个样子:


1
2
3
4
5
6
7
local function main()
    local t = split("nihfao,hehe,hen", ",");

    for k, v in pairs(t) do
        print(v);
    end
end

然后按F11运行,输出以下日志:
[LUA-print] nihfao
[LUA-print] hehe
[LUA-print] hen

成功了~

 

真正的解析Csv

我们的重点来了,解析Csv文件。
我们不仅仅是要解析,解析完,还要保存起来,方便以后取配置数据~

所以,为了以后更方便地读取数据,我想了一个好办法。

 

来看看代码,在mian函数的上面再加一个新函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function loadCsvFile(filePath)
    -- 读取文件
    local data = cc.FileUtils:getInstance():getStringFromFile(filePath);

    -- 按行划分
    local lineStr = split(data, '\n\r');

    --[[
                从第3行开始保存(第一行是标题,第二行是注释,后面的行才是内容)
           
                用二维数组保存:arr[ID][属性标题字符串]
    ]]
    local titles = split(lineStr[1], ",");
    local ID = 1;
    local arrs = {};
    for i = 3, #lineStr, 1 do
        -- 一行中,每一列的内容
        local content = split(lineStr[i], ",");

        -- 以标题作为索引,保存每一列的内容,取值的时候这样取:arrs[1].Title
        arrs[ID] = {};
        for j = 1, #titles, 1 do
            arrs[ID][titles[j]] = content[j];
        end

        ID = ID + 1;
    end

    return arrs;
end

 

是不是很简单?(小若:简单个毛线啊!)

这可比在C++里解析要简单多了~

一句句来分析吧:

1.通过FileUtils的getStringFromFile函数读取文件,取得字符串内容

2.local lineStr = split(data, ‘\n\r’); 通过split函数,把文件内容按行分割,得到一个lineStr,它存放了每一行的内容

3.local titles = split(lineStr[1], “,”); 还记得我们的Csv文件吗?第一行内容是英文字母,它们很重要

这里把文件的第一行内容用逗号分割,得到titles,它存放了第一行的每一个字段。

这些字段有什么用呢?以后我们将通过这些字段来获取配置数据。

4.接着,新建一个table变量:local arrs = {}; 它将完整地保存解析后的Csv文件数据。

5.接下来有两个for循环,先来看第一层循环:

for i = 3, #lineStr, 1 do
     — 一行中,每一列的内容
     local content = split(lineStr[i], “,”);

end

很早之前我们说过了,Csv的第一行是英文字段、第二行是中文解释,第三行才开始是真正的内容~所以我们要从第三行开始。

我们知道,Lua的table下标是从1开始的,于是,i的初始值为3

lineStr里存放的是每一行的内容,所以,第一层的for循环是为了取出每一行的内容,然后按逗号分割,保存到content变量里。

 

接下来,看第二层循环:

— 以标题作为索引,保存每一列的内容,取值的时候这样取:arrs[1].Title

arrs[ID] = {};
for j = 1, #titles, 1 do
      arrs[ID][titles[j]] = content[j];
end

arrs[ID] = {}是什么意思呢?这代表是一行的内容,arrs[ID]可以看做是一个数组,它保存了某一行的数据。

那么,arrs[ID]数组的下标自然就是我们的英文字母了(也就是之前一直说的,很重要的第一行内容)。

 

逻辑有点乱,我们慢慢理解:

虽然content保存了每一行的内容,但是它不方面获取数据,因为它的下标是数字。

所以,我们要把content的内容一个个复制到arrs[ID]里,把数字下标全部换成英文字符串。

第二层for循环就是在做这么一件事情。

 

测试一下

好了,就这么解释肯定还有点混乱吧,来测试一下就知道我倒底在说什么了。

修改main函数内容为:


1
2
3
4
5
6
local function main()
    local csvConfig = loadCsvFile("res/Mutou.Csv");
   
    print(csvConfig[1].Name .. ":" .. csvConfig[1].Des);
    print(csvConfig[2].Name .. ":" .. csvConfig[2].Des);
end

 

按F11运行,将输出以下日志:

[LUA-print] 广告:www.benmutou.com
[LUA-print] 否认:呵呵,广告什么的,我才不会加

 

首先,loadCsvFile会解析Csv文件,然后返回解析后的内容。

接着,我想获取ID为1的配置数据,比如,要获取它的Name字段属性,就非常简单了:csvConfig[1].Name 就可以了~

 

如果你对Lua的table比较生疏的话,你可能会无法理解本文的“精髓”(小若:因为根本就没有精髓啊!)

关于Lua的table,大家可以看看我的这篇文章,相信你会对table有一个初步的了解(足以看懂这篇文章)

巧说table的几种构造方式http://www.benmutou.com/archives/627 

 

后话

我个人是认为这么做很方便,我不是说这么解析很方便,而是,解析之后,要获取字段值的时候,很方便~

以后要给配置文件新增字段的话,对于解析和使用都完全没有影响,这是最舒服的地方。

当然了,这就要求我们的配置文件的第一行一定不能写错了,否则就不太好玩了。

 

如果是在C++里,可能就没法达到这么方便的程度了,虽然也能模仿,通过getValue(“key”)这样的方式也行~

但不管怎么说,还是不如脚本来得自然~

 

虽然它方便,但对于全脚本开发手游,还是不太舒服,有些地方,脚本完全比不上C++(不是指运行效率)~

 

好了,就说这么多~不拉仇恨了~

 

 

 

15 评论

  1. 博主您好,我是您的受众,解析csv文件的时候,最后一列是没有逗号的,因此是读不出来的,所以如果想获取最后一列的数值的话,要在最后一列上,再加上一列,这样就可以读出来了,假如说,我们在判断是’n’的情况下,就是’n’前面添加一个逗号,那就可以解决了,你什么时候再出书?你的第一本书我已经买了,下一本书,建议您写多一点网络编程,还有协议方面的,最好加上网络游戏的设计模式,读取配置文件之类的,哈哈,我肯定会买的

    1. 谢谢提醒,还真是疏忽了… 关于新书,应该9月份就能出版了,但不算是全新的书,是之前那本的升级版~很巧的是,我确实加了网络编程的内容,不过,也只能算是入门的内容

      1. 博主您好,新书是3.0以上版本的吧,我肯定会买的,另外建议您多出一点,关于cocostudio+lua这两个的知识点,因为大部分游戏都是这样来用了,这样学起来会比较快,而且lua是目前手机游戏的主要方向,如果加上这两个的话,相信您的书肯定会大卖的,话说,你之前的书畅销了多少本?

    2. 之前没仔细考虑,我再次检查了,最后一列是能读出来的,但是我的代码还是有一个地方错误了:– 按行划分local lineStr = split(data, ‘nr’);这里的分隔符应该用 nr ,我之前用的是n,这样会有问题,可能这就是导致最后一列“读不出来”的原因了,因为每行的最后都解析出问题了。文章已经修改好了,使用 nr 就没问题了

    1. 省事是省事,但不便于管理和修改~我们想想,为什么会有配置文件这些东西?为什么会有CSV、JSON、XML等各种格式的配置文件?因为不同格式的配置文件有其特定的使用场合,如果仅仅是把配置数据写在Lua里,那可能就不方便阅读和修改了(不一定是我们开发者自己去阅读和修改嘛)当然了,Lua里也可以写成特定格式,方便阅读和修改,但,这样的话,不就绕了个大圈,又回到配置文件了么…因为配置文件就是固定格式的文本…以上是我个人的见解~

发表评论

电子邮件地址不会被公开。 必填项已用*标注