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

Cocos2dx 3.13 “模态对话框”的一种实现方式的更多相关文章

  1. ios – 链接点击监听器上的WKWebView?

    在WKWebView类中是否存在类似onLinkClickListener的东西?我试着谷歌搜索但没有发现任何东西,我也发现了一些关于simillar类型的stackoverflow的未解答的问题.我需要一个linkClickListener的原因是,当我点击链接并且页面尚未加载时,它不会加载网站.当页面加载了监听器时,我也可以创建一个花哨的加载屏幕.解决方法你可以这样做将WKNavigation

  2. 多个监听器用于委托iOS

    我有一个带有代理didSelectString的类搜索栏.我有一个实现委托的A类和一个实现委托的B类.但是只有来自A类的代理才被执行.代表可以有多个监听器吗?并且如何实现这一点解决方法该委托是单一消息传递协议.如果要发送更改的多个对象,则需要使用NSNotifications.您可以使用通知中心传递对象,如下所示:想要收听通知时并设置选择器

  3. swift中提供属性监听器,可以监听属性的改变

  4. [swift]-属性监听器

    1:swift中监听属性的变化是通过属性监听器来监听OC中监听属性变化是通过set方法来监听属性变化2:代码演示意见反馈邮件:1415429879@qq.com欢迎你们的阅读和赞赏、谢谢!

  5. Swift - 属性监听器

    属性监听器介绍:属性监听器,监听属性的值改变,就像按钮的点击事件一样来监听其他的值改变举例:监听scrollView的contentOffset属性的改变

  6. android – 使用回调/监听器链接RxJava observable

    这是我到目前为止:解决方法调整clemp6r的解决方案,这是另一个既不需要主题也不需要嵌套订阅的解决方案:一般来说,我认为总是可以使用Observable.create()在Observable中包装任何基于回调的异步操作.

  7. Android – 可靠地获取当前位置

    错了,因为如果用户的位置已经稳定,那么我的听众永远不会被调用,因为位置没有改变.但GPS将一直运行,直到我的听众被移除,耗尽电池……获取当前位置的正确方法是什么,而不会误认为当前位置的旧位置?我不介意等几分钟.编辑:有可能我错误的是没有被叫的听众,它可能只需要比我想象的要长一点……

  8. android – 如何在片段打开的自定义对话框中保留监听器?

    我遇到了一些障碍.我的场景非常类似于DialogFragment–retaininglistenerafterscreenrotation所描述的场景建议的解决方案适用于作者,因为他的对话框是从活动中调用的.我的情况完全相同,但我的自定义对话框是从片段而不是活动调用的.(IEActivity->Fragment->Dialog)我实现了相同的解决方案(从调用Fragment设置onResume中的

  9. android – Toast与Dialog框:何时使用?

    谢谢.解决方法Toast主要用于告知用户一些不重要且不需要交互的东西,所以我会使用Toastforthethat.此外,Toast不会阻止用户使用设备/应用程序,您仍然可以激活,例如显示Toast时的基础图标.对话框通常要求用户做出选择,或者显示不需要交互的进度但是将使用户在此期间不做其他事情,这可能是重要的,例如,一旦用户在完成参数之前更改参数,您进行的计算将失败.

  10. android如何停止gps

    通过以下代码启动监听器后工作正常.一段时间后,我通过以下代码停止监听器但问题是它仍在搜索我的gps任何解决方案???

随机推荐

  1. 【cocos2d-x 3.x 学习笔记】对象内存管理

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部