cocos2d-x 3.x小记

Aug 13, 2014


在做碰撞检测的时候,有时候需要微调一些效果,例如碰撞比例啊,在碰撞之前的那些位置做处理啊,如果没有边框的话难以分辨,其实在cocos2d-x里面是可以开启图片绘制边框的。在ccConfig.h文件中找到:

/** @def CC_SPRITE_DEBUG_DRAW
 If enabled, all subclasses of Sprite will draw a bounding box
 Useful for debugging purposes only. It is recommended to leave it disabled.
 
 To enable set it to a value different than 0. Disabled by default:
 0 -- disabled
 1 -- draw bounding box
 2 -- draw texture box
*/
#ifndef CC_SPRITE_DEBUG_DRAW
#define CC_SPRITE_DEBUG_DRAW 0
#endif

CC_SPRITE_DEBUG_DRAW这个宏定义改成1重编译就可以看到图片的绘制都出现了边框便于调试。

由于我是用C++硬编码的,游戏中一些地方用到了汉字,cocos2d-x里面对于字符都是以UTF-8编码进行处理的,于是就面临了将汉字转换成UTF-8编码的问题。(另外说一句:网上有很多童鞋说把cpp文件改成以UTF-8 + BOM保存就可以了,但是实际上是不行的,如果改成 VS 中高级保存项中的“签名的UTF-8”编码保存会引起其他的问题,目测是编译器在语法分析阶段无法识别某些字符。所以最完美的解决方案还是转换成UTF-8进行处理)

有两种办法:

  1. 利用cocos2d-x自带的iconvj进行编码转换,但是我看到iconv的头文件是在win32-specific目录下的,所以我目测这种转换方法貌似只能在windows上用(链接libiconv.lib文件),上代码:

    #include “iconv.h”

    class Cast { public: static const int atoi(const char* str); static const std::string itoa(const int& iparam); static const int codeConvert(const char from_charset, const char *to_charset, const char *inbuf, size_t inlen, char *outbuf, size_t outlen); static const std::string a2u(const char inbuf); static const std::string u2a(const char* inbuf); };

    const int Cast::codeConvert(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); return 0; }

    const std::string Cast::a2u(const char* inbuf) { size_t inlen = strlen(inbuf); char * outbuf = new char[inlen * 2 + 2]; std::string strRet; if (codeConvert(“gb2312”, “utf-8”, inbuf, inlen, outbuf, inlen * 2 + 2) == 0) { strRet = outbuf; } delete[] outbuf; return strRet; }

    const std::string Cast::u2a(const char* inbuf) { size_t inlen = strlen(inbuf); char * outbuf = new char[inlen * 2 + 2]; std::string strRet; if (codeConvert(“utf-8”, “gb2312”, inbuf, inlen, outbuf, inlen * 2 + 2) == 0) { strRet = outbuf; } delete[] outbuf; return strRet; }

第二种方法有一个缺点就是容易被玩家修改,还需要单独写读取代码。

  1. 将所有需要显示的汉字写入plist文件中,以UTF-8保存,然后在需要显示汉字的地方读取相应的汉字出来即可:

     <?xml version="1.0" encoding="UTF-8"?>
     <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     <plist version="1.0">
     	<dict>
     		<key>gametext</key>
     		<array>
     			<dict>
     				<key>11</key>
     				<string>疯狂奔跑火柴人!</string>
     			</dict>
    
    12 开始 13 简单 14 困难 15 噩梦 16 呵呵,地狱 </array> </dict>

    </plist>

做法就是在进入第一个场景的时候先初始化一遍,将所有的汉字都读出来,然后放进一个map中,随用随取。

class StringDict
{
public:
	static StringDict* getInstance();
	std::string getStringById(int id);
	void destory();
	void init();
private:
	DISALLOW_COPY_AND_ASSIGN(StringDict);
	StringDict();
	static StringDict* _pStringDict;
	std::map<int, std::string> _stringMap;
	bool _bIsInited;
};

