在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,源码:

#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,源码:

#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内容如下:

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) 下面来看看如何使用,源码:

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文件,源码:

#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,源码:

#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部分内容为:

<string name="a">b</string>

上面的代码的作用是把key=a,value=b存储到字典中。
(3)下面来看看如何使用XmlParser,源码:

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的内容:

<?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中添加下面的字符串替换函数:

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函数:

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:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">你好世界\n</string>
	<string name="name">Alex Zhou</string>
</resources>

运行效果如下图:

cocos2d-x手游性能优化总结

近段时间在使用cocos2d-x开发2D手游,技术方案使用的是cocos2d-x+lua,因为游戏使用的是cocos2d-x 2.1.5版本,有些优化方案在最新版的cocos2d-x版本已经实现...

阅读全文

cocos2dx-html5 实现网页版flappy bird游戏

我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的。游戏体...

阅读全文

【cocos2d-x开发实战 特训99-终结篇】移植到android平台和添加admob广告

上一篇已经完成特性99在win32平台下的开发,现在把它移植到android上,首先修改Android.mk文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_...

阅读全文

10 条评论

  1. 您好,请问mk文件里怎么设置路径啊?我没太看懂能给具体解释一下吗?谢谢!

  2. 我想用第二种方式,但XMLParser *pXmlParser = XMLParser::parseWithFile(“strings.xml”); pXmlParser是null,为什么呢。 有: res/values/strings.xml

  3. 你好,我用了iconv的方法来实现转码的问题,但是在调试的时候发现了几个不懂的地方,想请教一下:
    在转换的过程中我用到了一个转换函数:

    int code_convert(const char *from_charset, const char *to_charset, const char *inbuf, size_t inlen, char *outbuf, size_t outlen)
    {
    iconv_t cd;
    const char *temp = inbuf;
    const char **pin = &temp;
    char **pout = &outbuf;
    memset(outbuf,0,outlen);
    cd = iconv_open(to_charset,from_charset);
    if(cd==0) return -1;
    if(iconv(cd,pin,&inlen,pout,&outlen)==-1) return -1;
    iconv_close(cd);
    CCLOG(outbuf);
    CCLOG(“test”);
    return 0;
    }
    这个函数本来是很简单的,但在CCLOG(outbuf)处,输出了空字符串?
    但是在return函数返回之后,outbuf却有了值了,而且转换正确了,这个地方不解?

    1. iconv(cd,pin,&inlen,pout,&outlen)函数改变了outbuf,所以打印不出来,你可以在这个函数的前面和后面分别加上下面的代码,打印地址:
      CCLog(“outbuf=%x”, outbuf);
      两个值不一样的。

  4. 你好,请问你的意思是不是,2dx带的libiconv.lib只适用于win32,所以还需要下载你上传的iconv库重新编译一次android的适用版?

  5. 能给个具体的实例吗,我不清楚在Android.mk里如何设置正确的路径,谢谢了

    1. 在Classes目录下的Android.mk文件里添加
      LOCAL_C_INCLUDES += \
      $(LOCAL_PATH)/../iconv/include \
      $(LOCAL_PATH)/../libiconv/libcharset \
      $(LOCAL_PATH)/../libiconv/lib \
      $(LOCAL_PATH)/../libiconv/libcharset/include \
      $(LOCAL_PATH)/../libiconv/srclib \
      $(LOCAL_PATH)/../iconv

      LOCAL_WHOLE_STATIC_LIBRARIES += libiconv
      $(call import-module,iconv)

    1. 一般是跟Classes和Resources同一目录,不过没关系,只要你在编译的时候,在Android.mk里设置好正确的路径就行~

欢迎留言