Easy way to manage pop-up scenes or dialogs?


#1

I need to show a dialog box above a scene. No UIKits, just cocos2dx code.
The dialog could be implemented either as separate scene, or node/layer, whatsoever.
Dialog will contain touch-controlled gui elements such as buttons etc.
The main problem here is to block any touch input in the underlaying scene while not hiding it.
Is there an easy/standard way to implement this? An example with just an Alert Box would be great.


#2

Have faced with the same problem. Does anybody have a solution?

Thanks!


#3

What about override addChild() and removeChild() and enable/disable touches of last screen/dialog,
before add/remove new screen/dialog?


#4

I need the code too,thanks!


#5

I have solved this by creating a subclass of CCLayerColor with all the popup menu buttons and text as children added in it’s init function. I then add this as a child to the current scene when a specific event happens in my game, calling a method on my scene. My scene is called PlayMenu and my popup is an instance variable called mEndGamePopup of class EndGamePopup. The for this is as follows:

void PlayMenu::showEndGamePopup() {
    // Remove the scene from the touch dispatcher to prevent anything behind the
    // popup from receiving touches.
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);

    // Also remove the current menu so that all buttons are disabled too.
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(mControlsMenu);

    // Show the popup.
    mEndGamePopup->setIsVisible(true);
}

void PlayMenu::hideEndGamePopup() {
    // Hide the popup again.
    mEndGamePopup->setIsVisible(false);

    // Add this scene to the touch dispatcher again so that we enable all our touch input.
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);

    // Add the menu again so that our buttons are enabled again.
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(mControlsMenu, kCCMenuHandlerPriority, true);
}

These are the important bits of my EndGamePopup class:

bool EndGamePopup::init() {
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();

    CCMenuItemImage *mPlayButton = CCMenuItemImage::itemWithNormalImage("img/PlayButtonNormal.png", "img/PlayButtonPressed.png", "img/PlayButtonDisabled.png");
    mPlayButton->setTarget(this, menu_selector(EndGamePopup::backCallback));
    CCMenu *playButtonMenu = CCMenu::menuWithItem(mPlayButton);
    playButtonMenu->setPosition( ccp(winSize.width * 0.5, winSize.height * 0.4) );
    this->addChild(playButtonMenu, 1);

    setIsTouchEnabled(true);

    return initWithColor(ccc4(0,0,0,170), winSize.width, winSize.height);
}

