您的位置:首页 > 移动开发 > Cocos引擎

cocos2D-x 学习笔记 【三】事件优先级和自定义事件

2014-08-03 15:57 597 查看
上一篇博文讲到,在没有修改 优先级 的情况 和 没有设置 setSwallowTouches
为true 的情况下,事件机制是 从下到上,从后到前 的顺序来分发事件的。

其实这样描述还是不够准确,所以就来研究一下这个事件分发的优先级到底受什么因素影响,要怎么做可以达到想要的效果。

本博文基于以下环境:

Cocos2D-x v3.2

vc++ 2012

win7

=============================正文开始=====================================

第一种情况,使用 addEventListenerWithSceneGraphPriority()
方法注册监听。

在官网上,对这个方法的解释是,通过添加事件监听器,将精灵以显示优先级
(SceneGraphPriority) 添加到事件分发器。这就是说,当我们点击精灵按钮时,根据屏幕显示的“遮盖”实际情况,进行有序的函数回调。


什么意思呢,就是 在前面显示的 ,优先得到回调(也就是事件分发)。比如游戏中,显示在前面的UI,比地图优先得到回调;同一个场景中,显示在上面的层,比下面的优先得到回调。

我们来解释一下其中的原理。

这个函数所添加的所有事件,优先级都默认为0,也就是最小。

而事件分发的先后由所绑定的节点与屏幕的远近来判断,也就是离屏幕近,该节点就显示在上层,优先得到事件分发。

如何影响节点的绘制远近?

有两个函数:setLocalZOder(int localZOder) 和 setGlobalZOrder(int globalZOder)。

globalZOrder 是用于 渲染器 中用来给“绘制命令”排序的。

localZOrder 是用于父节点的子节点数组中给 节点 对象排序的。

这两个函数都会影响到 节点在屏幕上的层次分布,而globalZOder更彻底,直接控制着渲染的次序,值越大,离屏幕就越近。

说说两个函数用法:

localZOder:

第一个,在 addchild 时,之间传入。

auto sprite1 = Sprite::create("CyanSquare.png");
   sprite1->setAnchorPoint(Vec2(0.5,0.5));
   sprite1->setPosition(Vec2(origin.x + visibleSize.width/2 +50 , origin.y + visibleSize.height/2 + 50 ));
   this->addChild(sprite1,1); //后一个参数为 localZOder,即 z 轴大小。


第二个修改localZOder的方法是直接传入。

auto sprite1 = Sprite::create("CyanSquare.png");
   sprite1->setAnchorPoint(Vec2(0.5,0.5));
   sprite1->setPosition(Vec2(origin.x + visibleSize.width/2 -75 , origin.y + visibleSize.height/2 - 75 ));
   sprite1->setLocalZOrder(1);//效果等同。
   this->addChild(sprite1);
要注意的是,子节点的Z轴受父节点影响,如果一个父节点 Z轴值 比 另一个父节点的 Z轴值 大,那么不论第二个父节点的子节点设置多大的 localZOder值,都会显示在第一个父节点以及它的子节点下面。

而子节点,会显示在父节点上面。

请看下面代码:

