如果没有看过cocos2d-x 建立自己的层级窗口消息机制(一),请先看它,否则,下面论述你可能不知道我在说什么。
最近在学习《design patterns》的时,看到其中的decorator设计模式,就想到了cocos2d-x 建立自己的层级窗口消息机制(一),并思考比较了一下。因为decorator(wrapper)模式,通过继承基类并转接基类接口来达到一个添加额外功能或者职责的目的。这个模式在书中解决这样一个问题,不用为所有的类重新派生新类来实现添加边框的功能。这正是之前的层级窗口消息机制的弊端:
[red_box]

  1. 为了让控件加入我们的层级窗口消息机制。我们必须要派生所有cocos2d控件,去掉他们的消息注册功能。
  2. 还需要为每个新添加的控件,到wmTouchDelegate里面添加nonTrivialTouchHandler标志。

[/red_box]
通过一些分析后,我找到一些方法,可以改善我的层级窗口消息机制,基本解决上面两个弊端。新的层级窗口机制将实现一下目标:
[green_box]

  1. 原本的所有cocos2d控件,不需经过任何修改即可加入层级窗口消息机制。
  2. 且新添加的控件不要到wmTouchDelegate中添加任何标志。

[/green_box]
我是通过下面几点修改达到这个目标的:
[green_box]

  1. 其实能实现清除消息注册功能的方法,不止继承,派生这一种。这里使用了另一种,通过在祖层(实际是BYLayer)的onEnter中添加部分功能(并调用原来的CCLayer::onEnter()),来实现清除消息注册。onEnter里的这个功能是通过遍历所有的子对象,判断他们是否继承自CCLayer,如果是,我们将调用setTouchEnable( false )来去掉他们消息注册功能。
  2. 通过权衡后,我放弃完全以改变cocos2d源码的承诺,因为为了区分各个继承自CCLayer的类是否改了ccTouchX()函数,实在是没太大必要。当初这么做主要是为了避免CCLayer::ccTouchBegin()中的CCAssert语句。但其实这个语句只是其惊醒的作用。所以我唯一对cocos2d-x的修改就是将这个断言注释掉。这样的Debug模式下,就不会被中断了。

[/green_box]
通过修改后的层级窗口消息机制(新的机制也更改了的类名前缀),可以让已经存在的工程很快采用这个体系,只需要将每个场景创建的Layer派生自BYLayerAncestor即可。其他代码不需修改,因为新的机制已经兼容cocos2d所有原始的控件。这里还是贴上一张效果图吧(没有变)。
cocos2d-x消息机制
上次由于种种原因没有给出完整源码,让部分读者只能私下给发信息才能获得源码。这次直接展示6个完整源码文件。以方便读者测试,使用,修改,请提出宝贵意见。
//
// BYCocos.h
// TableTest
//
// Created by jason on 12-12-26.
//
//
#ifndef TableTest_BYCocos_h
#define TableTest_BYCocos_h
#include "BYMacros.h"
#include "BYUtility.h"
#include "BYTouchDelegate.h"
#include "BYLayer.h"
#endif

