在一个游戏中,通常会有很多怪物,以及怪物的生命值,魔法值等属性数据,这些数据不可能在代码里面写死,一般都会用配置文件来保存,使用时再加载到内存。
我们常用的配置文件是CSV文件,即逗号分隔值(Comma-Separated Values),如下图所示。
今天,我就来介绍一个来读取CSV文件的工具类——MyCsvUtil。
在接受读取CSV文件工具类之前,先介绍一个读取字符串的工具类——StringUtil。
//头文件StringUtil.h
#ifndef __StringUtil_H_
#define __StringUtil_H_
#include "cocos2d.h"
class StringUtil : public cocos2d::Ref
{
public:
static StringUtil * getInstance();
virtual bool init();
//用分隔符分割字符窜,结果存放到一个列表中,列表中的对象为Value
cocos2d::ValueVector split(const char * srcStr, const char * sSep);
private:
static StringUtil * m_StringUtil;//Util 是工具的意思!
};
//函数实现文件
#include "StringUtil.h"
USING_NS_CC;
//初始化
StringUtil * StringUtil::m_StringUtil = nullptr;
StringUtil * StringUtil::getInstance()
{
if (m_StringUtil == nullptr)
{
m_StringUtil = new StringUtil();
if (m_StringUtil && m_StringUtil->init() )
{
m_StringUtil->autorelease();
m_StringUtil->retain();
}
else
{
CC_SAFE_DELETE(m_StringUtil);
m_StringUtil = nullptr;
}
}
return m_StringUtil;
}
bool StringUtil::init()
{
////拆分字符串
//auto strsList = StringUtil::getInstance()->split("zhaolong,want,to,work!",",");
//for (auto str : strsList)
//{
// log("str = %s",str.asString().c_str());
//}
return true;
}
//分离函数,srcStr是要进行分离的字符串,sSep是分隔符 separator
ValueVector StringUtil::split(const char * srcStr, const char * sSep)
{
ValueVector stringList; //typedef std::vector ValueVector;
int size = strlen(srcStr);
//将数据转换为字符串对象
Value str = Value(srcStr);
int startIndex = 0;
int endIndex = 0;
endIndex = str.asString().find(sSep);
std::string lineStr;
//根据分隔符拆分字符串,并添加到列表中
while (endIndex > 0)
{
//拆分的字符串
//substr函数的作用是从给定的字符表达式或备注字段中返回一个子字符串。
lineStr = str.asString().substr(startIndex,endIndex);
//放入列表中
stringList.push_back(Value(lineStr));
//更新要分离的字符串
str = Value(str.asString().substr(endIndex + 1,size));
//更新索引号
endIndex = str.asString().find(sSep);
}
//剩下的字符串也添加进入列表
//将剩下的字符串与空字符串相比,若不为空
if (str.asString().compare("") != 0)
{
//就把该字符串放入到列表中
stringList.push_back(Value(str.asString()));
}
return stringList;
}
#endif
上面那个字符串工具类,最主要的就是一个split函数,因为已经写了足够多的注释了,这里就不再多做解释了。以后要进行字符串读取的话只需要将这个头文件包含进去,再调用split函数就行了。
我们要想更好的处理和保存csv文件数据,我们应该把它当作对象来处理,新建一个CsvData类,他的类对象就代表一份Csv文件数据。
//头文件CsvData.h
#ifndef __CsvData_H_
#define __CsvData_H_
#include "cocos2d.h"
class CsvData : public cocos2d::Ref
{
public:
CREATE_FUNC(CsvData);
virtual bool init();
//添加一行数据
void addLineData(cocos2d::ValueVector lineData);
//获取某行的数据
cocos2d::ValueVector getSingleLineData(int iLine);
//获取行列大小
cocos2d::Size getRowColNum();
private:
//用来存放Csv文件所有行的数据
cocos2d::ValueVector m_allLinesVec;
};
//函数实现文件
#include "CsvData.h"
USING_NS_CC;
bool CsvData::init()
{
return true;
}
void CsvData::addLineData(ValueVector lineData)
{
m_allLinesVec.push_back(Value(lineData));
}
ValueVector CsvData::getSingleLineData(int iLine)
{
return m_allLinesVec.at(iLine).asValueVector();
}
Size CsvData::getRowColNum()
{
Size size = Size();
//获得Csv文件的行数,因为m_allLinesVec变量保存的就是每一行的数据,所以m_allLinesVec的大小就是行的数量
size.width = m_allLinesVec.size();
//如果Csv文件中的行数大于0
if (size.width > 0)
{
//就求Csv文件第0行的数据的个数,得到列的数量
size.height = m_allLinesVec.at(0).asValueVector().size();
}
return size;
}
#endif
这个CsvData类比较简单,不必多说了,只需要注意的是getRowColNum这个函数。这个函数是获取Csv文件的规模,即行数和列数(行号和列号都是从0开始的)。
因为Csv文件保存的就是每一行的数据,所以Csv的size大小就是行的数量,要求列数的话,就求Csv文件第0行的数据的个数,得到列的数量。
然后,然后就是真正的编写Csv文件读取工具类啦!
//头文件MyCsvUtil.h
#ifndef __MyCsvUtil_H_
#define __MyCsvUtil_H_
#include "CsvData.h"
class MyCsvUtil : public cocos2d::Ref
{
public:
static MyCsvUtil * getInstance();
virtual bool init();
//加载配置文件
void loadFile(const char * sPath);
//获取某行某列的值
cocos2d::Value getValue(int iRow, int iCol, const char * csvFilePath);
//获取值并转化为字符串
const std::string getStr(int iRow, int iCol, const char * csvFilePath);
//获取值并转换为整形
const int getInt(int iRow, int iCol, const char * csvFilePath);
//获取文件的行和列的数量
const cocos2d::Size getFileSize(const char * csvFilePath);
private:
static MyCsvUtil * ms_CsvUtil;
cocos2d::Map<std::string, CsvData *> m_CsvMap;
};
#endif
//函数实现文件
#include "MyCsvUtil.h"
#include "StringUtil.h"
USING_NS_CC;
MyCsvUtil * MyCsvUtil::ms_CsvUtil = nullptr;
MyCsvUtil * MyCsvUtil::getInstance()
{
if (ms_CsvUtil == nullptr)
{
ms_CsvUtil = new MyCsvUtil();
if (ms_CsvUtil && ms_CsvUtil->init() )
{
ms_CsvUtil->autorelease();
ms_CsvUtil->retain();
}
else
{
CC_SAFE_DELETE(ms_CsvUtil);
ms_CsvUtil = nullptr;
}
}
return ms_CsvUtil;
}
bool MyCsvUtil::init()
{
if (!TBack::init())
{
return false;
}
return true;
}
//加载文件
void MyCsvUtil::loadFile(const char * sPath)
{
//存放一个csv文件的对象
CsvData * csvData = CsvData::create();
//读取路径所在文件的数据,保存到列表中
std::string str = FileUtils::getInstance()->getStringFromFile(sPath);
//将得到的数据按换行符分开,放到列表中
ValueVector linesList = StringUtil::getInstance()->split(str.c_str(),"\n");
//把每一行的字符拆分开来(按都好分隔)
for (auto value : linesList)
{
//将一行的字符串按逗号分隔,然后存放到列表中,最后将列表存放到CsvData对象里
//换句话说,csvData将成为一个二维表格,记录了配置文件行和列的字符串
ValueVector tArr = StringUtil::getInstance()->split(value.asString().c_str(),",");
csvData->addLineData(tArr);
}
//将CsvData对象添加到字典里
m_CsvMap.insert(sPath,csvData);
}
//获取文件规格
const Size MyCsvUtil::getFileSize(const char * csvFilePath)
{
//取出配置文件的二维表格
auto csvData = m_CsvMap.at(csvFilePath);
//如果配置文件不存在,则加载配置文件
if (csvData == nullptr)
{
loadFile(csvFilePath);
csvData = m_CsvMap.at(csvFilePath);
}
//调用了csvData类的函数
Size size = csvData->getRowColNum();
return size;
}
//获取文件某行某列
Value MyCsvUtil::getValue(int iRow, int iCol, const char * csvFilePath)
{
auto csvData = m_CsvMap.at(csvFilePath);
if (csvData == nullptr)
{
loadFile(csvFilePath);
csvData = m_CsvMap.at(csvFilePath);
}
//获取第iRow行数据
ValueVector rowVector = csvData->getSingleLineData(iRow);
//获取第iCol列数据
Value colValue = rowVector.at(iCol);
return colValue;
}
//获取文件某行某列并转换为字符串
const std::string MyCsvUtil::getStr(int iRow, int iCol, const char * csvFilePath)
{
Value val = Value();
val = getValue(iCol,iCol,csvFilePath);
return val.asString();
}
//获取文件某行某列并转换为整型
const int MyCsvUtil::getInt(int iRow, int iCol, const char * csvFilePath)
{
Value val = Value();
val = getValue(iRow,iCol,csvFilePath);
return val.asInt();
}
最后,我们就可以在其他的程序里面包含这个Csv文件读取工具类的头文件,就可以使用它了。
这是我在调试模式下测试的结果:
#include "MyCsvUtil.h"
/*------测试代码段-------*/
const char * sPath = "monster.csv";//文件路径
MyCsvUtil::getInstance()->loadFile(sPath);//加载文件
//获得第1行第1列数据
Value firstMonsterName = MyCsvUtil::getInstance()->getValue(1,1,sPath);
//获得第2行第1列数据
Value secMonsterName = MyCsvUtil::getInstance()->getValue(2,1,sPath);
//打印结果
log("%s",firstMonsterName.asString().c_str());
log("%s",secMonsterName.asString().c_str());