在cocos2d-x中直接显示中文的时候会出现乱码,虽然在实际开发中把字符串直接写在代码里也不是好的做法,但是有时候也是为了更方便了。
本文采用两种方案来解决这个问题:
1. 使用iconv,引擎也提供了这个库,不过只是win32平台,移植到android上还得自己去下载iconv库编译。
2. 把字符串写到xml文件中,然后解析xml文件,格式按照android中的strings.xml
这是一种更好的做法,特别是需要提供国际化支持时。
下面来看具体的实现:
1. 使用iconv库
iconv的作用是将文本在多种国际编码格式之间进行转换。
(1) 首先包含iconv.h头文件,c++->常规->附加包含目录:cocos2dx\platform\third_party\win32\iconv,如图:
(2) 创建头文件IconvString.h,源码:
1
2
3
4
5
6
7
8
|
#ifndef ICONV_STRING_H
#define ICONV_STRING_H
int
convert(
char
*from_charset,
char
*to_charset,
char
*inbuf,
size_t
inlen,
char
*outbuf,
size_t
outlen);
int
gbk2utf8(
char
*inbuf,
size_t
inlen,
char
*outbuf,
size_t
outlen);
#endif
|
(3) 创建源文件IconvString.cpp,源码:
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
|
#include <string>
#include "iconv.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
// 编译链接的时候指定静态库
#pragma comment(lib,"libiconv.lib")
#endif
int
convert(
char
*from_charset,
char
*to_charset,
char
*inbuf,
size_t
inlen,
char
*outbuf,
size_t
outlen)
{
iconv_t iconvH;
iconvH = iconv_open(to_charset, from_charset);
if
( !iconvH )
return
NULL;
memset
(outbuf, 0, outlen);
#if(CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
const
char
*temp = inbuf;
const
char
**pin = &temp;
char
**pout = &outbuf;
if
( !iconv(iconvH, pin, &inlen, pout, &outlen) )
{
iconv_close(iconvH);
return
NULL;
}
#else
if
( !iconv(iconvH, &inbuf, &inlen, &outbuf, &outlen) )
{
iconv_close(iconvH);
return
NULL;
}
#endif
iconv_close(iconvH);
return
NULL;
}
int
gbk2utf8(
char
*inbuf,
size_t
inlen,
char
*outbuf,
size_t
outlen)
{
return
convert(
"gb2312"
,
"utf-8"
, inbuf, inlen, outbuf, outlen);
}
|
代码比较简单,需要注意的是win32和android对应的iconv函数参数不一样。在win32平台需要指定lib库,它是iconv.h头文件对应的源码实现。android平台需要下载iconv库,编译的时候在iconv库的根目录下创建一个Android.mk文件就行,Android.mk内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
LOCAL_PATH:= $(call my-
dir
)
#libiconv.so
include $(CLEAR_VARS)
LOCAL_MODULE := libiconv
LOCAL_CFLAGS := \
-Wno-multichar \
-DAndroid \
-DLIBDIR=
"c"
\
-DBUILDING_LIBICONV \
-DIN_LIBRARY
LOCAL_SRC_FILES := \
libcharset
/lib/localcharset
.c \
lib
/iconv
.c \
lib
/relocatable
.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)
/include
\
$(LOCAL_PATH)
/libcharset
\
$(LOCAL_PATH)
/lib
\
$(LOCAL_PATH)
/libcharset/include
\
$(LOCAL_PATH)
/srclib
include $(BUILD_STATIC_LIBRARY)
|
(4) 下面来看看如何使用,源码:
1
2
3
4
5
6
7
8
9
|
char
*inBuf =
"iconv: 你好,Alex Zhou"
;
size_t
inLen =
strlen
(inBuf);
size_t
outLen = inLen << 1;
char
*outBuf = (
char
*)
malloc
(outLen);
gbk2utf8(inBuf, inLen, outBuf, outLen);
CCLabelTTF* pLabel = CCLabelTTF::create(outBuf,
"Arial"
, 30);
pLabel->setColor(ccBLACK);
free
(outBuf);
|
要注意转码前后的字符串占的空间是不一样的, 需要知道每种编码格式字符占的字节数:我的vs选择的是Unicode字符集,所以中文字符占2个字节,英文字符占1个字节;gb2312每个字符占2个字节,而UTF-8中文字符占3个字节,英文字符占1个字节。所以这里把存储输出的字符串的数组容量扩大了一部。
效果图:
2. 使用xml的方式
这里使用了引擎提供的CCSAXParser来解析xml,它内部是用libxml2来实现的。
(1) 创建XmlParser.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
|
#ifndef XML_PARSE_H
#define XML_PARSE_H
#include <string>
#include "cocos2d.h"
class
XMLParser :
public
cocos2d::CCObject,
public
cocos2d::CCSAXDelegator
{
public
:
static
XMLParser* parseWithFile(
const
char
*xmlFileName);
static
XMLParser* parseWithString(
const
char
*content);
XMLParser();
virtual
~XMLParser();
// 从本地xml文件读取
bool
initWithFile(
const
char
*xmlFileName);
// 从字符中读取,可用于读取网络中的xml数据
bool
initWithString(
const
char
*content);
/**
*对应xml标签开始,如:<string name="alex">, name为string,atts为string的属性,如["name","alex"]
*/
virtual
void
startElement(
void
*ctx,
const
char
*name,
const
char
**atts);
/**
*对应xml标签结束,如:</string>
*/
virtual
void
endElement(
void
*ctx,
const
char
*name);
/**
*对应xml标签文本,如:<string name="alex">Alex Zhou</string>的Alex Zhou
*/
virtual
void
textHandler(
void
*ctx,
const
char
*s,
int
len);
cocos2d::CCString* getString(
const
char
*key);
private
:
cocos2d::CCDictionary *m_pDictionary;
std::string m_key;
std::string startXMLElement;
std::string endXMLElement;
};
#endif
|
代码里已对主要的函数进行了解释,这里就不啰嗦了。
(2) 创建XmlParser.cpp,源码:
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
|
#include "XMLParser.h"
using
namespace
std;
using
namespace
cocos2d;
// 空格
const
static
int
SPACE = 32;
// 换行
const
static
int
NEXTLINE = 10;
// tab 横向制表符
const
static
int
TAB = 9;
XMLParser* XMLParser::parseWithFile(
const
char
*xmlFileName)
{
XMLParser *pXMLParser =
new
XMLParser();
if
( pXMLParser->initWithFile(xmlFileName) )
{
pXMLParser->autorelease();
return
pXMLParser;
}
CC_SAFE_DELETE(pXMLParser);
return
NULL;
}
bool
XMLParser::initWithFile(
const
char
*xmlFileName)
{
m_pDictionary =
new
CCDictionary();
CCSAXParser _parser;
_parser.setDelegator(
this
);
const
char
*fullPath = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(xmlFileName);
return
_parser.parse(fullPath);
}
XMLParser* XMLParser::parseWithString(
const
char
*content)
{
XMLParser *pXMLParser =
new
XMLParser();
if
( pXMLParser->initWithString(content) )
{
pXMLParser->autorelease();
return
pXMLParser;
}
CC_SAFE_DELETE(pXMLParser);
return
NULL;
}
bool
XMLParser::initWithString(
const
char
*content)
{
m_pDictionary =
new
CCDictionary();
CCSAXParser _parse;
_parse.setDelegator(
this
);
return
_parse.parse(content,
strlen
(content) );
}
void
XMLParser::startElement(
void
*ctx,
const
char
*name,
const
char
**atts)
{
this
->startXMLElement = (
char
*)name;
CCLog(
"start=%s"
, startXMLElement.c_str());
if
(
this
->startXMLElement ==
"string"
)
{
while
(atts && *atts)
{
const
char
*attsKey = *atts;
if
(0 ==
strcmp
(attsKey,
"name"
))
{
++ atts;
const
char
*attsValue = *atts;
m_key = attsValue;
break
;
}
++ atts;
}
}
}
void
XMLParser::endElement(
void
*ctx,
const
char
*name)
{
this
->endXMLElement = (
char
*)name;
CCLog(
"end=%s"
, endXMLElement.c_str());
}
void
XMLParser::textHandler(
void
*ctx,
const
char
*s,
int
len)
{
string value((
char
*)s, 0, len);
//是否全是非正常字符
bool
noValue =
true
;
for
(
int
i = 0; i < len; ++i)
{
if
(s[i] != SPACE && s[i] != NEXTLINE && s[i] != TAB)
{
noValue =
false
;
break
;
}
}
if
(noValue)
return
;
CCString *pString = CCString::create(value);
CCLog(
"key=%s value=%s"
, m_key.c_str(), pString->getCString());
this
->m_pDictionary->setObject(pString,
this
->m_key);
}
CCString* XMLParser::getString(
const
char
*key)
{
string strKey(key);
return
(CCString *)
this
->m_pDictionary->objectForKey(strKey);
}
XMLParser::XMLParser()
{
}
XMLParser::~XMLParser()
{
CC_SAFE_DELETE(
this
->m_pDictionary);
}
|
如xml部分内容为:
1
|
<
string
name
=
"a"
>b</
string
>
|
上面的代码的作用是把key=a,value=b存储到字典中。
(3)下面来看看如何使用XmlParser,源码:
1
2
3
4
5
6
|
XMLParser *pXmlParser = XMLParser::parseWithFile(
"strings.xml"
);
CCString *pValue1 = pXmlParser->getString(
"hello"
);
CCString *pValue2 = pXmlParser->getString(
"name"
);
CCString *pValue = CCString::createWithFormat(
"%s%s%s%s"
,
"XML: "
, pValue1->getCString(),
","
, pValue2->getCString());
CCLabelTTF* pLabel = CCLabelTTF::create(pValue->getCString(),
"Arial"
, 30);
pLabel->setColor(ccBLACK);
|
strings.xml的内容:
1
2
3
4
5
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
string
name
=
"hello"
>你好</
string
>
<
string
name
=
"name"
>Alex Zhou</
string
>
</
resources
>
|
ok,到这里就结束了,android对应的iconv库我已经打包到源码中了。
源码:http://download.csdn.net/detail/zhoujianghai/5031137
2013年8月11号更新:
有朋友反馈说在xml中的换行符(“\n”)不起作用,原因是XMLParser解析xml时,把”\n”变成了”\\n”,这样就会把换行符原样输出了,解决方法很简单,直接把”\\n”替换成”\n”就可以了,添加一个字符串替换函数。原理:遍历原字符串,查找要替换的字符串在原字符串中的位置pos,然后截取从i到pos的子字符串再跟新字符串拼接,然后更新i,继续查找。在XmlParser.cpp中添加下面的字符串替换函数:
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
|
string replace(string source, string pattern, string dstPattern)
{
string result;
string::size_type pos;
int
lenSource = source.length();
int
i = 0;
for
(i = 0; i < lenSource; ++i)
{
pos = source.find(pattern, i);
if
(string::npos == pos)
break
;
if
(pos < lenSource)
{
string s = source.substr(i, pos - i);
result += s;
result += dstPattern;
i = pos + pattern.length() - 1;
}
}
result += source.substr(i);
return
result;
}
|
修改XmlParser.cpp的textHandler函数:
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
|
void
XMLParser::textHandler(
void
*ctx,
const
char
*s,
int
len)
{
if
(
this
->m_key.length() == 0)
return
;
string value((
char
*)s, 0, len);
CCLog(
"s=%s, len=%d"
, value.c_str(), value.length());
//是否全是非正常字符
bool
noValue =
true
;
for
(
int
i = 0; i < value.length(); ++i)
{
char
c = value.at(i);
CCLog(
"v=%d"
, c);
if
(c != SPACE && c != NEXTLINE && c != TAB)
{
noValue =
false
;
break
;
}
}
if
(noValue)
return
;
string result = replace(value, string(
"\\n"
), string(
"\n"
));
CCString *pString = CCString::create(result);
CCLog(
"key=%s value=%s"
, m_key.c_str(), pString->getCString());
this
->m_pDictionary->setObject(pString,
this
->m_key);
}
|
更新strings.xml:
1
2
3
4
5
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
string
name
=
"hello"
>你好世界\n</
string
>
<
string
name
=
"name"
>Alex Zhou</
string
>
</
resources
>
|
运行效果如下图: