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

前端之家收集整理的这篇文章主要介绍了【cocos2d-x 3D游戏开发】2: 2D基础回顾---理解CCMenu类的实现, 实现点击放大的菜单按钮前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

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

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

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

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

CCMenu.h:

  1. #ifndef __CCMENU_H_
  2. #define __CCMENU_H_
  3.  
  4. #include "CCMenuItem.h"
  5. #include "layers_scenes_transitions_nodes/CCLayer.h"
  6.  
  7. NS_CC_BEGIN
  8.  
  9. // 用于判断菜单被触摸的状态
  10. typedef enum
  11. {
  12. kCCMenuStateWaiting,// 没被摸呢
  13. kCCMenuStateTrackingTouch // 正在被摸
  14. } tCCMenuState;
  15.  
  16. enum {
  17. // 菜单的触摸优先级,-128,一般人抢不过它,你要想比它先响应触摸事件,就要比-128还要小
  18. kCCMenuHandlerPriority = -128,};
  19.  
  20. // CCMenu是个Layer.
  21. class CC_DLL CCMenu : public CCLayerRGBA
  22. {
  23. bool m_bEnabled; // 禁用了吗
  24.  
  25. public:
  26. CCMenu() : m_pSelectedItem(NULL) {}
  27.  
  28. virtual ~CCMenu(){}
  29.  
  30. // 创建方法,最后殊途同归到:createWithArray
  31. static CCMenu* create();
  32. static CCMenu* create(CCMenuItem* item,...);
  33. static CCMenu* createWithItem(CCMenuItem* item);
  34. static CCMenu* createWithItems(CCMenuItem *firstItem,va_list args);
  35. static CCMenu* createWithArray(CCArray* pArrayOfItems);
  36.  
  37. bool init();
  38. // 真正的创建在这里完成,init() == initWithArray(nullptr)
  39. bool initWithArray(CCArray* pArrayOfItems);
  40.  
  41.  
  42. // 如何摆放自己身上的“零件”, 垂直、水平、按列对齐、按行对齐
  43. void alignItemsVertically();
  44. void alignItemsVerticallyWithPadding(float padding);
  45. void alignItemsHorizontallyWithPadding(float padding);
  46. void alignItemsInColumns(unsigned int columns,va_list args);
  47. void alignItemsInColumnsWithArray(CCArray* rows);
  48. void alignItemsInRows(unsigned int rows,...);
  49. void alignItemsInRows(unsigned int rows,va_list args);
  50. void alignItemsInRowsWithArray(CCArray* columns);
  51.  
  52. // 菜单触摸响应的优先级 默认是kCCMenuHandlerPriority(-128)
  53. void setHandlerPriority(int newPriority);
  54.  
  55. // 重写CCNode的这一堆方法为了什么? 就为了判断你是不是往菜单里塞了不是CCMenuItem*类型的东西.
  56. virtual void addChild(CCNode * child);
  57. virtual void addChild(CCNode * child,int zOrder);
  58. virtual void addChild(CCNode * child,int zOrder,int tag);
  59. virtual void removeChild(CCNode* child,bool cleanup);
  60.  
  61. // CCLayer的方法注册到触摸事件分发中心:CCTouchDispatcher
  62. virtual void registerWithTouchDispatcher();
  63.  
  64. // 重点,响应单点触摸
  65. virtual bool ccTouchBegan(CCTouch* touch,CCEvent* event);
  66. virtual void ccTouchEnded(CCTouch* touch,CCEvent* event);
  67. virtual void ccTouchCancelled(CCTouch *touch,CCEvent* event);
  68. virtual void ccTouchMoved(CCTouch* touch,CCEvent* event);
  69.  
  70. // 状态机,在被removeChild的时候被调用
  71. virtual void onExit();
  72.  
  73. // 暂时忽略
  74. virtual void setOpacityModifyRGB(bool bValue) {CC_UNUSED_PARAM(bValue);}
  75. virtual bool isOpacityModifyRGB(void) { return false;}
  76.  
  77. // 不用说了
  78. virtual bool isEnabled() { return m_bEnabled; }
  79. virtual void setEnabled(bool value) { m_bEnabled = value; };
  80.  
  81. protected:
  82. CCMenuItem* itemForTouch(CCTouch * touch); // 判断自身的哪个“零件”被摸到了
  83. tCCMenuState m_eState; // 触摸状态,
  84. CCMenuItem *m_pSelectedItem; // 被摸到的那个“零件”
  85. };
  86.  
  87. NS_CC_END
  88.  
  89. #endif//__CCMENU_H_

