I'm a beginner in the Cocos2D world, and even more in Box2D's. I'm developing an iOS game with these two engines, and what I want, is to have a guy (hero) to pick up coins in a scrolling map.
Well, the problem is the "Contact Listener". I can't seem to understand what it really does, and how I can use it to make the difference between my coins and other bodies (that are all static bodies) around the map.
I set all the coins .isSensor = true to disable collisions. I'm using MyContactListener from raywenderlich.com tutorial.
So, the main question is: How to know what type of body is in collision with the hero's body through the contact listener?
(i've read many tutorials and questions on here and I can't seem to know where to begin)
Thanks!
Edit:
Si, I finally got it to work with sprite.tag, but I can't destroy the bodies and the textures. Sometimes when using [textureB removeFromParentAndCleanup:YES]; I get EXC_BAD_ACCESS.
And as I understood, I can't destroy the body since I'm actually using it.
But when trying to use body->SetUserData(self); in my Hero class
#interface Hero : CCNode {
CCSprite * texture;
CGPoint position;
b2Body* body;
TypeCase type;
}
I get EXC_BAD_ACCESS in my update method:
for(b2Body *b = world_->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
if ([b->GetUserData() isKindOfClass:[Hero class]])
{
Hero* hero = (Hero*)b->GetUserData();
CCSprite *heroSprite = hero.texture;
heroSprite.position = ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
}
else if ([b->GetUserData() isKindOfClass:[Tile class]])
{
Tile* tile = (Tile*)b->GetUserData();
CCSprite *tileSprite = tile.texture;
tileSprite.position = ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
}
}
}
I get EXC_BAD_ACCESS when calling isKindOfClass.
Hero userData
b2BodyDef heroBodyDef;
heroBodyDef.fixedRotation = true;
heroBodyDef.type = b2_dynamicBody;
heroBodyDef.position.Set(position.x/PTM_RATIO, position.y/PTM_RATIO);
body = world_->CreateBody(&heroBodyDef);
body->SetUserData(self);
Tile userData
b2BodyDef blockBodyDef;
blockBodyDef.type = b2_staticBody;
blockBodyDef.position.Set(position.x/PTM_RATIO, position.y/PTM_RATIO);
blockBodyDef.userData = self;
b2Body *body = world ->CreateBody(&blockBodyDef);
Don't use physics objects for coins.. this will affect on fps :)
create CCSprite for coins and detect collisions with coin sprite and body->UserData;
Add your coins in CCArray and write something like this in your update method:
CCSprite *sprite = (CCSprite*)yourObject.body->GetUserData();
Coins *coinObject;
CCARRAY_FOREACH(coinsArray, coinObject)
{
if (CGRectIntersectsRect([sprite boundingBox], [coinObject boundingBox]))
{
// collision detected;
}
}
if you want to use physics body for your coins, you need to create typedef for yourObject states, and in your contact listener void ContactListener::EndContact(b2Contact* contact) method change yourObject currentState. currentState is yourObject's property.
Do something like this in void ContactListener::EndContact(b2Contact* contact)
yourObject.currentState = isCollideOnCoin;
and after this in your update add this line:
if (yourObject.currentState == isCollideOnCoin)
{
// collision detected;
}
You can create a custom class (say BodyType) which indicates bodies type (in your case player/coin)
then you can use the user data property on your created bodies :
playerBody->SetUserData(new BodyType(1));
coinBody->SetUserData(new BodyType(2));
In the contact listener there is a method named beginContact which has an argument of type b2Contact* named contact.
Use this argument to get the two bodies :
b2Body* bodyA = contact->GetFixtureA()->GetBody();
b2Body* bodyB = contact->GetFixtureB()->GetBody();
then you can know if your player hit a coin by checking :
if ((bodyA->GetUserData().getType() == 1 && bodyB->GetUserData().getType() == 2) ||
(bodyA->GetUserData().getType() == 2 && bodyB->GetUserData().getType() == 1)) {
// Mark collision or save coin body in a member of your contact listener for removing it later
}
Of course make sure the user data is not null as all bodies will be passed to the contact listener and not all might have the user data member set.
EDIT
Regarding the BAD_ACCESS do the following :
if (b->GetUserData() != NULL) {
id myUserData = (id)b->GetUserData();
if ([myUserData isKindOfClass:[Hero class]])
{
Hero *hero = (Hero *)myUserData;
// Do your stuff
}
}
Related
The only way I can think to do it is to check velocities for all physics bodies during every collisions.
- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair piece:(CCNode *)pieceA piece:(CCNode *)pieceB{
float x = 0;
float y = 0;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
x = x + [[[_physicsWorld children][i] physicsBody] velocity].x;
y = y + [[[_physicsWorld children][i] physicsBody] velocity].y;
}
if ( x == 0 && y == 0 ) {
NSLog(#"stopped");
}
return YES;
}
This logs “stopped” multiple times when the scene first loads, then doesn’t log “stopped” again, even after physics bodies have clearly started moving and colliding and then come to a stop.
Ideally I'd like a delegate method that would notify me when all physics bodies have stopped moving, but I can't seem to find one.
FYI: I'm using the standard Chipmunk physics engine that's baked into Cocos2d V3.0
Chipmunk has a internal mechanism, which can, if activated, automatically deactivate physics bodies. My approach (I am using cocos2dx 3.11.1 and not -obj version with chipmunk 7.0.1) is:
activate the chipmunk idle mechanism (0.5 second - meaning, if an object is not moving for longer than 0.5 second it will be deactivated):
cpSpaceSetSleepTimeThreshold(space, 0.5f);
You do not need to use
cpSpaceSetIdleSpeedThreshold(space, <speed>);
because chipmunk calculates the threshold speed for you (according the gravitation used).
use this code for determination if all objects are not moving (static and kinetic bodies never sleep):
bool isAnyPhysicsBodyMoving(){
int i = 0; bool isMoving = false;
const Vector<PhysicsBody*>& bodies = getPhysicsWorld()->getAllBodies();
while( i < bodies.size() && !isMoving){
PhysicsBody *body = bodies.at(i);
isMoving = cpBodyGetType(body->getCPBody()) == CP_BODY_TYPE_DYNAMIC
&& !body->isResting();
i++;
}
return isMoving;
}
use static (and not kinetic) body for walls, in order to let objects sleep:
// wall
Size visibleSize = Director::getInstance()->getWinSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
float border = 10.0f;
Size wallBodySize = Size(visibleSize.width+2*border, visibleSize.height+2*border);
PhysicsBody *wallBody = PhysicsBody::createEdgeBox(wallBodySize, PhysicsMaterial(1.0f, 1.0f, 0.5f), border);
Node* wall = Node::create();
wall->addComponent(wallBody);
wall->setAnchorPoint(Vec2(0.5f, 0.5f));
wall->setPosition(Point(visibleSize.width/2+origin.x, visibleSize.height/2+origin.y));
cpVect tt;
tt.x = wall->getPosition().x; tt.y = wall->getPosition().y;
//set position manually and BEFORE adding the object into the space
cpBodySetPosition(wallBody->getCPBody(), tt);
cpBodySetType(wallBody->getCPBody(), CP_BODY_TYPE_STATIC);
addChild(wall);
Any dynamic body connected to a kinetic body (for example laying on) will never sleep.
test it with DEBUG activated
getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
the boxes (their content) must become grey (=sleeping) and not red (=active):
In order to let it work, I have:
added an access method (to get cpSpace) in CCPhysicsWorld.h:
inline cpSpace* getSpace() const { return _cpSpace; }
Fix call of
cpBodySetTorque(body, 0.0f);`
in CCPhysicsBody.cpp to
if (body->t != 0.0f){
cpBodySetTorque(body, 0.0f);
}
Fix call of
cpBodySetPosition(_cpBody, tt);`
in CCPhysicsBody.cpp to
if (!cpveql(tt, cpBodyGetPosition(_cpBody))){
cpBodySetPosition(_cpBody, tt);
}
Steps 2. and 3. are necessary to avoid setting of the same physics body properties, which wake up a sleeping body.
The advantage of this approach is, that the chipmunk does not make any calculations for such physical bodies - saving CPU and battery.
I found something that works.
tl;dr
The basic idea is to keep track of the positions of the sprites myself, and then periodically check them to see if any of them have moved since they were last checked.
Longer version
I created a subclass of CCNode with the class name Piece.
These are my objects that are added to the physics world.
#implementation Piece {
float _previousX;
float _previousY;
}
-(void)updatePreviousScreenXandY{
_previousX = self.position.x;
_previousY = self.position.y;
}
-(BOOL)hasntMoved{
float currentX = self.position.x;
float currentY = self.position.y;
if ( currentX == _previousX && currentY == _previousY ) {
return TRUE;
}else{
return FALSE;
}
}
This is in my CCNode that acts as the game scene
-(void)doStuffAfterPiecesStopMoving:(NSTimer*)timer{
BOOL noPiecesHaveMoved = TRUE;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
if ( [[_physicsWorld children][i] hasntMoved] == FALSE ) {
noPiecesHaveMoved = FALSE;
break;
}
}
if ( noPiecesHaveMoved ) {
[timer invalidate];
NSLog(“Pieces have stopped moving”);
}else{
NSLog(“Pieces are still moving”);
[self updateAllPreviousPiecePositions];
}
}
-(void)updateAllPreviousPiecePositions{
for (int i=0; i < [[_physicsWorld children] count]; i++) {
Piece *piece = (Piece*)[_physicsWorld children][i];
[piece updatePreviousScreenXandY];
}
}
All I have to do is
[NSTimer scheduledTimerWithTimeInterval:TIME_BETWEEN_CHECKS
target:_gamePlay
selector:#selector(doStuffAfterPiecesStopMoving:)
userInfo:nil
repeats:YES];
and it’ll run whatever code I want after all Piece nodes have stopped moving.
The key to getting it to work well is to get the values for the Chipmunk space’s sleepTimeThreshold and the timer above’s time as low as possible.
My experimenting suggests the following settings work okay, but anything lower will cause problems (i.e. collisions not taking place properly):
sleepTimeThreshold = 0.15
my timer = 0.05
If anyone has a different/better solution or improvements to the above code, please post.
Thanks for your time reading this!
I used Cocos2d and Box2d in my project. The logic of the project is pretty simple. Player just shoots at enemies.If the bullet hits an enemy, the bullet as well as the enemy will be destroyed.If any enemy walks through the screen without getting shot, then the game is over. Part of code is as follows:
- (void)birdDone:(ZLBird*) birdToDelete {
CCSprite *sprite = (CCSprite *)birdToDelete;
b2Body *spriteBody = NULL;
for(b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *curSprite = (__bridge CCSprite *)b->GetUserData();
if (sprite == curSprite) {
spriteBody = b;
break;
}
}
}
if (spriteBody != NULL) {
world->DestroyBody(spriteBody);
}
[sprite removeFromParentAndCleanup:YES];
sprite = NULL;
}
-(void) update:(ccTime)delta{
SOME CODE HERE
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
CCSprite *myActor = (__bridge CCSprite*)b->GetUserData();
if (b->GetUserData() != NULL)
{
//Synchronize the AtlasSprites position and rotation with the corresponding body
myActor.position = (CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO));
myActor.rotation = (-1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
}
}
// goes through all the rocks
for (ZLRock *rockToAct in rocksArr){
[rockToAct incrementTime];
if ([rockToAct getExistedTime] >= 400)
{
// Remove the rock several seconds after it has been shot
[self rockDone:rockToAct];
}
}
// goes through all the elements in BirdsArr
for (ZLBird *birdToAct in birdsArr){
[birdToAct incrementTime];
if ([birdToAct getDeadTime] >= 400 || birdToAct.position.x > WIDTH_WINDOW+20)
{
// Remove the bird several seconds after it has been dead
[self birdDone:birdToAct];
}
// JUST FOR TEST
if (birdToAct.position.x > WIDTH_WINDOW && !birdToAct.isDead)
NSLog(#"YOU LOSSSSSSSS!");
}
When I run this project on my iphone, somehow it gets really slow. I checked CPU usage on Instruments, the CPU usage is like 70%.
Why is that? I guess there's no memory leak since I enabled ARC. Is it because I go through several arrays in each frame, and that really makes the device slow down?
Thanks for your help!
Just found out the problem!
I didn't remove those objects from array when I remove them from their parent. So the array size keep growing, and finally cause crush.
Thanks for the answers!
Thanks advance for your time!
I'm doing a project with box2d. When I first touch the screen, a bird will be created in the box2d world, as follows
- (void)createBird
{
//init bird
bird = [CCSprite spriteWithFile:#"bird.png"];
bird.scale = 23/[bird boundingBox].size.width;
bird.position = center;
[self addChild:bird z:12];
}
Then, if the touch ends, the bird will be given a force
//add box2d body
b2BodyDef birdBodyDef;
birdBodyDef.type=b2_dynamicBody;
birdBodyDef.userData = (__bridge void*)bird;
birdBodyDef.position.Set(bird.position.x/PTM_RATIO,bird.position.y/PTM_RATIO);
birdBody=world->CreateBody(&birdBodyDef);
//create circle shape
b2CircleShape birdShape;
birdShape.m_radius=([bird boundingBox].size.width-6)/PTM_RATIO/2;
// Create shape definitio and add to body
b2FixtureDef birdFixtureDef;
birdFixtureDef.shape=&birdShape;
birdFixtureDef.density=1.6f;
birdFixtureDef.friction=0.3f;
birdFixtureDef.restitution=0.3f;
birdFixture = birdBody -> CreateFixture(&birdFixtureDef);
// Apply force
b2Vec2 force = b2Vec2(-2.0f*distance,-2.0f*distance);
birdBody->ApplyForce(force,birdBody->GetWorldCenter());
birdBody->SetLinearDamping(0.2f);
Then, when I touch the screen again, another bird will be created with the "createBird" method.
I want to delete the first bird after it has been created for 5 seconds. But at that time, the "CCSprite *bird" and "b2Body *birdBody" will be pointing to the second bird, so how can I delete the first one?
Thanks!
I'm not sure I understand your question exactly, but this might help. Call before you create the new bird.
if ( bird ) {
id delayTimeAction = [CCDelayTime actionWithDuration:5.0];
id removeMySprite = [CCCallFuncND actionWithTarget:bird selector:#selector(removeFromParentAndCleanup:) data:(void*)NO];
[bird runAction:[CCSequence actions: delayTimeAction, removeMySprite, nil]];
}
edit:
init an array
NSMutableArray *birds = [NSMutableArray new];
Then call this in create bird function
[birds addObject:newBird];
if ( [birds count] > 1 ) {
oldBird = [birds firstObject];
//Call remove function on oldBird
[birds removeObject:oldBird];
}
One way would be giving your bird tag and then saving them into an Nsmutuable dictionary.
Keep traversing that dictionary and then after your bool variable for destroy is been set destroy the body.....
I have done the same thing using classes.... The fact is you will need to destroy body any case. (Else you will face fps issues later)
I'm looking at doing the best way to collect items with my hero in my spriteKit game for iOs, and after to try a few ways to do it, my conclusion is the best way would be to have an item with a physic body which can detect collisions but don't collide with my hero. Is it possible to do it? to deactivate collisions of a physic body without deactivating its capabilities to detect collisions?? Sounds a bit contradictory I know... Because, the other way would be to create only a SKSpriteNode without physic body, then there wouldn't being collisions, but the way to "detect" collisions would be hand made and much more harder, because i would need to set a coordinate system detection in my hero, that when he will be in those specifics coordinates (over the item) then i'll make the item disappears. Any idea of how to do any of the two ways easier?
Check out collisionBitMask, categoryBitMask, and contactTestBitMask in the SKPhysicsBody class.
Essentially, physics bodies with the same collisionBitMask value will "pass-through" each other.
Correction: If the category and collision bits match, they will interact. If they do not match, those two will not interact. And if the collision bits, and category bits, are both zero, of course that item will interact with nothing whatsoever.
Then, you set the categoryBitMask and contactTestBitMask values to create an SKPhysicsContact Object on contact. Finally, your Class should adopt the SKPhysicsContactDelegate protocol. Use the - didBeginContact: method to detect and handle the SKPhysicsContact object.
static const uint8_t heroCategory = 1;
static const uint8_t foodCategory = 2;
--
food.physicsBody.categoryBitMask = foodCategory;
food.physicsBody.contactTestBitMask = heroCategory;
food.physicsBody.collisionBitMask = 0;
--
hero.physicsBody.categoryBitMask = heroCategory;
hero.physicsBody.contactTestBitMask = foodCategory;
hero.physicsBody.collisionBitMask = 0;
--
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody = contact.bodyA;
SKPhysicsBody *secondBody = contact.bodyB;
}
Short answer:
yourHero.physicsBody.collisionBitMask = 0;
The default value of collisionBitMask is 0xFFFFFFFF (all bits set), that's why the node collides with others
you can do this by setting the categoryBitMask and contactBitMasks of the player and the item objects, but making sure that you do not set the collisionBitMask for either to interact with each other (see below)
static const int playerCategory = 1;
static const int worldCategory = 2;
static const int objectCategory = 4;
....
SKSpriteNode *player, *item;
....
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = worldCategory;
player.physicsBody.contactTestBitMask = worldCategory;
....
item.physicsBody.categoryBitMask = objectCategory;
item.physicsBody.contactTestBitMask = playerCategory | worldCategory;
item.physicsBody.collisionBitMask = worldCategory;
this way the physics body will pick up collisions between the player and world objects, the item and world objects, but not between the player and items. It will trigger a call to didBeginContact, where you can delete your item node, add health, etc.
Hope this helps!
contactTestBitMask is used to trigger didBeginContact. collisionBitMask is used to activate physics on nodes.
// add a physics body
ship.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ship.size.width/2];
// set the category for ship
ship.physicsBody.categoryBitMask = shipCategory;
// detect collisions with asteroids and edges
ship.physicsBody.contactTestBitMask = asteroidCategory | edgeCategory;
// physically collide with asteroids
ship.physicsBody.collisionBitMask = asteroidCategory;
There is a class associated with the program Physics Editor called GB2ShapeCache that loads shapes that I make in the program. I noticed that it is not currently possible to change the scale of the shapes on the fly so I would like to be able to scale the fixtures for the shapes that I made in Physics Editor. Now the scale of my CCSprite in my app can be random so currently in the addShapesWithFile method, I do this for polygons:
vertices[vindex].x = (offset.x * sprite.scaleX) / ptmRatio_;
vertices[vindex].y = (offset.y * sprite.scaleY) / ptmRatio_;
and this for circles:
circleShape->m_radius = ([[circleData objectForKey:#"radius"] floatValue] / ptmRatio_) *sprite.scale;
I also changed the method so that I can pass in my sprite so I can get the scale to:
-(void) addShapesWithFile:(NSString*)plist forSprite:(CCSprite*)sprite
so that I can pass in my sprite so I can get the scale.
HOWEVER, I find this to be inefficient because I should not have to reload ALL my shapes in my plist since they are already added.
So is there any way to do what I am doing now but in the addFixturesToBody method? This way I do not re-create the already added plist shapes and I only scale the fixtures when it is ready to be added to my body.
If anyone needs to see more code or needs more info, feel free to ask. I know this issue must be simple!!!
Thanks!
I would recommend implementing it in the addFixturesToBody method.
(see https://github.com/AndreasLoew/GBox2D/blob/master/GBox2D/GB2ShapeCache.mm)
Try this method below, this should scale the shapes accordingly to the sprite's they are for. Just pass in your CCSprite and this method will handle the rest.
- (void)addFixturesToBody:(b2Body*)body forShapeName:(NSString*)shape forSprite:(CCSprite*)sprite {
BodyDef *so = [shapeObjects_ objectForKey:shape];
assert(so);
FixtureDef *fix = so->fixtures;
if ((sprite.scaleX == 1.0f) && (sprite.scaleY == 1.0f)) {
// simple case - so do not waste any energy on this
while(fix) {
body->CreateFixture(&fix->fixture);
fix = fix->next;
}
} else {
b2Vec2 vertices[b2_maxPolygonVertices];
while(fix) {
// make local copy of the fixture def
b2FixtureDef fix2 = fix->fixture;
// get the shape
const b2Shape *s = fix2.shape;
// clone & scale polygon
const b2PolygonShape *p = dynamic_cast<const b2PolygonShape*>(s);
if(p)
{
b2PolygonShape p2;
for(int i=0; i<p->m_vertexCount; i++)
{
vertices[i].x = p->m_vertices[i].x * sprite.scaleX;
vertices[i].y = p->m_vertices[i].y * sprite.scaleY;
}
p2.Set(vertices, p->m_vertexCount);
fix2.shape = &p2;
}
// clone & scale circle
const b2CircleShape *c = dynamic_cast<const b2CircleShape *>(s);
if(c) {
b2CircleShape c2;
c2.m_radius = c->m_radius * sprite.scale;
c2.m_p.x = c->m_p.x * sprite.scaleX;
c2.m_p.y = c->m_p.y * sprite.scaleY;
fix2.shape = &c2;
}
// add to body
body->CreateFixture(&fix2);
fix = fix->next;
}
}
}