现在的位置: 首页 > 综合 > 正文

Cocos2d-x 自定义可接收处理触摸消息精灵类

2013年12月11日 ⁄ 综合 ⁄ 共 6680字 ⁄ 字号 评论关闭

这篇文章将讲解一下如何自定义的创建精灵类,并且该精灵类可以接受和处理触摸信息。

本文涉及程序代码下载地址:点击打开链接

参考文章:http://blog.csdn.net/xzongyuan/article/details/9187825

下面从两个方面来记述:


一、自定义精灵类。

显然要继承自CCSprite这个类,然后实现一个创建该类对象的类方法就可以了,也就是类似CCSprite中的create方法,可以参照引擎中的源码实现(如下):

CCSprite* CCSprite::create(const char *pszFileName)
{
    CCSprite *pobSprite = new CCSprite();
    if (pobSprite && pobSprite->initWithFile(pszFileName))
    {
        pobSprite->autorelease();
        return pobSprite;
    }
    CC_SAFE_DELETE(pobSprite);
    return NULL;
}

下面就是我所自定义的ViewSprite精灵类。

ViewSprite();
    ~ViewSprite();
    
    static ViewSprite* createWithPic(const char* name);
    bool setUpdateView();

ViewSprite* ViewSprite::createWithPic(const char *name)
{
    ViewSprite* pobView = new ViewSprite();
    if (pobView && pobView->initWithFile(name) && pobView->setUpdateView()) {
        pobView->autorelease();
        return pobView;
    }
    CC_SAFE_DELETE(pobView);
    return NULL;
}

bool ViewSprite::setUpdateView()
{
    bool isRet = false;
    do {
        
        isRet = true;
    } while (0);
    return isRet;
}

关于其中的 setUpdateView() 方法,你可以在其中做一些自定义的处理。

