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

前端之家收集整理的这篇文章主要介绍了Cocos2dx 3.13 “模态对话框”的一种实现方式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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,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));

    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));

    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",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.",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框架,如果本文或者示例中有什么错误或者不规范的地方,请指正,谢谢。

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