本文介绍了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类的实现, 实现点击放大的菜单按钮所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)