Possible Bug in changing physics masks when in contact?

I have two object types:

Player:

Category: 1
Collision: 26
Contact: 26

Ground:

Category: 2
Collision: 1
Contact: 1

This works fine. Collisions and contacts are detected.

If before the player collides with the ground, I set the category, collision and contact masks of the player to (0), then the player falls through the ground (this is also fine).

But if the player is standing on the ground (in contact), and then I change the category, collision and contact masks of the player to (0), then the player does not fall through the ground!

Does this mean we cannot change the bit masks if an object is in contact with something else ? I set the bitmasks by calling:

 pb->setCategoryBitmask(_category);
        pb->setContactTestBitmask(_contact);
        pb->setCollisionBitmask(_collision);

Where pb is the PhysicsBody of the sprite, and _category,_contact,_collision are uint32_t representing the masks.

I suppose that you are using built in physics. Did you put breakpoints in the contact callback functions?(contactBegin,presolve,postSolve) If the breakpoints are not triggering, I guess that the collisions are not happening because they already happened and chipmunk physics doesn’t support Continuous Collision Detection. If you want that you should use Box2D. If you “jump” and contact again with the platform then you should get a new collision and then the result you want. One solution could be to add a different platform if is a scrolling platform game with different mask and then a new collision between these two objects will occur

EDIT: Don’t know if I’m right, I never made a platform game before. If the collision still happening maybe you can ask for the collision masks and then return false or just remove or move the physicsbody of the platform to make the player fall

after you change back to zero, is another contact event triggered?

No, another contact event is not triggered.

Initially when the player lands on the ground contactBegin() event is triggered.

But after changing the bit masks onContactSeparate() is Never called! onContactSeparate() is only called when I move the player from contact with the ground by changing its position.

When I look at the value of the bit masks in the shape, all are set to zero. So not sure how it can still be in contact. Thats why I think there is some bug.

Here is some code to make it easier to test:

class TestPhysicsInfo{ 
    //A simple helper class for setting the bits
public:
    TestPhysicsInfo() : TestPhysicsInfo(0,0,0){};
    TestPhysicsInfo(uint32_t category,uint32_t contact,uint32_t collision){
        _category = category;
        _contact = contact;
        _collision = collision;
    }
    
    uint32_t _category;
    uint32_t _contact;
    uint32_t _collision;
    
    void setPhysicsBodyMaskBits(PhysicsBody* pb)const{
        pb->setCategoryBitmask(_category);
        pb->setContactTestBitmask(_contact);
        pb->setCollisionBitmask(_collision);
    }
};

//Instantiate this test class in your hello world scene. 
class TestBug{
    
    Sprite* player = nullptr;
    Sprite* ground = nullptr;

// https://www.codeandweb.com/physicseditor/tutorials/creating-physics-shapes-for-cocos2d-x
//https://github.com/CodeAndWeb/PhysicsEditor-Loaders/tree/master/cocos2d-x
    PhysicsShapeCache* _shapeCache; //The loader for loading objects physics created with physics editor. Use GitHub link to download source cpp & h file
    float _timeElapsed = 0;
    
    // mask bits
    uint32_t PLAYER = 0x00000001;
    uint32_t ENVIRO = 0x00000002;
    uint32_t HOLDER = 0x00000004;
    uint32_t ENEMY  = 0x00000008;
    uint32_t ITEM   = 0x00000010;
    