CCMenu.cpp

  1. #include "CCMenu.h"
  2. #include "CCDirector.h"
  3. #include "CCApplication.h"
  4. #include "support/CCPointExtension.h"
  5. #include "touch_dispatcher/CCTouchDispatcher.h"
  6. #include "touch_dispatcher/CCTouch.h"
  7. #include "CCStdC.h"
  8. #include "cocoa/CCInteger.h"
  9.  
  10. #include <vector>
  11. #include <stdarg.h>
  12.  
  13. using namespace std;
  14.  
  15. NS_CC_BEGIN
  16.  
  17. static std::vector<unsigned int> ccarray_to_std_vector(CCArray* pArray)
  18. {
  19. std::vector<unsigned int> ret;
  20. CCObject* pObj;
  21. CCARRAY_FOREACH(pArray,pObj)
  22. {
  23. CCInteger* pInteger = (CCInteger*)pObj;
  24. ret.push_back((unsigned int)pInteger->getValue());
  25. }
  26. return ret;
  27. }
  28.  
  29. enum
  30. {
  31. kDefaultPadding = 5,};
  32.  
  33. /***********************创建类方法*******************************/
  34. CCMenu* CCMenu::create()
  35. {
  36. return CCMenu::create(NULL,NULL);
  37. }
  38.  
  39. CCMenu * CCMenu::create(CCMenuItem* item,...)
  40. {
  41. va_list args;
  42. va_start(args,item);
  43.  
  44. CCMenu *pRet = CCMenu::createWithItems(item,args);
  45.  
  46. va_end(args);
  47.  
  48. return pRet;
  49. }
  50.  
  51. CCMenu* CCMenu::createWithArray(CCArray* pArrayOfItems)
  52. {
  53. CCMenu *pRet = new CCMenu();
  54. if (pRet && pRet->initWithArray(pArrayOfItems))
  55. {
  56. pRet->autorelease();
  57. }
  58. else
  59. {
  60. CC_SAFE_DELETE(pRet);
  61. }
  62.  
  63. return pRet;
  64. }
  65.  
  66. CCMenu* CCMenu::createWithItems(CCMenuItem* item,va_list args)
  67. {
  68. CCArray* pArray = NULL;
  69. if( item )
  70. {
  71. pArray = CCArray::create(item,NULL);
  72. CCMenuItem *i = va_arg(args,CCMenuItem*);
  73. while(i)
  74. {
  75. pArray->addObject(i);
  76. i = va_arg(args,CCMenuItem*);
  77. }
  78. }
  79.  
  80. return CCMenu::createWithArray(pArray);
  81. }
  82.  
  83. CCMenu* CCMenu::createWithItem(CCMenuItem* item)
  84. {
  85. return CCMenu::create(item,NULL);
  86. }
  87.  
  88. bool CCMenu::init()
  89. {
  90. return initWithArray(NULL);
  91. }
  92.  
  93. /**********************初始化方法***************************/
  94. bool CCMenu::initWithArray(CCArray* pArrayOfItems)
  95. {
  96. if (CCLayer::init())
  97. {
  98. // 设置触摸响应优先级
  99. setTouchPriority(kCCMenuHandlerPriority);
  100. // 单点触摸
  101. setTouchMode(kCCTouchesOneByOne);
  102. // 开启触摸
  103. setTouchEnabled(true);
  104.  
  105. // 创建即启用
  106. m_bEnabled = true;
  107.  
  108. CCSize s = CCDirector::sharedDirector()->getWinSize();
  109.  
  110. // 忽略锚点
  111. this->ignoreAnchorPointForPosition(true);
  112. setAnchorPoint(ccp(0.5f,0.5f));
  113.  
  114. // Menu的默认大小是整个可视窗口的大小
  115. this->setContentSize(s);
  116.  
  117. // 初始位置在屏幕中心,因为忽略了锚点,所以通过它的左下角(0,0)点来定位,放在了屏幕中心
  118. setPosition(ccp(s.width/2,s.height/2));
  119.  
  120. if (pArrayOfItems != NULL)
  121. {
  122. int z=0;
  123. CCObject* pObj = NULL;
  124. CCARRAY_FOREACH(pArrayOfItems,pObj)
  125. {
  126. // 添加“零件”,都是CCMenuItem类型的,在addChild里面会做RTTI类型检查。
  127. CCMenuItem* item = (CCMenuItem*)pObj;
  128. this->addChild(item,z);
  129. z++;
  130. }
  131. }
  132.  
  133. // 默认没有选中任何“零件”
  134. m_pSelectedItem = NULL;
  135.  
  136. // 初始状态为等待被摸
  137. m_eState = kCCMenuStateWaiting;
  138.  
  139. // 开启级联透明和颜色
  140. setCascadeColorEnabled(true);
  141. setCascadeOpacityEnabled(true);
  142.  
  143. return true;
  144. }
  145. return false;
  146. }
  147.  
  148. // 重写addChild,防止用户往CCMenu里塞不是CCMenuItem的东西
  149. void CCMenu::addChild(CCNode * child)
  150. {
  151. CCLayer::addChild(child);
  152. }
  153.  
  154. void CCMenu::addChild(CCNode * child,int zOrder)
  155. {
  156. CCLayer::addChild(child,zOrder);
  157. }
  158.  
  159. void CCMenu::addChild(CCNode * child,int tag)
  160. {
  161. // 重写addChild,防止用户往CCMenu里塞不是CCMenuItem的东西
  162. CCAssert( dynamic_cast<CCMenuItem*>(child) != NULL,"Menu only supports MenuItem objects as children");
  163. CCLayer::addChild(child,zOrder,tag);
  164. }
  165.  
  166. void CCMenu::onExit()
  167. {
  168. // 在触摸过程中被移除,清空“零件”的选中状态
  169. if (m_eState == kCCMenuStateTrackingTouch)
  170. {
  171. if (m_pSelectedItem)
  172. {
  173. m_pSelectedItem->unselected();
  174. m_pSelectedItem = NULL;
  175. }
  176.  
  177. m_eState = kCCMenuStateWaiting;
  178. }
  179.  
  180. CCLayer::onExit();
  181. }
  182.  
  183. void CCMenu::removeChild(CCNode* child,bool cleanup)
  184. {
  185. // 移除时候也在检查是不是CCMenuItem
  186. CCMenuItem *pMenuItem = dynamic_cast<CCMenuItem*>(child);
  187. CCAssert(pMenuItem != NULL,"Menu only supports MenuItem objects as children");
  188.  
  189. if (m_pSelectedItem == pMenuItem)
  190. {
  191. m_pSelectedItem = NULL;
  192. }
  193.  
  194. CCNode::removeChild(child,cleanup);
  195. }
  196.  
  197. /**************************触摸事件先关代码********************************/
  198. void CCMenu::setHandlerPriority(int newPriority)
  199. {
  200. CCTouchDispatcher* pDispatcher = CCDirector::sharedDirector()->getTouchDispatcher();
  201. pDispatcher->setPriority(newPriority,this);
  202. }
  203.  
  204. // 注册单点触摸,吞噬
  205. void CCMenu::registerWithTouchDispatcher()
  206. {
  207. CCDirector* pDirector = CCDirector::sharedDirector();
  208. pDirector->getTouchDispatcher()->addTargetedDelegate(this,this->getTouchPriority(),true);
  209. }
  210.  
  211. // 开始触摸j
  212. bool CCMenu::ccTouchBegan(CCTouch* touch,CCEvent* event)
  213. {
  214. CC_UNUSED_PARAM(event);
  215. // 已经开始触摸,或不可见,或被禁用,直接返回. 注意:此时触摸没被吞噬,其它节点能够响应此次触摸
  216. if (m_eState != kCCMenuStateWaiting || ! m_bVisible || !m_bEnabled)
  217. {
  218. return false;
  219. }
  220.  
  221. // 在它的祖先中,任何一个是不可见的,意味着它自己也不可见,不响应触摸事件
  222. for (CCNode *c = this->m_pParent; c != NULL; c = c->getParent())
  223. {
  224. if (c->isVisible() == false)
  225. {
  226. return false;
  227. }
  228. }
  229.  
  230. // 检查通过,可以进行进一步判断了
  231.  
  232. // 判断摸在哪个“零件”上
  233. m_pSelectedItem = this->itemForTouch(touch);
  234. // 真的摸在了“零件”上
  235. if (m_pSelectedItem)
  236. {
  237. // 置触摸状态为: 正在被摸
  238. m_eState = kCCMenuStateTrackingTouch;
  239. // 触发零件的selected方法
  240. m_pSelectedItem->selected();
  241. // 吞噬触摸,继续监听touchMoved等事件
  242. return true;
  243. }
  244. return false;
  245. }
  246.  
  247. // 松开手指,触摸结束
  248. void CCMenu::ccTouchEnded(CCTouch *touch,CCEvent* event)
  249. {
  250. CC_UNUSED_PARAM(touch);
  251. CC_UNUSED_PARAM(event);
  252. // 校验触摸状态
  253. CCAssert(m_eState == kCCMenuStateTrackingTouch,"[Menu ccTouchEnded] -- invalid state");
  254.  
  255. // 最后的触摸点是否落在了某个“零件”上?
  256. if (m_pSelectedItem)
  257. {
  258. // 松开按钮,调用其unselected方法
  259. m_pSelectedItem->unselected();
  260. // activate将调用CCMenuItem的回调函数
  261. m_pSelectedItem->activate();
  262. }
  263. // 重置触摸状态
  264. m_eState = kCCMenuStateWaiting;
  265. }
  266.  
  267. void CCMenu::ccTouchCancelled(CCTouch *touch,CCEvent* event)
  268. {
  269. CC_UNUSED_PARAM(touch);
  270. CC_UNUSED_PARAM(event);
  271. CCAssert(m_eState == kCCMenuStateTrackingTouch,"[Menu ccTouchCancelled] -- invalid state");
  272. if (m_pSelectedItem)
  273. {
  274. m_pSelectedItem->unselected();
  275. }
  276. m_eState = kCCMenuStateWaiting;
  277. }
  278.  
  279. // 在菜单上移动手指
  280. void CCMenu::ccTouchMoved(CCTouch* touch,CCEvent* event)
  281. {
  282. CC_UNUSED_PARAM(event);
  283. // 校验状态
  284. CCAssert(m_eState == kCCMenuStateTrackingTouch,"[Menu ccTouchMoved] -- invalid state");
  285.  
  286. // 现在,触摸点移动到哪个“零件”了?
  287. CCMenuItem *currentItem = this->itemForTouch(touch);
  288.  
  289. // 如果新的“零件”和刚才选中的“零件”不一样
  290. // 不一样包括多种情形:
  291. // 1. 从一个“零件”移动到了空白处;
  292. // 2. 从空白处,移动到了某个“零件”
  293. // 3. 从一个“零件”移动到另一个“零件”
  294. if (currentItem != m_pSelectedItem)
  295. {
  296. if (m_pSelectedItem)
  297. {
  298. // 第1种或第3种情形
  299. // 取消当前“零件”的选中状态
  300. m_pSelectedItem->unselected();
  301. }
  302.  
  303. // 更新新的“零件”选中状态,可能变为空
  304. m_pSelectedItem = currentItem;
  305. if (m_pSelectedItem)
  306. {
  307. // 第2或3种情形,新“零件”选中
  308. m_pSelectedItem->selected();
  309. }
  310. }
  311. }
  312.  
  313. /************************排列“零件”**********************************/
  314. void CCMenu::alignItemsVertically()
  315. {
  316. this->alignItemsVerticallyWithPadding(kDefaultPadding);
  317. }
  318.  
  319. void CCMenu::alignItemsVerticallyWithPadding(float padding)
  320. {
  321. float height = -padding;
  322. if (m_pChildren && m_pChildren->count() > 0)
  323. {
  324. CCObject* pObject = NULL;
  325. CCARRAY_FOREACH(m_pChildren,pObject)
  326. {
  327. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  328. if (pChild)
  329. {
  330. height += pChild->getContentSize().height * pChild->getScaleY() + padding;
  331. }
  332. }
  333. }
  334.  
  335. float y = height / 2.0f;
  336. if (m_pChildren && m_pChildren->count() > 0)
  337. {
  338. CCObject* pObject = NULL;
  339. CCARRAY_FOREACH(m_pChildren,pObject)
  340. {
  341. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  342. if (pChild)
  343. {
  344. pChild->setPosition(ccp(0,y - pChild->getContentSize().height * pChild->getScaleY() / 2.0f));
  345. y -= pChild->getContentSize().height * pChild->getScaleY() + padding;
  346. }
  347. }
  348. }
  349. }
  350.  
  351. void CCMenu::alignItemsHorizontally(void)
  352. {
  353. this->alignItemsHorizontallyWithPadding(kDefaultPadding);
  354. }
  355.  
  356. void CCMenu::alignItemsHorizontallyWithPadding(float padding)
  357. {
  358. float width = -padding;
  359. if (m_pChildren && m_pChildren->count() > 0)
  360. {
  361. CCObject* pObject = NULL;
  362. CCARRAY_FOREACH(m_pChildren,pObject)
  363. {
  364. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  365. if (pChild)
  366. {
  367. width += pChild->getContentSize().width * pChild->getScaleX() + padding;
  368. }
  369. }
  370. }
  371.  
  372. float x = -width / 2.0f;
  373. if (m_pChildren && m_pChildren->count() > 0)
  374. {
  375. CCObject* pObject = NULL;
  376. CCARRAY_FOREACH(m_pChildren,pObject)
  377. {
  378. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  379. if (pChild)
  380. {
  381. pChild->setPosition(ccp(x + pChild->getContentSize().width * pChild->getScaleX() / 2.0f,0));
  382. x += pChild->getContentSize().width * pChild->getScaleX() + padding;
  383. }
  384. }
  385. }
  386. }
  387.  
  388. void CCMenu::alignItemsInColumns(unsigned int columns,columns);
  389.  
  390. this->alignItemsInColumns(columns,args);
  391.  
  392. va_end(args);
  393. }
  394.  
  395. void CCMenu::alignItemsInColumns(unsigned int columns,va_list args)
  396. {
  397. CCArray* rows = CCArray::create();
  398. while (columns)
  399. {
  400. rows->addObject(CCInteger::create(columns));
  401. columns = va_arg(args,unsigned int);
  402. }
  403. alignItemsInColumnsWithArray(rows);
  404. }
  405.  
  406. void CCMenu::alignItemsInColumnsWithArray(CCArray* rowsArray)
  407. {
  408. vector<unsigned int> rows = ccarray_to_std_vector(rowsArray);
  409.  
  410. int height = -5;
  411. unsigned int row = 0;
  412. unsigned int rowHeight = 0;
  413. unsigned int columnsOccupied = 0;
  414. unsigned int rowColumns;
  415.  
  416. if (m_pChildren && m_pChildren->count() > 0)
  417. {
  418. CCObject* pObject = NULL;
  419. CCARRAY_FOREACH(m_pChildren,pObject)
  420. {
  421. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  422. if (pChild)
  423. {
  424. CCAssert(row < rows.size(),"");
  425.  
  426. rowColumns = rows[row];
  427. // can not have zero columns on a row
  428. CCAssert(rowColumns,"");
  429.  
  430. float tmp = pChild->getContentSize().height;
  431. rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp);
  432.  
  433. ++columnsOccupied;
  434. if (columnsOccupied >= rowColumns)
  435. {
  436. height += rowHeight + 5;
  437.  
  438. columnsOccupied = 0;
  439. rowHeight = 0;
  440. ++row;
  441. }
  442. }
  443. }
  444. }
  445.  
  446. // check if too many rows/columns for available menu items
  447. CCAssert(! columnsOccupied,"");
  448.  
  449. CCSize winSize = CCDirector::sharedDirector()->getWinSize();
  450.  
  451. row = 0;
  452. rowHeight = 0;
  453. rowColumns = 0;
  454. float w = 0.0;
  455. float x = 0.0;
  456. float y = (float)(height / 2);
  457.  
  458. if (m_pChildren && m_pChildren->count() > 0)
  459. {
  460. CCObject* pObject = NULL;
  461. CCARRAY_FOREACH(m_pChildren,pObject)
  462. {
  463. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  464. if (pChild)
  465. {
  466. if (rowColumns == 0)
  467. {
  468. rowColumns = rows[row];
  469. w = winSize.width / (1 + rowColumns);
  470. x = w;
  471. }
  472.  
  473. float tmp = pChild->getContentSize().height;
  474. rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp);
  475.  
  476. pChild->setPosition(ccp(x - winSize.width / 2,y - pChild->getContentSize().height / 2));
  477.  
  478. x += w;
  479. ++columnsOccupied;
  480.  
  481. if (columnsOccupied >= rowColumns)
  482. {
  483. y -= rowHeight + 5;
  484.  
  485. columnsOccupied = 0;
  486. rowColumns = 0;
  487. rowHeight = 0;
  488. ++row;
  489. }
  490. }
  491. }
  492. }
  493. }
  494.  
  495. void CCMenu::alignItemsInRows(unsigned int rows,rows);
  496.  
  497. this->alignItemsInRows(rows,args);
  498.  
  499. va_end(args);
  500. }
  501.  
  502. void CCMenu::alignItemsInRows(unsigned int rows,va_list args)
  503. {
  504. CCArray* pArray = CCArray::create();
  505. while (rows)
  506. {
  507. pArray->addObject(CCInteger::create(rows));
  508. rows = va_arg(args,unsigned int);
  509. }
  510. alignItemsInRowsWithArray(pArray);
  511. }
  512.  
  513. void CCMenu::alignItemsInRowsWithArray(CCArray* columnArray)
  514. {
  515. vector<unsigned int> columns = ccarray_to_std_vector(columnArray);
  516.  
  517. vector<unsigned int> columnWidths;
  518. vector<unsigned int> columnHeights;
  519.  
  520. int width = -10;
  521. int columnHeight = -5;
  522. unsigned int column = 0;
  523. unsigned int columnWidth = 0;
  524. unsigned int rowsOccupied = 0;
  525. unsigned int columnRows;
  526.  
  527. if (m_pChildren && m_pChildren->count() > 0)
  528. {
  529. CCObject* pObject = NULL;
  530. CCARRAY_FOREACH(m_pChildren,pObject)
  531. {
  532. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  533. if (pChild)
  534. {
  535. // check if too many menu items for the amount of rows/columns
  536. CCAssert(column < columns.size(),"");
  537.  
  538. columnRows = columns[column];
  539. // can't have zero rows on a column
  540. CCAssert(columnRows,"");
  541.  
  542. // columnWidth = fmaxf(columnWidth,[item contentSize].width);
  543. float tmp = pChild->getContentSize().width;
  544. columnWidth = (unsigned int)((columnWidth >= tmp || isnan(tmp)) ? columnWidth : tmp);
  545.  
  546. columnHeight += (int)(pChild->getContentSize().height + 5);
  547. ++rowsOccupied;
  548.  
  549. if (rowsOccupied >= columnRows)
  550. {
  551. columnWidths.push_back(columnWidth);
  552. columnHeights.push_back(columnHeight);
  553. width += columnWidth + 10;
  554.  
  555. rowsOccupied = 0;
  556. columnWidth = 0;
  557. columnHeight = -5;
  558. ++column;
  559. }
  560. }
  561. }
  562. }
  563.  
  564. // check if too many rows/columns for available menu items.
  565. CCAssert(! rowsOccupied,"");
  566.  
  567. CCSize winSize = CCDirector::sharedDirector()->getWinSize();
  568.  
  569. column = 0;
  570. columnWidth = 0;
  571. columnRows = 0;
  572. float x = (float)(-width / 2);
  573. float y = 0.0;
  574.  
  575. if (m_pChildren && m_pChildren->count() > 0)
  576. {
  577. CCObject* pObject = NULL;
  578. CCARRAY_FOREACH(m_pChildren,pObject)
  579. {
  580. CCNode* pChild = dynamic_cast<CCNode*>(pObject);
  581. if (pChild)
  582. {
  583. if (columnRows == 0)
  584. {
  585. columnRows = columns[column];
  586. y = (float) columnHeights[column];
  587. }
  588.  
  589. // columnWidth = fmaxf(columnWidth,[item contentSize].width);
  590. float tmp = pChild->getContentSize().width;
  591. columnWidth = (unsigned int)((columnWidth >= tmp || isnan(tmp)) ? columnWidth : tmp);
  592.  
  593. pChild->setPosition(ccp(x + columnWidths[column] / 2,y - winSize.height / 2));
  594.  
  595. y -= pChild->getContentSize().height + 10;
  596. ++rowsOccupied;
  597.  
  598. if (rowsOccupied >= columnRows)
  599. {
  600. x += columnWidth + 5;
  601. rowsOccupied = 0;
  602. columnRows = 0;
  603. columnWidth = 0;
  604. ++column;
  605. }
  606. }
  607. }
  608. }
  609. }
  610.  
  611. // 判断触摸点落在哪个“零件”上
  612. CCMenuItem* CCMenu::itemForTouch(CCTouch *touch)
  613. {
  614. // 获得当前触摸点的OpenGL坐标
  615. CCPoint touchLocation = touch->getLocation();
  616.  
  617. // 遍历所有“零件”
  618. if (m_pChildren && m_pChildren->count() > 0)
  619. {
  620. CCObject* pObject = NULL;
  621. // m_pChildren是所有“零件”的集合,他们是如何排序的呢?在CCNode中,是按照zOrder从大到小排序的,zOrder大的“零件”在前面; zOrder相同的,后添加的在前面,优先判断被摸状态
  622. CCARRAY_FOREACH(m_pChildren,pObject)
  623. {
  624. CCMenuItem* pChild = dynamic_cast<CCMenuItem*>(pObject);
  625.  
  626. // 忽略掉不可见或者被禁用的“零件”
  627. if (pChild && pChild->isVisible() && pChild->isEnabled())
  628. {
  629. // 将触摸点坐标转换到该“零件”的节点坐标系下
  630. CCPoint local = pChild->convertToNodeSpace(touchLocation);
  631.  
  632. // 得到“零件”的矩形轮廓,(0,width,height)
  633. CCRect r = pChild->rect();
  634. r.origin = CCPointZero;
  635.  
  636. // 判断触摸点转换为“零件”的节点坐标之后,是否在矩形轮廓内
  637. if (r.containsPoint(local))
  638. {
  639. // 在,那就是这个“零件”被摸了,返回它
  640. return pChild;
  641. }
  642. }
  643. }
  644. }
  645.  
  646. return NULL;
  647. }
  648.  
  649. NS_CC_END

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

