守望者--AIR技术交流

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: ANE FlasCC 炼金术
查看: 1057|回复: 0

[技术资料] 像素碰撞的一种实现

[复制链接]
  • TA的每日心情
    擦汗
    2018-4-10 15:18
  • 签到天数: 447 天

    [LV.9]以坛为家II

    1742

    主题

    2094

    帖子

    13万

    积分

    超级版主

    Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

    威望
    562
    贡献
    29
    金币
    51723
    钢镚
    1422

    开源英雄守望者

    发表于 2015-1-17 10:48:07 | 显示全部楼层 |阅读模式

    环境: cocos2d-x 2.2.3 , win7

    碰撞相信大家都不陌生了,无论在手游的游戏按钮对触摸事件的响应 还是页游时代游戏按钮对鼠标点击事件的侦听,都需要使用碰撞来判断.例如以下一个按钮,规格为:200X200,第二张图绿色部分原本为透明

    按钮的原图



    绿色为透明部分



    坐标矩阵碰撞

    类似这样一个按钮 如果是单纯使用坐标碰撞的话,只要坐标落在按钮图片内部,就会判断为点中,在cocos2d-x中坐标碰撞的一般做法是:

    1. //pt 为鼠标 或者 触摸 击落时候的屏幕坐标
    2. //cc  为按钮对象 继承CCNode
    3. CCPoint pos = cc->convertToNodeSpace(pt);
    4. CCRect rect = cc->boundingBox();
    5. rect = CCRectApplyAffineTransform(rect, nodeToParentTransform());
    6. if (rect.containsPoint( pos ))
    7. {
    8.     printf("用户点击了cc");
    9. }
    复制代码

    这种处理方式显然并不理想,用户只要触摸或者点击了按钮的任何一个地方 都会被判定为点中.当然可以修改按钮图片,但是现在游戏的很多按钮并不是形状规则的,例如我现在项目的大部分按钮 就需要做成不规则的,动态的,有特效的(一个按钮都要做得那么炫酷…)

    像素碰撞

    我记得在以前做AS的时候就有像素碰撞的实现,在这里应该也可以实现类似的功能.在cocos2d-x中实现像素碰撞有几种思路:

    • 获取坐标点下的像素值,判断alpha值
    • 给按钮加一层触摸区域的遮罩,需美术配合
    • 把按钮图片的alpha分布剥离出来,当坐标落在按钮的时候判断

    总和我目前情况的考虑之后(我目前这种按钮较少,为了几个按钮改动CCSprite和更底层的引擎代码不值当),我决定实现第三种.

    剥离图片alpha值

    如何解析带通道的图片数据?其实cocos2d-x中的CCImage已经帮我们做好了.我们发现CCImage中保存的数据是这个样子的.

    1. //CCImageCommon_cpp 500-514
    2. if (channel == 4)
    3. {
    4.     m_bHasAlpha = true;
    5.     unsigned int *tmp = (unsigned int *)m_pData;
    6.     for(unsigned short i = 0; i < m_nHeight; i++)
    7.     {
    8.         for(unsigned int j = 0; j < rowbytes; j += 4)
    9.         {
    10.             *tmp++ = CC_RGB_PREMULTIPLY_ALPHA(
    11.                 row_pointers[i][j], //R
    12.                 row_pointers[i][j + 1],//G
    13.                 row_pointers[i][j + 2],//B
    14.                 row_pointers[i][j + 3] );//A
    15.         }
    16.     }
    17.          
    18.     m_bPreMulti = true;
    19. }

    20. //CC_RGB_PREMULTIPLY_ALPHA  宏定义
    21. // premultiply alpha, or the effect will wrong when want to use other pixel format in CCTexture2D,
    22. // such as RGB888, RGB5A1
    23. #define CC_RGB_PREMULTIPLY_ALPHA(vr, vg, vb, va) \
    24.     (unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + 1)) >> 8) | \
    25.     ((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + 1) >> 8) < < 8) | \
    26.     ((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + 1) >> 8) < < 16) | \
    27.     ((unsigned)(unsigned char)(va) << 24))

    28. // on ios, we should use platform/ios/CCImage_ios.mm instead
    复制代码

    由CCImageCommon_cpp的代码可以看出 CCImage把图片数据按照RGBA的顺序并且每个占用8位保存起来.

    知道这些我们想剥离图片的alpha值就只需要分析CCImage的图片数据即可.首先按照8字节一个通道定义一个数据结构:

    1. //BYTE 占8位
    2. struct S_RGBA
    3. {
    4.     BYTE vr;
    5.     BYTE vg;
    6.     BYTE vb;
    7.     BYTE va;
    8.     S_RGBA()
    9.     {
    10.         vr = 0;
    11.         vg = 0;
    12.         vb = 0;
    13.         va = 0;
    14.     }
    15. };
    复制代码

    剥离实现代码:

    1. /-------------------------------------------------------------------------
    2. bool CPNGParse::parse(const string& strCSVFileName)
    3. {
    4.     //strCSVFileName为图片路径
    5.     cocos2d::CCImage *pImage = new cocos2d::CCImage();
    6.     pImage->initWithImageFile( strCSVFileName.c_str() );
    7.     S_RGBA * pData = (S_RGBA*)pImage->getData();
    8.     int nWidth = pImage->getWidth();
    9.     int nHeight = pImage->getHeight();
    10.     int nLen = pImage->getDataLen();

    11.     //新建文件
    12.     cocos2d::engine::CWareFileWrite File(false);

    13.     char cFile[1024];
    14.     memset(cFile,0,1024);
    15.     sprintf( cFile, "%s.%s",strCSVFileName.c_str(),"alpha");

    16.     if( !File.open( cFile) )
    17.     {
    18.         return false;
    19.     }


    20.     char cTmp[20];

    21.     sprintf( cTmp, "%d", nWidth);
    22.     File.write(cTmp,4);
    23.     sprintf( cTmp, "%d", nHeight);
    24.     File.write(cTmp,4);

    25.     int nBit = 0;
    26.     long  nTmp = 0;
    27.     int  lTest = 0;
    28.     for (int j = 0;j < nHeight;j++)
    29.     {
    30.         for (int i = 0; i < nWidth ; i++)
    31.         {
    32.             S_RGBA srgba = pData[lTest];//j * nWidth + i

    33.             if (nBit < 7 )
    34.             {
    35.                 nTmp = (nTmp * 2) + (srgba.va > 60 ? 1 : 0);
    36.                 nBit++;
    37.             }
    38.             else
    39.             {
    40.                 nTmp = (nTmp * 2) + (srgba.va > 60 ? 1 : 0);
    41.                 sprintf( cTmp, "%c",(int)nTmp);
    42.                 File.write(cTmp,1);
    43.                 nTmp  = 0;
    44.                 nBit = 0;
    45.             }

    46.             lTest++;
    47.         }
    48.     }

    49.     delete [] pData;

    50.     return true;
    51. }
    复制代码

    为了让我们生成的文件足够小,我们按照 某个像素透明则写入二进制0,不透明则写入二进制1 的原则.生成的文件发现只占原图片的1/10左右,虽然在手机上控制应用包大小非常重要,但是由于我本身这种按钮并不多.所以为每个不规则带透明通道的按钮图片素材增加 1/10 大小的文件是可以接受的.生成的文件用二进制打开大概会是这个样子:



    最后就是像素碰撞检测了:

    1. //strFileName 通道文件名字
    2. //pos 用户触摸坐标
    3. bool CPointHitPixel::hitTestByName(std::string& strFileName,cocos2d::CCPoint& pos)
    4. {
    5.     cocos2d::engine::CWareFileRead File(false);
    6.     if( !File.open( strFileName.c_str() ) )
    7.     {
    8.         return false;
    9.     }
    10.     // 行
    11.     unsigned int nLine = pos.y;
    12.     // 所在行的第几位
    13.     unsigned int ncolumn = pos.x;
    14.     unsigned int nBitChat = ncolumn/8  - 1;
    15.     nBitChat += ncolumn%8 > 0 ? 1 : 0;
    16.     // 读取 图片寛高
    17.     char cData[4];
    18.     File.read(cData);
    19.     unsigned int nWidth = atoi(cData);
    20.     File.read(cData);
    21.     unsigned int nHeight = atoi(cData);
    22.     nLine = nHeight - nLine;

    23.     // 位置超出了
    24.     if (nLine > nHeight || ncolumn > nWidth)
    25.     {
    26.         File.close();
    27.         return false;
    28.     }
    29.    
    30.     long lSeek = (nLine*nWidth + ncolumn)/8  +  8;

    31.     if(File.getSize() < lSeek)
    32.     {
    33.         File.close();
    34.         return false;
    35.     }

    36.     File.seek(lSeek,FILE_POS_BEGIN);

    37.     BYTE cc;
    38.     File.read(cc);
    39.     // 判断是否透明
    40.     if (cc != 0)
    41.     {
    42.         File.close();
    43.         return true;
    44.     }

    45.     File.close();
    46.     return false;
    47. }
    复制代码

    这个检测碰撞代码其实还不是100%精确的,但是精度已经接近到8像素了.大概就是在你触摸的像素 的周围8个像素中 如果不透明 则判断点中了按钮.如果需要精度更高 可以在取出字节后判断像素对应的位是否为1.

    目前这种方式暂时满足我项目的需求.



    本文来自:http://shadowkong.com/archives/1840


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    
    关闭

    站长推荐上一条 /4 下一条

    QQ|手机版|Archiver|网站地图|小黑屋|守望者 ( 京ICP备14061876号

    GMT+8, 2019-9-24 16:49 , Processed in 0.043223 second(s), 32 queries .

    守望者AIR

    守望者AIR技术交流社区

    本站成立于 2014年12月31日

    快速回复 返回顶部 返回列表