    TestPhysicsInfo pPhys;//Player masks:
    TestPhysicsInfo gPhys; //Ground masks;
    TestPhysicsInfo nPhys; // Nothing masks
public:
    //Pass in a node to act as parent to the sprites.
    TestBug(Node* parent,std::string spriteFrameName1,std::string spriteFrameName2){
        _shapeCache = PhysicsShapeCache::getInstance();
        pPhys = TestPhysicsInfo(PLAYER,ENVIRO|ENEMY|ITEM,ENVIRO|ENEMY|ITEM); //Player masks:
        gPhys = TestPhysicsInfo(ENVIRO,PLAYER,PLAYER); //Ground masks;
        nPhys = TestPhysicsInfo(0,0,0); // Nothing masks

       //Initial positions so player is placed at a height above ground
        auto visibleSize = Director::getInstance()->getVisibleSize();
        auto origin = Director::getInstance()->getVisibleOrigin();
        auto groundPosition = Vec2(origin.x + visibleSize.width/2, origin.y+visibleSize.height*0.7);
        auto playerInitialPosition = Vec2(origin.x + visibleSize.width/2, origin.y+visibleSize.height*0.9);
        
        player = Sprite::createWithSpriteFrameName(spriteFrameName1);
        ground = Sprite::createWithSpriteFrameName(spriteFrameName2);
        
        parent->addChild(player);
        parent->addChild(ground);
        
        player->setPosition(playerInitialPosition);
        ground->setPosition(groundPosition);
        
        //For setting shape using physics editor loader class
        _shapeCache->setBodyOnSprite(spriteFrameName1, player);
        player->getPhysicsBody()->setGravityEnable(true); //Make sure player is affected by gravity
        _shapeCache->setBodyOnSprite(spriteFrameName2, ground); //Make sure not affected by gravity, so player has something to land on.
//
        pPhys.setPhysicsBodyMaskBits(player->getPhysicsBody());
        gPhys.setPhysicsBodyMaskBits(ground->getPhysicsBody());
    }
    //Call this update method from schedule update in hello world
    void update(const float& dt){
        _timeElapsed += dt;
        if(_timeElapsed > 3){
            nPhys.setPhysicsBodyMaskBits(player->getPhysicsBody()); // Setting to none, so it should fall through the ground
        }
    }

To make it even simpler, instead of using physics editor, I just create a simple box programmatically for the physics body. Not sure how to work around this bug. When debugging, the shapes show the correct mask bits, so not sure why the collision is still happening:

class TestPhysicsInfo{
    
public:
    TestPhysicsInfo() : TestPhysicsInfo(0,0,0){};
    TestPhysicsInfo(uint32_t category,uint32_t contact,uint32_t collision){
        _category = category;
        _contact = contact;
        _collision = collision;
    }
    
    uint32_t _category;
    uint32_t _contact;
    uint32_t _collision;
    
    void setPhysicsBodyMaskBits(PhysicsBody* pb)const{
        pb->setCategoryBitmask(_category);
        pb->setContactTestBitmask(_contact);
        pb->setCollisionBitmask(_collision);
        //pb->setCollisionBitmask(<#int bitmask#>)
    }
};

class TestBug{
    
    Sprite* player = nullptr;
    Sprite* ground = nullptr;
    float _timeElapsed = 0;
    
    //For category bits
    uint32_t PLAYER = 0x00000001;
    uint32_t ENVIRO = 0x00000002;
    
    TestPhysicsInfo pPhys;//Player masks:
    TestPhysicsInfo gPhys; //Ground masks;
    TestPhysicsInfo nPhys; // Nothing masks
public:
    
    TestBug(Node* parent,std::string spriteFrameName1,std::string spriteFrameName2){
        pPhys = TestPhysicsInfo(PLAYER,ENVIRO,ENVIRO); //Player masks:
        gPhys = TestPhysicsInfo(ENVIRO,PLAYER,PLAYER); //Ground masks;
        nPhys = TestPhysicsInfo(0,0,0); // Nothing masks

        auto visibleSize = Director::getInstance()->getVisibleSize();
        auto origin = Director::getInstance()->getVisibleOrigin();
        auto groundPosition = Vec2(origin.x + visibleSize.width/2, origin.y+visibleSize.height*0.7);
        auto playerInitialPosition = Vec2(origin.x + visibleSize.width/2, origin.y+visibleSize.height*0.9);
        
        player = Sprite::createWithSpriteFrameName(spriteFrameName1);
        ground = Sprite::createWithSpriteFrameName(spriteFrameName2);
        
        parent->addChild(player);
        parent->addChild(ground);
        
        player->setPosition(playerInitialPosition);
        ground->setPosition(groundPosition);
        
        
        auto pphysicsBody = PhysicsBody::createBox(Size(65.0f, 81.0f),
                            PhysicsMaterial(0.0f, 0.0f, 0.0f));

        pphysicsBody->setGravityEnable(true);
        pphysicsBody->setDynamic(true);
        player->setPhysicsBody(pphysicsBody);
        
        
        auto gphysicsBody = PhysicsBody::createBox(Size(165.0f, 81.0f),
                            PhysicsMaterial(0.0f, 0.0f, 0.0f));

        gphysicsBody->setGravityEnable(false);
        gphysicsBody->setDynamic(false);
        ground->setPhysicsBody(gphysicsBody);
        
        //For setting shape using physics editor loader class
        pPhys.setPhysicsBodyMaskBits(player->getPhysicsBody());
        gPhys.setPhysicsBodyMaskBits(ground->getPhysicsBody());
    }
    
