Cocos2dx 3.13 “模态对话框”的一种实现方式

来源:互联网 发布:linux配置环境变量 编辑:程序博客网 时间:2024/05/17 18:27

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

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

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

3.代码实现
  首先,给出派生类的头文件。

#ifndef MYGAME_POPUPLAYER_H#define MYGAME_POPUPLAYER_H#include "cocos2d.h"USING_NS_CC;class PopupLayer:public cocos2d::Layer{public:    PopupLayer();    virtual ~PopupLayer();private:    std::string backgroundImage;    EventListenerMouse *listenerMouse;public:    virtual bool init() override;    CREATE_FUNC(PopupLayer);    static PopupLayer* create(const std::string backgroundImage);    bool setBackgroundImage(const std::string &backgroundImage);    void okMenuItemCallback(Ref *pSender);    void cancelMenuItemCallback(Ref *pSender);    /**     * Event callback that is invoked every time when Node enters the 'stage'.     * If the Node enters the 'stage' with a transition, this event is called when the transition starts.     * During onEnter you can't access a "sister/brother" node.     * If you override onEnter, you shall call its parent's one, e.g., Node::onEnter().     * @lua NA     */    virtual void onEnter() override;    /**     * Event callback that is invoked every time the Node leaves the 'stage'.     * If the Node leaves the 'stage' with a transition, this event is called when the transition finishes.     * During onExit you can't access a sibling node.     * If you override onExit, you shall call its parent's one, e.g., Node::onExit().     * @lua NA     */    virtual void onExit() override;};#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()方法来创建该类的一个实例,所以我们先从它开始介绍。

PopupLayer* PopupLayer::create(const std::string backgroundImage){    PopupLayer *pl = new(std::nothrow) PopupLayer();    if (pl && pl->setBackgroundImage(backgroundImage) && pl->init())    {        pl->autorelease();        return pl;    }    else    {        delete pl;        pl = nullptr;        return nullptr;    }}

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

bool PopupLayer::init(){    // 1. super init first    if ( !Layer::init() )    {        return false;    }    auto visibleSize = Director::getInstance()->getVisibleSize();    Vec2 origin = Director::getInstance()->getVisibleOrigin();    this->setAnchorPoint(Vec2::ZERO);    // add "HelloWorld" splash screen"    auto sprite = Sprite::create(backgroundImage);    // position the sprite on the center of the screen    sprite->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));    // add the sprite as a child to this layer    this->addChild(sprite, 1);    auto label = Label::createWithTTF("Are u sure exit?", "fonts/Marker Felt.ttf", 24);    label->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));    label->setColor(Color3B(255,0,0));    this->addChild(label, 1);    auto okMenuItem = MenuItemFont::create("OK", CC_CALLBACK_1(PopupLayer::okMenuItemCallback, this));    okMenuItem->setPosition(Vec2(visibleSize.width / 2 - 100, visibleSize.height / 2 - 50));    okMenuItem->setColor(Color3B(255,0,0));    auto cancelMenuItem = MenuItemFont::create("Cancel", CC_CALLBACK_1(PopupLayer::cancelMenuItemCallback, this));    cancelMenuItem->setPosition(Vec2(visibleSize.width / 2 + 100, visibleSize.height / 2 - 50));    cancelMenuItem->setColor(Color3B(255,0,0));    auto pMenu = Menu::create(okMenuItem, cancelMenuItem, NULL);    pMenu->setPosition(Vec2::ZERO);    this->addChild(pMenu, 1);    return true;}

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

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

void PopupLayer::onEnter(){    Layer::onEnter();    if (listenerMouse == nullptr)    {        listenerMouse = EventListenerMouse::create();        listenerMouse->onMouseDown = [](EventMouse* event) {        };    }    if (listenerMouse)    {        _eventDispatcher->pauseEventListenersForTarget(this->getParent(), true);        _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerMouse, this);    }}void PopupLayer::onExit(){    Layer::onExit();    if (listenerMouse)    {        _eventDispatcher->removeEventListener(listenerMouse);        _eventDispatcher->resumeEventListenersForTarget(this->getParent(), true);    }}

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

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

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

bool HelloWorld::init(){    //////////////////////////////    // 1. super init first    if ( !Layer::init() )    {        return false;    }    auto visibleSize = Director::getInstance()->getVisibleSize();    Vec2 origin = Director::getInstance()->getVisibleOrigin();    /////////////////////////////    // 2. add a menu item with "X" image, which is clicked to quit the program    //    you may modify it.    // add a "close" icon to exit the progress. it's an autorelease object    auto closeItem = MenuItemImage::create(                                           "CloseNormal.png",                                           "CloseSelected.png",                                           CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));    closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,                                origin.y + closeItem->getContentSize().height/2));    // create menu, it's an autorelease object    auto menu = Menu::create(closeItem, NULL);    menu->setPosition(Vec2::ZERO);    this->addChild(menu, 1);    /////////////////////////////    // 3. add your codes below...    // add a label shows "Hello World"    // create and initialize a label    auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);    // position the label on the center of the screen    label->setPosition(Vec2(origin.x + visibleSize.width/2,                            origin.y + visibleSize.height - label->getContentSize().height));    // add the label as a child to this layer    this->addChild(label, 1);    auto label1 = Label::createWithTTF("Press the Enter key or click the left mouse button.", "fonts/Marker Felt.ttf", 12);    // position the label on the center of the screen    label1->setPosition(Vec2(origin.x + visibleSize.width/2, 100));    // add the label as a child to this layer    this->addChild(label1, 1);    // add "HelloWorld" splash screen"    auto sprite = Sprite::create("HelloWorld.png");    // position the sprite on the center of the screen    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));    // add the sprite as a child to this layer    this->addChild(sprite, 0);    //添加鼠标事件侦听    auto listenerMouse = EventListenerMouse::create();    listenerMouse->setEnabled(true);    listenerMouse->onMouseDown = [&](EventMouse* event) {        int mouseBtn = event->getMouseButton();        if (0 == mouseBtn) {            auto sprite = Sprite::create("HelloWorld.png");            sprite->setPosition(event->getLocationInView().x, event->getLocationInView().y);            sprite->setScale(0.5f,0.5f);            this->addChild(sprite, 1);        }    };    _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerMouse, this);    //添加键盘事件监听    auto listenerKeyboard = EventListenerKeyboard::create();    listenerKeyboard->setEnabled(true);    listenerKeyboard->onKeyPressed = [&](EventKeyboard::KeyCode keycode, Event* event){       if(EventKeyboard::KeyCode::KEY_ENTER == keycode){            auto pl = PopupLayer::create("background.png");            pl->setScale(0.5f,0.5f);            pl->setParent(this);            pl->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));            pl->setAnchorPoint(Vec2(0.5f,0.5f));            this->addChild(pl, 2);        }    };    _eventDispatcher->addEventListenerWithSceneGraphPriority(listenerKeyboard, this);    return true;}

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

0 0
原创粉丝点击