今天暖手宝已到,就是键盘还不太熟悉。躺在床上写博客的感觉就是好啊。上篇讲解了不同平台的touch事件是如何进入到cocos2d-x的,现在来看看cocos2d-x是如何对其处理的。同样的道理,我们先看看handleTocuhesBegin中的代码:
void CCEGLViewProtocol::handleTouchesBegin(int num, int ids[], float xs[], float ys[])
{
CCSet set;
for (int i = 0; i < num; ++i)
{
int id = ids[i];
float x = xs[i];
float y = ys[i];
CCInteger* pIndex = (CCInteger*)s_TouchesIntergerDict.objectForKey(id);
int nUnusedIndex = 0;
// it is a new touch
if (pIndex == NULL)
{
nUnusedIndex = getUnUsedIndex();
// The touches is more than MAX_TOUCHES ?
if (nUnusedIndex == -1) {
CCLOG("The touches is more than MAX_TOUCHES, nUnusedIndex = %d", nUnusedIndex);
continue;
}
CCTouch* pTouch = s_pTouches[nUnusedIndex] = new CCTouch();
pTouch->setTouchInfo(nUnusedIndex, (x - m_obViewPortRect.origin.x) / m_fScaleX,
(y - m_obViewPortRect.origin.y) / m_fScaleY);
//CCLOG("x = %f y = %f", pTouch->getLocationInView().x, pTouch->getLocationInView().y);
CCInteger* pInterObj = new CCInteger(nUnusedIndex);
s_TouchesIntergerDict.setObject(pInterObj, id);
set.addObject(pTouch);
pInterObj->release();
}
}
if (set.count() == 0)
{
CCLOG("touchesBegan: count = 0");
return;
}
m_pDelegate->touchesBegan(&set, NULL);
}
老实说,这段代码还是做一些转换事情。其主要内容就是将多件触摸的消息转化为一个set集合。而这个set集合中所包含的对象是CCTouch指针。我们来看看它的结构:
private:
int m_nId;
bool m_startPointCaptured;
CCPoint m_startPoint;
CCPoint m_point;
CCPoint m_prevPoint;
从这些结构上来看,cocos2d-x之所以要对touch事件进一步封装,是为了支持一些内建的函数,方便游戏开发。它记录了每个touch消息的生命周期,并且提供了一些函数方便操作,其中一个比较生僻的是:
CCPoint CCTouch::getDelta() const
{
return ccpSub(getLocation(), getPreviousLocation());
}
有时候我不尽感慨,cocos2d-x的确发展的相当人性化了,在之前这些都是要手动处理的啊。还有其他几个handleTouch函数都是差不多,逻辑也很简单。其中要注意的是cocos2d-x使用了一些全局静态对象来进行管理,之后生成一个全新的set集合传递给下层函数。
m_pDelegate->touchesBegan(&set,NULL);
看其代码,它只是做一个检测而已,真正的逻辑实现在touches中:
void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent)
{
if (m_bDispatchEvents)
{
this->touches(touches, pEvent, CCTOUCHBEGAN);
}
}
其他几个函数也是类似,只是最后一个参数不同。一个十分十分有意思的事情是,touch事件是通过不同的函数收集起来的,这里却传递给同一个函数作为处理,而且我们知道cocos2d-x的API是将不同的触摸事件分开调用的,也就是说在这个函数内部,他又将不同的事情给分发出去了。这不是多此一举吗?而且这个touches的参数十分奇怪,居然接受的是一个unsigned int作为不同事件的标识,鬼知道是什么意思啊?
void touches(CCSet* pTouches,CCEvent* pEvent,unsigned int uIndex);
这个函数很是复杂,我开始怀疑花这么多时间来看这些代码是否是值得的。虽然还没有到失控的地步。我简单解释下这个函数的内部处理,代码就不贴了:
- 首先处理TargetTouch handers,按优先级将消息分发出去,并且检查是否吞掉消息。
- 处理Standard Handers,将不同的事件分发出去。
- 处理需要移除的touch hander。
- 处理需要添加的touch hander。
- 处理是否要全部移除touch hander。
要注意的是Standard touch 和 Target touch对消息的处理方式是不同的。前者收到的是一个touch集合,也就是iOS标准的处理方式,后者是遍历这个touch集合,再将里面的touch分发给不同的Target handler,也就是说他们收到的是一个点,如果有多点触摸,他们会被调用多次。了解其时序的不同或许对解决某些棘手的问题有帮助,不过我是没机会遇到了。
我记得之前有个问题,如果在touch的循环之中弹出原生的UI,这时cocos2d-x会失去焦点,进而触发cancel消息,从而导致程序崩溃。不知道新版会不会还有这个问题。不管有没有,我认为在一个集合的遍历过程中执行有可能改变这个集合内容的行为是十分愚蠢的。或许你能保证做了充足的考虑确保其没有问题(比如之前的ActionManager),但是别人也要付出相当的代价来理解这种特殊逻辑。不做这样的事情是最好的办法,哪怕代码繁琐一点,只要逻辑清晰也是值得的。
虽然cocos2d-x封装了一些比较人性化的操作,但是实际使用过程中对于Touch设计的评价普遍不高,因为它没有一个有效实现模态UI的方法,而且能不能点击和显示的层次是独立的,所以你要自己手动同步好这部分逻辑。cocos2d-x承诺会修改,而且第三方已经出现了这种框架。但是在没有这种便利设施的时候如何优雅的实现和管理,才是考验程序设计能力的事。