    void update(const float& dt){
        _timeElapsed += dt;
        if(_timeElapsed > 2 && _timeElapsed <4){
            player->setVisible(false);
            nPhys.setPhysicsBodyMaskBits(player->getPhysicsBody()); // Setting to none, so it should fall through the ground
            nPhys.setPhysicsBodyMaskBits(ground->getPhysicsBody());
        }
        if(_timeElapsed>10){
            if(ground!=nullptr){
                ground->removeFromParent();
                ground = nullptr;
            }
        }
    }
};

Any idea if it is a bug? I added some sample code to help reproduce it.

Thanks. I’ll ask engineering to have a look

Thanks! Any idea what the problem was ?

Let me ask for an update.

I’m sorry, I don’t quite understand your question. Can you provide a recorded video demonstrating this problem?

Here is a short video demonstrating the problem, using the sample code above.

At first, the expected behavior happens - the player box (small box), lands on the ground box (large box) and does not pass through.

After two seconds, I change the category / collision / contact masks to 0,0,0 for both, yet the player box does not fall through the ground box. The point in this video when this change is made can be seen when I remove the small white dot sprite in the player box.

Expected behavior : Setting the category, contact and collision bits to zero, should result in the player box passing through, rather than remaining above and in contact with the ground box.

After a few seconds, I simply remove the ground box and the player box falls - this is to show that the ground box was still holding the player box up because it was still in contact.

EDIT: Even if I just change only the player masks to 0,0,0 and leave the ground masks as they are, the same behavior exists.

Stepping through the code, shows that the masks are updated, yet the contact does not change when you change the mask.

So for this reason, I was wondering if this is a bug, or if I have misunderstood what one can do in real time when changing the masks.

IMPORTANT:, if I change the masks before the player box lands on the ground box, then the player box falls through as expected. So it seems to have something to do with changing the masks AFTER a contact has already begun. Thats why I feel maybe it is a bug. Setting breakpoints shows that the contact hasn’t ended. So just because masks have been changed, doesn’t make the contact end.

Hi, so is it a bug ?

I’ll ask for the engineer to follow up.

Thanks! Any news ?

I’d be happy to double-check on it.

There is no abnormality in the video. I think you have made a simple test project, please upload it, we will double-checkyou project. :rofl:

Possibly the objects are “sleeping” and you have to keep them “awake” instead? I’m not sure the terminology, but I remember when viewing a debug mode test the various 2d boxes would turn gray to signify they were no longer getting simulated?

I don’t actually know, as I’ve not really used Cocos2d physics, beyond debugging others and simple testing.

I think there is a misunderstanding, either on my part or yours.

In the video and code, it clearly shows that I am changing the category, collision and contact bits of the ground to 0,0,0. Since I am changing it to this, shouldn’t the player fall immediately ? Instead the player is falling only after I remove the ground.

It seems that once a contact is initiated, regardless of what you change the bits to, the contact will remain.

Conversely, if you change the bits to zero before the contact begins, then the player falls through the ground as expected.

You can use the above code to test it on your system. Just create the class, and call the update method from the update method in your scene. As parent you can pass your HelloWorld layer . For the two sprites, pass any two sprite frame names of frames you have loaded into memory.

That does not seem to be the case. If you change the bits to 0 before the contact begins, the player passes through the ground as expected. If you change the bits after the contact has begun, the player does not fall through. He falls through only when the ground object is removed.