关于cocos2dx 2.x CCLabelBMFont的解析优化

  因为系统字体已经没办法满足项目的需求,需要用一个新的字体,但由于担心字体版权等问题,因为改用通用做法,做一套全字体的BMFont ,全字体9千多个汉字和其他符号,还好2048X2048堆下来了,只用了19号字体,而且万幸经过压缩后,图片也没有多大了。

  但是问题来了,在安卓上,首次引用到这个字体时,字体解析居然花了10多秒。

  打开.fnt文件

info face="STHeitiSC-Light" size=19 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
common lineHeight=26 base=19 scaleW=2048 scaleH=2048 pages=1 packed=0
page id=0 file="youyuan.png"
chars count=9237
char id=12298 x=1 y=1 width=11 height=21 xoffset=10 yoffset=1 xadvance=20 page=0 chnl=0 letter="《"
char id=12299 x=13 y=1 width=11 height=21 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0 letter="》"
char id=12300 x=25 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter="「"
char id=12301 x=32 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter="」"
char id=65288 x=39 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter="("
char id=65289 x=46 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter=")"
char id=124 x=53 y=1 width=2 height=21 xoffset=4 yoffset=3 xadvance=9 page=0 chnl=0 letter="|"
char id=30306 x=77 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter="癢"
char id=30221 x=98 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter="瘍"
char id=23518 x=119 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter="寞"
char id=22815 x=139 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter="够"

查看CCLabelBMFont.cpp,查看其解析函数

 1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
 2 {
 3     std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
 4     CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
 5
 6     CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
 7
 8     set<unsigned int> *validCharsString = new set<unsigned int>();
 9
10     if (!contents)
11     {
12         CCLOG("cocos2d: Error parsing FNTfile %s", controlFile);
13         return NULL;
14     }
15
16     // parse spacing / padding
17     std::string line;
18     std::string strLeft = contents->getCString();
19     while (strLeft.length() > 0)
20     {
21         int pos = strLeft.find(‘\n‘);
22
23         if (pos != (int)std::string::npos)
24         {
25             // the data is more than a line.get one line
26             line = strLeft.substr(0, pos);
27             strLeft = strLeft.substr(pos + 1);
28         }
29         else
30         {
31             // get the left data
32             line = strLeft;
33             strLeft.erase();
34         }
35
36         if(line.substr(0,strlen("info face")) == "info face")
37         {
38             // XXX: info parsing is incomplete
39             // Not needed for the Hiero editors, but needed for the AngelCode editor
40             //            [self parseInfoArguments:line];
41             this->parseInfoArguments(line);
42         }
43         // Check to see if the start of the line is something we are interested in
44         else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
45         {
46             this->parseCommonArguments(line);
47         }
48         else if(line.substr(0,strlen("page id")) == "page id")
49         {
50             this->parseImageFileName(line, controlFile);
51         }
52         else if(line.substr(0,strlen("chars c")) == "chars c")
53         {
54             // Ignore this line
55         }
56         else if(line.substr(0,strlen("char")) == "char")
57         {
58             // Parse the current line and create a new CharDef
59             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
60             this->parseCharacterDefinition(line, &element->fontDef);
61
62             element->key = element->fontDef.charID;
63             HASH_ADD_INT(m_pFontDefDictionary, key, element);
64
65             validCharsString->insert(element->fontDef.charID);
66         }
67 //        else if(line.substr(0,strlen("kernings count")) == "kernings count")
68 //        {
69 //            this->parseKerningCapacity(line);
70 //        }
71         else if(line.substr(0,strlen("kerning first")) == "kerning first")
72         {
73             this->parseKerningEntry(line);
74         }
75     }
76
77     return validCharsString;
78 }

执行最多次数的应该是这里:

 1  else if(line.substr(0,strlen("char")) == "char")
 2         {
 3             // Parse the current line and create a new CharDef
 4             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
 5             this->parseCharacterDefinition(line, &element->fontDef);
 6
 7             element->key = element->fontDef.charID;
 8             HASH_ADD_INT(m_pFontDefDictionary, key, element);
 9
10             validCharsString->insert(element->fontDef.charID);
11         }