.
//
// BYMacros.h
// TableTest
//
// Created by jason on 12-12-26.
//
//
#ifndef TableTest_BYMacros_h
#define TableTest_BYMacros_h
#define BY_ASSERT_POINTER( x ) assert( ( x ) != NULL )
#define BY_DEFAULT_CHARSET "Arial"
#define BY_DOUBLE_CLICK_DELTA 0.4f
//anchor point
#define BY_ANCHOR_POINT_CENTER ccp( 0.5f, 0.5f )
#define BY_ANCHOR_POINT_LEFTBOTTOM ccp( 0.0f, 0.0f )
#define BY_ANCHOR_POINT_RIGHTBOTTOM ccp( 1.0f, 0.0f )
#define BY_ANCHOR_POINT_RIGHTTOP ccp( 1.0f, 1.0f )
#define BY_ANCHOR_POINT_LEFTTOP ccp( 0.0f, 1.0f )
//size
#define BY_IPAD_SIZE_WIDTH 1024
#define BY_IPAD_SIZE_HEIGHT 768
#define BY_IPAD_SIZE_WIDTH_HALF 512
#define BY_IPAD_SIZE_HEIGHT_HALF 384
#define BY_WIN_SIZE_WIDTH ( CCDirector::sharedDirector()->getWinSize().width )
#define BY_WIN_SIZE_HEIGHT ( CCDirector::sharedDirector()->getWinSize().height )
#define BY_WIN_SIZE_WIDTH_HALF ( CCDirector::sharedDirector()->getWinSize().width * 0.5f )
#define BY_WIN_SIZE_HEIGHT_HALF ( CCDirector::sharedDirector()->getWinSize().height * 0.5f )
#define BY_WIN_CENTER_POINT ccp( BY_WIN_SIZE_WIDTH_HALF, BY_WIN_SIZE_HEIGHT_HALF )
#define BY_NODE_WIDTH( n ) ( ( n )->getContentSize().width )
#define BY_NODE_HEIGHT( n ) ( ( n )->getContentSize().height )
#define BY_NODE_WIDTH_HALF( n ) ( ( n )->getContentSize().width * 0.5f )
#define BY_NODE_HEIGHT_HALF( n ) ( ( n )->getContentSize().height * 0.5f )
//menu items
#define BY_MENU_ITEM_COMMON_SPACING 20
//functions
#define BY_GET_DIRECTOR() cocos2d::CCDirector::sharedDirector()
#define BY_GET_BROTHER( brotherClassName, brotherTag ) ( ( brotherClassName* )getParent()->getChildByTag( ( brotherTag ) ) )
#define BY_GET_CHILD( childClassName, childTag ) ( ( childClassName* )getChildByTag( ( childTag ) ) )
#define BY_MESSAGE_CONSTRUCTOR( ownerClassName ) \
ownerClassName() : BYTouchDelegate( this ){}
#define BY_MESSAGE_BRIDGE() \
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) \
{ \
return byTouchBegan( pTouch, pEvent ); \
} \
virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) \
{ \
byTouchMoved( pTouch, pEvent ); \
} \
virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) \
{ \
byTouchEnded( pTouch, pEvent ); \
} \
virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) \
{ \
byTouchCancelled( pTouch, pEvent ); \
}
#define BY_INIT_DEFAULT( parentClassName ) \
virtual bool init() \
{ \
if( !parentClassName::init() ) \
{ \
return false; \
} \
\
return true; \
}
#define BY_TOUCH_REGISTER_DEFAULT( iPriority ) \
virtual void registerWithTouchDispatcher( void ) \
{ \
CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate( this, iPriority, true ); \
}
#define BY_TOUCH_REGISTER_EMPTY() \
virtual void registerWithTouchDispatcher( void ){}
//BYTouchDelegate
#define BY_ADD_NON_TRIVIAL_TOUCH_HANDLER_CLASS( className ) \
BYTouchDelegate::addTrivialTouchHandlerClass( ( void* )&typeid( className ) );
#endif

.
//
// BYUtility.h
// TableTest
//
// Created by jason on 12-12-28.
//
//
#ifndef TableTest_BYUtility_h
#define TableTest_BYUtility_h
#include "BYMacros.h"
#include "BYTouchDelegate.h"
#include
//class for detect double click
class BYDoubleClick
{
public:
BYDoubleClick()
{
memset( &m_Prev, 0, sizeof( m_Prev ) );
memset( &m_Now, 0, sizeof( m_Now ) );
}
//is there a double click
bool detection()
{
bool bDoubleClick = false;
gettimeofday( &m_Now, NULL );
float fTimeDelta = ( m_Now.tv_sec - m_Prev.tv_sec ) + ( m_Now.tv_usec - m_Prev.tv_usec ) * 0.000001f;
if( fTimeDelta < BY_DOUBLE_CLICK_DELTA ) { bDoubleClick = true; } m_Prev = m_Now; return bDoubleClick; } timeval& getNow(){ return m_Now; } timeval& getPrev(){ return m_Prev; } private: timeval m_Prev; timeval m_Now; }; class BYClock { public: BYClock() : m_DeltaTime ( 0 ) { memset( &m_BeginTime, 0, sizeof( m_BeginTime ) ); memset( &m_EndTime, 0, sizeof( m_EndTime ) ); } void begin() { gettimeofday( &m_BeginTime, NULL ); } void end() { gettimeofday( &m_EndTime, NULL ); m_DeltaTime = ( m_EndTime.tv_sec - m_BeginTime.tv_sec ) + ( m_EndTime.tv_usec - m_BeginTime.tv_usec ) * 0.000001; } float getDelta() { return m_DeltaTime; } void printDelta() { std::cout << "BYClock::PrintDelta = " << m_DeltaTime << " second." << std::endl; } private: timeval m_BeginTime; timeval m_EndTime; float m_DeltaTime; }; #endif