StringDict* StringDict::_pStringDict = nullptr;

StringDict* StringDict::getInstance()
{
	if (_pStringDict == nullptr)
	{
		_pStringDict = new (std::nothrow) StringDict;
	}
	return _pStringDict;
}

StringDict::StringDict()
{
	_bIsInited = false;
}

void StringDict::init()
{
	using namespace cocos2d;
	const char* fileName = "text.plist";
	std::string plistFile= FileUtils::getInstance()->fullPathForFilename(fileName);
	auto rootDict = Dictionary::createWithContentsOfFile(plistFile.c_str());
	auto textArray = dynamic_cast<Array*>(rootDict->objectForKey("gametext"));
	for (auto i = 0; i < textArray->count(); i++)
	{
		auto dict = dynamic_cast<Dictionary*>(textArray->getObjectAtIndex(i));
		XY_FAILED_JUMP(dict);
		Array* pAllKeys = dict->allKeys();
		XY_FAILED_JUMP(pAllKeys);
		for (auto k = 0; k < pAllKeys->count(); k++)
		{
			auto keyPointer = (String*)pAllKeys->getObjectAtIndex(k);
			XY_FAILED_JUMP(keyPointer);
			auto value = (String*)dict->valueForKey(keyPointer->getCString());
			if (value != nullptr)
			{
				auto key = Cast::atoi(keyPointer->getCString());
				std::pair<int, std::string>mapVal(key, value->getCString());
				_stringMap.insert(mapVal);
			}
		}
	}
	_bIsInited = true;
	return;

Exit0:
	Log(eLogInfo, "There is some error in 'text.plist' file...");
}

std::string StringDict::getStringById(int id)
{
	if (id <= 0)
	{
		return "";
	}

	auto iter = _stringMap.find(id);
	if (iter != _stringMap.end())
	{
		return iter->second;
	}
	return "";
}

void StringDict::destory()
{
	_stringMap.clear();
}

游戏里面需要有一个暂停和继续的功能,正常想法一般就是在游戏场景里加入一个暂停层,在游戏场景里响应暂停事件,在暂停层里响应继续事件,开始的时候,我是这么干的,伪代码如下:

void GameScene::onPauseClick(Node* pSender)
{
	PauseLayer->setVisible(true);
	Director::getInstance()->pause();
}

void PauseLayer::onContinueClick(Node* pSender)
{
	Director::getInstance()->resume();
}

但是这种做法带来了一个很明显的问题,就是当游戏场景pause了之后,暂停层并不是立即出现,而是等一会儿才会出现,这样带来的视觉变化就是点击了暂停按钮之后,暂停层要等几秒才会出现,玩家体验不好。单步调试之后发现是pause耗时了,pause里面做的很多东西,略微比较耗时,而PauseLayer->setVisible(true)之后还要等待pause返回才会刷新界面即显示暂停层,因为等待pause而导致的这个问题。避免这个问题的方式:

void GameScene::menuPauseCallback(cocos2d::Ref* pSender)
{	
	if (!_bIsDealComplete)
	{
		return;
	}
	
	_bIsDealComplete = false;
	// 之所有要弄成回调是因为Director::getInstance()->pause();做的事情太多,如果以同步的方式进行调用的话
	// 会造成界面延迟,Sequence纯回调不会等待
	auto seq = Sequence::create(
		CallFuncN::create(CC_CALLBACK_1(GameScene::visibleContinueScene, this)),
		CallFuncN::create(CC_CALLBACK_1(GameScene::pauseGame, this)),
		nullptr
		);
	_pContinue->runAction(seq);
	
}

void GameScene::pauseGame(Node* sender)
{
	Director::getInstance()->pause();
}

void GameScene::visibleContinueScene(Node* sender)
{
	_pContinue->setVisible(true);

}

void GameScene::onContinueCallback(cocos2d::Ref* pSender)
{
	_bIsDealComplete = true;
	Director::getInstance()->resume();
	_pContinue->setVisible(false);
}