知易游戏开发教程cocos2d-x移植版004

我们知道cocos2d-x是cocos2d-iphone项目的C++移植版本,它拥有跨平台的特性。同时cocos2d-x与cocos2d-iphone保持着高度地同步,这也就从根本上限制住它是一个为手机、平板等设备量身定做的游戏引擎。而对Win32等平台的支持,仅仅是为了方便开发与调试。
如果你正准备开发PC版的游戏,使用那种专为PC设计的引擎才是你正确的选择。虽然在一篇介绍cocos2d-x的文章里出现这样的句子有些扎眼,但我想有些事情还是讲明白的好,任何人的时间都不应该被浪费。

如果你也像我一样是从PC端开发加入到cocos2d-x中的,那么有很多概念是需要注意的。
比如,在PC上我们经常使用的鼠标在手机和平板上变成了手指头。这当然不单单是介质的转变,发生变化的还包括使用习惯。

消失的鼠标经过状态
大家都知道,在PC上,一个按钮通常包含4种状态——普通、鼠标经过、鼠标按下、禁用,而在手机和平板上,一个按钮通常只有3种状态——普通、按下、禁用。
这是很容易理解的。
第一,不管鼠标放在什么地方,它终究是脱离不了显示器屏幕的,而让一个人时刻把手指按在触摸屏上,要困难得多。
第二,鼠标是有按键的,而人手没有按键,想实现移动而脱离点击是有难度的。
所以在手机和平板上,传统意义的鼠标经过状态几乎不存在。

神奇的多点触摸
对于手机和平板用户来说,多点触屏稀松平常。虽然PC也有支持多点输入的外设,但支持这一特性的软件却不常见。对于用惯了PC的人来说,还是鼠标的单点输入更容易接受。

所谓习惯嘛,就是习以为常,时间久了,也就是那么回事儿了。今天我们就来认识认识这个Touch(触摸)。

cocos2d-x的触摸事件处理

在cocos2d-x中,触摸是一种重要的交互手段。虽然我们之前没有明确指出这个触摸事件,但是我们一刻也没有离开它。每一个Menu的触发,其实就是一次触摸。

CCStandardTouchDelegate

准确来说,我们与Touch有过一面之缘。在第2篇中,我们实现了一个点击屏幕后,飞船移动过去的功能。

1 void GameLayer::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent)
2 {
3 CCTouch *touch = (CCTouch *)pTouches->anyObject();
4 // 获得触摸点坐标
5 CCPoint location = touch->locationInView(touch->view());
6 CCPoint convertedLocation = CCDirector::sharedDirector()->convertToGL(location);
7 // 让飞船在1秒钟内移动过去
8 flight->runAction(CCMoveTo::actionWithDuration(1.0f, ccp(convertedLocation.x, convertedLocation.y)));
9 }

但是,想要上面的代码生效,还必须在初始化中开启触摸事件。

1 bool GameLayer::init(void)
2 {
3 ...
4 // accept touch now!
5 setIsTouchEnabled(true);
6 ...
7 }

跟进去看看开启触摸事件的实现。

 1 void CCLayer::setIsTouchEnabled(bool enabled)
2 {
3 if (m_bIsTouchEnabled != enabled)
4 {
5 m_bIsTouchEnabled = enabled;
6 if (m_bIsRunning)
7 {
8 if (enabled)
9 {
10 this->registerWithTouchDispatcher();
11 }
12 else
13 {
14 // have problems?
15 CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);
16 }
17 }
18 }
19 }
20
21 void CCLayer::registerWithTouchDispatcher()
22 {
23 CCTouchDispatcher::sharedDispatcher()->addStandardDelegate(this,0);
24 }

上面的代码通过CCTouchDispatcher添加标准代理实现开启触摸的功能。

所谓标准代理就是指你可以接收到所有的消息——开始(Began)、移动(Moved)、结束(Ended)、取消(Cancelled)。触摸点的信息是由CCSet表示一个集合。

与高权限相随的是高灵活性,而要获得高灵活性的代价就是你必须自己来编写额外的代码。

灵活性太高,反而不知从何开始,干脆就不讲了,以后有实际需要的时候再说。不过有一点必须提一下,标准代理类型与苹果的CocoaTouch框架基本一致,所以CocoaTouch的大部分方案在cocos2d-x中也是可行的。

CCTargetedTouchDelegate