auto sprite1 = Sprite::create("CyanSquare.png");
   sprite1->setAnchorPoint(Vec2(0.5,0.5));
   sprite1->setPosition(Vec2(origin.x + visibleSize.width/2 -75 , origin.y + visibleSize.height/2 - 75 ));
   sprite1->setLocalZOrder(1);
   this->addChild(sprite1);
 
   auto sprite2 = Sprite::create("MagentaSquare.png");
   sprite2->setAnchorPoint(Vec2(0.5,0.5));
   sprite2->setPosition(Vec2(origin.x , origin.y  ));

   auto sprite3 = Sprite::create("YellowSquare.png");
   sprite3->setAnchorPoint(Vec2(0.5,0.5));
   sprite3->setPosition(Vec2(origin.x + visibleSize.width/2 -50, origin.y + visibleSize.height/2 - 50));

  
   sprite3->addChild(sprite2);
   this->addChild(sprite3);

   auto listener1 = EventListenerTouchOneByOne::create();
   listener1->setSwallowTouches(false);
   listener1->onTouchBegan = [](Touch* touch , Event* event ) {
	   log( "sprite1 is on touch began");
	

	   return true;
	   
   };
   listener1->onTouchEnded = [](Touch* touch , Event* event) {
	    log( "sprite1 is on touch ended ");
	
   };
   listener1->onTouchMoved = [](Touch* touch , Event* event ) {
	   log( "sprite1 is on touch moved" );
   };

   auto listener2 = EventListenerTouchOneByOne::create();
   listener2->setSwallowTouches(false);
   listener2->onTouchBegan = [](Touch* touch , Event* event ) {
	   log( "sprite2 is on touch began");

	   return true;

   };
   listener2->onTouchEnded = [](Touch* touch , Event* event) {
	   log( "sprite2 is on touch ended ");

   };
   listener2->onTouchMoved = [](Touch* touch , Event* event ) {
	   log( "sprite2 is on touch moved" );
   };

   auto listener3 = EventListenerTouchOneByOne::create();
   listener3->onTouchBegan = [](Touch* touch , Event* event ) {
	   log("sprite3 is on touch");
	   return true;
   };
   listener3->onTouchMoved = [](Touch* touch , Event* event ) {
	   log("sprite3 is on touch move" );
   };
   listener3 -> onTouchEnded = [](Touch* touch , Event* event ) {
	   log( "sprite3 is on touch end ");
   };
   _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1 , sprite1 );
   _eventDispatcher->addEventListenerWithSceneGraphPriority(listener2 , sprite2 );
   _eventDispatcher->addEventListenerWithSceneGraphPriority(listener3 ,sprite3 );



在上面代码中,sprite1的 localZOder 比 sprite3 要大,而sprite2是 sprite3 的子节点。

所以显示如图:


sprite1
在 sprite2 和 sprite3 上面,第一个显示,而sprite2 作为 sprite3 的节点在第二位显示,最后是sprite3显示。

点击屏幕任意一点,得到 事件监听的反馈如图:



sprite1、2、3分别依次调用,和它们在屏幕上的显示排序一致。

globalZOrder


auto sprite3 = Sprite::create("YellowSquare.png");
   sprite3->setAnchorPoint(Vec2(0.5,0.5));
   sprite3->setPosition(Vec2(origin.x + visibleSize.width/2 -50, origin.y + visibleSize.height/2 - 50));

  
   sprite3->addChild(sprite2);
   this->addChild(sprite3);
   sprite3->setGlobalZOrder(100);
和上面的程序唯一不同的地方就是,在添加sprite3之后,将它的globalZOrder
设置为100。

看效果:





可以看到,原本在底下的***sprite3跑到了最上面,也第一个触发了事件监听。

*******************************************************************************************************

说完第一种用 addEventListenerWithSceneGraphPriority()
方法注册监听的方法,说说第二种,用addEventListenerWithFixedPriority()。

第二种方法的使用方式和第一种不一样


这个方法的传参是(EventListener * listener,int
fixedPriority),第一个是 事件监听器,第二个是事件优先级。它没有像第一种方法那样,直接绑定事件对象,它的默认绑定对象是当前节点,如果这条语句写在layer里面,那么注册的节点就是这个layer的实例,sprite等同理。

也就是说,这个方法大多数时候都用在我们的自定义类里面,比如继承sprite,写我们自己的sprite,只要传入一个
priority(优先级)作为参数,就可以让这个自定义的显示类自带一个事件监听器而不用在外面进行注册了。

看源码:NewEventDispatcherTest.cpp