点击缩放功能菜单按钮

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

  1. CCMenuItemImage *pCloseItem = CCMenuItemImage::create( "CloseNormal.png","CloseSelected.png",this,menu_selector(HelloWorld::menuCloseCallback));

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

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

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

  1. void CCMenuItemLabel::selected()
  2. {
  3. if(m_bEnabled)
  4. {
  5. CCMenuItem::selected();
  6.  
  7. CCAction *action = getActionByTag(kZoomActionTag);
  8. if (action)
  9. {
  10. this->stopAction(action);
  11. }
  12. else
  13. {
  14. m_fOriginalScale = this->getScale();
  15. }
  16.  
  17. // 缩放到原始尺寸的1.2倍
  18. CCAction *zoomAction = CCScaleTo::create(0.1f,m_fOriginalScale * 1.2f);
  19. zoomAction->setTag(kZoomActionTag);
  20. this->runAction(zoomAction);
  21. }
  22. }
  23.  
  24. void CCMenuItemLabel::unselected()
  25. {
  26. if(m_bEnabled)
  27. {
  28. CCMenuItem::unselected();
  29. this->stopActionByTag(kZoomActionTag);
  30. // 缩放回原来的尺寸
  31. CCAction *zoomAction = CCScaleTo::create(0.1f,m_fOriginalScale);
  32. zoomAction->setTag(kZoomActionTag);
  33. this->runAction(zoomAction);
  34. }
  35. }