.
//
// 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;
//Users shouldn derive from BYLayerAncestor, BYLayerDescendant, BYLayerModal instead BYLayer.
//BYLayer can't be touched.
//BYLayer pass message to it's all descendant of CCLayer
class BYLayer : public CCLayer, public BYTouchDelegate
{
//static
public:
CREATE_FUNC( BYLayer );
protected:
BY_INIT_DEFAULT( CCLayer );
BY_MESSAGE_CONSTRUCTOR( BYLayer );
BY_MESSAGE_BRIDGE();
virtual void onEnter()
{
setChildrenTouchDisable( this );
CCLayer::onEnter();
}
void setChildrenTouchDisable( CCNode* pParent )
{
if( !pParent )
{
return;
}
CCArray* pChildren = pParent->getChildren();
if( !pChildren )
{
return;
}
int iNumChildren = pChildren->count();
for( int i = 0; i < iNumChildren; ++i ) { CCLayer* pLayer = NULL; CCNode* pChild = ( CCNode* )pChildren->objectAtIndex( i );
if ( ( pLayer = dynamic_cast< CCLayer* >( pChild ) ) )
{
pLayer->setTouchEnabled( false );
}
setChildrenTouchDisable( pChild );
}
}
};
//BYLayerAncestor can be touched.
//all secene should have only one BYLayerAncestor for bottom layer.
//all the other layer should be BYLayerDescendant.
class BYLayerAncestor : public BYLayer
{
protected:
bool virtual init()
{
if( !BYLayer::init() )
{
return false;
}
setTouchEnabled( true );
return true;
}
BY_TOUCH_REGISTER_DEFAULT( 0 );
//static
public:
CREATE_FUNC( BYLayerAncestor );
//data
};
class BYLayerDescendant : public BYLayer
{
protected:
BY_INIT_DEFAULT( BYLayer );
//static
public:
CREATE_FUNC( BYLayerDescendant );
//data
};
//BYLayerModal stopes touch messages from being passed to other layers which is not it's children.
//And will pass touch messages to it's children normally.
class BYLayerModal : public BYLayerAncestor
{
protected:
BY_INIT_DEFAULT( BYLayerAncestor );
BY_TOUCH_REGISTER_DEFAULT( kCCMenuHandlerPriority );
//static
public:
CREATE_FUNC( BYLayerModal );
//data
};
#endif /* defined(__TableTest__BYLayer__) */

.//
// BYTouchDelegate.h
// TableTest
//
// Created by jason on 12-12-25.
//
//
#ifndef __TableTest__BYTouchDelegate__
#define __TableTest__BYTouchDelegate__
#include "cocos2d.h"
USING_NS_CC;
//boyo message mechanism
//node who want to receive boyo message must derive from BYTouchDelegate.
//if node is layer, it need use macro( BY_TOUCH_DELEGATE_IMPLEMENT_IN_HEAD_FILE() ) to connect it to boyo message instead of ccTouchMessage.
class BYTouchDelegate
{
public:
enum
{
MAX_NON_TRIVIAL_TOUCH_HANDLER_CLASS = 256,
};
public:
BYTouchDelegate( CCNode* pOwner ) :
m_pOwner( pOwner ),
m_bDraging( false )
{
m_pItemsClaimTouch = CCArray::createWithCapacity( CHILD_MAX );
assert( m_pItemsClaimTouch );
m_pItemsClaimTouch->retain();
}
virtual ~BYTouchDelegate()
{
CC_SAFE_RELEASE_NULL( m_pItemsClaimTouch );
}
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;
};
#endif /* defined(__TableTest__BYTouchDelegate__) */

