实际开发中,我们经常会遇到这样的情况。我们有一个层layer1,这个层包含一个menu层,menu1层里又包含了一个节点按钮button1。现在需要实现一个效果:点击button1弹出一个对话框,这个对话框里也包含一个menu2和一个按钮button2,点击button2能够关闭这个对话框。这个情况很普遍,在游戏ui中我们有大量的二级弹窗都需要用到这种效果(在这里我们不考虑直接在layer2里removefromparent,这样就不能达成学习目的了)。我们可以用三种方法实现这个效果,分别是:
1、通过代理中转;
2、通过parent指针获得父节点;
3、使用NotificationCenter消息管理器发送和接收消息;
我们一个一个的来探究它们的实现方式。
一、通过代理中转
首先我们需要在layer1里新建一个代理类Delegate,这个代理类只含有一个public的纯虚函数stop()。然后我们让layer1继承自Delegate,再在layer1中实现这个stop函数,具体内容先空着,等会再写。接着layer2中加一条成员变量Delegate* example;然后我们回到layer1的按钮button1的回调函数里在createlayer2之后,将layer1的this指针赋给layer2的example。之后在layer2的button2的回调里调用example的stop函数。最后我们在layer1的stop函数里将layer2的对象remove掉。文字还是很难理解,我们还是直接上代码吧:
这是layer1的头文件:
1 #ifndef __LAYER1_H__ 2 #define __LAYER1_H__ 3 4 #include "cocos2d.h" 5 USING_NS_CC; 6 class Delegate//代理类 7 { 8 public: 9 virtual void stop() = 0;//纯虚函数 10 }; 11 class layer1 :public Scene,public Delegate 12 { 13 public: 14 static Scene* scene(); 15 CREATE_FUNC(layer1); 16 bool init(); 17 void stop();//重写stop 18 void loadMenuItem();//加载按钮 19 void func();//按钮的回调 20 Layer* lay;//layer2的指针 21 }; 22 23 #endif
这是layer1的cpp文件:
1 #include "layer1.h" 2 #include "layer2.h" 3 Scene* layer1::scene() 4 { 5 Scene* scene1 = Scene::create(); 6 Layer* layer = Layer::create(); 7 scene1->addChild(layer); 8 return scene1; 9 } 10 bool layer1::init() 11 { 12 Scene::init(); 13 loadMenuItem(); 14 Size size = Director::getInstance()->getWinSize(); 15 Sprite* sprite = Sprite::create("HelloWorld.png");//背景 16 sprite->setPosition(size.width / 2, size.height / 2); 17 addChild(sprite); 18 return true; 19 } 20 void layer1::loadMenuItem() 21 { 22 MenuItem* item = MenuItemImage::create("CloseNormal.png", "CloseSelected.png", CC_CALLBACK_0(layer1::func, this));//按钮 23 item->setZOrder(100); 24 Menu* menu = Menu::create(); 25 item->setPositionX(item->getPositionX() + 300); 26 menu->addChild(item); 27 addChild(menu,1); 28 } 29 void layer1::func() 30 { 31 layer2* layer = layer2::create(); 32 layer->example = this;//将layer1的指针传进去 33 lay = layer; 34 addChild(lay); 35 } 36 void layer1::stop() 37 { 38 lay->removeFromParent();//将layer2删除释放 39 }
这是layer2的头文件:
1 #ifndef __LAYER2_H__ 2 #define __LAYER2_H__ 3 #include "cocos2d.h" 4 #include "layer1.h" 5 USING_NS_CC; 6 class layer2:public Layer 7 { 8 public: 9 CREATE_FUNC(layer2); 10 bool init(); 11 void loadMenuItem(); 12 Delegate* example;//存放layer1的指针 13 void func(); 14 }; 15 #endif
这是layer2的cpp文件:
1 #include "layer2.h" 2 3 bool layer2::init() 4 { 5 Sprite* sprite = Sprite::create("background.png"); 6 Size size = Director::getInstance()->getWinSize(); 7 sprite->setPosition(size.width / 2, size.height / 2); 8 addChild(sprite); 9 loadMenuItem(); 10 return true; 11 } 12 void layer2::loadMenuItem() 13 { 14 MenuItem* item = MenuItemImage::create("CloseNormal.png", "CloseSelected.png", CC_CALLBACK_0(layer2::func, this)); 15 item->setZOrder(100); 16 item->setPositionY(item->getPositionY() + 20); 17 Menu* menu = Menu::create(); 18 menu->addChild(item); 19 addChild(menu, 1); 20 } 21 void layer2::func() 22 { 23 example->stop(); 24 }
现在我们可以实现这个效果了,如下图:
但是我发现了一个致命的bug。因为layer2的背景层比较小,并没有遮住layer1的button1按钮。问题来了:在layer2层弹出以后,我们依然可以点击layer1层的按钮,之后在点击button2就会崩溃。怎么解决这个bug呢?这就要说到3.x 版本的触摸事件分发机制了。在2.x版本中,我们想要处理触摸事件,需要重载相应的touch函数,那时为了处理这种bug,我们需要将一个层的优先级设到-130(因为menu也是一个层,而且优先级相当之高,有-128,数值越低优先级越高,为了不让menu先处理触摸事件,我们需要将拦截层的优先级设置的比menu高才行),然后在这个层的触摸函数里决定如何调用其他层的触摸函数,如此达到分发触摸事件的目的。
然而3.x版本对触摸监听做出了重大改动,所有的触摸监听统一由事件分发器_eventDispatcher注册后处理。
我的方法是这样的:我的原始层是layer1,对话框是layer2,我在layer2里注册了一个触摸监听,把它的优先级设为-130(这个触摸监听需要你自己在onExit函数里release),这样在对话框弹出来之后,这个触摸监
听就会拦截所有的触摸信号,我在这个监听里面调用layer2的menu的touch函数,注意,当menu的touchbegan返回false的时候,我们不执行menu的touchend函数。这样就可以实现我们想要的效果了!
接下来上代码!
因为layer1的代码基本无改动,我就只贴layer2的代码了。
头文件:
1 #ifndef __LAYER2_H__ 2 #define __LAYER2_H__ 3 #include "cocos2d.h" 4 #include "layer1.h" 5 USING_NS_CC; 6 class layer2:public Layer 7 { 8 public: 9 CREATE_FUNC(layer2); 10 bool init(); 11 void loadMenuItem(); 12 Delegate* example; 13 void func(); 14 Sprite* background; 15 EventListenerTouchOneByOne* _ev;//我们需要保存监听事件,因为它不受cocos自动管理,我们需要手动释放它 16 bool flag; 17 void onExit() 18 { 19 Layer::onExit(); 20 _eventDispatcher->removeEventListener(_ev);//手动释放 21 } 22 }; 23 #endif
这是cpp文件:
122 #include "layer2.h" 123 124 bool layer2::init() 125 { 126 Sprite* sprite = Sprite::create("background.png"); 127 Size size = Director::getInstance()->getWinSize(); 128 sprite->setPosition(size.width / 2, size.height / 2); 129 background = sprite; 130 addChild(sprite); 131 loadMenuItem(); 132 return true; 133 } 134 void layer2::loadMenuItem() 135 { 136 MenuItem* item = MenuItemImage::create("CloseNormal.png", "CloseSelected.png", CC_CALLBACK_0(layer2::func, this)); 137 item->setPositionY(item->getPositionY() + 20); 138 Menu* menu = Menu::create(); 139 menu->addChild(item); 140 addChild(menu, 1); 141 EventListenerTouchOneByOne* ev = EventListenerTouchOneByOne::create();//创建一个单点触摸监听事件 142 ev->setSwallowTouches(true);//这个函数用于设置触摸吞噬,如果两个精灵相重叠,那么触摸信号只会由优先级最高的处理,不会向下传递 143 ev->onTouchBegan = [=](Touch* touch,Event* ev){ 144 flag = menu->onTouchBegan(touch, ev);//保存ontouchbegan的返回值,因为当flag为false的时候我们不需要执行ontouchend 145 return true; 146 }; 147 ev->onTouchEnded = [=](Touch* touch,Event* ev){ 148 if(!flag) 149 { 150 return; 151 } 152 menu->onTouchEnded(touch, ev);//调用menu的touchend函数 153 }; 154 _ev = ev; 155 _eventDispatcher->addEventListenerWithFixedPriority(ev, -130);//注册并设置优先级 156 } 157 void layer2::func() 158 { 159 example->stop(); 160 }
二、通过parent指针获得父节点
这个就很简单了,直接强转:
1 void layer2::func() 2 { 3 layer1* layer = (layer1*)getParent(); 4 //example->stop(); 5 layer->lay->removeFromParent(); 6 }
三、使用NotificationCenter消息管理器发送和接收消息
这个也非常简单,首先你要在button2的回调里设置发送消息:
1 NotificationCenter::getInstance()->postNotification("xuan",NULL);
接着在需要layer1的init函数里设置监听:
1 NotificationCenter::getInstance()->addObserver(this,callfuncO_selector(layer1::stop),"xuan",NULL);
在这里附上一篇博文的地址,详细的解释了NotificationCenter用法:http://blog.csdn.net/yangxuan0261/article/details/21793513
好了,这篇博文到此结束!
Cocos2dx 3.2 节点之间相互通信与设置触摸吞噬的方法