事件分发源码疑问

引擎版本3.6

void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
    sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);
    sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    
    auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
    auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
..............
}

这一段代码是我的疑问所在。
来看两种情况

情况1

auto scene = Scene::create();
Director::runWithScene(scene);
auto btn1 = Button::create();
scene:addChild(btn1);
auto btn2 = Button::create()
scene:addChild(btn2)

运行后,在

sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);

后打断点,发现排序后的监听器顺序是btn2的在btn1之前


情况2

auto node = Director::getNotificationNode()
auto scene = Scene::create();
Director::runWithScene(scene);
auto btn1 = Button::create();
node :addChild(btn1);
auto btn2 = Button::create()
node :addChild(btn2)

同样,运行后,在

sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);

后断点,发现排序后的监听器顺序居然是btn1在btn2之前,也就是说和情况1刚好是hi反过来的?

对于这种现象;请问是否是我的测试有问题,如果我的测试有问题,希望指出;
如果没有问题,那么为什么会产生这种情况,在Director::getNotificationNode()上添加的节点,接收事件的顺序和普通场景里添加节点接收顺序不同是有意为之么?这样做的目的是什么。还请引擎组以及各位大腿解惑

@jare
帮忙看看,谢谢

跟进了下源码,发现我的测试是正确的,的确通过Director::getNotificationNode()添加的节点,他们接收事件的顺序是按添加的顺序;具体原因是因为

void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID)

这个函数会对指定id的监听器排序,其中分为

sortEventListenersOfFixedPriority(listenerID);

sortEventListenersOfSceneGraphPriority(listenerID, rootNode);

而我们添加的节点,都是通过sortEventListenersOfSceneGraphPriority(listenerID, rootNode);排序的,
这个函数中有调用
visitTarget(rootNode, true);
这个rootNode,传入的是当前场景,这个函数的作用就是把rootNode里的所有节点做一个排序,这个排序的结果,就是作为上层监听器排序的依据,但是Director::getNotificationNode()这个节点不在场景里头,也就是说,这个排序根本影响不到Director::getNotificationNode(),这就导致了在对Director::getNotificationNode()中节点对应监听器排序的时候,根本不起作用,顺序依然是当初创建的顺序,这也解释了为什么上面说的btn1会在btn2之前,因为btn1是先创建的;

原理倒是搞清楚了,但是我依然不解;为什么要这样?目的是什么?我现在想在Director::getNotificationNode()上做一些常驻节点,根本没法实现,虽然用addEventListenerWithFixedPriority可以接受事件,但是对于事件穿透的逻辑却有着致命的影响

因为notificationNode在设计出来的时候就没有考虑过这么多东西,后来这个功能用的人也少,慢慢的就被引擎组淡忘了

我去,还有这种操作!那对于要存在于所有场景中的东西,你是如何解决的,继承场景再添加,然后之后的场景都用这个创建?如果项目开始前没考虑到这一层的话,有解决方案不

事件后面直接写数字 不用传入任何NODE
另外,像这种每个场景可能都要侦听的事件,实际上不应该放在场景中的。
而是你应该有一个类似AppManager (我自己起的名字,类似的还有AudioManager, NetManager等)这样继承于cc.Class 的管理类 应该是单例模式的。
这种事件什么的 就放在这样的类里面。

addEventListenerWithFixedPriority

你说的是这个吧,这个不会根据图层顺序来传递事件,如果我的UI很复杂,我不可能为每个控件去手动设定一个监听器;
对于你提的方案,能解决一部分问题,但是当碰到诸如教程这种需要一种存在的东西,就会显得有局限性了

按照我的理解 只有触摸事件才需要跟着NODE层级相关。
这样的事件当然是跟着对应NODE走了 并没有什么问题啊

另外,getNotificationNode这样的东西 从来没有用过。 也从来没有需要这样用过。。

全局通告,或者网络传输等待,这些都应该通过一个独立于所有scene之外的节点来显示吧

