【cocos2d-x 3D游戏开发】2: 2D基础回顾---理解CCMenu类的实现, 实现点击放大的菜单按钮

【cocos2d-x 3D游戏开发】2: 2D基础回顾---理解CCMenu类的实现, 实现点击放大的菜单按钮,第1张

概述前言 本文介绍了CCMenu类的实现原理,并在CCMenu的基础上稍加改造,实现了一个点击自动缩放的菜单类。 CCMenu的实现 – 单点触摸事件很好的使用范例 在上一篇文中里回顾了cocos中单点触摸的用法和触摸事件分发机制、优先级控制等内容,也给出了自己写的小demo,其实在cocos本身就有很好的触摸事件的使用范例,其中就包括CCMenu的实现,下面我们结合引擎源代码来分析一下它的实现原理。 前言

本文介绍了Ccmenu类的实现原理,并在Ccmenu的基础上稍加改造,实现了一个点击自动缩放的菜单类。

Ccmenu的实现 – 单点触摸事件很好的使用范例

在上一篇文中里回顾了cocos中单点触摸的用法和触摸事件分发机制、优先级控制等内容,也给出了自己写的小demo,其实在cocos本身就有很好的触摸事件的使用范例,其中就包括Ccmenu的实现,下面我们结合引擎源代码来分析一下它的实现原理。

Ccmenu本质上就是一个touchable,并且是单点触摸的。它身上挂满了各种“零件”(各种CcmenuItem的子类),你摸它的时候,假设你摸在A点,它就判断A点落在它的哪个“零件”上,它就会叫那个“零件”做出反应,完成那个“零件”应该完成的任务(调用你在创建CcmenuItem子类时绑定的handler).

Ccmenu.h:

#ifndef __CcmeNU_H_#define __CcmeNU_H_#include "CcmenuItem.h"#include "layers_scenes_Transitions_nodes/cclayer.h"NS_CC_BEGIN// 用于判断菜单被触摸的状态typedef enum  {    kCcmenuStateWaiting,// 没被摸呢    kCcmenuStateTrackingtouch       // 正在被摸} tCcmenuState;enum {    // 菜单的触摸优先级,-128,一般人抢不过它,你要想比它先响应触摸事件,就要比-128还要小    kCcmenuHandlerPriority = -128,};// Ccmenu是个Layer.class CC_DLL Ccmenu : public cclayerRGBA{    bool m_bEnabled;    // 禁用了吗public:    Ccmenu() : m_pSelectedItem(NulL) {}    virtual ~Ccmenu(){}    // 创建方法,最后殊途同归到:createWithArray    static Ccmenu* create();    static Ccmenu* create(CcmenuItem* item,...);    static Ccmenu* createWithItem(CcmenuItem* item);    static Ccmenu* createWithItems(CcmenuItem *firstItem,va_List args);    static Ccmenu* createWithArray(CCArray* pArrayOfItems);    bool init();    // 真正的创建在这里完成,init() == initWithArray(nullptr)    bool initWithArray(CCArray* pArrayOfItems);    // 如何摆放自己身上的“零件”, 垂直、水平、按列对齐、按行对齐    voID alignItemsvertically();    voID alignItemsverticallyWithpadding(float padding);    voID alignItemsHorizontallyWithpadding(float padding);    voID alignItemsInColumns(unsigned int columns,va_List args);    voID alignItemsInColumnsWithArray(CCArray* rows);    voID alignItemsInRows(unsigned int rows,...);    voID alignItemsInRows(unsigned int rows,va_List args);    voID alignItemsInRowsWithArray(CCArray* columns);    // 菜单触摸响应的优先级 默认是kCcmenuHandlerPriority(-128)    voID setHandlerPriority(int newPriority);    // 重写CCNode的这一堆方法为了什么? 就为了判断你是不是往菜单里塞了不是CcmenuItem*类型的东西.    virtual voID addChild(CCNode * child);    virtual voID addChild(CCNode * child,int zOrder);    virtual voID addChild(CCNode * child,int zOrder,int tag);    virtual voID removeChild(CCNode* child,bool cleanup);    // cclayer的方法,注册到触摸事件分发中心:CCtouchdispatcher    virtual voID registerWithtouchdispatcher();    // 重点,响应单点触摸    virtual bool cctouchBegan(CCtouch* touch,CCEvent* event);    virtual voID cctouchended(CCtouch* touch,CCEvent* event);    virtual voID cctouchCancelled(CCtouch *touch,CCEvent* event);    virtual voID cctouchmoved(CCtouch* touch,CCEvent* event);    // 状态机,在被removeChild的时候被调用    virtual voID onExit();    // 暂时忽略    virtual voID setopacityModifyRGB(bool bValue) {CC_UNUSED_ParaM(bValue);}    virtual bool isOpacityModifyRGB(voID) { return false;}    // 不用说了    virtual bool isEnabled() { return m_bEnabled; }    virtual voID setEnabled(bool value) { m_bEnabled = value; };protected:    CcmenuItem* itemFortouch(CCtouch * touch);      // 判断自身的哪个“零件”被摸到了    tCcmenuState m_eState;                          // 触摸状态,    CcmenuItem *m_pSelectedItem;                    // 被摸到的那个“零件”};NS_CC_END#endif//__CcmeNU_H_