继续跟踪:parseCharacterDefinition() 这个函数

 1 void CCBMFontConfiguration::parseCharacterDefinition(std::string line, ccBMFontDef *characterDefinition)
 2 {
 3     //////////////////////////////////////////////////////////////////////////
 4     // line to parse:
 5     // char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=44    xadvance=14     page=0  chnl=0
 6     //////////////////////////////////////////////////////////////////////////
 7
 8     // Character ID
 9     int index = line.find("id=");
10     int index2 = line.find(‘ ‘, index);
11     std::string value = line.substr(index, index2-index);
12     sscanf(value.c_str(), "id=%u", &characterDefinition->charID);
13
14     // Character x
15     index = line.find("x=");
16     index2 = line.find(‘ ‘, index);
17     value = line.substr(index, index2-index);
18     sscanf(value.c_str(), "x=%f", &characterDefinition->rect.origin.x);
19     // Character y
20     index = line.find("y=");
21     index2 = line.find(‘ ‘, index);
22     value = line.substr(index, index2-index);
23     sscanf(value.c_str(), "y=%f", &characterDefinition->rect.origin.y);
24     // Character width
25     index = line.find("width=");
26     index2 = line.find(‘ ‘, index);
27     value = line.substr(index, index2-index);
28     sscanf(value.c_str(), "width=%f", &characterDefinition->rect.size.width);
29     // Character height
30     index = line.find("height=");
31     index2 = line.find(‘ ‘, index);
32     value = line.substr(index, index2-index);
33     sscanf(value.c_str(), "height=%f", &characterDefinition->rect.size.height);
34     // Character xoffset
35     index = line.find("xoffset=");
36     index2 = line.find(‘ ‘, index);
37     value = line.substr(index, index2-index);
38     sscanf(value.c_str(), "xoffset=%hd", &characterDefinition->xOffset);
39     // Character yoffset
40     index = line.find("yoffset=");
41     index2 = line.find(‘ ‘, index);
42     value = line.substr(index, index2-index);
43     sscanf(value.c_str(), "yoffset=%hd", &characterDefinition->yOffset);
44     // Character xadvance
45     index = line.find("xadvance=");
46     index2 = line.find(‘ ‘, index);
47     value = line.substr(index, index2-index);
48     sscanf(value.c_str(), "xadvance=%hd", &characterDefinition->xAdvance);
49 }

居然全部为字符串一行一行的解析,因为肯定是会很慢的。

查看.fnt文件 ,其char 解析部分,全部都是固定格式的。因此思路很简单,修改.fnt文件 ,改为二进制解析,这样肯定可以提升很高的效率。

当然首先要做的是一个转换工具,将.fnt 文件转化为二进制文件。这里,我做的并不是所有的数据都转为二进制,因为时间关系,也没过多的去研究其他的字符。因此,我做的是保留其他所有的符为原本的内容,只修改char id = 这些项的格式

为了确保原本的.fnt 文件还继续能读,新生成的.fnt 文件中加入了标记“type=sxbmfont”

修改fnt文件代码,这里只贴出关键代码,其他部分我用的是MFC简单写的,就不贴出来了

结构体格式定义:

1 struct BMFontCharNode
2 {
3     int id;
4     int x,y;
5     int width,height;
6     int xoffset,yoffset;
7     int xadvance;
8 };
9 typedef struct  BMFontCharNode BMFontNode;

