环境: cocos2d-x 2.2.3 , win7 碰撞相信大家都不陌生了,无论在手游的游戏按钮对触摸事件的响应 还是页游时代游戏按钮对鼠标点击事件的侦听,都需要使用碰撞来判断.例如以下一个按钮,规格为:200X200,第二张图绿色部分原本为透明 按钮的原图
绿色为透明部分
坐标矩阵碰撞
类似这样一个按钮 如果是单纯使用坐标碰撞的话,只要坐标落在按钮图片内部,就会判断为点中,在cocos2d-x中坐标碰撞的一般做法是: - //pt 为鼠标 或者 触摸 击落时候的屏幕坐标
- //cc 为按钮对象 继承CCNode
- CCPoint pos = cc->convertToNodeSpace(pt);
- CCRect rect = cc->boundingBox();
- rect = CCRectApplyAffineTransform(rect, nodeToParentTransform());
- if (rect.containsPoint( pos ))
- {
- printf("用户点击了cc");
- }
复制代码这种处理方式显然并不理想,用户只要触摸或者点击了按钮的任何一个地方 都会被判定为点中.当然可以修改按钮图片,但是现在游戏的很多按钮并不是形状规则的,例如我现在项目的大部分按钮 就需要做成不规则的,动态的,有特效的(一个按钮都要做得那么炫酷…) 像素碰撞
我记得在以前做AS的时候就有像素碰撞的实现,在这里应该也可以实现类似的功能.在cocos2d-x中实现像素碰撞有几种思路: - 获取坐标点下的像素值,判断alpha值
- 给按钮加一层触摸区域的遮罩,需美术配合
- 把按钮图片的alpha分布剥离出来,当坐标落在按钮的时候判断
总和我目前情况的考虑之后(我目前这种按钮较少,为了几个按钮改动CCSprite和更底层的引擎代码不值当),我决定实现第三种. 剥离图片alpha值 如何解析带通道的图片数据?其实cocos2d-x中的CCImage已经帮我们做好了.我们发现CCImage中保存的数据是这个样子的. - //CCImageCommon_cpp 500-514
- if (channel == 4)
- {
- m_bHasAlpha = true;
- unsigned int *tmp = (unsigned int *)m_pData;
- for(unsigned short i = 0; i < m_nHeight; i++)
- {
- for(unsigned int j = 0; j < rowbytes; j += 4)
- {
- *tmp++ = CC_RGB_PREMULTIPLY_ALPHA(
- row_pointers[i][j], //R
- row_pointers[i][j + 1],//G
- row_pointers[i][j + 2],//B
- row_pointers[i][j + 3] );//A
- }
- }
-
- m_bPreMulti = true;
- }
- //CC_RGB_PREMULTIPLY_ALPHA 宏定义
- // premultiply alpha, or the effect will wrong when want to use other pixel format in CCTexture2D,
- // such as RGB888, RGB5A1
- #define CC_RGB_PREMULTIPLY_ALPHA(vr, vg, vb, va) \
- (unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + 1)) >> 8) | \
- ((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + 1) >> 8) < < 8) | \
- ((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + 1) >> 8) < < 16) | \
- ((unsigned)(unsigned char)(va) << 24))
- // on ios, we should use platform/ios/CCImage_ios.mm instead
复制代码由CCImageCommon_cpp的代码可以看出 CCImage把图片数据按照RGBA的顺序并且每个占用8位保存起来. 知道这些我们想剥离图片的alpha值就只需要分析CCImage的图片数据即可.首先按照8字节一个通道定义一个数据结构: - //BYTE 占8位
- struct S_RGBA
- {
- BYTE vr;
- BYTE vg;
- BYTE vb;
- BYTE va;
- S_RGBA()
- {
- vr = 0;
- vg = 0;
- vb = 0;
- va = 0;
- }
- };
复制代码剥离实现代码: - /-------------------------------------------------------------------------
- bool CPNGParse::parse(const string& strCSVFileName)
- {
- //strCSVFileName为图片路径
- cocos2d::CCImage *pImage = new cocos2d::CCImage();
- pImage->initWithImageFile( strCSVFileName.c_str() );
- S_RGBA * pData = (S_RGBA*)pImage->getData();
- int nWidth = pImage->getWidth();
- int nHeight = pImage->getHeight();
- int nLen = pImage->getDataLen();
- //新建文件
- cocos2d::engine::CWareFileWrite File(false);
- char cFile[1024];
- memset(cFile,0,1024);
- sprintf( cFile, "%s.%s",strCSVFileName.c_str(),"alpha");
- if( !File.open( cFile) )
- {
- return false;
- }
- char cTmp[20];
- sprintf( cTmp, "%d", nWidth);
- File.write(cTmp,4);
- sprintf( cTmp, "%d", nHeight);
- File.write(cTmp,4);
- int nBit = 0;
- long nTmp = 0;
- int lTest = 0;
- for (int j = 0;j < nHeight;j++)
- {
- for (int i = 0; i < nWidth ; i++)
- {
- S_RGBA srgba = pData[lTest];//j * nWidth + i
- if (nBit < 7 )
- {
- nTmp = (nTmp * 2) + (srgba.va > 60 ? 1 : 0);
- nBit++;
- }
- else
- {
- nTmp = (nTmp * 2) + (srgba.va > 60 ? 1 : 0);
- sprintf( cTmp, "%c",(int)nTmp);
- File.write(cTmp,1);
- nTmp = 0;
- nBit = 0;
- }
- lTest++;
- }
- }
- delete [] pData;
- return true;
- }
复制代码为了让我们生成的文件足够小,我们按照 某个像素透明则写入二进制0,不透明则写入二进制1 的原则.生成的文件发现只占原图片的1/10左右,虽然在手机上控制应用包大小非常重要,但是由于我本身这种按钮并不多.所以为每个不规则带透明通道的按钮图片素材增加 1/10 大小的文件是可以接受的.生成的文件用二进制打开大概会是这个样子:
最后就是像素碰撞检测了: - //strFileName 通道文件名字
- //pos 用户触摸坐标
- bool CPointHitPixel::hitTestByName(std::string& strFileName,cocos2d::CCPoint& pos)
- {
- cocos2d::engine::CWareFileRead File(false);
- if( !File.open( strFileName.c_str() ) )
- {
- return false;
- }
- // 行
- unsigned int nLine = pos.y;
- // 所在行的第几位
- unsigned int ncolumn = pos.x;
- unsigned int nBitChat = ncolumn/8 - 1;
- nBitChat += ncolumn%8 > 0 ? 1 : 0;
- // 读取 图片寛高
- char cData[4];
- File.read(cData);
- unsigned int nWidth = atoi(cData);
- File.read(cData);
- unsigned int nHeight = atoi(cData);
- nLine = nHeight - nLine;
- // 位置超出了
- if (nLine > nHeight || ncolumn > nWidth)
- {
- File.close();
- return false;
- }
-
- long lSeek = (nLine*nWidth + ncolumn)/8 + 8;
- if(File.getSize() < lSeek)
- {
- File.close();
- return false;
- }
- File.seek(lSeek,FILE_POS_BEGIN);
- BYTE cc;
- File.read(cc);
- // 判断是否透明
- if (cc != 0)
- {
- File.close();
- return true;
- }
- File.close();
- return false;
- }
复制代码这个检测碰撞代码其实还不是100%精确的,但是精度已经接近到8像素了.大概就是在你触摸的像素 的周围8个像素中 如果不透明 则判断点中了按钮.如果需要精度更高 可以在取出字节后判断像素对应的位是否为1. 目前这种方式暂时满足我项目的需求.
本文来自:http://shadowkong.com/archives/1840
|