.
//
// 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.
if( false == m_bDraging )
{
for( int i = 0; i < m_pItemsClaimTouch->count(); )
{
CCLayer* pItem = ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i );
//menu items doesn't process ccTouchMove(), cancel it.
assert( NULL != pItem );
//if it's menu
if( dynamic_cast< CCMenu* >( pItem ) )
{
pItem->ccTouchCancelled( pTouch, pEvent );
m_pItemsClaimTouch->removeObjectAtIndex( i );
}
else
{
++i;
}
}
}
//pass ccTouchMoved message to un-CCMenu item
int iNumItemsNotMenu = m_pItemsClaimTouch->count();
for( int i = 0; i < iNumItemsNotMenu; ++i ) { BYTouchDelegate* pItemBY = NULL; CCLayer* pItem = NULL; pItem = ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i );
assert( NULL != pItem );
//boyo items whose class name is prefixed with BY
if( ( pItemBY = dynamic_cast< BYTouchDelegate* >( pItem ) ) )
{
pItemBY->byTouchMoved( pTouch, pEvent );
}
else//coscos2d-x items
{
pItem->ccTouchMoved( pTouch, pEvent );
}
}
m_bDraging = true;
}
void BYTouchDelegate::byTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
{
int iNumItems = m_pItemsClaimTouch->count();
for( int i = 0; i < iNumItems; ++i ) { BYTouchDelegate* pItemBY = NULL; CCLayer* pItem = NULL; pItem = ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i );
assert( NULL != pItem );
//boyo items whose class name is prefixed with BY
if( ( pItemBY = dynamic_cast< BYTouchDelegate* >( pItem ) ) )
{
pItemBY->byTouchEnded( pTouch, pEvent );
}
else//coscos2d-x items
{
pItem->ccTouchEnded( pTouch, pEvent );
}
}
m_pItemsClaimTouch->removeAllObjects();
m_bDraging = false;
}
void BYTouchDelegate::byTouchCancelled(CCTouch *pTouch, CCEvent *pEvent)
{
int iNumItems = m_pItemsClaimTouch->count();
for( int i = 0; i < iNumItems; ++i ) { BYTouchDelegate* pItemBY = NULL; CCLayer* pItem = NULL; pItem = ( CCLayer* )m_pItemsClaimTouch->objectAtIndex( i );
assert( NULL != pItem );
//by items
if( ( pItemBY = dynamic_cast< BYTouchDelegate* >( pItem ) ) )
{
pItemBY->byTouchCancelled( pTouch, pEvent );
}
else//coscos2d-x items
{
pItem->ccTouchCancelled( pTouch, pEvent );
}
}
m_pItemsClaimTouch->removeAllObjects();
m_bDraging = false;
}
bool BYTouchDelegate::passMessage( CCNode* pParent, CCTouch *pTouch, CCEvent *pEvent )
{
if( !pParent || !pParent->isVisible() )
{
return false;
}
CCPoint pt;
CCRect rcBoundingBox;
//hande message to items
int iNumChildren = 0;
CCArray* pChildren = NULL;
//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 )
{
pt = pParent->convertTouchToNodeSpace( pTouch );
rcBoundingBox.setRect( 0, 0, pParent->getContentSize().width, pParent->getContentSize().height );
//whether hit the node
if( !rcBoundingBox.containsPoint( pt ) )
{
return false;
}
}
pChildren = pParent->getChildren();
//no children, but user touch this item, so return true.
if( !pChildren )
{
return true;
}
iNumChildren = pParent->getChildren()->count();
//pass to all children
for( int iChildIndex = 0; iChildIndex < iNumChildren; ++iChildIndex ) { //if the item claims the touch message bool bClaim = false; BYTouchDelegate* pBYItems = NULL; CCLayer* pLayer = NULL; CCNode* pNode = NULL; pNode = ( CCNode* )( pChildren->objectAtIndex( iChildIndex ) );
assert( pNode );
//items derives from BYTouchDelegate
if( ( pBYItems = dynamic_cast< BYTouchDelegate* >( pNode ) ) )
{
//pass message to all children and do itself things.
bClaim = pBYItems->byTouchBegan( pTouch, pEvent );
}
else if( ( pLayer = dynamic_cast< CCLayer* >( pNode ) ) )//layers
{
bClaim = pLayer->ccTouchBegan( pTouch, pEvent );
//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.
passMessage( pNode, pTouch, pEvent );
}
else//neither BYTouchDelegate subclasses nor layers
{
//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.
passMessage( pNode, pTouch, pEvent );
}
//if this item is interested in this message, add it to array for other messages
if( bClaim )
{
m_pItemsClaimTouch->addObject( pNode );
}
}
return true;
}

总结:

工程使用时,只需包含BYCocos.h即可。
这个体系中有一点需要注意,因为我们添加了窗体点击判断,所以加入这个体系的窗口应该显示地,正确地设置自己的窗体大小。
另外由于使用了dynamic_cast,可能会让部分读者担心效率问题。但个人认为dynamic_cast并不是C++的垃圾,其带来的性能影响应该是很有限的。
代码上有比较多的注释,如果注释有误还请告诉我。习惯练习英文注释了,希望我的注释没太大语法错误,能够让你理解。这里就不多讲述了。
感谢开发者xujiezhige的分享!