这是我的MFC点击编码时:

 1 void CtxtToBinDlg::OnBnClickedEncode()
 2 {
 3     // TODO: 在此添加控件通知处理程序代码
 4     if (mOutContent == "")
 5     {
 6         MessageBox("请先选择输出目录");
 7         return;
 8     }
 9     CString path = mOutContent + "/" +  m_fileName;
10     fstream fin(m_filePath);
11     const int LINE_LENGTH = 150;
12     char str[LINE_LENGTH];
13     memset(str,0,LINE_LENGTH);
14     fstream fout(path,ios::out);
15     fout.write("type=sxbmfont\r\n",strlen("type=sxbmfont\r\n"));
16     for ( int i=0; i < 4 ; i ++)
17     {
18         if(fin.getline(str,LINE_LENGTH) )
19         {
20             for (int j=0;j<LINE_LENGTH;j++)
21                 if (str[j] < 0)
22                     str[j] = 0;
23             CString str1 = CString(str) + "\r\n";
24             if (str1.Left(strlen("char id=")) != "char id=")
25             {
26                 fout.write(str1.GetBuffer(),str1.GetLength());
27             }
28             else
29             {
30                 break;
31             }
32             memset(str,0,LINE_LENGTH);
33         }
34     }
35     fout.close();
36
37
38     fstream binary_file(path,ios::out|ios::binary|ios::app);
39     while( fin.getline(str,LINE_LENGTH) )
40     {
41         for (int j=0;j<LINE_LENGTH;j++)
42             if (str[j] < 0)
43                 str[j] = 0;
44         CString str1 = str;
45         if (str1.Left(strlen("char id=")) == "char id=")
46         {
47             BMFontNode t;
48             sscanf_s(str1.GetBuffer(), "char id=%d x=%d y=%d width=%d height=%d xoffset=%d yoffset=%d xadvance=%d",
49                 &t.id,&t.x,&t.y,&t.width,&t.height,&t.xoffset,&t.yoffset,&t.xadvance);
50             // int pase = 1;
51             binary_file.write(reinterpret_cast<char *>(&t),sizeof(t));
52         }
53         memset(str,0,LINE_LENGTH);
54     }
55     binary_file.close();
56     if(!isContent)
57         MessageBox("生成成功!");
58 }

生成新的.fnt文件事,需要修改CCLabelBMFont 来读取

修改的部分如下:

在头文件加入新的结构体定义:(与保存时的结构一致)

CCLabelBMFont.h中加入结构定义

1 struct BMFontCharNode
2 {
3     int id;
4     int x,y;
5     int width,height;
6     int xoffset,yoffset;
7     int xadvance;
8 };
9 typedef struct  BMFontCharNode BMFontNode;

修改CCLabelBMFont.cpp

添加重载函数(重载char id = 行的解析)

 1 void CCBMFontConfiguration::parseCharacterDefinition(BMFontNode *node, ccBMFontDef *characterDefinition)
 2 {
 3     characterDefinition->charID = node->id;
 4     characterDefinition->rect.origin.x = node->x;
 5     characterDefinition->rect.origin.y = node->y;
 6     characterDefinition->rect.size.width = node->width;
 7     characterDefinition->rect.size.height = node->height;
 8     characterDefinition->xOffset = node->xoffset;
 9     characterDefinition->yOffset = node->yoffset;
10     characterDefinition->xAdvance = node->xadvance;
11 }