按照相同的处理方式,我可以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是一样的:

  1. #ifndef CPP_DEMO_CUSTOM_MENU_H
  2. #define CPP_DEMO_CUSTOM_MENU_H
  3.  
  4. #include "cocos2d.h"
  5. #include "cocos_include.h"
  6.  
  7.  
  8. NS_BEGIN(elloop); // namespace elloop {
  9.  
  10.  
  11. // 这枚举去掉了 CC, 避免与CCMenu中同名枚举混淆
  12. typedef enum
  13. {
  14. kMenuStateWaiting,kMenuStateTrackingTouch
  15. } tMenuState;
  16.  
  17.  
  18. enum {
  19. kMenuHandlerPriority = -128,// 去掉了kCCMenuHandlerPriority里面的CC
  20. };
  21.  
  22. class Menu : public cocos2d::CCLayerRGBA
  23. {
  24. public:
  25. // 添加了一个缩放成员变量,其它部分跟CCMenu.h内容完全一致
  26. Menu() : m_pSelectedItem(NULL),itemOriginScale_(1.f) {}
  27. protected:
  28. float itemOriginScale_;
  29. };
  30.  
  31. NS_END(elloop); // } end of namespace elloop
  32.  
  33. #endif//CPP_DEMO_CUSTOM_MENU_H

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

  1. NS_BEGIN(elloop);
  2.  
  3. bool Menu::ccTouchBegan(CCTouch* touch,CCEvent* event)
  4. {
  5. CC_UNUSED_PARAM(event);
  6. if (m_eState != kMenuStateWaiting || !m_bVisible || !m_bEnabled)
  7. {
  8. return false;
  9. }
  10.  
  11. for (CCNode *c = this->m_pParent; c != NULL; c = c->getParent())
  12. {
  13. if (c->isVisible() == false)
  14. {
  15. return false;
  16. }
  17. }
  18.  
  19. m_pSelectedItem = this->itemForTouch(touch);
  20. if (m_pSelectedItem)
  21. {
  22. m_eState = kMenuStateTrackingTouch;
  23. m_pSelectedItem->selected();
  24.  
  25. // begin : 控制CCMenuItem缩放的代码
  26. itemOriginScale_ = m_pSelectedItem->getScale();
  27. m_pSelectedItem->setScale(itemOriginScale_ * 1.2);
  28. // end
  29.  
  30. return true;
  31. }
  32. return false;
  33. }
  34.  
  35. void Menu::ccTouchEnded(CCTouch *touch,CCEvent* event)
  36. {
  37. CC_UNUSED_PARAM(touch);
  38. CC_UNUSED_PARAM(event);
  39. CCAssert(m_eState == kMenuStateTrackingTouch,"[Menu ccTouchEnded] -- invalid state");
  40. if (m_pSelectedItem)
  41. {
  42. m_pSelectedItem->unselected();
  43. m_pSelectedItem->activate();
  44.  
  45. // begin : 控制CCMenuItem缩放的代码
  46. m_pSelectedItem->setScale(itemOriginScale_);
  47. // end
  48. }
  49. m_eState = kMenuStateWaiting;
  50. }
  51.  
  52. void Menu::ccTouchCancelled(CCTouch *touch,"[Menu ccTouchCancelled] -- invalid state");
  53. if (m_pSelectedItem)
  54. {
  55. m_pSelectedItem->unselected();
  56.  
  57. // begin : 控制CCMenuItem缩放的代码
  58. m_pSelectedItem->setScale(itemOriginScale_);
  59. // end
  60. }
  61. m_eState = kMenuStateWaiting;
  62. }
  63.  
  64. void Menu::ccTouchMoved(CCTouch* touch,CCEvent* event)
  65. {
  66. CC_UNUSED_PARAM(event);
  67. CCAssert(m_eState == kMenuStateTrackingTouch,"[Menu ccTouchMoved] -- invalid state");
  68. CCMenuItem *currentItem = this->itemForTouch(touch);
  69. if (currentItem != m_pSelectedItem)
  70. {
  71. if (m_pSelectedItem)
  72. {
  73. m_pSelectedItem->unselected();
  74.  
  75. // begin : 控制CCMenuItem缩放的代码
  76. m_pSelectedItem->setScale(itemOriginScale_);
  77. // end
  78. }
  79. m_pSelectedItem = currentItem;
  80. if (m_pSelectedItem)
  81. {
  82. m_pSelectedItem->selected();
  83.  
  84. // begin : 控制CCMenuItem缩放的代码
  85. itemOriginScale_ = m_pSelectedItem->getScale();
  86. m_pSelectedItem->setScale(itemOriginScale_ * 1.2);
  87. // end
  88. }
  89. }
  90. }
  91.  
  92. NS_END(elloop);