void EndGamePopup::registerWithTouchDispatcher() {
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

bool EndGamePopup::ccTouchBegan(cocos2d::CCTouch *touch, cocos2d::CCEvent *event) {
    return true;
}

void EndGamePopup::backCallback(CCObject *sender) {
    PlayMenu *parent = (PlayMenu *) getParent();
    parent->hideEndGamePopup();
}

This all works nicely except for one issue I’ve found. If a button is held down when PlayMenu::showEndGamePopup() is called the popup is shown properly and can be dismissed by pressing the button. However, now none of the menu buttons in the scene work after the popup has disappeared.

I think this happens because the menu is removed from the touch dispatcher in between a ccTouchBegan call and a ccTouchEnded call. This is unfortunately also likely to happen in my game since PlayMenu::showEndGamePopup() is called as a reaction to an event and the player might be holding down any button in my menu.

Does anyone know how I can solve this problem? Is there a better way of disabling the menu buttons? Should cocos2d-x be changed to allow this?


#6

thank you


#7

As a temporary mesure I have now moved my menu creating code into it’s own method. I call this when creating the scene and when hiding my popup to recreate the menu from scratch (it also removes the old menu). This seems to work fine.

Is there any cleaner way of doing this? This IS a hack.


#8

I tried a different approach to remove all of the touch delegates from within my Dialog layer.

I’ve change setParent to:

    virtual void setParent(CCNode* parent)
    {
        CCLayerColor::setParent(parent);        

        if (parent)
        {
            saveTouchesRecursive(parent);

            for (unsigned int i=0; igetTouchDispatcher()->removeDelegate(mSavedTouches[i]);
            }
        }
        else
        {
            for (unsigned int i=0; i(mSavedTouches[i]);
                CCTargetedTouchDelegate* targetedTouchDelegate =  dynamic_cast(mSavedTouches[i]);

                CCLayer* layer =  dynamic_cast(mSavedTouches[i]);

                if (layer)
                {
                    layer->registerWithTouchDispatcher();
                }
                else if (standardTouchDelegate)
                {
                    CCDirector::sharedDirector()->getTouchDispatcher()->addStandardDelegate(mSavedTouches[i], 0);
                }
                else if (targetedTouchDelegate)
                {
                    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(mSavedTouches[i], 0,  false);
                }
                else
                {
                    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(mSavedTouches[i], 0,  false);
                }
            }
        }
    }

And added the following recursive code to save the touch delegates so I can restore them when the dialog dismissed:

std::vector mSavedTouches;

    void saveTouchesRecursive(CCNode* node)
    {
        if (node != this)
        {   
            CCTouchDelegate* touchDelegate = dynamic_cast(node);

            if (touchDelegate)
            {
                CCTouchHandler* handler = CCDirector::sharedDirector()->getTouchDispatcher()->findHandler(touchDelegate);

                if (handler)
                {
                    mSavedTouches.push_back(touchDelegate);
                }
            }

            for (unsigned int i=0; igetChildrenCount(); i++)
            {
                CCNode* childNode = dynamic_cast(node->getChildren()->objectAtIndex(i));

                if (childNode)
                {
                    saveTouchesRecursive(childNode);
                }
            }

        }
    }

I tested it and it works for me, but didn’t test it in all cases.


#9

look at JACModalAlert

Basically, you need to overwrite ccTouchBegan to eat all touches outside the “dialogbox”

@
virtual bool ccTouchBegan(CCTouch touch, CCEventevent)
{

CCPoint touchLocation = this~~>convertTouchToNodeSpace;
CCNode *dialogBox = this~~>getChildByTag(kDialogTag);
CC_ASSERT(dialogBox);
CCRect const bbox = dialogBox->boundingBox();

// eat all touches outside of dialog box.
return !CCRect::CCRectContainsPoint(bbox, touchLocation);
}

virtual void registerWithTouchDispatcher()
{
CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(
this,
INT_MIN+1,
true); // swallows touches.
}
@


#10

I like to use the JACModalAlert or the PSModalAlert, but they are very obsolete (more than 2 years ago) and needing full of fix.
Do someone got a functional and updated modal alert?


#11

I’ve found this good popup: https://github.com/leafsoar/ls-cocos2d-x/tree/master/MacCpp/Classes/PopupLayer


#12

you can use below code to show the pop up or dialog
void SleepyFiver::showDialog()
{

//	if(dialogLayer==NULL)
//	{
CCLog("showDialog NULL");
CCSize winSize = CCDirector::sharedDirector()->getWinSize();

CCPoint centerPt=	CCPointZero;

CCSprite* gameBg=CCSprite::create("popup.png");


float width=gameBg->getContentSize().width;
float height=gameBg->getContentSize().height;
centerPt.x=winSize.width/2-width/2;
centerPt.y=winSize.height/2-height/2;
gameBg->setPosition(ccp(width/2,height/2));


dialogLayer=CCLayerColor::create(ccc4(0,0,0,0),width*.95,height);
dialogLayer->setPosition(ccp(centerPt.x,centerPt.y));

dialogLayer->addChild(gameBg,ZOrderBackground);

CCLabelTTF* labelMessage = CCLabelTTF::create("Do you want to save the game ?", "comic.ttf", 20);
dialogLayer->addChild(labelMessage, ZOrderMenu);
labelMessage->setAnchorPoint(ccp(0,0.5));
labelMessage->setPosition(ccp(centerPt.x+width*.025, height*.60));
labelMessage->setDimensions(ccp(width*.80, labelMessage->getContentSize().height*2) );
labelMessage->setHorizontalAlignment(kCCTextAlignmentLeft);
labelMessage->setColor(ccWHITE);
//labelMessage->setVisible(true);

CCMenuItemImage* yesMenu = CCMenuItemImage::create("yes_btn.png","yes_btn.png",this,menu_selector(SleepyFiver::yes));
yesMenu->setPosition(ccp(width/2,height*.4));

CCMenuItemImage* noMenu=NULL;
noMenu = CCMenuItemImage::create("no_btn.png","no_btn.png",this,menu_selector(SleepyFiver::no));
noMenu->setPosition(ccp(width/2,height*.3));

CCMenuItemImage* replayMenu=NULL;
replayMenu = CCMenuItemImage::create("continue_btn.png","continue_btn.png",this,menu_selector(SleepyFiver::replay));
replayMenu->setPosition(ccp(width/2,height*.2));

CCMenu* menu=NULL;
menu=CCMenu::create(yesMenu,noMenu,replayMenu,NULL);
//CC_BREAK_IF(!menu);
menu->setPosition(CCPointZero);
dialogLayer->addChild(menu,1);
dialogLayer->setVisible(true);

addChild(dialogLayer,15);
//	}else
//	{
//		CCLog("showDialog not NULL");
//		dialogLayer->setVisible(true);
//	}

}


#13

I found very easy solution.
Here it is:
1 Create class inherited from ColorLayer(Layer or even Node)
2 Make it full-screen size and set color and opacity for background

  auto ss = Director::getInstance()->getVisibleSize();
  ScreenInhertedFromColorLayer *scl = new ScreenInhertedFromColorLayer();
  if (scl && scl->initWithColor(Color4B(0,0,0,128), ss.width, ss.height))
  //other initialisation for create() method. Create your best popup layout here 

3 Create touch event listener for your layer and call yourTouchListener->setSwallowTouches(true);

   auto listener1 = EventListenerTouchOneByOne::create();
    listener1->setSwallowTouches(true);
    listener1->onTouchBegan = [=](Touch* touch, Event* event){
        return false;
    };
    listener1->onTouchEnded = [=](Touch* touch, Event* event){
    };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

4 That’s all. All touches swallow your listener. You can add sprite for dialog into center of the screen and add some ui to it. It will be real and easy popup dialog.


#14

Wishing this would stay on the board long enough for me to try it. It won’t so I’ll comment, I can’t unreservedly like something advocating use of Layer and the, IMHO, cryptic code that follows, ie setSwallowTouches, EventListenerTouchOneByOne. I suppose these are the comprimises one must accept to have writing the boiler plate abstracted away for us, that and massive binaries :wink:


#15

This should be simple (and in fact it is), but it took me the whole day to figure it out. My solution:

void GameScene::showDialogLost()
{
        _eventDispatcher->pauseEventListenersForTarget(this, true);

        auto dl = DialogLost::create();
        addChild(dl);
}

GameScene is current Layer, DialogLost is modal dialog, content of the dialog should be placed in DialogLost::init().

Alternatively removeEventListenersForTarget(this, true) can be used instead of pauseEventListenersForTarget(this, true) if we don’t need touch events anymore (for instance there is no option to close the dialog).

Works with cocos2d-x 3.2.