Cocos2d-x 3.0 点击交互的四种处理

1、概述

 
    游戏也好,程序也好,只有能与用户交互才有意义。手机上的交互大致可以分为两部分:点击和输入。其中点击更为重要,几乎是游戏中全部的交互。在Cocos2d-x 3.0中,更改了dispatch机制。同时加入了两种新的交互形式:listener 和touchEvent回调。加上先前版本中的点击函数回调,与重写layer层的touch消息响应,构成了一个相对完整的交互模式。先上一张Demo的图:
 

2、四种点击

 

1、函数回调

函数回调是最简单的响应形式,一直以来被用于MenuItem中的点击处理。在新版本中,此处发生了些小改变。我们可以看到在生成的程序中相关代码是这样的:
 
  1. // a selector callback 
  2. void menuCloseCallback(Object* pSender); 
  3.  
  4. auto closeItem = MenuItemImage::create("CloseNormal.png","CloseSelected.png", 
  5.                         CC_CALLBACK_1(HelloWorld::menuCloseCallback, this)); 
  6.  
  7. void HelloWorld::menuCloseCallback(Object* pSender) 
  8.     Director::getInstance()->end(); 
  9.  
  10. #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) 
  11.     exit(0); 
  12. #endif 
// a selector callback
void menuCloseCallback(Object* pSender);

auto closeItem = MenuItemImage::create("CloseNormal.png","CloseSelected.png",
                        CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

void HelloWorld::menuCloseCallback(Object* pSender)
{
    Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}
 

    其中CC_CALLBACK_1宏是将函数与对象绑定在一起,1表示这个函数有一个参数。当点击这个按钮时,会调用这个回调函数。

    除了基于c++11的这个形式的改变,使用方法与先前相同。

 

此种方式已经被舍弃,可以使用第四种方法做替代。

   2、Layer的touch消息响应

    虽然重写了底层的dispatch,但对这层的使用影响并不大。我们同样需要重写:

 

  1. //单点响应 
  2. virtual bool onTouchBegan(Touch* touch, Event  *event) override; 
  3. virtual void onTouchMoved(Touch* touch, Event  *event) override; 
  4. virtual void onTouchEnded(Touch* touch, Event  *event) override; 
  5. virtual void onTouchCancelled(Touch *touch, Event *event) override; 
  6.  
  7. //多点响应 
  8. virtual bool onTouchesBegan(Touch* touch, Event  *event) override; 
  9. virtual void onTouchesMoved(Touch* touch, Event  *event) override; 
  10. virtual void onTouchesEnded(Touch* touch, Event  *event) override; 
  11. virtual void onTouchesCancelled(Touch *touch, Event *event) override; 
//单点响应
virtual bool onTouchBegan(Touch* touch, Event  *event) override;
virtual void onTouchMoved(Touch* touch, Event  *event) override;
virtual void onTouchEnded(Touch* touch, Event  *event) override;
virtual void onTouchCancelled(Touch *touch, Event *event) override;

//多点响应
virtual bool onTouchesBegan(Touch* touch, Event  *event) override;
virtual void onTouchesMoved(Touch* touch, Event  *event) override;
virtual void onTouchesEnded(Touch* touch, Event  *event) override;
virtual void onTouchesCancelled(Touch *touch, Event *event) override;

 

    重写这些函数来对layer的点击做处理。当然,我们需要:

 

  1. setTouchEnabled(true)。 
setTouchEnabled(true)。

 

   此外有个小改动。对于单点触控响应,可以调用:

 

  1. //设置为单点响应 
  2. setTouchMode(Touch::DispatchMode::ONE_BY_ONE); 
  3. //设置为多点响应(默认) 
  4. setTouchMode(Touch::DispatchMode::ALL_AT_ONCE); 
//设置为单点响应
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
//设置为多点响应(默认)
setTouchMode(Touch::DispatchMode::ALL_AT_ONCE);

 

    进行设置,而不需要再用设置Delegate的方式来做了。

 

    3、TouchEvent响应

    这是新加入的响应方式。它主要是使用在UIWidget上的。可以将其看做是函数回调的一个扩展,为更多的响应处理提供可能。使用方法大致是:
 
  1. //声明 
  2. void touchButton(Object* object,TouchEventType type); 
  3.  
  4. //挂接到控件上 
  5. uiButton->addTouchEventListener(this,toucheventselector(HelloWorld::touchButton)); 
  6.  
  7. //实现 
  8. void HelloWorld::touchButton(Object* object,TouchEventType type) 
  9.     LabelTTF* label; 
  10.     switch (type) 
  11.     { 
  12.     case TouchEventType::TOUCH_EVENT_BEGAN: 
  13.         label = static_cast<LabelTTF*>(getChildByTag(11)); 
  14.         label->setString("按下按钮"); 
  15.         break; 
  16.     case TouchEventType::TOUCH_EVENT_MOVED: 
  17.         label = static_cast<LabelTTF*>(getChildByTag(11)); 
  18.         label->setString("按下按钮移动"); 
  19.         break; 
  20.     case TouchEventType::TOUCH_EVENT_ENDED: 
  21.         label = static_cast<LabelTTF*>(getChildByTag(11)); 
  22.         label->setString("放开按钮"); 
  23.         break; 
  24.     case TouchEventType::TOUCH_EVENT_CANCELED: 
  25.         label = static_cast<LabelTTF*>(getChildByTag(11)); 
  26.         label->setString("取消点击"); 
  27.         break; 
  28.     default: 
  29.         break; 
  30.     } 
//声明
void touchButton(Object* object,TouchEventType type);

//挂接到控件上
uiButton->addTouchEventListener(this,toucheventselector(HelloWorld::touchButton));

//实现
void HelloWorld::touchButton(Object* object,TouchEventType type)
{
	LabelTTF* label;
	switch (type)
	{
	case TouchEventType::TOUCH_EVENT_BEGAN:
		label = static_cast<LabelTTF*>(getChildByTag(11));
		label->setString("按下按钮");
		break;
	case TouchEventType::TOUCH_EVENT_MOVED:
		label = static_cast<LabelTTF*>(getChildByTag(11));
		label->setString("按下按钮移动");
		break;
	case TouchEventType::TOUCH_EVENT_ENDED:
		label = static_cast<LabelTTF*>(getChildByTag(11));
		label->setString("放开按钮");
		break;
	case TouchEventType::TOUCH_EVENT_CANCELED:
		label = static_cast<LabelTTF*>(getChildByTag(11));
		label->setString("取消点击");
		break;
	default:
		break;
	}
}

   因为所有的UIWidget都要添加到UILayer上,而UILayer通常作为UI的Widget都会在最上层,所以可以“基本上”认为这种使用方式会优先于其他方式处理点击消息。因为UILayer也会有层级的改变,比如它和MenuItem之间的关系。所以说“基本上”。

4、Listener消息响应方式

    这种实现也是新加入的。它更像是点击的一个层次过滤器。点击时,在listener队里中进行过滤。每一个listener检查自己保存的touch消息响应是否会被触发。一层一层过滤,最后在到Layer的touch消息响应。

    我觉得它的设计的初衷是为任意sprite提供一套自己制定的点击响应。但这样的实现仍然要写很多条件判断,没有能够控件化。可能我的理解有些偏差,欢迎讨论。

    它被设计成一个全局点击响应控制。具体的用法大致是这样:

 

 

  1. //auto dispatcher = EventDispatcher::getInstance(); 
  2. //  auto myListener = EventListenerTouch::create(Touch::DispatchMode::ONE_BY_ONE); 
  3. auto dispatcher = Director::getInstance()->getEventDispatcher(); 
  4. auto myListener = EventListenerTouchOneByOne::create(); 
  5.  
  6. //如果不加入此句消息依旧会向下传递 
  7. myListener->setSwallowTouches(true); 
  8.  
  9. myListener->onTouchBegan = [=](Touch* touch,Event* event) 
  10.     //some check 
  11.     if (pass) 
  12.     { 
  13.         return true; 
  14.     } 
  15.     return false; 
  16. }; 
  17.  
  18. myListener->onTouchMoved = [=](Touch* touch,Event* event) 
  19.     //do something 
  20. }; 
  21.  
  22. myListener->onTouchEnded = [=](Touch* touch,Event* event) 
  23.     //do something 
  24. }; 
  25.  
  26. dispatcher->addEventListenerWithSceneGraphPriority(myListener,mySprite1); 
  27. dispatcher->addEventListenerWithSceneGraphPriority(myListener,mySprite2); 