class TouchableSprite : public Sprite  //继承sprite的自定义类
{
public:
    static TouchableSprite* create(int priority = 0)//静态创建函数,传入优先级,返回一个自定义的sprite
    {
        auto ret = new TouchableSprite(priority);
        if (ret && ret->init())
        {
            ret->autorelease();
        }
        else
        {
            CC_SAFE_DELETE(ret);
        }
        return ret;
    }

protected:
    TouchableSprite(int priority)
    : _listener(nullptr)
    , _fixedPriority(priority)
    , _removeListenerOnTouchEnded(false)
    {
    }
    
public:
    void onEnter() override //在创建到舞台的时候调用
    {
        Sprite::onEnter();
        
        auto listener = EventListenerTouchOneByOne::create();//创建事件监听
        listener->setSwallowTouches(true);
        
        listener->onTouchBegan = [=](Touch* touch, Event* event){
            
            Vec2 locationInNode = this->convertToNodeSpace(touch->getLocation());
            Size s = this->getContentSize();
            Rect rect = Rect(0, 0, s.width, s.height);
            
            if (rect.containsPoint(locationInNode))
            {
                this->setColor(Color3B::RED);
                return true;
            }
            return false;
        };
        
        listener->onTouchEnded = [=](Touch* touch, Event* event){
            this->setColor(Color3B::WHITE);
            
            if (_removeListenerOnTouchEnded)
            {
                _eventDispatcher->removeEventListener(listener);
            }
        };
        
        if (_fixedPriority != 0) //如果传入的优先级不等于0,那么就调用addEventListenerWithFixedPriority方法
        {
            _eventDispatcher->addEventListenerWithFixedPriority(listener, _fixedPriority);//注册事件监听器,传入优先级
        }
        else
        {
            _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);//优先级为0,绑定当前对象
        }

        _listener = listener;
    }
    
    void onExit() override //离开舞台的时候调用
    {
        _eventDispatcher->removeEventListener(_listener);
        
        Sprite::onExit();
    }

    void removeListenerOnTouchEnded(bool toRemove) { _removeListenerOnTouchEnded = toRemove; };
    
    inline EventListener* getListener() { return _listener; };
    
private:
    EventListener* _listener;
    int _fixedPriority;
    bool _removeListenerOnTouchEnded;
};


不可以在注册第二种事件监听器的时候传入优先级 0 ,因为有着第一个方法对 0 值进行注册,这两个方法不能混淆。

依据官方文档,事件分发的优先级次序为 (<0,
scene graph (0 priority), >0 )。

即 优先级 priority值 越小,那么就越先被调用。

如此一来,我们可知,使用addEventListenerWithSceneGraphPriority() 方法注册的事件监听器,优先级高于addEventListenerWithFixedPriority()。

好了,两种事件注册方法都说完了,事件分发的次序也分析了一遍。

所以,原来的话应该改成:相同优先级的,受 渲染次序 影响,离屏幕越近,越优先被触发事件监听;而优先级不同的,受 priority值影响,值越小,越先被触发。

****************************************************自定义事件***********************************************************

感觉口水说了一堆,只说了一半的目标啊,接下来说说自定义事件。

我没有去研究键盘事件和鼠标事件,而对自定义事件兴趣满满,这也是AS3的影响。

个人认为自定义事件最大的作用就是解耦,传递数据。

试想一下,两个模块间的信息传递,是通过事件,一边只需要分发事件,一边只需要接收,耦合大大减少。

只是,完全使用自定义事件来解耦不大现实,单单调试就是个大问题哈哈,还是依据需要吧。

自定义事件的注册和分发都十分简单,看代码:

<span style="white-space:pre">	</span>_listener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event){ //创建一个自定义事件,传入事件名称 和 lambda 函数
        std::string str("Custom event 1 received, ");
        char* buf = static_cast<char*>(event->getUserData()); //从event中获得传递的自定义数据,不限于字符。
        str += buf;
        str += " times";
        statusLabel->setString(str.c_str());
    });
    
    _eventDispatcher->addEventListenerWithFixedPriority(_listener, 1);//注册
分发:

<span style="white-space:pre">	</span>static int count = 0;
        ++count;
        char* buf = new char[10];
        sprintf(buf, "%d", count);
        EventCustom event("game_custom_event1"); <span style="white-space:pre">	</span>// 创建事件,传入约定好的事件名称
        event.setUserData(buf);<span style="white-space:pre">				</span>//传入数据
        _eventDispatcher->dispatchEvent(&event);<span style="white-space:pre">	</span>//分发事件
        CC_SAFE_DELETE_ARRAY(buf);
真是简单粗爆。

约定一个自定义事件名称,然后创建事件监听器,传入事件名称和lambda函数,注册。

分发只要创建一个自定义事件,传入名称和数据,再使用分发器分发出去就ok了,随时随地,无所顾忌。

重要的代码只是注释的那些,其他都可以自由变化。

以上,能力有限,错漏难免,欢迎指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: