cocos2d-x 建立自己的层级窗口消息机制

在开发一些窗口层次比复杂的cocos2d项目时,会发现一些由于没有窗口层次而引起的bug。这些bug让一些从windows平台过来的人觉得很无奈。比如,我们有一个列表控件,我们在其中放了一些菜单,当我们滑动列表控件使菜单选项(称为A)滑出列表控件的可视范围时,按理我们是无法再点击到A的,因为它滚动出了父控件可视范围,不被用户所看到。但是cocos2d的默认情况是能点击到的,这是因为cocos2d的消息管理是优先级消息机制,只要控件登记接收消息,那么cocos2d会一如既往的发给他。所以我们刚才讲的情形在cocos2d看来,它无法根据A被遮挡而不给A发消息。究其根本,是没有一个层级窗口消息机制(当然你能通过其他的方法帮助cocos2d,但我个人觉得有点不够彻底)。

我想建立一个相对完整cocos2d的的层级窗口消息机制,且不改变cocos2d任何源码(因为改变源码的话,不知道以后升级起来是不是很麻烦)。基本思路有如下几条:

  1. 在任何一个场景中,我们会有一个最底层的Layer(我称为祖层),这个Layer将接受cocos2d的触摸消息。并且这个Layer能将触摸消息传递给其所有的子控件。
  2. 一个场景中除了祖层之外,所有其他的控件都将不接受任何触摸消息,其触摸消息的来源于父控件。
  3. 消息将从底层往上层传递。在每层中,节点根据order值从高到低排列(即order值高的表明该控件位于此层中的较上层的节点)并遍历,直到遇到消息感兴趣的节点,并停止本层遍历,进入下一层。
  4. 尽可能兼容已知和未知的cocos2d控件库。

下面看一下类的组织架构图:

我们分别说一下各个类的大体作用:

  1. BYTouchDelegate并非继承于ccTouchDelegate或其他类(但消息处理函数名同ccTouchDelegate一样)。它是一个消息传播大使,所有继承于该类的类都能自动地将消息传播到所有的子控件。不继承于ccTouchDelegate主要出于设计原则中避免多重继承中的基类重复。
  2. BYLayer继承于BYTouchDelegate和CCLayer(图中未指出),负责将消息处理函数转接到BYTouchDelegate的消息处理函数,这是因为BYTouchDelegate的消息处理函数同ccTouchDelegate是一样的,而CCLayer已经继承了ccTouchDelegate,这样如果不显示的转接处理函数,C++编译器会提示有两个版本选择的错误。

此消息机制的目的有两点:

  1. 让大型Cocos2d-x网友有一个层级窗口管理机制。
  2. 让所有的Cocos2d-x元素(即各类UI)能够天生融入到这个机制中。

需要通过下面几点修改达到这个目标的:

  1. 屏蔽所有子节点的消息注册。通过实践发现,为了不动源码,必须写一个新BYCocos::addChild函数,来代替原来的addChild函数族,这样就要求项目里面调用addChild的地方更改成BYCocos::addChild。这个函数内,会将子节点的所有子节点的消息都关闭掉。
  2. Cocos2d-x源码修改一处。
    1.注释掉CCLayer的ccTouchBegan中的CCAssert语句。这个语句只是起警醒的作用,为了让Cocos2d-x适应该机制,将此句注释掉。并修改return true为return false。因为在这个机制里面,返回true表明此layer对窗口消息感兴趣,这样会阻止消息往兄弟节点传递。

重点:

  • 由于此消息机制会判断是否点击中窗口,所以窗口大小变得尤为重要。Cocos2d-x的CCControl的各类控件已经做好了这些。但自己写的新的类,需要注意正确设置窗口大小。

下面来看源码:

  1. BYTouchDelegate:
    BYTouchDelegate.h
    //
    //  BYTouchDelegate.h
    //  TableTest
    //
    //  Created by jason on 12-12-25.
    //
    //
    
    #ifndef __TableTest__BYTouchDelegate__
    #define __TableTest__BYTouchDelegate__
    
    #include "cocos2d.h"
    
    USING_NS_CC;
    
    //by message mechanism
    //node who want to receive by message must derive from BYTouchDelegate.
    //if node is layer, it need use macro( BY_MESSAGE_BRIDGE() ) to connect it to by message instead of ccTouchMessage.
    class BYTouchDelegate
    {
    public:
        BYTouchDelegate( CCNode* pOwner ) :
        m_pOwner( pOwner ),
        m_bDraging( false )
        {
            m_pItemsClaimTouch = CCArray::createWithCapacity( CHILD_MAX );
            assert( m_pItemsClaimTouch );
            m_pItemsClaimTouch->retain();
    
            m_pMenusClaimTouch = CCArray::createWithCapacity( CHILD_MAX );
            assert( m_pMenusClaimTouch );
            m_pMenusClaimTouch->retain();
        }
        virtual ~BYTouchDelegate()
        {
            CC_SAFE_RELEASE_NULL( m_pItemsClaimTouch );
            CC_SAFE_RELEASE_NULL( m_pMenusClaimTouch );
        }
    protected:
        // default implements are used to call script callback if exist
        virtual bool byTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
        virtual void byTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
        virtual void byTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
        virtual void byTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
    
        //return value:
        //true: pParent is touched by user
        //false: pParent isn't touched by user.
        bool passMessage( CCNode* pParent, CCTouch *pTouch, CCEvent *pEvent );
    private:
        CCNode* m_pOwner;
        bool m_bDraging;
        //items claim touch message
        CCArray* m_pItemsClaimTouch;
        CCArray* m_pMenusClaimTouch;
    };
    #endif /* defined(__TableTest__BYTouchDelegate__) */

    BYTouchDelegate.cpp

    //
    //  BYTouchDelegate.cpp
    //  TableTest
    //
    //  Created by jason on 12-12-25.
    //
    //
    
    #include "BYTouchDelegate.h"
    #include "BYUtility.h"
    #pragma mark- input touche
    bool BYTouchDelegate::byTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
    {
        //pass message to all children
        return passMessage( m_pOwner, pTouch, pEvent );
    }
    
    void BYTouchDelegate::byTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
    {
        //special process for menu, we won't pass ccTouchMoved message to menu. Because we think menu doesn't need ccTouchMoved message in ios device where user always want to dray layer instead menu. The fllowing block for menu will only go once.
        int iNumMenus = m_pMenusClaimTouch->count();
        for( int i = 0; i < iNumMenus; ++i )
        {
            ( ( CCMenu* )m_pMenusClaimTouch->objectAtIndex( i ) )->ccTouchCancelled( pTouch, pEvent );
        }
    
        if( iNumMenus > 0 )
        {
            m_pMenusClaimTouch->removeAllObjects();
        }
    
        //pass ccTouchMoved message to un-CCMenu item
        for( int i = 0; i < m_pItemsClaimTouch->count(); ++i )
        {
            ( ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i ) )->ccTouchMoved( pTouch, pEvent );
        }
    }
    
    void BYTouchDelegate::byTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
    {
        //for menus
        for( int i = 0; i < m_pMenusClaimTouch->count(); ++i )
        {
            ( ( CCMenu* )m_pMenusClaimTouch->objectAtIndex( i ) )->ccTouchEnded( pTouch, pEvent );
        }
        m_pMenusClaimTouch->removeAllObjects();
    
        //for items not menu
        for( int i = 0; i < m_pItemsClaimTouch->count(); ++i )
        {
            ( ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i ) )->ccTouchEnded( pTouch, pEvent );
        }
        m_pItemsClaimTouch->removeAllObjects();
    }
    
    void BYTouchDelegate::byTouchCancelled(CCTouch *pTouch, CCEvent *pEvent)
    {
        //for menus
        for( int i = 0; i < m_pMenusClaimTouch->count(); ++i )
        {
            ( ( CCMenu* )m_pMenusClaimTouch->objectAtIndex( i ) )->ccTouchCancelled( pTouch, pEvent );
        }
        m_pMenusClaimTouch->removeAllObjects();
    
        //for items not menu
        for( int i = 0; i < m_pItemsClaimTouch->count(); ++i )
        {
            ( ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i ) )->ccTouchCancelled( pTouch, pEvent );
        }
        m_pItemsClaimTouch->removeAllObjects();
    }
    
    bool BYTouchDelegate::passMessage( CCNode* pParent, CCTouch *pTouch, CCEvent *pEvent )
    {
        if( !pParent || !pParent->isVisible() )
        {
            return false;
        }
    
        //if the item'size > 1, check whether use touches it. Such as TableView.
        //some items doesn't get size. they are medium for maintaining some children. Such as CCTableViewCell.
        if( pParent->getContentSize().width * pParent->getContentSize().height > 1.0f )
        {
            CCPoint pt = pTouch->getLocation();
            CCRect rcBoundingBox( 0, 0, pParent->getContentSize().width, pParent->getContentSize().height );
    
            rcBoundingBox = CCRectApplyAffineTransform( rcBoundingBox, pParent->nodeToWorldTransform() );
    
            if( !rcBoundingBox.containsPoint( pt ) )
            {
                return false;
            }
        }
    
        //hande message to items
        CCArray* pChildren = pParent->getChildren();
    
        //no children, but user touch this item, so return true.
        if( !pChildren )
        {
            return true;
        }
    
    	//sort all children in ascending order.
        pParent->sortAllChildren();
    
    	CCObject* pObject = NULL;
    	//traverse in descending order.
    	//we only send message to the first child handling message.
    	CCARRAY_FOREACH_REVERSE( pChildren, pObject )
    	{
    		//if the item claims the touch message
    		bool bClaim = false;
    
            CCLayer* pLayer = NULL;
            CCNode* pNode = NULL;
    
            pNode = ( CCNode*  )pObject;
            assert( pNode );
    
            //if it's layer, we should invoke its ccTouchBegan()
            //Make sure that you have commented the CCAssertion statement in CCLayer::ccTouchBegan().
            if( ( pLayer = dynamic_cast< CCLayer* >( pNode ) ) )
            {
                bClaim = pLayer->ccTouchBegan( pTouch, pEvent );
            }
    
            //pass message for its child which doesn't derive BYTouchDelegate since child deriving BYTouchDelegate
		//has pass message via ccTouchBegan().
    
    		//items who doesn't derive from BYTouchDelegate can't pass touch message to its children,
    		//so we have to help them to pass touch message.
    
    		//we don't care the result of passMessage() since passMessage here is just
    		//passing messages for the node. It doesn't mean the node is interested
    		//in the message.
    		passMessage( pNode, pTouch, pEvent );
    
            //if this item is interested in this message, add it to array for other messages
            if( bClaim )
            {
                //we don't use condition of &typeid( *pNode ) == &typeid( CCMenu ) since user may derive CCMenu.
                if ( dynamic_cast< CCMenu* >( pNode ) )
                {
                    m_pMenusClaimTouch->addObject( pNode );
                }
                else
                {
                    m_pItemsClaimTouch->addObject( pNode );
                }
    
    			//we only send message to the first immediate child claiming message.
    			break;
            }
    	}
    
    	//if there is any item who claims the message, return true.
    	return m_pItemsClaimTouch->count() + m_pMenusClaimTouch->count() > 0 ? true : false;
    }
    

    其中比较重要的地方解释一下:
    1.首先是m_bDragging是为了判断拖动用的,主要用于菜单处理,当点击了一下之后,菜单对此消息感兴趣,我们记录菜单,并传递后续消息,然后点击完之后传来的是TouchMoved消息,那么就停止菜单消息处理。
    2.passMessage同ccTouchBegan一起来完成消息递归传递。passMessage还将帮助没有继承wmTouchDelegate的类(这些类的ccTouchBegan不具备消息传递功能)传递消息给其子控件。

  2. BYLayer.因为主要功能都有wmTouchDelegate完成了,这些类只是做了简单功能和约束的的添加。
    BYLayer.h
    //
    //  BYLayer.h
    //  TableTest
    //
    //  Created by jason on 12-12-21.
    //
    //
    
    #ifndef __TableTest__BYLayer__
    #define __TableTest__BYLayer__
    
    #include "cocos2d.h"
    #include "BYTouchDelegate.h"
    #include "BYMacros.h"
    
    USING_NS_CC;
    
    //BYLayer can be touched.
    //Every secene should have only one BYLayer to represent message center.
    //All children nodes shouldn't register touche message.
    //To achieve it, you should use BYCocos::addChild() to add child node.
    
    class BYLayer : public CCLayer, public BYTouchDelegate
    {
        //static
    public:
        CREATE_FUNC( BYLayer );
    
    	//functions
    public:
    	BYLayer() : BYTouchDelegate( this ){}
    	bool virtual init()
        {
            setTouchEnabled( true );
            return true;
        }
    
        BY_MESSAGE_BRIDGE();
    
    protected:
    	BY_TOUCH_REGISTER_TWO_MODE( 0 );
    };
    #endif /* defined(__TableTest__BYLayer__) */

    里面通过一些宏,让我编写的更快一些。但希望没能阻碍你阅读。这里面没有太多需要说的,具体的请下载示例工程查看。

PS:

  1. 工程使用时,只需包含BYCocos.h即可。
  2. 确保cocos2d-x中CCLayer.cpp内的CCLayer::ccTouchBegan()函数内的CCAssertion()语句被注释并修改return true为return false。这是新层级窗口消息机制对cocos2d-x源码的唯一修改。
  3. 这个体系中有一点需要注意,因为我们添加了窗体点击判断,所以加入这个体系的窗口应该显示地,正确地设置自己的窗体大小(这经常是一些bug的原因所在)。如果想屏蔽同级节点接受消息,重写ccTouchBegan,并且返回true。
  4. 添加子节点,必须使用BYCocos::addChild()。
  5. 另外由于使用了dynamic_cast,可能会让部分读者担心效率问题。但个人认为dynamic_cast并不是C++的垃圾,其带来的性能影响应该是很有限的。
  6. 代码上有比较多的注释,如果注释有误还请告诉我。习惯练习英文注释了,希望我的注释没太大语法错误,能够让你理解。这里就不多讲述了。
  7. 任何后续修改都会列在下面的修订记录中。
  8. 鉴于很多网友向我寻求源码工程以进行测试,为了方便,我已经将源码工程传入我的网盘空间。【http://www.xujiezhige.ys168.com】->【工具】->【chaos.zip
    1,964KB
    】,
    如果上面链接打不开,请试着下载此csdn资源链接【Cocos2d-x层级窗口消息机制Demo】。(最新更新2013-3-25)

修订记录:

  1. 2013-2-1, 修改BYTouchDelegate.cpp。
    修订原因:按理来说,一个Layer上应该只有最上面的子节点才能相应消息。
    修订内容:将消息按节点的Zorder降序,只传递给第一个对此消息感兴趣的子节点,解决了一个Layer上,多个子节点相互重叠,都能响应消息的bug。

    修复当没有直接子节点对消息感兴趣时BYTouchDelegate不接受消息的bug。此bug会是的当间接子节点对消息感兴趣出问题。具体:修改BYTouchDelegate.cpp中passMessage(),修订的部分以//revision# began开始,以//revision# end结束标记了(#表示第几次修订。我本想用颜色标记出来,但是csdn的代码框内使用颜色格式有bug)。

  2. 2013-3-25,修改BYLayer.h
    修订原因:
    1.原来的版本重载了onEnter(),并在其中将所有子节点的消息屏蔽掉。但是onEnter是在创建的时候调用,如果创建完成后,再调用addChild(),添加的节点的消息就没有被屏蔽了,也就违背此消息机制。
    2.由于我们在addChild中屏蔽了子节点的消息,所有BYLayerModal没有存在的必要了(因为它原本设计的时候是通过自行注册最高优先级别的消息来达到屏蔽消息的),如果想让某窗口屏蔽消息,只要确保该窗口order最高,并且重写ccTouchBegan返回true。另外BYLayerDescendant的存在增加的机制的复杂度,与其带来的好处想比,还是去掉比较合理。
    3.将BYLayerAncestor合并到BYLayer去,以减少新添加的类数量,让系统精简。
    修订内容:删除原来屏蔽子节点消息注册的做法。添加BYCocos::addChild辅助函数,此函数将在内部屏蔽所有子节点的消息。
时间: 2024-08-28 04:38:18

cocos2d-x 建立自己的层级窗口消息机制的相关文章

vc++窗口的创建过程(MFC消息机制的经典文章)

一.什么是窗口类  在Windows中运行的程序,大多数都有一个或几个可以看得见的窗口,而在这些窗口被创建起来之前,操作系统怎么知道该怎样创建该窗口,以及用户操作该窗口的各种消息交给谁处理呢?所以VC在调用Windows的API(CreateWindow或者CreateWindowEx)创建窗口之前,要求程序员必须定义一个窗口类(不是传统C++意义上的类)来规定所创建该窗口所需要的各种信息,主要包括:窗口的消息处理函数.窗口的风格.图标. 鼠标.菜单等.其定义如下:  typedef struc

windows 消息机制、窗口过程与线程间消息传递

按照自己的理解好好整理一遍 消息机制 windows是一个消息驱动的系统,会有一个总的系统消息的队列,鼠标.键盘等等都会流入到这个队列中,同时会为每个线程维护一个消息队列(注意默认是有GUI调用的线程才有,对于没有GUI或者窗口的线程,只有当在线程内调用get/peek message 才会自动创建一个消息队列),线程是容纳消息队列的基本单元,系统会把属于不同线程的消息投递到属于线程的消息队列中 当线程调用get/peek message时会从系统的消息队列中取出一个本线程内的消息.(get方法

关于HGE渲染窗口作为子窗口时无法得到窗口消息的问题以及解决办法

只要稍微了解HGE的人都是知道的,如果在HGE中设置了父窗口,则HGE的渲染窗口作为传入的父窗口的子窗口存在的.如果要嵌入到MFC的窗口中,就需要这样做. 不过,奇怪的是,消息处理回调函数无法收到窗口消息,在System_Initiate()中注册窗口类的时候,是有设置父窗口的句柄的.但是,现在是没有接受到窗口消息,那很显然的事情就是,父窗口设置是失败的. 后来,我查了下资料,WS_POPUP风格的窗口默认父窗口是为空的,除非用::SetParent去特意设置.后面我尝试着在CreateWind

Windows窗口消息实例详解_C 语言

本文实例总结了Windows窗口消息.分享给大家供大家参考.具体如下: 复制代码 代码如下: //////////////////////////////////////////////////////////////////////////    #include "AFXPRIV.H"//消息值的定义来源    #include "Dde.h"//DDE消息值的定义来源    #include "CPL.H"//控制面板消息值的定义来源   

WPF的消息机制(三)- WPF内部的5个窗口之处理激活和关闭的消息窗口以及系统资源通知窗口

目录 WPF的消息机制(一)-让应用程序动起来 WPF的消息机制(二)-WPF内部的5个窗口 (1)隐藏消息窗口 (2)处理激活和关闭的消息窗口以及系统资源通知窗口 (3)用于用户交互的可见窗口 (4)用于UI窗口绘制的可见窗口 WPF的消息机制(三)-WPF输入事件的来源 WPF的消息机制(四)-WPF中UI的更新   处理应用程序激活和系统关闭的窗口(Window 2#) 创建时机:在调用Application.Run之后,运行到Application.EnsureHwndSource()方

深入VCL理解BCB的消息机制1

本文所谈及的技术内容都来自于Internet的公开信息.由CKER在闲暇之际整理后,贴出来以飴网友,姑且妄称原创. 『每次在国外网站上找到精彩文章的时候,心中都会暗自叹息为什么在中文网站难以觅得这类文章呢?其实原因大家都明白.』 时至今日,学习Windows编程的兄弟们都知道消息机制的重要性.所以理解消息机制也成了不可或缺的功课. 大家都知道,Borland的C++ Builder以及Delphi的核心是VCL.作为Win32平台上的开发工具,封装Windows的消息机制当然也是必不可少的. 那

J2EE的异步消息机制(上)

在分布式企业级应用程序中,异步消息机制用于有效地协调各个部分的工作. J2EE为我们提供了JMS和消息驱动豆(Message-Driven Bean),用来实现应用程序各个部件之间的异步消息传递. 一.什么是消息系统? 通常一个消息系统允许分开的未耦合的应用程序之间可靠地异步通信.在企业应用时,需要一种异步的,非阻塞的消息传递.比如,一个客户端可能希望给一个服务器发送一个请求后,不在乎是否马上能得到回应.这样,客户端没有理由必须等待服务器处理请求.客户端应用程序在递交一个请求之后,只需确保请求到

linux GTK教程(消息机制/标签/按钮/图像/文本/对话框/菜单/容器)

GTK+(GIMP Toolkit)是一套源码以LGPL许可协议分发.跨平台的图形工具包.最初是为GIMP写的,已成为一个功能强大.设计灵活的一个通用图形库,是GNU/Linux下开发图形界面的应用程序的主流开发工具之一. 1.GTK安装 2.一个简单的GTK窗口程序 #include <stdio.h> #include <gtk/gtk.h> int main(int argc, char **argv) { GtkWidget *window; gtk_init(&a

WPF的消息机制(一)- 让应用程序动起来

前言 谈起"消息机制"这个词,我们都会想到Windows的消息机制,系统将键盘鼠标的行为包装成一个Windows Message,然后系统主动将这些Windows Message派发给特定的窗口,实际上消息是被Post到特定窗口所在线程的消息队列,应用程序的消息循环再不断的从消息队列当中获取消息,然后再派发给特定窗口类的窗口过程来处理,在窗口过程中完成一次用户交互. 其实,WPF的底层也是基于Win32的消息系统,那么对于WPF应用程序来说,它是如何跟Win32的消息交互,这里到底存在