[SOLVED] Can someone please point out what i did wrong in my code for detecting collision in cocos2dx?

Hi,
After reading this post and this about collision detecting in cocos2dx, i decided to run a few test.
In my game there are player, player’s weapon and enemy. Enemy chase player. Weapon will be shoot from player’s position so Player-Weapon no collide, Player-Enemy collide and Weapon-Enemy collide.
#define PLAYER_CATEGORY_BITMASK 0x000001
#define PLAYER_COLLISION_BITMASK 0x000004

#define WEAPON_CATEGORY_BITMASK 0x000002
#define WEAPON_COLLISION_BITMASK 0x000004

#define ENEMY_CATEGORY_BITMASK 0x000004
#define ENEMY_COLLISION_BITMASK 0x000003

Then i create physic body for the sprites:

auto playerBody = PhysicsBody::createBox(_player->getContentSize());
playerBody->setCategoryBitmask(PLAYER_CATEGORY_BITMASK);
playerBody->setCollisionBitmask(PLAYER_COLLISION_BITMASK);
playerBody->setContactTestBitmask(true);
playerBody->setDynamic(true);
playerSprite->setPhysicsBody(playerBody);

auto weaponBody = PhysicsBody::createBox(_playerWeapon->getContentSize());
weaponBody->setDynamic(true);
weaponBody->setCategoryBitmask(WEAPON_CATEGORY_BITMASK);
weaponBody->setCollisionBitmask(WEAPON_COLLISION_BITMASK);
weaponBody->setContactTestBitmask(true);
weaponBody->setAngularVelocity(20);//the weapon will be spinning around
weaponSprite->setPhysicsBody(weaponBody);

auto enemyBody = PhysicsBody::createBox(_enemy->getContentSize());
enemyBody->setCategoryBitmask(ENEMY_CATEGORY_BITMASK);
enemyBody->setCollisionBitmask(ENEMY_COLLISION_BITMASK);
enemyBody->setContactTestBitmask(true);
enemyBody->setDynamic(true);
enemySprite->setPhysicsBody(enemyBody);

Next is handle contact, in init()
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(Game::onContactBegin, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this);

Then
bool Game::onContactBegin(cocos2d::PhysicsContact &contact)
{
PhysicsBody *a = contact.getShapeA()->getBody();
PhysicsBody *b = contact.getShapeB()->getBody();
CCLOG(“Contact”);
return true;
}

The result is good on screen, player-weapon no collide (player’s sprite stood still after shoot a weapon), player/weapon-enemy collide but the Event onContactBegin() is not called. But if i remove the following lines:

playerBody->setCategoryBitmask(PLAYER_CATEGORY_BITMASK);
weaponBody->setCategoryBitmask(WEAPON_CATEGORY_BITMASK);
enemyBody->setCategoryBitmask(ENEMY_CATEGORY_BITMASK);

Then the event onContactBegin() is called when there is contact, but now player-weapon collide each other (player’s sprite get recoil from shoot the weapon). What is the different with/without categortyBitmask and if i continue with no categortyBitmask how to make Player-Weapon not collide. Thanks.

I don’t see anything immediately obvious wrong - the bitmaps you’re using look OK.

But are you saying that the objects collide but no onContactBegin is called? If so, I don’t know how a collision is happening without a contact beginning!

Only guess I could make would be if the objects are created overlapping - but that still wouldn’t explain why it is called OK when the mask is set!

What happens if you set all of your category bitmasks to (for example) 7? This should behave exactly the same as not specifying one at all, I think?

Player category 1 bitmask 4, Enemy category 4 bitmask 1.
No onContactBegin call. If i remove the setCategoryBitmask for both then onContactBegin called.
Btw set 0x000001 vs 1 is the same right?

Setting the bitmaps is the right way to do it - we just need to figure out why its’ not working for you!

So - if the categories are all set to 7 (00000111) then they will all collide with anything that has a collision mask of 1,2,3,4,5,6 or 7 - so now we can see that onContactBegin is being called - so let’s try something else:

First can you simplify it and get rid of the weapon?

Then set player category to 1 and enemy category to 4 (as it is)
Set player mask to 4 and enemy mask to 1

We should still get our OnContactBegin happening

Player category 1 collision bitmask 4, Enemy category 4 bitmask 1.
No onContactBegin called, if i remove the setCategoryBitMask for both, the onContactBegin called.
With or without setCategoryBitMask, player still get contact with enemy (eg being push by enemy when contact), the event just don’t fired.
Btw set bitmask 0x0000001 same as 1 right?

Yes 0x0000001 is the same as 1.

I don’t use the built in physics engine so I’m not sure what is happening here!
The fact that the onContactBegin is being called when you have no category, or when you have category 7 (I guess the ‘no category’ is the equivalent of 11111111 so that kinda makes sense!) means your code is correct for that - but in that case I just can’t see how a collision is happening without that onContactBegin being called.

This may be a dumb question - but you don’t have any other code that could be pushing your player? i.e. you are sure its the physics engine doing the pushing?

Maybe someone who has used the inbuilt physics will chip in - because I’m at a loss to explain the behaviour your seeing!