//auto dispatcher = EventDispatcher::getInstance();
//	auto myListener = EventListenerTouch::create(Touch::DispatchMode::ONE_BY_ONE);
auto dispatcher = Director::getInstance()->getEventDispatcher();
auto myListener = EventListenerTouchOneByOne::create();

//如果不加入此句消息依旧会向下传递
myListener->setSwallowTouches(true);

myListener->onTouchBegan = [=](Touch* touch,Event* event)
{
    //some check
    if (pass)
    {
        return true;
    }
    return false;
};

myListener->onTouchMoved = [=](Touch* touch,Event* event)
{
	//do something
};

myListener->onTouchEnded = [=](Touch* touch,Event* event)
{
	//do something
};

dispatcher->addEventListenerWithSceneGraphPriority(myListener,mySprite1);
dispatcher->addEventListenerWithSceneGraphPriority(myListener,mySprite2);

 

    其原理是在dispatcher中检查listener列表,例如myListener或加进来的其他listener。然后每个listener检查自己中的Item看能否达到检查条件,例如:mySprite1,mySprite2。然后执行相应的操作。但这样的话,当控件很多的时候,每一次事件都进行这种双链表的检查操作不知会不会影响些性能?

3、实例

    光说不练假把式,于是就动手写了一个上面的Demo:

 

    点击背景区域可以移动整个场景,点击蓝色小方块可以半透明移动它,点击蓝色按钮可以更改文字,显示状态。点击右下角按钮退出程序。

    项目配置可参照:

Cocos2d-x 3.0 开发(十六)cocos2dx-3.0beta版建立新项目并加载CocoStudio导出文件

 

4、总结

    根据不同的交互需要,选择不同的实现方式,会更有利于我们的维护和扩展,相应例子可以在下面下载。

 

 

    Demo下载:http://download.csdn.net/detail/fansongy/6399291 不要资源分,觉得好劳烦点下 “顶” ~

   Demo For Beta2 下载:http://download.csdn.net/detail/fansongy/6892047

posted on 2014-07-14 09:46  the seal  阅读(463)  评论(0编辑  收藏  举报