[Solved] Label as child doesn't move when parent position updated with 2 Cameras

Has anyone ever had Label nodes not attach to its parent during an action? I have a hierarchy of nodes/sprites/labels in the HUD and I recently switched to using the DEFAULT camera mask for the HUD and USER1 for the gameplay. For some reason something I did recently, including switching how I use camera, is causing an issue where the labels don’t move along with their parent, but child Sprite nodes do move as expected.

node
-> label
-> sprite

node -> run action MoveTo only moves the non-label children along with it? I’ll post a reply when I figure out what the issue was, but if anyone has had a similar experience, I’m all ears. Using cocos2d-x 3.4 final.

Here’s test code in question. It works fine when on a scene with no Camera instances added.

// this is either a menu Layer, or is _hudLayer for the tests
auto layer = LayerColor::create(Color4B::MAGENTA);
auto scale = SCALE_LARGE_EXACT;
auto ws = Director::getInstance()->getWinSize();
float h = 30.f * scale;
auto bgSize = Size(ws.width, h);

auto offsetVisible = Vec2(100, ws.height / 2);
auto offsetHidden = Vec2(100, ws.height / 2 * 1.3f);
layer->setPosition(offsetHidden);

layer->setContentSize(bgSize);

auto test = Label::createWithBMFont(kFontVisitor20, "TEST BMFONT");
layer->addChild(test);
auto move1 = MoveTo::create(1.0f, offsetHidden);
auto move2 = MoveTo::create(1.0f, offsetVisible);
auto seq = Sequence::createWithTwoActions(move1, move2);
layer->runAction(RepeatForever::create(seq));
this->addChild(layer);

However with a two camera setup the label doesn’t follow the action. Where this above is _hudLayer from below.

// create layers and all children
this->setupHudLayer();
this->setupGameLayer();

// Game Camera - ws = getVisibleSize()
auto gameCamera = Camera::createOrthographic(ws.width, ws.height, -1024, 1024);
gameCamera->setCameraFlag(cocos2d::CameraFlag::USER1);
this->addChild(gameCamera);

_gameLayer->setCameraMask((unsigned short)cocos2d::CameraFlag::USER1);
_hudLayer->setCameraMask((unsigned short)cocos2d::CameraFlag::DEFAULT);

I figured maybe they’re on different cameras, but when I make gameCamera hidden it doesn’t hide any node for the test.

Edit: Oh weird, it actually does work correctly when gameCamera->setVisible(false)?

Edit2: The label also moves correctly when using layer->setPosition(Vec2(300,300)), for example.

Edit3: If I change the test node to be a Sprite instead of Label it works fine?
auto test = Sprite::createWithSpriteFrameName("mainmenu_on.png");

Hmm, so I’ve narrowed it down so that it appears the incorrect behavior only occurs when having a second camera in the scene as well as when using an action on a parent of a Label.

  • Everything behaves correctly when I don’t create a 2nd camera.
  • Everything behaves correctly when the action is run directly on the Label itself
  • The incorrect behavior only occurs with Labels (Sprites/LayerColor works fine)

My guess is that maybe all labels are added as children to a “global” SpriteBatch?? I’ll have to investigate further? I will next try creating an “action” only through scheduling an update and setting position of the parent layer manually. I would bet that will work correctly.

Edit1: NOPE, setposition doesn’t work when using layer->setPosition(Vec2(300,300)), for example. Tried schedule to change position. One or more setPosition calls only works before the layer is added into the scene. So I’ve updated the title.

Edit2: Figured out how to “workaround” the issue by forcing transformUpdated = true. Still not sure why this is only necessary when a second camera is added to the running scene (even when these nodes in test are on the DEFAULT camera).

Edit3: If I move the whole test layers, actions, etc all to the USER1 camera it works fine?

Edit4: If I create two extra cameras USER1 and USER2, it works fine when the camera mask for the hud layer is USER1, but does not work when hud layer is set to mask USER2. Something about the order?

Alright, so I’ve tracked it down to what I believe is the culprit. For some reason the variable flags in the Label’s visit method is set to 0 when it is a child (at any level in hierarchy) of a node that is masked to a camera not rendered first. flags is set to 1 when it is a child masked to the first camera to be rendered (so not including visible cameras).

/* inside visit( ... ) of Label.cpp */
uint32_t flags = processParentFlags(parentTransform, parentFlags);
// flags == 1 when rendered by first visible camera (default or user)
// flags == 0 when rendered by not the first visible camera (default or user)

I’m not sure what the fix is yet so I won’t file an issue in case it’s still something complex related to how our game is structured. I will have to now create a test project and add only a few things and see if I can reproduce.

If anyone knows why the Label node’s flags in visit() would be different (0 or 1) depending on camera order, let me know.

Edit: I think I figured out why it is occuring, the only thing left to be able to give code fix is to determine why the parent flags and updating the transforms is reset after the first camera. Essentially if a node has its transform updated it will update its own transform but its “transform dirty” flag will only be passed down to its children if the children’s mask is on the first camera to process through visit(...).

Edit2: I’m guessing this will fix itself once the camera system is overhauled, but I’d like to use the camera system to separate my HUD and game layer to not worry about setting correct depth or z order between them both.

k, I think this solves it. I am unsure of any side-effects at this point in time, but I’m going to go forward with this change until something breaks again.

void Label::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    if (! _visible || _originalUTF8String.empty())
    {
        return;
    }

    // moved process flags before return if not in camera's view 
    // because updating transforms needs to occur when parent 
    // transform changes, regardless of which camera is active
    uint32_t flags = processParentFlags(parentTransform, parentFlags);

    if(! isVisitableByVisitingCamera()) { return; }

    // ...
}
1 Like

Thank you. Ran into this problem today.

I have also ran into this problem. Label as a child of Sprite is not updating with Action of Sprite & its not fading out as Sprite. Even its Position updates are not correct. If anyone is having a solution or suggestion please reply.

Hi, I found out that when i apply action on Sprite & change its scaling its ContentSize doesn’t gets changed. thats why the child label was also not getting updated for the scale as well as Fading. & yeah if i was trying to UpdateTransform it gave assert that it can be only done with SpriteBatchNode. I did all the calculation about all the size & Position in Init itself. Might help Others also. I am using cocos2dx 3.7

I noticed that the problem only occurs when the object isn’t created in the visible area of the camera so a possible workaround is to put the object in the visible area with setOpacity(0) and then change its position right before the runAction.

In the init function:

titleLabel = cocos2d::ui::Text::create("Try Also!", "LuckiestGuy.ttf", 80);
titleLabel->setAnchorPoint(Vec2(0.5, 0.5));
titleLabel->setPosition(Vec2(CustomGlobals::visibleSize.width*0.5, CustomGlobals::visibleSize.height*1.5)); // this position is inside the visible area of the camera
titleLabel->setOpacity(0); // no opacity
addChild(titleLabel);

Before the runAction

titleLabel->setPosition(Vec2(CustomGlobals::screenSize.width*0.5, CustomGlobals::screenSize.height*0.92)); // this position is outside the visible area of the camera
titleLabel->setOpacity(255); // max opacity

It looks like a bug anyway.