全局的通告,断网提示,网络连接,这些都需要用到啊;不然你怎么做?每次创建场景的时候添加一个?

这些就是我前面提到的情况。

事件后面直接写数字 不用传入任何NODE
另外,像这种每个场景可能都要侦听的事件,实际上不应该放在场景中的。
而是你应该有一个类似AppManager (我自己起的名字,类似的还有AudioManager, NetManager等)这样继承于cc.Class 的管理类 应该是单例模式的。
这种事件什么的 就放在这样的类里面。

你说的那些通告什么的 就是通过一个单例类来实现,在里面实现一些全局的,并且一直存在的方法、事件。

我不清楚你是否做过强制性的教程;如果是一个游戏有N个场景,如何通过单例去实现,而不用把它加到场景上,有些逻辑你可以自己派发事件,但是对于触摸这种,就必须走引擎的逻辑,不然得到glview里去拦截自己写一套逻辑;
比如现在有两个场景,都需要引导
A场景需要你引导用户点击一个按钮;
B场景需要你引导用户拖动一个棋子;
我们必须保证用户只能按我们设定的规定走,请问你的实现方案是什么

以前做网页游戏 就做过这类似的
我是有一个引导管理类 会有配置文件配置 每个引导
在要引导的场景里去调用执行这个引导管理类,去执行对应的动作。 如果有队列的,比如点一个什么,出一个什么。
这些都是放队列里面。没有就没有 更简单了。
由这个引导管理类去负责调用这些个引导任务。
具体的每个引导任务的效果 位置 文字等 在具体的要执行引导的场景那里指定就可以了。

类似的代码:
//如果已经引导过 就不要引导了
if(_self._cachedData.guilder.isClickedRole==null){

     这里是加入一个点击0,90这个坐标的提示,如果点击了 执行这个回调,
       _self.guilderIsClickedRoleIndex= playerGuider.addTap(0,90,null,"isClickedRole",function(){
            _self._cachedData.guilder.isClickedRole=true;

        });
    }

大概的引导类的方法:

你这套做法是不是控件对应的点击事件需要管理在【引导管理类】中,如果是的话,那就比较难解决我的问题;如果不是,你能讲下,从用户在屏幕上点下某个点,到最后对应的控件的click回调被执行的过程么!
对于我的问题,我举个例子
比如要引导用户点击按钮A,
1,A的click回调是function aClick;
2,A在被按下的时候,要显示pa图片;
3,A在正常下,要显示pb图片;
对于第一点我们可以自己去调用,但是对于2,3点,最好是让引擎来控制,如果我们去干预,是不是要监测控件被按下,控件恢复正常呢?
所以我的想法就是只控制事件的传递与否,如果按下事件来了,我通过遮罩获取到这个事件,然后可以对按下的点做判断,是否点中了A,如果点中了,那么放这个事件传递下去,同理抬起是一样的;
通过对事件的控制,能够实现在引导的过程中,所有的被引导控件都做和平时一样的表现,该缩放缩放,该改变图片改变图片,而且不用把对应的点击回调管理起来;不知道我表述的是否清楚

觉得这样挺复杂的。。
我的做法 还是要在对应的按钮的点击里面添加逻辑代码。。。 就像你看到我上面写的例子。
因为我那个里面都不是强制引导(个人非常不喜欢强制引导)
强制引导,我记得我以前写的时候,也是需要在点击里去判断 是否引导过 没引导过就做没引导过的动作。。。。
不是很建议写在那个最上面的MASK上面。觉得这样好难,玩意你的游戏尺寸变化,或者按钮坐标是改变的,。。
那这个不是很为难吗?

利用事件的穿透来做,应该是最简单的吧。。。,你只需要额外的管理控件的坐标就行了,引导的判断区域就根据控件的坐标每帧刷新即可;对于引导,其实开放性引导比强制引导要简单的多;但是这个是策划说的,我们就负责解决,也是没办法的事情