Trigger第三篇:创建、读取和写入json文件

前言


       上一篇其实已经说完了这个游戏里的重要部分了,所以接下来开始写代码了。先从解析json文件开始,配置环境啥的就不写了,网上一搜大把的教程(感觉这部分其实都不用写,也有类似读取json文件的教程= =)

地图数据json


       这里使用rapidjson读取json文件。以3.2版本为例,rapidjson头文件在项目目录\cocos2d\external\json下,如下图所示:
rapidjson
       在使用rapidjson前,需要引入以下头文件:

1
2
3
4
5
#include "json/rapidjson.h"
#include "json/writer.h"
#include "json/document.h"
#include "json/prettywriter.h"
#include "json/stringbuffer.h"

       接下来上地图配置加载的代码:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//加载json并解析
bool CDataManager::LoadMapData()
{
string strContent = FileUtils::getInstance()->getStringFromFile(MAP_FILE_PATH);

rapidjson::Document oDoc;
oDoc.Parse<0>(strContent.c_str());
if (oDoc.HasParseError())
{
log("Parse Error: %s\n", oDoc.GetParseError());
return false;
}
if (oDoc.IsNull() || !oDoc.IsArray())
{
return false;
}

for (int i = 0; i < oDoc.Size(); i++)
{
MapInfo mapInfo;

//color
if (oDoc[i]["color"].IsNull())
{
return false;
}
mapInfo.iColor = oDoc[i]["color"].GetInt();

//id
if (oDoc[i]["id"].IsNull())
{
return false;
}
mapInfo.iID = oDoc[i]["id"].GetInt();

//beat
if (oDoc[i]["beat"].IsNull())
{
return false;
}
mapInfo.iBeat = oDoc[i]["beat"].GetInt();

//map
if (oDoc[i]["map"].IsNull() || (!oDoc[i]["map"].IsArray()))
{
return false;
}

//每行数据
for (int j = 0; j < oDoc[i]["map"].Size(); j++)
{
if (oDoc[i]["map"][j].IsNull()
|| (!oDoc[i]["map"][j].IsArray()))
{
return false;
}
for (int k = 0; k < oDoc[i]["map"][j].Size(); k++)
{
if (oDoc[i]["map"][j][k].IsNull())
{
return false;
}

int iValue = oDoc[i]["map"][j][k].GetInt();
//每行数据存入vecMap中
mapInfo.vecMap.push_back(iValue);
}
}

//mapitem
if (oDoc[i]["mapitem"].IsNull()
|| (!oDoc[i]["mapitem"].IsArray()))
{
return false;
}
for (int j = 0; j < oDoc[i]["mapitem"].Size(); j++)
{
if (oDoc[i]["mapitem"][j].IsNull())
{
return false;
}
string strMapItem = oDoc[i]["mapitem"][j].GetString();

int iArrow[3] = {0, 0, 0};
SplitString(strMapItem, '_', iArrow);

ArrowInfo arrowInfo;
//X和Y位置互换
arrowInfo.iY = iArrow[0];
arrowInfo.iX = iArrow[1];
arrowInfo.iType = iArrow[2];

//存入vecMapItem中
mapInfo.vecMapItem.push_back(arrowInfo);
}

//useritem
if (oDoc[i]["useritem"].IsNull()
|| (!oDoc[i]["useritem"].IsArray()))
{
return false;
}
for (int j = 0; j < oDoc[i]["useritem"].Size(); j++)
{
if (oDoc[i]["useritem"][j].IsNull())
{
return false;
}
int iNum = oDoc[i]["useritem"][j].GetInt();

mapInfo.vecUserItem.push_back(iNum);
}

m_oMapVec.push_back(mapInfo);
}

return true;
}

       以上代码涉及到了两个结构体,MapInfo和ArrowInfo,对应定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//箭头信息
struct ArrowInfo
{
int iX; //横向位置索引

int iY; //纵向位置索引

int iType; //箭头类型
};

struct MapInfo
{
int iColor; //颜色序号,也是主题序号

int iID; //关卡ID

int iBeat; //可点击次数

vector<int> vecMap; //六边形行列分布序列

vector<ArrowInfo> vecMapItem; //地图中箭头分布

vector<int> vecUserItem; //可用箭头数据
};

       另外还有一个函数SplitString,其负责解析出字符串中的三个数值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//移除string中所有位置出现过的某个字符
void CDataManager::SplitString(string &srcStr, char chTarget, int* pArray)
{
int iPos = srcStr.find_first_of('_');
string strTemp = srcStr.substr(0, iPos);
pArray[0] = atoi(strTemp.c_str());

int iNewPos = srcStr.find_first_of('_', iPos + 1);
strTemp = srcStr.substr(iPos + 1, iNewPos - iPos - 1);
pArray[1] = atoi(strTemp.c_str());

iPos = srcStr.find_first_of('_', iNewPos + 1);
strTemp = srcStr.substr(iNewPos + 1, iPos - iNewPos - 1);
pArray[2] = atoi(strTemp.c_str());
}

进度数据json


       现在需要考虑一个问题,游戏的进度该如何保存?使用UserDefault类?很显然是不行的,因为需要记录每个关卡的分数情况,200个关卡用UserDefault去存储的话显然够呛,所以这里可以考虑使用json文件保存游戏进度数据(当然,用xml也是可以的)。进度数据文件格式如下:

1
{"score":600,"level":200,"data":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}

       先写个创建进度数据文件的函数。因为第一次打开游戏时,进度是木有的,所以需要先建好进度数据文件:

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
31
32
33
34
35
36
37
void CDataManager::InitUserData(const char* pName)
{
rapidjson::Document oWriteDoc;
oWriteDoc.SetObject();
rapidjson::Document::AllocatorType& allocator = oWriteDoc.GetAllocator();
rapidjson::Value oArray(rapidjson::kArrayType);

oWriteDoc.AddMember("score", 0, allocator);
oWriteDoc.AddMember("level", 0, allocator);

//初始化数据
m_stUserInfo.iLevel = 0;
m_stUserInfo.iScore = 0;

for (int i = 0; i < TOTAL_LEVEL_NUM; i++)
{
m_stUserInfo.vecData.push_back(0);
oArray.PushBack(0, allocator);
}

oWriteDoc.AddMember("data", oArray, allocator);

rapidjson::StringBuffer oBuffer;
rapidjson::Writer<rapidjson::StringBuffer> oWriter(oBuffer);
oWriteDoc.Accept(oWriter);

//写入
FILE* pFile = fopen(pName, "wb+");
if (pFile == NULL)
{
log("Open File '%s' Failed.", pName);
return;
}

fputs(oBuffer.GetString(), pFile);
fclose(pFile);
}

       这里传入参数pName即要写入的进度数据文件路径,由于创建时没有进度,所以score和level都是0,各个关卡分数记录也是0。
       接下来写加载进度数据文件的函数:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//加载用户数据
bool CDataManager::LoadUserData()
{
string strPath = FileUtils::getInstance()->getWritablePath() + string(SAVE_DATA_PATH);

#ifdef _DEBUG_
log("UserData Path:%s\n", strPath.c_str());
#endif

string strContent = FileUtils::getInstance()->getStringFromFile(strPath);
rapidjson::Document oDoc;
oDoc.Parse<0>(strContent.c_str());
if (oDoc.HasParseError() || oDoc.IsNull())
{
//log("CDataManager::LoadUserData Load 'oDoc' Error.");
InitUserData(strPath.c_str());
return true;
}

//当前获得的分数
if (oDoc["score"].IsNull())
{
log("CDataManager::LoadUserData Load 'score' Error.");
return false;
}
m_stUserInfo.iScore = oDoc["score"].GetInt();

//已通过关卡数量,因为不可能跨关卡,所以也表示当前通过的最后一关
if (oDoc["level"].IsNull())
{
log("CDataManager::LoadUserData Load 'level' Error.");
return false;
}
m_stUserInfo.iLevel = oDoc["level"].GetInt();

//所有关卡当前信息
if (oDoc["data"].IsNull() || (!oDoc["data"].IsArray()))
{
log("CDataManager::LoadUserData Load 'data' Error.");
return false;
}
for (int i = 0; i < oDoc["data"].Size(); i++)
{
if (oDoc["data"][i].IsNull())
{
log("CDataManager::LoadUserData Load 'oDoc[data][%d]' Error.", i);
return false;
}

int iValue = oDoc["data"][i].GetInt();
m_stUserInfo.vecData.push_back(iValue);
}

return true;
}

       需要注意的是,默认当进度数据json文件读取失败时,说明json文件不存在,需要重新建立,即调用InitUserData函数创建进度数据文件。m_stUserInfo中保存了读取的进度数据,其类型是UserInfo结构体类型,UserInfo定义如下:

1
2
3
4
5
6
7
8
struct UserInfo
{
int iScore; //分数

int iLevel; //当前通过关卡数

vector<int> vecData; //关卡得分数据
};

       有了读取函数当然少不了写入函数,进度数据保存函数如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//用户数据写入到指定文件中
bool CDataManager::SaveUserData()
{
string strPath = FileUtils::getInstance()->getWritablePath() + string(SAVE_DATA_PATH);
string strContent = FileUtils::getInstance()->getStringFromFile(strPath);
rapidjson::Document oDoc;
oDoc.Parse<0>(strContent.c_str());
if (oDoc.HasParseError())
{
log("Parse Error: %s\n", oDoc.GetParseError());
return false;
}
if (oDoc.IsNull())
{
return false;
}

//更新分数
if (oDoc["score"].IsNull())
{
return false;
}
oDoc["score"].SetInt(m_stUserInfo.iScore);

//更新通过的关卡数量
if (oDoc["level"].IsNull())
{
return false;
}
oDoc["level"].SetInt(m_stUserInfo.iLevel);

//更新关卡当前信息
if (oDoc["data"].IsNull() || !oDoc["data"].IsArray())
{
return false;
}
int iVecSize = m_stUserInfo.vecData.size();
int iJsonSize = oDoc["data"].Size();
if (iVecSize != iJsonSize)
{
log("Error: %d(iVecSize) != %d(iJsonSize)\n", iVecSize, iJsonSize);
return false;
}
for (rapidjson::SizeType i = 0; i < oDoc["data"].Capacity(); i++)
{
if (oDoc["data"][i].IsNull())
{
return false;
}
oDoc["data"][i].SetInt(m_stUserInfo.vecData[i]);
}

//准备写入数据
rapidjson::StringBuffer oBuffer;
rapidjson::Writer<rapidjson::StringBuffer> oWriter(oBuffer);
oDoc.Accept(oWriter);

FILE* pFile = fopen(strPath.c_str(), "wb+");
if (pFile == NULL)
{
log("Error: Open File '%s' Failed.\n", strPath.c_str());
return false;
}

fputs(oBuffer.GetString(), pFile);
fclose(pFile);

#ifdef _DEBUG_
log("UserDataPath:%s", strPath.c_str());
#endif

return true;
}

       好了,json文件创建、读取和写入部分先说到这,如果有什么不明白的地方可以联系我。

文章目录
  1. 1. 前言
  2. 2. 地图数据json
  3. 3. 进度数据json