修改解析函数:

  1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
  2 {
  3     /*
  4      * 修改数据解析方式
  5      *
  6      */
  7     std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
  8
  9     set<unsigned int> *validCharsString = new set<unsigned int>();
 10
 11
 12     CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
 13     char contentstr[20];
 14     memcpy(contentstr,contents->getCString(),strlen("type=sxbmfont"));
 15     contentstr[strlen("type=sxbmfont")] = 0;
 16     if (strcmp(contentstr,"type=sxbmfont"))
 17     {
 18         CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
 19         // parse spacing / padding
 20         std::string line;
 21         std::string strLeft = contents->getCString();
 22         while (strLeft.length() > 0)
 23         {
 24             int pos = strLeft.find(‘\n‘);
 25
 26             if (pos != (int)std::string::npos)
 27             {
 28                 // the data is more than a line.get one line
 29                 line = strLeft.substr(0, pos);
 30                 strLeft = strLeft.substr(pos + 1);
 31             }
 32             else
 33             {
 34                 // get the left data
 35                 line = strLeft;
 36                 strLeft.erase();
 37             }
 38             if(line.substr(0,strlen("info face")) == "info face")
 39             {
 40                 // XXX: info parsing is incomplete
 41                 // Not needed for the Hiero editors, but needed for the AngelCode editor
 42                 //            [self parseInfoArguments:line];
 43                 this->parseInfoArguments(line);
 44             }
 45             // Check to see if the start of the line is something we are interested in
 46             else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
 47             {
 48                 this->parseCommonArguments(line);
 49             }
 50             else if(line.substr(0,strlen("page id")) == "page id")
 51             {
 52                 this->parseImageFileName(line, controlFile);
 53             }
 54             else if(line.substr(0,strlen("chars c")) == "chars c")
 55             {
 56                 // Ignore this line
 57             }
 58             else if(line.substr(0,strlen("char")) == "char")
 59             {
 60                 // Parse the current line and create a new CharDef
 61                 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
 62                 this->parseCharacterDefinition(line, &element->fontDef);
 63
 64                 element->key = element->fontDef.charID;
 65                 HASH_ADD_INT(m_pFontDefDictionary, key, element);
 66
 67                 validCharsString->insert(element->fontDef.charID);
 68             }
 69
 70             else if(line.substr(0,strlen("kerning first")) == "kerning first")
 71             {
 72                 this->parseKerningEntry(line);
 73             }
 74         }
 75
 76     }
 77     else
 78     {
 79         unsigned long len = 0;
 80         unsigned char* data = CCFileUtils::sharedFileUtils()->getFileData(fullpath.c_str(), "rb", &len);
 81         const int LINE_LENGTH = 150;
 82         char str[LINE_LENGTH];
 83         int nodeSize = sizeof(BMFontNode);
 84         int index = 0;
 85         int count = 0;
 86         for(int i=0 ; i <  len ; i++)
 87         {
 88             if(data[i] == ‘\n‘)
 89             {
 90                 memcpy(str,data+index,i-index+1);
 91                 str[i-index+1] = 0;
 92                 std::string line = str;
 93                 if(line.substr(0,strlen("info face")) == "info face")
 94                 {
 95                     this->parseInfoArguments(line);
 96                 }
 97                 // Check to see if the start of the line is something we are interested in
 98                 else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
 99                 {
100                     this->parseCommonArguments(line);
101                 }
102                 else if(line.substr(0,strlen("page id")) == "page id")
103                 {
104                     this->parseImageFileName(line, controlFile);
105                 }
106                 else if(line.substr(0,strlen("chars c")) == "chars c")
107                 {
108                     // Ignore this line
109                 }
110                 else if(line.substr(0,strlen("char")) == "char")
111                 {
112                     // Parse the current line and create a new CharDef
113                     tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
114                     this->parseCharacterDefinition(line, &element->fontDef);
115
116                     element->key = element->fontDef.charID;
117                     HASH_ADD_INT(m_pFontDefDictionary, key, element);
118
119                     validCharsString->insert(element->fontDef.charID);
120                 }
121                 else if(line.substr(0,strlen("kerning first")) == "kerning first")
122                 {
123                     this->parseKerningEntry(line);
124                 }
125                 index = i+1;
126                 count ++;
127                 if(count == 5)
128                     break;
129             }
130
131         }
132         while(index < len)
133         {
134             BMFontNode t;
135             memcpy(&t,data+index,nodeSize);
136             index = index + nodeSize;
137             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
138             this->parseCharacterDefinition(&t, &element->fontDef);
139
140             element->key = element->fontDef.charID;
141             HASH_ADD_INT(m_pFontDefDictionary, key, element);
142
143             validCharsString->insert(element->fontDef.charID);
144         }
145     }
146
147
148
149     return validCharsString;
150 }

至此优化完成。修改后,这样解析过程应该基本上不怎么消耗时间了,因为没有过多的字符解析

时间: 2024-08-25 18:26:55

关于cocos2dx 2.x CCLabelBMFont的解析优化的相关文章

cocos2d-x 3.0 使用Sax解析xml文档(解决中文显示问题)

今天是个好日子,心想的事儿都能成,明天是个好日子,打开了家门儿迎春风... 恩,听着歌写文档生活就是这么享受. 今天以前的邻居大神突然在qq上赞了我一下,这让我异常激动啊..这还要从前前前几天说起,那会无意间看到cocos微信上的一个实话实说活动,反正就是参加了可以抽奖这样子啦,没错,我就是本着那官方T恤去的,本着分子越大分母越大抽奖几率越大的原则,然后就连着发了一番感慨,而且还都是比较罗嗦,没想到隔天cocos君竟然给我回复了,中奖了有木有,cocos2dx的官方T恤,哈哈..然后就是以前的大

Cocos2d-x 3.0 使用TinyXml 解析XML文件

在cocos2d-x 3.0中Xml解析已经不用自己找库了,已经为我们集成好了. text.xml <!--?xml version ="1.0" encoding ="UTF8" standalone="yes" ?--> <!--?xml-stylesheet type="text/xsl" href="yxfqust.xsl" ?--> <!--下面是一个学生名单--&g