自定义菜单类的使用方法

  1. // 正常的方式来创建三个CCMenuItem,两个CCMenuItemImage,一个CCMenuItemLabel
  2. // 从左到右水平排列
  3.  
  4. auto menuItemImage1 = CCMenuItemImage::create(
  5. "DemoIcon/home_small.png","DemoIcon/home_small.png",menu_selector(TouchTestPage::menuCallback));
  6.  
  7. auto menuItemImage2 = CCMenuItemImage::create(
  8. "DemoIcon/home_small.png",menu_selector(TouchTestPage::menuCallback));
  9.  
  10. menuItemImage2->setPosition(CCPoint(menuItemImage1->getContentSize().width,0));
  11.  
  12. auto label = CCLabelTTF::create("hello","arial.ttf",20);
  13. auto menuItemLabel = CCMenuItemLabel::create(label);
  14. menuItemLabel->setPosition(CCPoint(menuItemImage2->getPositionX() + menuItemImage1->getContentSize().width,0));
  15.  
  16. // 指定使用elloop命名空间下的Menu.
  17. using elloop::Menu;
  18. // Menu的创建方式跟CCMenu的创建方式完全一样
  19. Menu *menu = Menu::create(menuItemImage1,menuItemImage2,menuItemLabel,nullptr);
  20. ADD_CHILD(menu);

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

下面是运行截图:

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

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

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

源码


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

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

猜你在找的Cocos2d-x相关文章