[关闭]
@TangWill 2019-06-15T18:38:15.000000Z 字数 19673 阅读 753

cocos2d-x 模态对话框的实现

DataStruct

在游戏中,我们常常需要实现弹出一个模态对话框,比如说游戏暂停,退出提示对话框等

对话框特点如下:

1.可定制的,比如说背景图,标题,文本,按钮等,根据需要添加和设置

2.需要屏蔽对话框下层的触摸

3.为了友好的效果显示,把不可触摸的部分变为灰色


模板一


Dialog.h

  1. #ifndef __DIALOG_H__
  2. #define __DIALOG_H__
  3. #include "cocos2d.h"
  4. #include "ui/CocosGUI.h"
  5. #include "Theme.h"
  6. #include "Network.h"
  7. #include "User.h"
  8. #define SCREEN_WIDTH 1200
  9. #define SCREEN_HEIGHT 900
  10. USING_NS_CC;
  11. using namespace cocos2d::ui;
  12. using std::string;
  13. class Dialog final :
  14. public LayerColor
  15. {
  16. public:
  17. Dialog() : menu(nullptr), labelMenu(nullptr), backGround(nullptr), title(nullptr), contentText(nullptr) {}
  18. ~Dialog();
  19. bool init() override;
  20. CREATE_FUNC(Dialog);
  21. static Dialog* create(const string& background, const Size& size);
  22. // touch事件监听
  23. bool onTouchBegan(Touch* touch, Event* event) override { return true; }
  24. void onTouchMoved(Touch* touch, Event* event) override {}
  25. void onTouchEnded(Touch* touch, Event* event) override {}
  26. void onEnter() override;
  27. void onExit() override;
  28. // 添加标题
  29. void setTitle(const string& title, int fontSize = 20);
  30. // 添加文本内容
  31. void setContentText(const string& text, const int fontSize, const int padding, const int paddingTop);
  32. // 添加button、label或listView
  33. bool addButton(MenuItem* menuItem) const;
  34. bool addLabel(MenuItem* menuItem) const;
  35. void addListView(bool dialogType, bool inMenu, bool classical, bool noLeadingIn = true);
  36. private:
  37. Theme* theme = Theme::getInstance();
  38. // 文字内容两边和顶部的空白区
  39. int contentPadding = 0;
  40. int contentPaddingTop = 0;
  41. Size dialogContentSize;
  42. // 初始化scene中心位置,方便使用
  43. const Point center = Point(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
  44. int position = 11; // 滚动条位置
  45. bool rank = false; // 对话框类型
  46. bool rankType = false; // 排行榜类型
  47. bool isInMenu = true; // 排行榜是否在menuScene
  48. bool isClassical = true; // 游戏模式
  49. bool changed = false; // 判断排行榜是否改变
  50. ListView* listView = nullptr;
  51. Label* nameLabel = nullptr;
  52. Label* rankLabel = nullptr;
  53. Label* scoreLabel = nullptr;
  54. Label* content = nullptr;
  55. Layout* layout = nullptr;
  56. EventListenerMouse* listenerMouse = nullptr;
  57. // 排行榜
  58. vector<pair<string, int>> classicalRank;
  59. vector<pair<string, int>> plusRank;
  60. void backgroundFinish(); // 初始化对话框内容及布局
  61. void initRankDialog(); // 初始化排行榜对话框
  62. void initNormalDialog(); // 初始化普通对话框
  63. void getRankByType(bool type); // 获取排行榜内容
  64. // set and get
  65. CC_SYNTHESIZE_RETAIN(Menu*, menu, MenuButton);
  66. CC_SYNTHESIZE_RETAIN(Menu*, labelMenu, MenuLabel);
  67. CC_SYNTHESIZE_RETAIN(ui::Scale9Sprite*, backGround, BackGround);
  68. CC_SYNTHESIZE_RETAIN(Label*, title, LabelTitle);
  69. CC_SYNTHESIZE_RETAIN(Label*, contentText, LabelContentText);
  70. };
  71. #endif

Dialog.cpp

  1. #include "Dialog.h"
  2. Dialog::~Dialog()
  3. {
  4. CC_SAFE_RELEASE(menu);
  5. CC_SAFE_RELEASE(labelMenu);
  6. CC_SAFE_RELEASE(contentText);
  7. CC_SAFE_RELEASE(title);
  8. }
  9. bool Dialog::init()
  10. {
  11. if (!LayerColor::init()) return false;
  12. // 初始化需要的 Menu button菜单
  13. auto menu = Menu::create();
  14. menu->setPosition(Size::ZERO);
  15. setMenuButton(menu);
  16. // label菜单
  17. auto labelMenu = Menu::create();
  18. labelMenu->setPosition(Size::ZERO);
  19. setMenuLabel(labelMenu);
  20. // 添加layer的事件监听事件
  21. auto listener = EventListenerTouchOneByOne::create();
  22. listener->onTouchBegan = CC_CALLBACK_2(Dialog::onTouchBegan, this);
  23. listener->onTouchMoved = CC_CALLBACK_2(Dialog::onTouchMoved, this);
  24. listener->onTouchEnded = CC_CALLBACK_2(Dialog::onTouchEnded, this);
  25. auto dispatcher = Director::getInstance()->getEventDispatcher();
  26. dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
  27. // 设置背景版透明,达到对话框下层变灰暗效果
  28. setColor(Color3B(0, 0, 0));
  29. setOpacity(128);
  30. return true;
  31. }
  32. Dialog* Dialog::create(const string& background, const Size& size)
  33. {
  34. auto layer = Dialog::create();
  35. layer->setBackGround(Scale9Sprite::create(background));
  36. layer->dialogContentSize = size;
  37. return layer;
  38. }
  39. #pragma region Override
  40. void Dialog::onEnter()
  41. {
  42. LayerColor::onEnter();
  43. Layer::onEnter();
  44. _eventDispatcher->pauseEventListenersForTarget(this->getParent(), true); // 阻止事件向下传递
  45. //添加背景图片
  46. auto background = getBackGround();
  47. background->setContentSize(dialogContentSize);
  48. background->setPosition(Point(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
  49. this->addChild(background, 0, 0);
  50. // 弹出效果
  51. Action* dialog = Sequence::create(
  52. ScaleTo::create(0.0f, 0.0f),
  53. ScaleTo::create(0.3f, 1.0f),
  54. CallFunc::create(CC_CALLBACK_0(Dialog::backgroundFinish, this)),
  55. nullptr
  56. );
  57. background->runAction(dialog);
  58. }
  59. void Dialog::onExit()
  60. {
  61. Layer::onExit();
  62. LayerColor::onExit();
  63. _eventDispatcher->resumeEventListenersForTarget(this->getParent(), true); // 还原事件监听
  64. }
  65. #pragma endregion
  66. #pragma region Add Component
  67. void Dialog::setTitle(const string& title, const int fontSize/*=20*/)
  68. {
  69. const auto label = Label::createWithTTF(title, theme->semiBoldFont, fontSize);
  70. setLabelTitle(label);
  71. }
  72. void Dialog::setContentText(const string& text, const int fontSize, const int padding, const int paddingTop)
  73. {
  74. const auto label = Label::createWithTTF(text, theme->semiBoldFont, fontSize);
  75. setLabelContentText(label);
  76. contentPadding = padding;
  77. contentPaddingTop = paddingTop;
  78. }
  79. bool Dialog::addButton(MenuItem* menuItem) const
  80. {
  81. // 添加精灵菜单项
  82. const auto center = Point(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
  83. menuItem->setPosition(center);
  84. getMenuButton()->addChild(menuItem);
  85. return true;
  86. }
  87. bool Dialog::addLabel(MenuItem* menuItem) const
  88. {
  89. // 添加标签菜单项
  90. const auto center = Point(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
  91. menuItem->setPosition(center);
  92. getMenuLabel()->addChild(menuItem);
  93. return true;
  94. }
  95. void Dialog::addListView(bool dialogType, bool inMenu, bool classical, bool noLeadingIn)
  96. {
  97. // 预加载排行榜,解决卡顿
  98. if (noLeadingIn && User::getInstance()->isConnected())
  99. {
  100. classicalRank = Network::getInstance()->getRank(true);
  101. plusRank = Network::getInstance()->getRank(false);
  102. }
  103. // 设置窗口类型为排行榜窗口,并判断排行榜显示游戏模式
  104. rank = dialogType;
  105. isInMenu = inMenu;
  106. isClassical = classical;
  107. }
  108. #pragma endregion
  109. void Dialog::backgroundFinish()
  110. {
  111. // 判断窗口类型为排行榜窗口或普通窗口,true为排行榜窗口
  112. if (rank)
  113. {
  114. initRankDialog();
  115. }
  116. else
  117. {
  118. initNormalDialog();
  119. }
  120. rank = false; // 默认为普通对话框
  121. }
  122. void Dialog::initNormalDialog()
  123. {
  124. // 显示对话框标题
  125. if (getLabelTitle())
  126. {
  127. getLabelTitle()->setPosition(
  128. center + Vec2(0, dialogContentSize.height / 2 - title->getContentSize().height / 2 - 15));
  129. this->addChild(getLabelTitle());
  130. }
  131. // 显示文本内容
  132. if (getLabelContentText())
  133. {
  134. auto content = getLabelContentText();
  135. content->setLineBreakWithoutSpace(true);
  136. content->setMaxLineWidth(dialogContentSize.width - 2 * contentPadding);
  137. content->setPosition(Vec2(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
  138. content->setHorizontalAlignment(TextHAlignment::LEFT);
  139. this->addChild(content);
  140. }
  141. // 添加label或button,并设置其位置
  142. //label
  143. this->addChild(getMenuLabel());
  144. auto adaptHeight = 0;
  145. if (title) adaptHeight = title->getContentSize().height;
  146. const auto labelHeight = (dialogContentSize.height - adaptHeight -40) / (getMenuLabel()
  147. ->getChildrenCount() + 2);
  148. // 布局labelMenuItem
  149. auto labels = getMenuLabel()->getChildren();
  150. auto i = 0;
  151. for (auto menuItem : labels)
  152. {
  153. auto node = dynamic_cast<Node*>(menuItem);
  154. node->setPosition(Point(SCREEN_WIDTH / 2,
  155. (SCREEN_HEIGHT + dialogContentSize.height / -adaptHeight + 50) /
  156. 2 - dialogContentSize.height / 2.5 + labelHeight * (getMenuLabel()->
  157. getChildrenCount() - i)));
  158. i++;
  159. }
  160. // button
  161. this->addChild(getMenuButton());
  162. const auto buttonWidth = dialogContentSize.width / (getMenuButton()->getChildrenCount() + 1);
  163. auto nodes = getMenuButton()->getChildren();
  164. i = 0;
  165. for (auto menuItem : nodes)
  166. {
  167. auto node = dynamic_cast<Node*>(menuItem);
  168. node->setPosition(Point(SCREEN_WIDTH / 2 - dialogContentSize.width / 2 + buttonWidth * (i + 1),
  169. SCREEN_HEIGHT / 2 - dialogContentSize.height / 2.5));
  170. i++;
  171. }
  172. }
  173. void Dialog::initRankDialog()
  174. {
  175. // 显示对话框标题
  176. if (getLabelTitle())
  177. {
  178. getLabelTitle()->setPosition(center + Vec2(0, dialogContentSize.height / 2 - 75.0f));
  179. this->addChild(getLabelTitle());
  180. }
  181. this->addChild(getMenuButton());
  182. // 添加button,设置布局
  183. const auto buttonWidth = dialogContentSize.width / (getMenuButton()->getChildrenCount() + 1);
  184. auto nodes = getMenuButton()->getChildren();
  185. auto i = 0;
  186. for (auto menuItem : nodes)
  187. {
  188. auto node = dynamic_cast<Node*>(menuItem);
  189. node->setPosition(Point(SCREEN_WIDTH / 2 - dialogContentSize.width / 2 + buttonWidth * (i + 1),
  190. SCREEN_HEIGHT / 2 - dialogContentSize.height / 2.5));
  191. i++;
  192. }
  193. listView = ListView::create();
  194. listView->setDirection(ScrollView::Direction::VERTICAL);
  195. listView->setTouchEnabled(true);
  196. listView->setBounceEnabled(true);
  197. listView->setBackGroundImageScale9Enabled(true);
  198. listView->setAnchorPoint(Point(0.5f, 0.5f));
  199. listView->setContentSize(Size(500, 400));
  200. listView->setPosition(Point(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
  201. listView->setScrollBarAutoHideTime(0);
  202. listView->setScrollBarColor(Color3B::WHITE);
  203. this->addChild(listView);
  204. //添加鼠标事件侦听
  205. listenerMouse = EventListenerMouse::create();
  206. listenerMouse->setEnabled(true);
  207. listenerMouse->onMouseScroll = [&](EventMouse* event)
  208. {
  209. const auto y = event->getScrollY(); //滚轮上滑y值大于0,下滑y值小于0
  210. if (y > 0)
  211. {
  212. if (position % 11 < 10)
  213. position++;
  214. listView->scrollToPercentVertical(10 * (position % 11), 0.5, true);
  215. }
  216. else
  217. {
  218. if (position % 11 > 0)
  219. position--;
  220. listView->scrollToPercentVertical(10 * (position % 11), 0.5, true);
  221. }
  222. };
  223. _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerMouse, this);
  224. // 不同游戏模式的排行榜切换按钮
  225. auto changeTypeButton = Button::create(theme->menuChangeTypeButtonNormalBackground,
  226. theme->menuChangeTypeButtonSelectedBackground,
  227. theme->menuChangeTypeButtonDisabledBackground); // 切换到进阶
  228. auto backTypeButton = Button::create(theme->menuBackTypeButtonNormalBackground,
  229. theme->menuBackTypeButtonSelectedBackground,
  230. theme->menuBackTypeButtonDisabledBackground); // 切换到经典
  231. changeTypeButton->setPosition(Point(SCREEN_WIDTH / 2 + listView->getContentSize().width / 2 + 50,
  232. SCREEN_HEIGHT / 2));
  233. backTypeButton->setPosition(Point(SCREEN_WIDTH / 2 - listView->getContentSize().width / 2 - 50,
  234. SCREEN_HEIGHT / 2));
  235. backTypeButton->setOpacity(0); // 切换经典初始不可见
  236. backTypeButton->addTouchEventListener(
  237. [&, changeTypeButton, backTypeButton](Ref* sender, ui::Widget::TouchEventType type)
  238. {
  239. if (type == Widget::TouchEventType::ENDED)
  240. {
  241. title->setString("经典挑战排行");
  242. backTypeButton->setOpacity(0);
  243. changeTypeButton->runAction(FadeIn::create(0.25));
  244. if (changed)
  245. getRankByType(true);
  246. changed = false;
  247. }
  248. });
  249. changeTypeButton->addTouchEventListener(
  250. [&, changeTypeButton, backTypeButton](Ref* sender, ui::Widget::TouchEventType type)
  251. {
  252. if (type == Widget::TouchEventType::ENDED)
  253. {
  254. title->setString("进阶挑战排行");
  255. changeTypeButton->setOpacity(0);
  256. backTypeButton->runAction(FadeIn::create(0.25));
  257. if (!changed)
  258. getRankByType(false);
  259. changed = true;
  260. }
  261. });
  262. // 如果在menuScene中,排行榜可切换,gameScene中排行榜不可切换
  263. if (isInMenu)
  264. {
  265. this->addChild(changeTypeButton);
  266. this->addChild(backTypeButton);
  267. }
  268. // 根据游戏模式初始化排行榜
  269. this->getRankByType(isClassical);
  270. }
  271. void Dialog::getRankByType(bool type)
  272. {
  273. rankType = type;
  274. if (content)
  275. {
  276. this->removeChild(content);
  277. listView->setScrollBarEnabled(true);
  278. listView->setScrollBarAutoHideTime(0);
  279. }
  280. if (!(rankType ? classicalRank.empty() : plusRank.empty()))
  281. {
  282. if (listView->getChildrenCount() > 0)listView->removeAllChildren();
  283. for (auto i = 0; i < 10; i++)
  284. {
  285. auto icon = ImageView::create(theme->iconSet + std::to_string(i) + ".png");
  286. layout = Layout::create();
  287. layout->setLayoutType(Layout::Type::ABSOLUTE);
  288. layout->setContentSize(Size(500, icon->getContentSize().height + 30));
  289. icon->setPosition(Vec2(icon->getContentSize().width / 2 + 60, 30));
  290. layout->addChild(icon);
  291. layout->setBackGroundColorType(Layout::BackGroundColorType::NONE);
  292. rankLabel = Label::createWithTTF(std::to_string(i + 1), theme->semiBoldFont, 30);
  293. rankLabel->setPosition(Vec2(icon->getContentSize().width / 2 + 10, 30));
  294. layout->addChild(rankLabel);
  295. if (i > (rankType ? classicalRank.size() : plusRank.size()) - 1)
  296. {
  297. nameLabel = Label::createWithTTF("------------------", theme->markerFeltFont, 30);
  298. nameLabel->setPosition(Vec2(
  299. icon->getContentSize().width + nameLabel->getContentSize().width / 2 + 75,
  300. 30));
  301. layout->addChild(nameLabel);
  302. scoreLabel = Label::createWithTTF("---------", theme->markerFeltFont, 30);
  303. scoreLabel->setPosition(Vec2(465 - scoreLabel->getContentSize().width / 2, 30));
  304. layout->addChild(scoreLabel);
  305. }
  306. else
  307. {
  308. nameLabel = Label::createWithTTF(rankType ? classicalRank.at(i).first : plusRank.at(i).first,
  309. theme->semiBoldFont, 30);
  310. nameLabel->setPosition(Vec2(
  311. icon->getContentSize().width + nameLabel->getContentSize().width / 2 + 75,
  312. 30));
  313. layout->addChild(nameLabel);
  314. scoreLabel = Label::createWithTTF(
  315. std::to_string(rankType ? classicalRank.at(i).second : plusRank.at(i).second),
  316. theme->semiBoldFont, 30);
  317. scoreLabel->setPosition(Vec2(465 - scoreLabel->getContentSize().width / 2, 30));
  318. layout->addChild(scoreLabel);
  319. }
  320. listView->pushBackCustomItem(layout);
  321. listView->setBottomPadding(icon->getContentSize().height);
  322. }
  323. }
  324. else
  325. {
  326. if (listView->getChildrenCount() > 0)listView->removeAllChildren();
  327. listView->setScrollBarEnabled(false);
  328. setContentText("抱歉! 网络连接失败!", 36, 60, 20);
  329. content = getLabelContentText();
  330. content->setLineBreakWithoutSpace(true);
  331. content->setMaxLineWidth(dialogContentSize.width - 2 * contentPadding);
  332. content->setPosition(Vec2(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
  333. content->setHorizontalAlignment(TextHAlignment::LEFT);
  334. this->addChild(content);
  335. }
  336. }

模板二


  最近在做个小的示例项目,确定后打算用cocos2dx框架来做UI部分,毕竟它易于使用还跨平台,像我这样几乎完全没有cocos2dx基础的童鞋,也能快速上手。在开发过程中,我想像在桌面应用中那样,弹出一个窗口并给出一些文本提示。无奈好像cocos2dx并没有给出现成可用的东西,只能自己琢磨实现了。考虑过后,打算通过Layer类来构建一个子类,然后在Scene中去显示该Layer,从而模拟出“窗口”的效果。我查阅了一些资料发现,网上有很多类似的实现代码,但他们大多已经过时,因这两年cocos2dx的版本变化好像挺大。

  按照上述的思路构建子类测试后发现,监听的事件不会被“阻隔”,例如:我们在Scene中监听了鼠标和键盘,然后在Scene中去添加了该Layer,并在该Layer中也监听了鼠标点击事件,当我们在该Layer中点击鼠标时,Scene中也会触发该事件。这就导致了一些奇怪的现象,和预期不符合。本文将试着简介如何用一种足够简单的方法,构建这样一个“模态对话框类”并解决包含事件不“阻隔”在内的几乎所有问题。

  在开始前,先简要说明一下环境。本次实验编写于Linux平台。因实验于PC平台,我在示例所监听的都是鼠标和键盘事件,对于移动平台的触摸事件未做任何测试,但理论上也是可行的,具体请读者自测和变更,以适应特定的场景。

  1. #ifndef MYGAME_POPUPLAYER_H
  2. #define MYGAME_POPUPLAYER_H
  3. #include "cocos2d.h"
  4. USING_NS_CC;
  5. class PopupLayer:public cocos2d::Layer
  6. {
  7. public:
  8. PopupLayer();
  9. virtual ~PopupLayer();
  10. private:
  11. std::string backgroundImage;
  12. EventListenerMouse *listenerMouse;
  13. public:
  14. virtual bool init() override;
  15. CREATE_FUNC(PopupLayer);
  16. static PopupLayer* create(const std::string backgroundImage);
  17. bool setBackgroundImage(const std::string &backgroundImage);
  18. void okMenuItemCallback(Ref *pSender);
  19. void cancelMenuItemCallback(Ref *pSender);
  20. /**
  21. * Event callback that is invoked every time when Node enters the 'stage'.
  22. * If the Node enters the 'stage' with a transition, this event is called when the transition starts.
  23. * During onEnter you can't access a "sister/brother" node.
  24. * If you override onEnter, you shall call its parent's one, e.g., Node::onEnter().
  25. * @lua NA
  26. */
  27. virtual void onEnter() override;
  28. /**
  29. * Event callback that is invoked every time the Node leaves the 'stage'.
  30. * If the Node leaves the 'stage' with a transition, this event is called when the transition finishes.
  31. * During onExit you can't access a sibling node.
  32. * If you override onExit, you shall call its parent's one, e.g., Node::onExit().
  33. * @lua NA
  34. */
  35. virtual void onExit() override;
  36. };
  37. #endif //MYGAME_POPUPLAYER_H

 首先 PopupLayer继承自Layer类,你也可以继承自其他Layer类,如LayerColor,这样你可以获得不同的特性。这里仅仅定义了两个数据成员,一个std::string用于存储整个“窗口”的背景图文件名,一个EventListenerMouse指针存储鼠标监听器,稍后可以看到是如何使用它们的。最后,来看看添加了哪些成员方法。首先,我重写了基类的init()方法,如果你对cocos2dx不陌生,那么你应该知道在该方法中要做哪些事情。其次,我使用 CREATE_FUNC 宏来构建了一个默认的静态create()方法,这基本上也是cocos2dx的传统。为了进一步满足需求,我还提供了静态create()方法的一个重载,它将包含一个参数用于指定背景资源名。setBackgroundImage()方法可以用来设置和更改背景资源。okMenuItemCallback()和cancelMenuItemCallback()是“模态对话框”上两个按钮的回调方法。最后我还重写了基类的onEnter()和onExit()方法,这两个方法至关重要,稍后你将看到它们是如何相互配合完成整个功能的。

  cocos2dx使用一个类对象前,总是先调用它的静态create()方法来创建该类的一个实例,所以我们先从它开始介绍。

  1. PopupLayer* PopupLayer::create(const std::string backgroundImage)
  2. {
  3. PopupLayer *pl = new(std::nothrow) PopupLayer();
  4. if (pl && pl->setBackgroundImage(backgroundImage) && pl->init())
  5. {
  6. pl->autorelease();
  7. return pl;
  8. }
  9. else
  10. {
  11. delete pl;
  12. pl = nullptr;
  13. return nullptr;
  14. }
  15. }

可以看到,我们只增加了setBackgroundImage()方法的调用。接着就是init()方法了。

  1. bool PopupLayer::init()
  2. {
  3. // 1. super init first
  4. if ( !Layer::init() )
  5. {
  6. return false;
  7. }
  8. auto visibleSize = Director::getInstance()->getVisibleSize();
  9. Vec2 origin = Director::getInstance()->getVisibleOrigin();
  10. this->setAnchorPoint(Vec2::ZERO);
  11. // add "HelloWorld" splash screen"
  12. auto sprite = Sprite::create(backgroundImage);
  13. // position the sprite on the center of the screen
  14. sprite->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
  15. // add the sprite as a child to this layer
  16. this->addChild(sprite, 1);
  17. auto label = Label::createWithTTF("Are u sure exit?", "fonts/Marker Felt.ttf", 24);
  18. label->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));
  19. label->setColor(Color3B(255,0,0));
  20. this->addChild(label, 1);
  21. auto okMenuItem = MenuItemFont::create("OK", CC_CALLBACK_1(PopupLayer::okMenuItemCallback, this));
  22. okMenuItem->setPosition(Vec2(visibleSize.width / 2 - 100, visibleSize.height / 2 - 50));
  23. okMenuItem->setColor(Color3B(255,0,0));
  24. auto cancelMenuItem = MenuItemFont::create("Cancel", CC_CALLBACK_1(PopupLayer::cancelMenuItemCallback, this));
  25. cancelMenuItem->setPosition(Vec2(visibleSize.width / 2 + 100, visibleSize.height / 2 - 50));
  26. cancelMenuItem->setColor(Color3B(255,0,0));
  27. auto pMenu = Menu::create(okMenuItem, cancelMenuItem, NULL);
  28. pMenu->setPosition(Vec2::ZERO);
  29. this->addChild(pMenu, 1);
  30. return true;
  31. }

init()方法也足够简单,创建了一个sprite来作为背景,一个label来显示一些文本信息和两个菜单按钮,并设置了这两个菜单按钮的回调方法。

  最后是onEnter()方法和onExit()方法,这是两个重点。onEnter()方法是在该“模态对话框”Layer显示时会被调用。相反,onExit()方法会在它消失时被调用。这两个机制可以用来做一些事情。

  1. void PopupLayer::onEnter()
  2. {
  3. Layer::onEnter();
  4. if (listenerMouse == nullptr)
  5. {
  6. listenerMouse = EventListenerMouse::create();
  7. listenerMouse->onMouseDown = [](EventMouse* event) {
  8. };
  9. }
  10. if (listenerMouse)
  11. {
  12. _eventDispatcher->pauseEventListenersForTarget(this->getParent(), true);
  13. _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerMouse, this);
  14. }
  15. }
  16. void PopupLayer::onExit()
  17. {
  18. Layer::onExit();
  19. if (listenerMouse)
  20. {
  21. _eventDispatcher->removeEventListener(listenerMouse);
  22. _eventDispatcher->resumeEventListenersForTarget(this->getParent(), true);
  23. }
  24. }

在onEnter()方法中,我们先创建一个鼠标监听对象,并设置了它的回调函数,只不过回调函数为空,什么也不做。然后通过eventDispatcher对象的pauseEventListenersForTarget()方法来暂停“模态对话框”父级窗口的事件监听器,并把自己的监听器添加到事件分发器中。所谓的父级窗口指的是“模态对话框”的下层Layer。这里是“模态对话框”下层Layer屏蔽事件监听的关键所在,如果不屏蔽它,则在“模态对话框”上发生的监听事件也会传递给它的下层Layer。也有的文章说可以设置监听器的优先级来解决该问题,但我没能找到解决方法。

  在onExit()方法中,我们来做一些清理还原工作,因为onExit()方法被调用,意味着“模态对话框”Layer即将消失,所以我们从事件分发器中删除它的鼠标监听器,并重新启用它下层Layer的事件监听器。这样便可以还原。

  最后,来看看如何使用它。我通过cocos2dx创建了一个新工程,并对HelloWorld场景做了一些小的改动,以方便测试。下面是它的init()方法。

  1. bool HelloWorld::init()
  2. {
  3. //////////////////////////////
  4. // 1. super init first
  5. if ( !Layer::init() )
  6. {
  7. return false;
  8. }
  9. auto visibleSize = Director::getInstance()->getVisibleSize();
  10. Vec2 origin = Director::getInstance()->getVisibleOrigin();
  11. /////////////////////////////
  12. // 2. add a menu item with "X" image, which is clicked to quit the program
  13. // you may modify it.
  14. // add a "close" icon to exit the progress. it's an autorelease object
  15. auto closeItem = MenuItemImage::create(
  16. "CloseNormal.png",
  17. "CloseSelected.png",
  18. CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
  19. closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
  20. origin.y + closeItem->getContentSize().height/2));
  21. // create menu, it's an autorelease object
  22. auto menu = Menu::create(closeItem, NULL);
  23. menu->setPosition(Vec2::ZERO);
  24. this->addChild(menu, 1);
  25. /////////////////////////////
  26. // 3. add your codes below...
  27. // add a label shows "Hello World"
  28. // create and initialize a label
  29. auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
  30. // position the label on the center of the screen
  31. label->setPosition(Vec2(origin.x + visibleSize.width/2,
  32. origin.y + visibleSize.height - label->getContentSize().height));
  33. // add the label as a child to this layer
  34. this->addChild(label, 1);
  35. auto label1 = Label::createWithTTF("Press the Enter key or click the left mouse button.", "fonts/Marker Felt.ttf", 12);
  36. // position the label on the center of the screen
  37. label1->setPosition(Vec2(origin.x + visibleSize.width/2, 100));
  38. // add the label as a child to this layer
  39. this->addChild(label1, 1);
  40. // add "HelloWorld" splash screen"
  41. auto sprite = Sprite::create("HelloWorld.png");
  42. // position the sprite on the center of the screen
  43. sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
  44. // add the sprite as a child to this layer
  45. this->addChild(sprite, 0);
  46. //添加鼠标事件侦听
  47. auto listenerMouse = EventListenerMouse::create();
  48. listenerMouse->setEnabled(true);
  49. listenerMouse->onMouseDown = [&](EventMouse* event) {
  50. int mouseBtn = event->getMouseButton();
  51. if (0 == mouseBtn) {
  52. auto sprite = Sprite::create("HelloWorld.png");
  53. sprite->setPosition(event->getLocationInView().x, event->getLocationInView().y);
  54. sprite->setScale(0.5f,0.5f);
  55. this->addChild(sprite, 1);
  56. }
  57. };
  58. _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerMouse, this);
  59. //添加键盘事件监听
  60. auto listenerKeyboard = EventListenerKeyboard::create();
  61. listenerKeyboard->setEnabled(true);
  62. listenerKeyboard->onKeyPressed = [&](EventKeyboard::KeyCode keycode, Event* event){
  63. if(EventKeyboard::KeyCode::KEY_ENTER == keycode){
  64. auto pl = PopupLayer::create("background.png");
  65. pl->setScale(0.5f,0.5f);
  66. pl->setParent(this);
  67. pl->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
  68. pl->setAnchorPoint(Vec2(0.5f,0.5f));
  69. this->addChild(pl, 2);
  70. }
  71. };
  72. _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerKeyboard, this);
  73. return true;
  74. }

我在里面增加了一个label,用于显示一些提示信息。增加了鼠标左键的事件监听,并在点击的位置绘制一张图片。这样做可以很好的演示当“模态对话框”Layer打开后,是否有效的屏蔽了下层Layer的事件监听。最后,添加了键盘监听,当我们敲击Enter键后,“模态对话框”Layer就会显示出来。值得特别注意的是,我调用了这样一个方法 pl->setParent(this); 用于设置了“模态对话框”Layer的父级Node,这是基类给提供的一个方法,千万别忘了设置它,其他的都足够简单了。
  好了,你可以试着获取完整的代码,并编译运行一下,希望能解决你的问题或者给你一些新的思路。最后,我不太了解cocos2dx框架,如果本文或者示例中有什么错误或者不规范的地方,请指正,谢谢。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注