Ccmenu.cpp

#include "Ccmenu.h"#include "CCDirector.h"#include "CCApplication.h"#include "support/CCPointExtension.h"#include "touch_dispatcher/CCtouchdispatcher.h"#include "touch_dispatcher/CCtouch.h"#include "CCStdC.h"#include "cocoa/CCInteger.h"#include <vector>#include <stdarg.h>using namespace std;NS_CC_BEGINstatic std::vector<unsigned int> ccarray_to_std_vector(CCArray* pArray){    std::vector<unsigned int> ret;    CCObject* pObj;    CCARRAY_FOREACH(pArray,pObj)    {        CCInteger* pInteger = (CCInteger*)pObj;        ret.push_back((unsigned int)pInteger->getValue());    }    return ret;}enum {    kDefaultpadding =  5,};/***********************创建类方法*******************************/Ccmenu* Ccmenu::create(){    return Ccmenu::create(NulL,NulL);}Ccmenu * Ccmenu::create(CcmenuItem* item,...){    va_List args;    va_start(args,item);    Ccmenu *pRet = Ccmenu::createWithItems(item,args);    va_end(args);    return pRet;}Ccmenu* Ccmenu::createWithArray(CCArray* pArrayOfItems){    Ccmenu *pRet = new Ccmenu();    if (pRet && pRet->initWithArray(pArrayOfItems))    {        pRet->autorelease();    }    else    {        CC_SAFE_DELETE(pRet);    }    return pRet;}Ccmenu* Ccmenu::createWithItems(CcmenuItem* item,va_List args){    CCArray* pArray = NulL;    if( item )    {        pArray = CCArray::create(item,NulL);        CcmenuItem *i = va_arg(args,CcmenuItem*);        while(i)        {            pArray->addobject(i);            i = va_arg(args,CcmenuItem*);        }    }    return Ccmenu::createWithArray(pArray);}Ccmenu* Ccmenu::createWithItem(CcmenuItem* item){    return Ccmenu::create(item,NulL);}bool Ccmenu::init(){    return initWithArray(NulL);}/**********************初始化方法***************************/bool Ccmenu::initWithArray(CCArray* pArrayOfItems){    if (cclayer::init())    {        // 设置触摸响应优先级         settouchPriority(kCcmenuHandlerPriority);        // 单点触摸        settouchMode(kCCtouchesOneByOne);        // 开启触摸        settouchEnabled(true);        // 创建即启用        m_bEnabled = true;        CCSize s = CCDirector::sharedDirector()->getWinSize();        // 忽略锚点        this->ignoreAnchorPointForposition(true);        setAnchorPoint(ccp(0.5f,0.5f));        // Menu的默认大小是整个可视窗口的大小        this->setContentSize(s);        // 初始位置在屏幕中心,因为忽略了锚点,所以通过它的左下角(0,0)点来定位,放在了屏幕中心        setposition(ccp(s.wIDth/2,s.height/2));        if (pArrayOfItems != NulL)        {            int z=0;            CCObject* pObj = NulL;            CCARRAY_FOREACH(pArrayOfItems,pObj)            {                // 添加“零件”,都是CcmenuItem类型的,在addChild里面会做RTTI类型检查。                CcmenuItem* item = (CcmenuItem*)pObj;                this->addChild(item,z);                z++;            }        }        // 默认没有选中任何“零件”        m_pSelectedItem = NulL;        // 初始状态为等待被摸        m_eState = kCcmenuStateWaiting;        // 开启级联透明和颜色        setCascadecolorEnabled(true);        setCascadeOpacityEnabled(true);        return true;    }    return false;}// 重写addChild,防止用户往Ccmenu里塞不是CcmenuItem的东西voID Ccmenu::addChild(CCNode * child){    cclayer::addChild(child);}voID Ccmenu::addChild(CCNode * child,int zOrder){    cclayer::addChild(child,zOrder);}voID Ccmenu::addChild(CCNode * child,int tag){    // 重写addChild,防止用户往Ccmenu里塞不是CcmenuItem的东西    CCAssert( dynamic_cast<CcmenuItem*>(child) != NulL,"Menu only supports MenuItem objects as children");    cclayer::addChild(child,zOrder,tag);}voID Ccmenu::onExit(){    // 在触摸过程中被移除,清空“零件”的选中状态    if (m_eState == kCcmenuStateTrackingtouch)    {        if (m_pSelectedItem)        {            m_pSelectedItem->unselected();            m_pSelectedItem = NulL;        }        m_eState = kCcmenuStateWaiting;    }    cclayer::onExit();}voID Ccmenu::removeChild(CCNode* child,bool cleanup){    // 移除时候也在检查是不是CcmenuItem    CcmenuItem *pMenuItem = dynamic_cast<CcmenuItem*>(child);    CCAssert(pMenuItem != NulL,"Menu only supports MenuItem objects as children");    if (m_pSelectedItem == pMenuItem)    {        m_pSelectedItem = NulL;    }    CCNode::removeChild(child,cleanup);}/**************************触摸事件先关代码********************************/voID Ccmenu::setHandlerPriority(int newPriority){    CCtouchdispatcher* pdispatcher = CCDirector::sharedDirector()->gettouchdispatcher();    pdispatcher->setPriority(newPriority,this);}// 注册单点触摸,吞噬voID Ccmenu::registerWithtouchdispatcher(){    CCDirector* pDirector = CCDirector::sharedDirector();    pDirector->gettouchdispatcher()->addTargetedDelegate(this,this->gettouchPriority(),true);}// 开始触摸jbool Ccmenu::cctouchBegan(CCtouch* touch,CCEvent* event){    CC_UNUSED_ParaM(event);    // 已经开始触摸,或不可见,或被禁用,直接返回. 注意:此时触摸没被吞噬,其它节点能够响应此次触摸    if (m_eState != kCcmenuStateWaiting || ! m_bVisible || !m_bEnabled)    {        return false;    }    // 在它的祖先中,任何一个是不可见的,意味着它自己也不可见,不响应触摸事件    for (CCNode *c = this->m_pParent; c != NulL; c = c->getParent())    {        if (c->isVisible() == false)        {            return false;        }    }    // 检查通过,可以进行进一步判断了    // 判断摸在哪个“零件”上    m_pSelectedItem = this->itemFortouch(touch);    // 真的摸在了“零件”上    if (m_pSelectedItem)    {        // 置触摸状态为: 正在被摸        m_eState = kCcmenuStateTrackingtouch;        // 触发零件的selected方法        m_pSelectedItem->selected();        // 吞噬触摸,继续监听touchmoved等事件        return true;    }    return false;}// 松开手指,触摸结束voID Ccmenu::cctouchended(CCtouch *touch,CCEvent* event){    CC_UNUSED_ParaM(touch);    CC_UNUSED_ParaM(event);    // 校验触摸状态    CCAssert(m_eState == kCcmenuStateTrackingtouch,"[Menu cctouchended] -- invalID state");    // 最后的触摸点是否落在了某个“零件”上?    if (m_pSelectedItem)    {        // 松开按钮,调用其unselected方法        m_pSelectedItem->unselected();        // activate将调用CcmenuItem的回调函数        m_pSelectedItem->activate();    }    // 重置触摸状态    m_eState = kCcmenuStateWaiting;}voID Ccmenu::cctouchCancelled(CCtouch *touch,CCEvent* event){    CC_UNUSED_ParaM(touch);    CC_UNUSED_ParaM(event);    CCAssert(m_eState == kCcmenuStateTrackingtouch,"[Menu cctouchCancelled] -- invalID state");    if (m_pSelectedItem)    {        m_pSelectedItem->unselected();    }    m_eState = kCcmenuStateWaiting;}// 在菜单上移动手指voID Ccmenu::cctouchmoved(CCtouch* touch,CCEvent* event){    CC_UNUSED_ParaM(event);    // 校验状态    CCAssert(m_eState == kCcmenuStateTrackingtouch,"[Menu cctouchmoved] -- invalID state");    // 现在,触摸点移动到哪个“零件”了?    CcmenuItem *currentItem = this->itemFortouch(touch);    // 如果新的“零件”和刚才选中的“零件”不一样    // 不一样包括多种情形:    // 1. 从一个“零件”移动到了空白处;    // 2. 从空白处,移动到了某个“零件”    // 3. 从一个“零件”移动到另一个“零件”    if (currentItem != m_pSelectedItem)     {        if (m_pSelectedItem)        {            // 第1种或第3种情形            // 取消当前“零件”的选中状态            m_pSelectedItem->unselected();        }        // 更新新的“零件”选中状态,可能变为空        m_pSelectedItem = currentItem;        if (m_pSelectedItem)        {            // 第2或3种情形,新“零件”选中            m_pSelectedItem->selected();        }    }}/************************排列“零件”**********************************/voID Ccmenu::alignItemsvertically(){    this->alignItemsverticallyWithpadding(kDefaultpadding);}voID Ccmenu::alignItemsverticallyWithpadding(float padding){    float height = -padding;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                height += pChild->getContentSize().height * pChild->getScaleY() + padding;            }        }    }    float y = height / 2.0f;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                pChild->setposition(ccp(0,y - pChild->getContentSize().height * pChild->getScaleY() / 2.0f));                y -= pChild->getContentSize().height * pChild->getScaleY() + padding;            }        }    }}voID Ccmenu::alignItemsHorizontally(voID){    this->alignItemsHorizontallyWithpadding(kDefaultpadding);}voID Ccmenu::alignItemsHorizontallyWithpadding(float padding){    float wIDth = -padding;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                wIDth += pChild->getContentSize().wIDth * pChild->getScaleX() + padding;            }        }    }    float x = -wIDth / 2.0f;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                pChild->setposition(ccp(x + pChild->getContentSize().wIDth * pChild->getScaleX() / 2.0f,0));                 x += pChild->getContentSize().wIDth * pChild->getScaleX() + padding;            }        }    }}voID Ccmenu::alignItemsInColumns(unsigned int columns,columns);    this->alignItemsInColumns(columns,args);    va_end(args);}voID Ccmenu::alignItemsInColumns(unsigned int columns,va_List args){    CCArray* rows = CCArray::create();    while (columns)    {        rows->addobject(CCInteger::create(columns));        columns = va_arg(args,unsigned int);    }    alignItemsInColumnsWithArray(rows);}voID Ccmenu::alignItemsInColumnsWithArray(CCArray* rowsArray){    vector<unsigned int> rows = ccarray_to_std_vector(rowsArray);    int height = -5;    unsigned int row = 0;    unsigned int rowHeight = 0;    unsigned int columnsOccupIEd = 0;    unsigned int rowColumns;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                CCAssert(row < rows.size(),"");                rowColumns = rows[row];                // can not have zero columns on a row                CCAssert(rowColumns,"");                float tmp = pChild->getContentSize().height;                rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp);                ++columnsOccupIEd;                if (columnsOccupIEd >= rowColumns)                {                    height += rowHeight + 5;                    columnsOccupIEd = 0;                    rowHeight = 0;                    ++row;                }            }        }    }        // check if too many rows/columns for available menu items    CCAssert(! columnsOccupIEd,"");    CCSize winSize = CCDirector::sharedDirector()->getWinSize();    row = 0;    rowHeight = 0;    rowColumns = 0;    float w = 0.0;    float x = 0.0;    float y = (float)(height / 2);    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                if (rowColumns == 0)                {                    rowColumns = rows[row];                    w = winSize.wIDth / (1 + rowColumns);                    x = w;                }                float tmp = pChild->getContentSize().height;                rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp);                pChild->setposition(ccp(x - winSize.wIDth / 2,y - pChild->getContentSize().height / 2));                x += w;                ++columnsOccupIEd;                if (columnsOccupIEd >= rowColumns)                {                    y -= rowHeight + 5;                    columnsOccupIEd = 0;                    rowColumns = 0;                    rowHeight = 0;                    ++row;                }            }        }    }    }voID Ccmenu::alignItemsInRows(unsigned int rows,rows);    this->alignItemsInRows(rows,args);    va_end(args);}voID Ccmenu::alignItemsInRows(unsigned int rows,va_List args){    CCArray* pArray = CCArray::create();    while (rows)    {        pArray->addobject(CCInteger::create(rows));        rows = va_arg(args,unsigned int);    }    alignItemsInRowsWithArray(pArray);}voID Ccmenu::alignItemsInRowsWithArray(CCArray* columnArray){    vector<unsigned int> columns = ccarray_to_std_vector(columnArray);    vector<unsigned int> columnWIDths;    vector<unsigned int> columnHeights;    int wIDth = -10;    int columnHeight = -5;    unsigned int column = 0;    unsigned int columnWIDth = 0;    unsigned int rowsOccupIEd = 0;    unsigned int columnRows;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                // check if too many menu items for the amount of rows/columns                CCAssert(column < columns.size(),"");                columnRows = columns[column];                // can't have zero rows on a column                CCAssert(columnRows,"");                // columnWIDth = fmaxf(columnWIDth,[item contentSize].wIDth);                float tmp = pChild->getContentSize().wIDth;                columnWIDth = (unsigned int)((columnWIDth >= tmp || isnan(tmp)) ? columnWIDth : tmp);                columnHeight += (int)(pChild->getContentSize().height + 5);                ++rowsOccupIEd;                if (rowsOccupIEd >= columnRows)                {                    columnWIDths.push_back(columnWIDth);                    columnHeights.push_back(columnHeight);                    wIDth += columnWIDth + 10;                    rowsOccupIEd = 0;                    columnWIDth = 0;                    columnHeight = -5;                    ++column;                }            }        }    }    // check if too many rows/columns for available menu items.    CCAssert(! rowsOccupIEd,"");    CCSize winSize = CCDirector::sharedDirector()->getWinSize();    column = 0;    columnWIDth = 0;    columnRows = 0;    float x = (float)(-wIDth / 2);    float y = 0.0;    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        CCARRAY_FOREACH(m_pChildren,pObject)        {            CCNode* pChild = dynamic_cast<CCNode*>(pObject);            if (pChild)            {                if (columnRows == 0)                {                    columnRows = columns[column];                    y = (float) columnHeights[column];                }                // columnWIDth = fmaxf(columnWIDth,[item contentSize].wIDth);                float tmp = pChild->getContentSize().wIDth;                columnWIDth = (unsigned int)((columnWIDth >= tmp || isnan(tmp)) ? columnWIDth : tmp);                pChild->setposition(ccp(x + columnWIDths[column] / 2,y - winSize.height / 2));                y -= pChild->getContentSize().height + 10;                ++rowsOccupIEd;                if (rowsOccupIEd >= columnRows)                {                    x += columnWIDth + 5;                    rowsOccupIEd = 0;                    columnRows = 0;                    columnWIDth = 0;                    ++column;                }            }        }    }}// 判断触摸点落在哪个“零件”上CcmenuItem* Ccmenu::itemFortouch(CCtouch *touch){    // 获得当前触摸点的OpenGL坐标    CCPoint touchLocation = touch->getLocation();    // 遍历所有“零件”    if (m_pChildren && m_pChildren->count() > 0)    {        CCObject* pObject = NulL;        // m_pChildren是所有“零件”的集合,他们是如何排序的呢?在CCNode中,是按照zOrder从大到小排序的,zOrder大的“零件”在前面; zOrder相同的,后添加的在前面,优先判断被摸状态        CCARRAY_FOREACH(m_pChildren,pObject)        {            CcmenuItem* pChild = dynamic_cast<CcmenuItem*>(pObject);            // 忽略掉不可见或者被禁用的“零件”            if (pChild && pChild->isVisible() && pChild->isEnabled())            {                // 将触摸点坐标转换到该“零件”的节点坐标系下                CCPoint local = pChild->convertToNodeSpace(touchLocation);                // 得到“零件”的矩形轮廓,(0,wIDth,height)                CCRect r = pChild->rect();                r.origin = CCPointZero;                // 判断触摸点转换为“零件”的节点坐标之后,是否在矩形轮廓内                if (r.containsPoint(local))                {                    // 在,那就是这个“零件”被摸了,返回它                    return pChild;                }            }        }    }    return NulL;}NS_CC_END