The enemy will chase the player so the enemy body will try to go to the player’s center, get contacted on player’s physic body hence the pushing, i guest.
What is your way of handle collision, i don’t mind if it’s not use built in fuction, always try to learn something new.

I use physics, but I use Box2d rather than the built in physics .

Oh yeah right, i totally forgot about box2d is in cocos2d too, got to find some tutorial about using box2d in cocos2d. Hope someone find what wrong with my code in the meantime.

The collision bitmask for the enemy is wrong. Collision bitmasks define with which category they collide. However they only collide if they are set in both bodies not just one.

Try this:
enemyBody->setCollisionBitmask(PLAYER_CATEGORY_BITMASK |WEAPON_CATEGORY_BITMASK );

Here is a good tutorial for that btw: http://www.iforce2d.net/b2dtut/collision-filtering

Hi Rambazamba,
I tried your way with just player and enemy collide:
playerBody->setCollisionBitmask(ENEMY_CATEGORY_BITMASK);
enemyBody->setCollisionBitmask(PLAYER_CATEGORY_BITMASK);
with
#define PLAYER_CATEGORY_BITMASK 0x000001
#define ENEMY_CATEGORY_BITMASK 0x000004
And the result still the same, onContactBegin won’t fired on contact.

Ok, thanks guys i am good for now. Just found another tutorial about this and seem to solve my problem. What left for me is to figure it out why use setContactTestBitmask instead of setCollisionBitmask work
enemyBody->setCategoryBitmask(ENEMY_CATEGORY_BITMASK);
enemyBody->setCollisionBitmask(0);
enemyBody->setContactTestBitmask(PLAYER_CATEGORY_BITMASK);

I think people need to understand this to use it properly.

Whenever you create any physics body these three must need to be set setCategoryBitmask, setCollisionBitmask, setContactTestBitmask, if any one is missing without initialize then it will not give you desire outout.
please dont use 'true', 'false' or '0' '1', it will make you difficult to understand.
Also dont set different collision mask for CATEGORY & COLLISION, use single mask per object, like below.

#define COL_MASK_BLANK		0x00000000
#define COL_MASK_PLAYER		0x00000001
#define COL_MASK_WEAPON		0x00000002
#define COL_MASK_ENEMY		0x00000004

Use setCategoryBitmask to set object’s own mask, like below

playerBody->setCategoryBitmask(COL_MASK_PLAYER);
weaponBody->setCategoryBitmask(COL_MASK_WEAPON);
enemyBody->setCategoryBitmask(COL_MASK_ENEMY);

Use setCollisionBitmask for physics response, with which objects you need physics response?
Do you want velocity should change when 2 objects collide? then mention this.
I think in your case, all blank is enough

playerBody->setCollisionBitmask(COL_MASK_BLANK);
weaponBody->setCollisionBitmask(COL_MASK_BLANK);
enemyBody->setCollisionBitmask(COL_MASK_BLANK);

To understand, i will give you another example, suppose there is ball & player which should jump on platform then initialize like below

platformBody->setCollisionBitmask(COL_MASK_BALL + COL_MASK_PLAYER);
playerBody->setCollisionBitmask(COL_MASK_PLATFORM);
ballBody->setCollisionBitmask(COL_MASK_PLATFORM);

Use setContactTestBitmask for listener response, with which objects you need contact listener should be called? onContactBegin etc

playerBody->setCollisionBitmask(COL_MASK_ENEMY);
weaponBody->setCollisionBitmask(COL_MASK_ENEMY);
enemyBody->setCollisionBitmask(COL_MASK_PLAYER + COL_MASK_WEAPON);

So in future if you add new objects then just ‘+’ with old mask values.
I hope this will help you and others as well.
Let me know if you have any doubts.

2 Likes

Mind if I use this in our docs? Super detailed.

Yeah, sure.
Just edit as per your requirement.

Thank you for the detail explanation, i think i got a hang of it now.

Great explanation - just a comment; should not the ‘+’ in the final line really be a ‘&&’?

Thank you.
But that ‘+’ thing is correct.
Like below,
enemyBody->setCollisionBitmask(COL_MASK_PLAYER + COL_MASK_WEAPON + COL_MASK_BULLET);

IN a simple case like you propose, then ‘+’ is fine, as
1 + 2 + 4 == 1 || 2 || 4

But in potentially more complex cases, that doesn’t hold true - e.g.

1 + 3 + 4 != 1 || 3 || 4

( I posted && by mistake - of course I meant ||)

??

Its hex value, it should be power-of-2
Total 8-bit, so total 32 possibilities.

#define COL_MASK_BLANK		0x00000000
#define COL_MASK_PLAYER		0x00000001
#define COL_MASK_WEAPON		0x00000002
#define COL_MASK_ENEMY		0x00000004
#define COL_MASK_WALL	    0x00000008
#define COL_MASK_PLATFORM	0x00000010
#define COL_MASK_BALL		0x00000020
#define COL_MASK_OTHER_1	0x00000040
#define COL_MASK_OTHER_2	0x00000080
#define COL_MASK_OTHER_3	0x00000100
#define COL_MASK_OTHER_4	0x00000200
#define COL_MASK_OTHER_5	0x00000400
#define COL_MASK_OTHER_6	0x00000800

and so on…