与下位机或设备的通信解析优化的一点功能(续补):动态编译

原文:与下位机或设备的通信解析优化的一点功能(续补):动态编译 继上一篇<与下位机或设备的通信解析优化的一点功能:T4+动态编译>  ,现在已经生成出解析用的类的C#源码了,接下来,就轮到动态编译生成Type了. 在实现上,.net framework和.net core上,有些不同: .Net Framework的: 1 var transfer = ""; //解析后的C#源码字符串 2 3 ICodeCompiler comp = new CSharpCodeProv

与下位机或设备的通信解析优化的一点功能:T4+动态编译

原文:与下位机或设备的通信解析优化的一点功能:T4+动态编译     去年接触的一个项目中,需要通过TCP与设备进行对接的,传的是Modbus协议的数据,然后后台需要可以动态配置协议解析的方式,即寄存器的解析方式,,配置信息有:Key,数据Index,源数据类型,数据库列类型,数据排列方式 一开始使用的方式是,从数据库读取出协议的配置,然后在接收到数据的时候,循环每个配置项根据配置-----解析数据------转换类型----存临时列表,,后来改进了一下,配置项存缓存,,数据库修改的时候,同时更

Cocos2dx项目--动作类游戏内存优化--Spine结构分析

项目接近尾声,需要做加载效率优化和内存优化. 在加载Spine制作的资源时候,我们需要将文件(.json)进行解析,然后创建对象 spAtlas* t_atlas = spAtlas_createFromFile(altas_name.c_str(), 0); 第一步 //加载纹理文件 spSkeletonJson* json = spSkeletonJson_create(t_atlas); 第二步 //根据上步产生的对象穿件JSON骨架 spSkeletonData* skeletonDat

cocos2d-x 3.0 使用Sax解析xml文件(中国显示器问题解决)

今天是个好日子.我以为事情可以变得,明天是个好日子.打开门儿春风... 恩,听着歌写文档生活就是这么享受. 今天曾经的邻居大神突然在qq上赞了我一下,这让我异常激动啊.. 这还要从前前前几天说起,那会无意间看到cocos微信上的一个实话实说活动.反正就是參加了能够抽奖这样子啦.没错.我就是本着那官方T恤去的,本着分子越大分母越大抽奖几率越大的原则,然后就连着发了一番感慨.并且还都是比較罗嗦,没想到隔天cocos君居然给我回复了,中奖了有木有,cocos2dx的官方T恤,哈哈. .然后就是曾经的大

cocos2d-x 手游体积及运行内存优化 - 参考自 http://www.himigame.com

内容参考自 http://www.himigame.com 这两者优化有共同的地方: 对同一时刻使用的资源打包(成大图,省内存空间,省总大小),但不宜把不同需要的资源打包(加载了多余资源) 更好的图片压缩,可以减少包大小,但不会减少运行内存. 降低图片质量(不影响游戏体验前提,例如动画). 序列帧降低帧率. 如果不影响体验,缩小资源图片,程序放大: 复用资源,例如瓦片,拉伸,九宫格等: 用骨骼动画: 检查资源加载部分,优化加载:

Cocos2dx项目--动作类游戏内存优化--Spine结构分析1

SPine数据组织 spAtlas:这个是从.atlas文件中解出来的结构,其中包含了纹理 struct spAtlas { spAtlasPage* pages; spAtlasRegion* regions; void* rendererObject; int ref; }; 先不要管,看它的数据组织,spAtlasPage,spAtlasRegion,rendererObject都是什么东西?往下看 struct spAtlasPage { const spAtlas* atlas; co

解析&#183;优化 ZKW线段树

德鲁伊!大自然已经听命于我了! 死亡之翼长子奈法利安 ZKW天下第一! 摘自某群聊天记录 ZKW线段树,即非递归形式的线段树,出自终极大犇ZKW的论文<统计的力量>.与普通的线段树相比,ZKW线段树由于是非递归形式,效率极高,代码也极短,成为了OI比赛中极为实用的优化算法之一.虽然ZKW线段树无法处理有运算优先级的线段树问题,但是在一般的问题上和常数偏大的问题上总能带来极强的游戏体验. ZKW线段树的建树 普通线段树 1 / 2 3 <---------------"弱小,可伶