相较标准代理来说,目标代理的功能自然是弱一些,但使用起来要简便得多。

使用目标代理有两个显著的好处:
1.你不用跟CCSet打交道,调度程序会替你做好拆分,每次使用时你会得到单一的Touch事件。
2.你可以通过让ccTouchBegan返回true来“认领”一个触摸事件。被“认领”的触摸事件只会被分发给“认领”它的代理对象。这样你就可以从多点触摸的检测苦海中解脱出来。

我们来实战演练一下。假设我们接到一个任务,要求在屏幕上显示一个精灵,这个精灵是一个循环播放的序列帧动画。当我们按在精灵上的时候可以拖动它,如果点在精灵外面,那就不能拖动。

首先分析下我们接到的任务。显示一个循环播放的序列帧动画精灵,这个没有难点,我们之前已经做过多次了。按在精灵上的时候可以拖动它,按在外面就不能拖动,这一条如果使用标准代理对象实现,必然要添加大量的判断和状态,显然目标代理是我们应该优先考虑的。

因此,我们需要的是一个符合目标代理的精灵,所以这是一个多重继承的类,它有2个父类——CCSprite和CCTargetedTouchDelegate。

1 class KillingLight : public CCSprite, public CCTargetedTouchDelegate
2 {
3 public:
4 KillingLight(void);
5 virtual ~KillingLight(void);
6 }

要接收触摸事件必须先进行注册,当不再需要的时候要进行反注册。我们重写CCNode的onEnter和onExit函数来完成触摸事件的注册与反注册。

 1 void KillingLight::onEnter(void)
2 {
3 CCSprite::onEnter();
4 // 我们希望独占被“认领”的触摸事件,所以第3个参数传入true
5 CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);
6 }
7
8 void KillingLight::onExit(void)
9 {
10 CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);
11 CCSprite::onExit();
12 }

在目标代理对象中,只有先“认领”触摸事件,才能使用。所以我们要重写CCTargetedTouchDelegate的ccTouchBegan函数。

因为任务要求只有点在精灵上面时才能拖动,所以我们需要增加判断触摸点是否在精灵上的判断。

 1 CCRect KillingLight::rect(void)
2 {
3 // 后面的比较是以锚点为标准的
4 const CCSize& s = getTextureRect().size;
5 const CCPoint& p = this->getAnchorPointInPixels();
6 return CCRectMake(-p.x, -p.y, s.width, s.height);
7 }
8
9 bool KillingLight::containsTouchLocation(CCTouch *touch)
10 {
11 return CCRect::CCRectContainsPoint(rect(), convertTouchToNodeSpaceAR(touch));
12 }
13
14 bool KillingLight::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
15 {
16 if (! containsTouchLocation(pTouch))
17 return false;
18
19 return true;
20 }

接着,我们重写CCTargetedTouchDelegate的ccTouchMoved函数,来实现拖动功能。

1 void KillingLight::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
2 {
3 CCPoint touchPoint = pTouch->locationInView(pTouch->view());
4 touchPoint = CCDirector::sharedDirector()->convertToGL(touchPoint);
5 setPosition(touchPoint);
6 }

为了便于使用,我们再添加一个静态成员函数KillingLightWithBatchNode来创建KillingLight对象。

 1 KillingLight* KillingLight::KillingLightWithBatchNode(cocos2d::CCSpriteBatchNode *batchNode, const cocos2d::CCRect& rect)
2 {
3 KillingLight *pobSprite = new KillingLight();
4 if (pobSprite && pobSprite->initWithBatchNode(batchNode, rect))
5 {
6 pobSprite->autorelease();
7 return pobSprite;
8 }
9 CC_SAFE_DELETE(pobSprite);
10 return NULL;
11 }

这样,一个支持点击拖动的精灵类就完成了,你可以像使用CCSprite那样使用它。

如果你希望让这个类再完美一些,比如只响应第一个按上的触摸事件以免多点抢一个精灵等等,你可以参考TouchesTest中的Paddle类。

代码下载:
http://files.cnblogs.com/cocos2d-x/ZYG004.rar

在处理触摸事件的时候,最需要注意的是坐标转换。这包括很多层面,是屏幕坐标系还是GL坐标系,是世界坐标还是局部坐标,还有坐标是相对哪个点计算的等等。

posted @ 2012-03-25 18:51 Bugs Bunny 阅读(...) 评论(...) 编辑 收藏