可以看到Ccmenu的实现还是比较清晰、简单的,它本身作为一个CcmenuItem的容器,响应单点触摸事件,判断哪个item被点击,并调用其对应方法完成菜单消息响应,核心在于对触摸事件的处理,坐标点的判断。

点击缩放功能的菜单按钮

在实际的游戏开发中,最常用的CcmenuItem要属CcmenuItemImage了,在HelloWorld的Demo中可以看到,要创建一个关闭按钮通常是这样写:

CcmenuItemImage *pCloseItem = CcmenuItemImage::create( "Closenormal.png","CloseSelected.png",this,menu_selector(HelloWorld::menuCloseCallback));

其中用到了两张图片,第一张是按钮在正常状态下的图片,第二张是被点击时候的选中状态的图片。如果两种状态下按钮的图片是相近的那最好就只用一张,正常和选中都用同一个图片,然后在按钮被按下的时候让它有一个放大的效果,恢复正常之后再自动恢复原来的缩放。这样就节省了一张图片素材。

那么如果要实现一个点击缩放的菜单按钮该如何做呢?

我们知道CcmenuItemLabel在被选中的时候是有一个缩放效果的,它的selected和unselected方法是这样:

voID CcmenuItemLabel::selected(){    if(m_bEnabled)    {        CcmenuItem::selected();        CCAction *action = getActionByTag(kZoomActionTag);        if (action)        {            this->stopAction(action);        }        else        {            m_fOriginalScale = this->getScale();        }        // 缩放到原始尺寸的1.2倍        CCAction *zoomAction = CCScaleto::create(0.1f,m_fOriginalScale * 1.2f);        zoomAction->setTag(kZoomActionTag);        this->runAction(zoomAction);    }}voID CcmenuItemLabel::unselected(){    if(m_bEnabled)    {        CcmenuItem::unselected();        this->stopActionByTag(kZoomActionTag);        // 缩放回原来的尺寸        CCAction *zoomAction = CCScaleto::create(0.1f,m_fOriginalScale);        zoomAction->setTag(kZoomActionTag);        this->runAction(zoomAction);    }}

按照相同的处理方式,我可以copy一份CcmenuItemImage的实现,改个名字,然后按照CcmenuItemLabel的方式重写selected和unselected方法就可以实现和CcmenuItemLabel同样的缩放效果,但是这里会有一个问题,我实现了一个新的CcmenuItemImage达到了缩放的效果,那对于它的父类CcmenuItemSprite呢,其实跟CcmenuItemImage是一个东西,只是创建方式是传入Sprite,而不是纹理的名字,对于它也要缩放,那还要实现一遍CcmenuItemSprite的翻版,对于新定义的按钮,要缩放也要重写selected和unseleceted,加入的内容也都是相同的,即CCScaleto的action动作。与其每个CcmenuItem的子类都重写一遍加入缩放代码,还不如在顶层只搞一次。顶层在哪里,我们从Ccmenu的源码中已经看到,是Ccmenu的触摸响应里调用的CcmenuItem的selected和unseleceted等方法,那么干脆在Ccmenu里加上缩放行不行。

下面我自己copy了一份Ccmenu的实现,改名为Menu,为避免名字冲突放在一个单独的命名空间里,暂且叫这个namespace为elloop.

下面就是这个Menu类的实现代码:

自定义菜单类Menu.h,仅列出改动的部分,其它部分跟Ccmenu.h是一样的:

#ifndef CPP_DEMO_CUSTOM_MENU_H#define CPP_DEMO_CUSTOM_MENU_H#include "cocos2d.h"#include "cocos_include.h"NS_BEGIN(elloop);           // namespace elloop {// 这枚举去掉了 CC, 避免与Ccmenu中同名枚举混淆typedef enum  {    kMenuStateWaiting,kMenuStateTrackingtouch} tMenuState;enum {    kMenuHandlerPriority = -128,// 去掉了kCcmenuHandlerPriority里面的CC};class Menu : public cocos2d::cclayerRGBA{public:    // 添加了一个缩放成员变量,其它部分跟Ccmenu.h内容完全一致    Menu() : m_pSelectedItem(NulL),itemOriginScale_(1.f) {}protected:    float                   itemOriginScale_;};NS_END(elloop);    // } end of namespace elloop#endif//CPP_DEMO_CUSTOM_MENU_H

自定义缩放按钮实现文件:Menu.cpp,也仅列出改变的部分

NS_BEGIN(elloop);bool Menu::cctouchBegan(CCtouch* touch,CCEvent* event){    CC_UNUSED_ParaM(event);    if (m_eState != kMenuStateWaiting || !m_bVisible || !m_bEnabled)    {        return false;    }    for (CCNode *c = this->m_pParent; c != NulL; c = c->getParent())    {        if (c->isVisible() == false)        {            return false;        }    }    m_pSelectedItem = this->itemFortouch(touch);    if (m_pSelectedItem)    {        m_eState = kMenuStateTrackingtouch;        m_pSelectedItem->selected();        // begin : 控制CcmenuItem缩放的代码        itemOriginScale_ = m_pSelectedItem->getScale();        m_pSelectedItem->setScale(itemOriginScale_ * 1.2);        // end        return true;    }    return false;}voID Menu::cctouchended(CCtouch *touch,CCEvent* event){    CC_UNUSED_ParaM(touch);    CC_UNUSED_ParaM(event);    CCAssert(m_eState == kMenuStateTrackingtouch,"[Menu cctouchended] -- invalID state");    if (m_pSelectedItem)    {        m_pSelectedItem->unselected();        m_pSelectedItem->activate();        // begin : 控制CcmenuItem缩放的代码        m_pSelectedItem->setScale(itemOriginScale_);        // end    }    m_eState = kMenuStateWaiting;}voID Menu::cctouchCancelled(CCtouch *touch,"[Menu cctouchCancelled] -- invalID state");    if (m_pSelectedItem)    {        m_pSelectedItem->unselected();        // begin : 控制CcmenuItem缩放的代码        m_pSelectedItem->setScale(itemOriginScale_);        // end    }    m_eState = kMenuStateWaiting;}voID Menu::cctouchmoved(CCtouch* touch,CCEvent* event){    CC_UNUSED_ParaM(event);    CCAssert(m_eState == kMenuStateTrackingtouch,"[Menu cctouchmoved] -- invalID state");    CcmenuItem *currentItem = this->itemFortouch(touch);    if (currentItem != m_pSelectedItem)    {        if (m_pSelectedItem)        {            m_pSelectedItem->unselected();            // begin : 控制CcmenuItem缩放的代码            m_pSelectedItem->setScale(itemOriginScale_);            // end        }        m_pSelectedItem = currentItem;        if (m_pSelectedItem)        {            m_pSelectedItem->selected();            // begin : 控制CcmenuItem缩放的代码            itemOriginScale_ = m_pSelectedItem->getScale();            m_pSelectedItem->setScale(itemOriginScale_ * 1.2);            // end        }    }}NS_END(elloop);

自定义菜单类的使用方法

// 正常的方式来创建三个CcmenuItem,两个CcmenuItemImage,一个CcmenuItemLabel// 从左到右水平排列auto menuItemImage1 = CcmenuItemImage::create(        "DemoIcon/home_small.png","DemoIcon/home_small.png",menu_selector(touchTestPage::menuCallback));auto menuItemImage2 = CcmenuItemImage::create(        "DemoIcon/home_small.png",menu_selector(touchTestPage::menuCallback));menuItemImage2->setposition(CCPoint(menuItemImage1->getContentSize().wIDth,0));auto label = cclabelTTF::create("hello","arial.ttf",20);auto menuItemLabel = CcmenuItemLabel::create(label);menuItemLabel->setposition(CCPoint(menuItemImage2->getpositionX() + menuItemImage1->getContentSize().wIDth,0));// 指定使用elloop命名空间下的Menu.using elloop::Menu;// Menu的创建方式跟Ccmenu的创建方式完全一样Menu *menu = Menu::create(menuItemImage1,menuItemImage2,menuItemLabel,nullptr);ADD_CHILD(menu);

代码中之所以加上一个CcmenuItemLabel类型的按钮是为了测试,本身就带有缩放功能的CcmenuItem会不会和带有缩放功能的Menu父容器产生冲突,是否会产生叠加放大的效果?

下面是运行截图:

从图中能看到,两个CcmenuItemImage是可以实现自动缩放效果的,CcmenuItemLabel的缩放动作也没有与Menu的缩放发生冲突(这是为什么?涉及到Action的更新,在后面总结到动作系统的时候再做出分析)。

此外,如果觉得Menu固定的把CcmenuItem放大到1.2不够灵活,可以把数字抽离成一个成员变量,并设置setter来灵活控制缩放比例.

测试的代码,跟上一篇文章的代码放在了一起,在touchTestPage.cpp中。感兴趣的朋友可以到代码仓库瞅瞅。

源码

缩放按钮Menu的实现

缩放按钮Menu的使用范例

作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

欢迎访问github博客,与本站同步更新

总结

以上是内存溢出为你收集整理的【cocos2d-x 3D游戏开发】2: 2D基础回顾---理解CCMenu类的实现, 实现点击放大的菜单按钮全部内容,希望文章能够帮你解决【cocos2d-x 3D游戏开发】2: 2D基础回顾---理解CCMenu类的实现, 实现点击放大的菜单按钮所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1075520.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存