(这部分内容比较简单大笑


二、自定义精灵类接收和处理触摸信息

传统意义上说,我们都是在Layer中处理触摸信息的,但是实际上,我们可以通过继承 CCTargetedTouchDelegate  或者 CCStandardTouchDelegate 这两个触摸事件的委托代理类,实现其中相关的触摸回调方法就可以实现精灵类接受和处理触摸信息了。而这两个委托代理类的区别呢就是单点触摸(targeted)和多点触摸(standard)了。


1、下面选择继承 CCTargetedTouchDelegate ,处理单点触摸

在其onEnter方法中,注册触摸事件;在onExit方法中,删除该触摸事件。

void ViewSprite::onEnter()
{
    CCSprite::onEnter();
    CCLog("view sprite onEnter");
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

void ViewSprite::onExit()
{
    CCSprite::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

关于这里的注册触摸事件,有一点需要重点注意:


自定义精灵类的接受触摸事件的优先级一定要大于(或者等于)我们所有添加到的layer的触摸优先级。

“大于” 应该很好理解,这样就可以让自定义的精灵类对象先接受到触摸消息。

至于“等于“,由于自定义的精灵类对象是添加到layer上的,那么即使二者的优先级是相等的,那么也是自定义精灵先接受到触摸消息。

注意:①关于addTargetDelegate方法的第二个参数,触摸优先级;优先数值越小,代表接收触摸优先级越大

   
②关于addTargetDelegate方法的第三个参数,是否吞噬该触摸事件;也即是,如果当前的自定义精灵类首先接受到触摸事件的消息了,那么如果不想将这个触摸消息往下传递了,就true,表示吞噬掉这个触摸事件。否则,返回false,表示继续传递这个触摸消息,那么触摸优先级比这个自定义精灵类对象小的节点就可以接收到这个触摸消息了。

2、根据自定义精灵类的实现大小,限定其接受处理触摸信息的范围。

创建自定义的精灵类对象,添加到layer中;那么接收触摸的范围也就是该自定义精灵所显示的大小,那么就需要对触摸点坐标和该精灵对象做一个碰撞检测,判断是否是点击到了这个自定义精灵对象。

由于触摸回调方法所返回的触摸点坐标是opengl屏幕坐标系下的坐标,那么在进行碰撞检测的时候注意将触摸点坐标转换成自定义精灵对象所在的节点坐标。

下面给出解决实现方法:

CCRect ViewSprite::rect()
{
    CCSize size = getTexture()->getContentSize();
    
    //如果自定义精灵对象的锚点在其中心ccp(0.5,0.5)
    return CCRectMake(-size.width / 2, -size.height / 2, size.width, size.height);
    
    //如果自定义精灵对象性的锚点在其左下角ccp(0,0)
//    return CCRectMake(0, 0, size.width, size.height);
}

bool ViewSprite::containsTouchLocation(cocos2d::CCTouch *touch)
{
    CCRect my = rect();
    
    //    CCLog("%f %f %f %f",my.size.width,my.size.height,my.origin.x,my.origin.y);
    
    CCPoint p = convertTouchToNodeSpaceAR(touch);
    //    CCLog("point = %f   %f",p.x,p.y);
    
    return my.containsPoint(p);
}

对于这两个方法的说明:

(1)、第一个方法是用于返回自定义精灵对象添加到layer后的 rect 。注意到如果锚点位置不同,那么其返回的值也不同(参看代码)

(2)、第二个方法,接收从触摸回调方法中获取的touch信息,进行碰撞检测,判断是否点击到了该自定义精灵对象,在进行注意需要将触摸点的坐标转成自定义精灵对象所在的节点坐标,并且与锚点相关--AR

这里看到方法中有CCLog的debug代码,注意到里面 %f本人开始没有注意,直接用了%d ,导致输出的debug信息异常,后来才发现原来这些数值的类型都是float,所以要注意细节呀。


3、触摸回调方法

bool ViewSprite::ccTouchBegan(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    bool bRet = false;
    if (containsTouchLocation(touch))
    {
        CCLog("Touchable Sprite Began");
        
        bRet = true;
    }
    
    return bRet;
}

void ViewSprite::ccTouchMoved(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    if (containsTouchLocation(touch))
    {
        CCLog("Touchable Sprite Moved");
    }
}

void ViewSprite::ccTouchEnded(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    if (containsTouchLocation(touch))
    {
        CCLog("Touchable Sprite Ended");
    }
}

显然在接收到触摸消息touch之后,先判断是否触摸到了这个自定义精灵类对象,然后再作相应的处理。

4、在layer中创建添加自定义精灵类对象

(1)首先我们先处理一下 layer 的接收触摸事件。

void EditScene::onEnter()
{
    CCLayer::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 1, true);
}

void EditScene::onExit()
{
    CCLayer::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

bool EditScene::ccTouchBegan(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    CCLog("touch edit scene began");
    return true;
}

void EditScene::ccTouchMoved(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    CCLog("touch edit scene moved");
}

void EditScene::ccTouchEnded(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
{
    CCLog("touch edit scene ended");
}

注意到其接收触摸事件的优先级数值为1,数值比自定义精灵类的优先级大,但是其优先级比自定义精灵类小。


(2)这里特别要注意触摸事件的传递顺序:

对于  virtualbool ccTouchBegan(CCTouch
*touch,
CCEvent *event);  这个方法:

如何返回如果返回false,表示不处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,而交由后续接收触屏消息的对象处理;

如果返回true,表示会处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,并且消耗掉此触屏消息。

总结如下:

1.CCLayer 只有一层的情况:

virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);

a.返回false,则ccTouchMoved(),ccTouchEnded()不会再接收到消息
b.返回true,则ccTouchMoved(),ccTouchEnded()可以接收到消息

2.CCLayer 有多层的情况:
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
a.返回false,则本层的ccTouchMoved(),ccTouchEnded()不会再接收到消息,但是本层之下的其它层会接收到消息
b.返回true,则本层的ccTouchMoved(),ccTouchEnded()可以接收到消息,但是本层之下的其它层不能再接收到消息

3.有自定义接收触摸消息的精灵的情况:
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
a.返回false,则此精灵的ccTouchMoved(),ccTouchEnded()不会再接收到消息,而此精灵所在的层会接收到触摸消息(如果精灵所在层没有设置接收触摸消息,则向下传递
b.返回true,则此精灵的ccTouchMoved(),ccTouchEnded()会继续接收消息,并消耗此消息(即不再向所在层和其他层传递)

根据上面的总结第三点,如果我们的触摸点刚好落在自定义精灵范围内,那么返回true,此自定义的精灵对象的其他触摸回调方法也会接收这个触摸消息,继续处理。否则的话,返回false,此精灵所在的层会接受到这个触摸消息。


(3)接着就是创建自定义精灵类对象,然后添加到 layer 中

按照通常的方法,我们在layer的 init() 方法中创建和添加:

CCSize size = CCDirector::sharedDirector()->getWinSize();
    ViewSprite* vSprite = ViewSprite::createWithPic("Icon.png");
    vSprite->cocos2d::CCNode::setPosition(size.width/2, size.height/2);
    this->addChild(vSprite);

运行正常:



如果点击自定义的精灵对象区域,那么会有相应的显示:

Touchable Sprite Began

Touchable Sprite Moved

Touchable Sprite Ended

如果点击非自定义精灵区域,那么也会有相应的显示:

touch edit scene began

touch edit scene moved

touch edit scene ended


大致关于,如何自定义创建一个可接收和处理触摸消息的精灵就差不多结束了,但是下面我还想补充一点内容,就是我在实现这个内容过程中遇到的很坑很坑的一个问题。抓狂

--- onEnter() 方法的执行问题。


同时贴出这个例子中自定义精灵 ViewSprite 和 EditScene 中的onEnter方法和onExit方法:

void ViewSprite::onEnter()
{
    CCSprite::onEnter();
    CCLog("view sprite onEnter");
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

void ViewSprite::onExit()
{
    CCSprite::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

void EditScene::onEnter()
{
    CCLayer::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 1, true);
}

void EditScene::onExit()
{
    CCLayer::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

那么由于疏忽,开始的时候我在EditScene中的onEnter中并没有添加  CCLayer::onEnter();  这个语句,那么有问题吗?当然有很大的问题了。

导致的问题是:添加到该layer的自定义精灵对象的onEnter方法没有调用,那么也就没有给该自定义精灵添加触摸事件,那么也就导致了该精灵无法接收触摸消息,处理触摸事件。

那么这个是为什么呢?

首先我们要知道,为什么EditScene中onEnter方法中,没有执行 CCLayer::onEnter(); 这个语句,怎么就会导致没有执行
其子节点(也就是自定义精灵对象) 的onEnter方法呢?

我们可以在正确的代码上跟踪一下这个过程:

①在 EditScene 中的onEnter方法添加断点:

②进入 CCLayer::onEnter();
这个方法



大家看到没有呀!  layer会遍历所有的子节点,然后调用执行其onEnter方法。

----- 哦,原来子节点的onEnter方法的执行是在CCLayer的onEnter方法里面呀!

-----------为了找到这个原因,我可是费了不少时间呀!算了,不吐槽了,说多了都是泪呀!大哭


好,这个问题也解决了!关于这个问题呢,还有一个重要的启示:一般重载方法,都是要先调用父类的方法。

---要不然,你有时候会遇到一些十分莫名其妙的问题,我已经深有体会了。委屈


本文涉及程序代码下载地址:点击打开链接

抱歉!评论已关闭.