callback within b2contactlistener begincontact - iphone

I have a contact listener to detect collisions between a bullet and an asteroid. Inside the beginContact function i check for the sprite types and fire a block from within this function.
The block is set by the scene class when it is allocated, before the contact listener is assigned to the box2dworld. The bullets and asteroids both get box2d bodies as well as sprites.
The problem is that i can access the sprites and do whatever i want with them (run actions, stop actions etc.).
However, as soon as i call the removeChild function on the sprite, I get an EXC_BAD_ACCESS in the b2Contact::Update on listener->BeginContact(this).
Can anyone point me in the right direction?
My tick function:
//Physics simulation
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *)b->GetUserData();
b2Vec2 b2Position = b2Vec2(sprite.position.x/PTM_RATIO,
sprite.position.y/PTM_RATIO);
float32 b2Angle = -1 * CC_DEGREES_TO_RADIANS(sprite.rotation);
b->SetTransform(b2Position, b2Angle);
}
}
for(CCSprite *sprite in spritesToDelete) {
[self removeSprite:sprite];
[spritesToDelete removeObject:sprite];
sprite = nil;
}
My contact listener callback block:
contactListener->contactBlock = ^(CCSprite *A, CCSprite *B) {
NSLog(#"A: %#", [A class]);
NSLog(#"B: %#", [B class]);
CCSprite *bullet = nil , *asteroid = nil;
if ([(NSString *)A.userData isEqualToString:#"asteroid"] && [(NSString *)B.userData isEqualToString:#"bullet"])
{
asteroid = A;
bullet = B;
} else if ([(NSString *)B.userData isEqualToString:#"asteroid"] && [(NSString *)A.userData isEqualToString:#"bullet"])
{
asteroid = B;
bullet = A;
}
if (asteroid != nil && bullet != nil) {
NSLog(#"Asteroid Hit!");
[asteroid stopAllActions];
[bullet stopAllActions];
//If I keep the line below uncommented, I get the error. Adding actions and stuff does not make an issue, only removal of sprite is when I get the error.
[spritesToDelete addObject:bullet];
}
};

I guess you put a pointer to your sprite in b2UserData of b2Body or b2Fixure in order to retrieve when collision happens. When you delete the sprite, this pointer will point to a deleted instance and thus you receive EXC_BAD_ACCESS.
You should nullify the pointer to eliminate the problem

Related

detect collision with sprite in sprite sheet

I have a sprite sheet in which there a four images(bat1,bat2,bat3.bat4) , in Image there is a person with holding the bat and when all images are combined in animation then it looks like playing baseball.
below is the code that i have used to add sprite sheet.
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"baseball.plist"];
spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"baseball.plist"];
[self addChild:spriteSheet];
background = [CCSprite spriteWithSpriteFrameName:#"bat4.png"];
background.position = ccp(220, 185);
background.tag = 10;
[self addChild:background];
for(int i = 1; i < 5; i++) {
[walkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"bat%d.png", i]]];
}
CCAnimation *walkAnim = [CCAnimation animationWithSpriteFrames:walkAnimFrames delay:5.0f];
self.walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim]];
[background runAction:_walkAction];
And i am detecting the collision of ball with bat. by below code
for (CCSprite *monster in _monsters) {
if (CGRectIntersectsRect(background.boundingBox, monster.boundingBox)) {
if (((background.position.x -5 > monster.position.x + (monster.contentSize.width/2)) && background.position.y > monster.position.y)) {
isCollision = 1;
[monstersToDelete addObject:monster];
}
// [monstersToDelete addObject:monster];
//[self addFishToBoat];
}
}
here wat happen is collision is detected but it always detect collision with rect of bat4.. as the bat is moving and the cgrect of all bat is different so even when bas is very much far away from bat1 then it will detect the collision because ball's rect will intersect with rect of bat4.
But i want the collision will de detect only when the ball will collide wiith different bats, i mean when bal hit with bat1,bat2,bat3,bat4 then only it will detect the collision rather then detect with bat4 always
Here is great tutorials by raywenderlich on sprites collision detection.
Or
You can try this tutorial also Sprite Collision
Hope these will help you.
You try this code it may help you...
_contactListener = new MyContactListener();
_world->SetContactListener(_contactListener);
// Preload effect
[[SimpleAudioEngine sharedEngine] preloadEffect:#"hahaha.caf"];
std::vector<b2Body *>toDestroy;
std::vector<MyContact>::iterator pos;
for(pos = _contactListener->_contacts.begin();
pos != _contactListener->_contacts.end(); ++pos) {
MyContact contact = *pos;
b2Body *bodyA = contact.fixtureA->GetBody();
b2Body *bodyB = contact.fixtureB->GetBody();
if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
CCSprite *spriteA = (CCSprite *) bodyA->GetUserData();
CCSprite *spriteB = (CCSprite *) bodyB->GetUserData();
if (spriteA.tag == 1 && spriteB.tag == 2) {
toDestroy.push_back(bodyA);
} else if (spriteA.tag == 2 && spriteB.tag == 1) {
toDestroy.push_back(bodyB);
}
}
}
std::vector<b2Body *>::iterator pos2;
for(pos2 = toDestroy.begin(); pos2 != toDestroy.end(); ++pos2) {
b2Body *body = *pos2;
if (body->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *) body->GetUserData();
[_spriteSheet removeChild:sprite cleanup:YES];
}
_world->DestroyBody(body);
}
if (toDestroy.size() > 0) {
[[SimpleAudioEngine sharedEngine] playEffect:#"hahaha.caf"];
}
Fore more detail about this code visit
http://www.raywenderlich.com/606/how-to-use-box2d-for-just-collision-detection-with-cocos2d-iphone

Sprite jumping around when i use resumeSchedulerAndActions

Ok I am attempting some AI stuff here and I have been following some Ray Wenderlich tutorials. I have some strange behavior going on. Maybe I am just doing this all wrong... but here you go. When a sprite is within 75 pixels of the target it switches to the Defending AIState and i call pauseSchedulerAndActions and set it to a predetermined safe spot via getDefensePosition method. What I am trying to do is after 2 seconds resume the actions so the sprite will move around again. so I call resumeSchedulerAndActions. Now this just goes through the getDefenseMethod and it moves te sprite between these three places but this is the strange behavior i have two slog calls one before getDefenseMethod and one after the sprite is jumping around from the center of the screen then back to the new spawnPoint:
2013-03-04 20:08:14.897 10-8[2629:c07] before: {217.533, 177.32}
2013-03-04 20:08:14.898 10-8[2629:c07] spawnPoint 1
2013-03-04 20:08:14.899 10-8[2629:c07] after: {100, 100}
dont understand why it is doing that. Why does it not just start from the position it was in?
- (void)execute:(GangMembers *)player {
// Check if should change state
NSArray * enemies = [player.layer enemiesOutsideRange:75 ofPlayer:player];
if (enemies.count > 0) {
NSLog(#"outside range 75");
[player changeState:[[Attacking alloc] init]];
return;
}
[player.layer setPlayer:player attacking:NO];
// Make build decision
[player.layer unschedule:#selector(shoot:)];
[player pauseSchedulerAndActions];
NSLog(#"before: %#", NSStringFromCGPoint(player.position));
[self getDefensePosition];
player.position = spawnPoint;
NSLog(#"after: %#", NSStringFromCGPoint(player.position));
[player performSelector:#selector(resumeSchedulerAndActions) withObject:player afterDelay:2];
}
- (void)getDefensePosition {
// CGSize winSize = [CCDirector sharedDirector].winSize;
int spawnChoice = arc4random() % 3;
spawnPoint = ccp(100, 100);
if(spawnChoice == 0){
spawnPoint = ccp(100, 100);
NSLog(#"spawnPoint 1");
}
else if(spawnChoice == 1){
spawnPoint = ccp(100, 200);
NSLog(#"spawnPoint 2");
}
else {
spawnPoint = ccp(100, 300);
NSLog(#"spawnPoint 3");
}
}
FWIW, I suspect your player object has some CCMove type of actions (which you are pausing). Even though you change the position while paused, when the action resumes, the action sets the position to its current state (startPosition, endPosition, duration, time elapsed since start), which may be quite different from the position you set during the pause.
not certain of your object model/class structure, but something like this:
[player stopAllActions];
player.position = spawnPoint;
[player runAction: [CCSequence actions:
[CCDelayTime actionWithDuration:2.0],
[CCMoveTo actionWithDuration:arc4random()%5+1 position: randomPoint],
[CCCallBlock actionWithBlock:^{ [self performSelector:#selector(moveRandom:) withObject:s afterDelay:0.5]; }],
nil]
];
this way, you recreate a moveto action that will be executed from spawnPoint, and your player.position is not in contention with a running action. Written from memory, you mileage may vary :)

How do I get a particle effect to follow my sprite in cocos2d?

I want a snow particle effect to follow my sprite and I tried some methods but all that ends up happening is the snow will just stay still instead of following. I did this one tutorial (will post as soon as I find it) thats shows how it do it with fire but didn't work out at all. Any tutorials or suggestions will be appreciated. I believe i have to add some kind of code to the snippet part where it says create enemy off screen.
[self schedule:#selector(gameLogicboss:) interval:180 ];
[self schedule:#selector(updateboss:)];
-(void)addTarget1 {
Boss *target1 = nil;
if ((arc4random() % 2) == 0) {{
target1 = [WeakAndFastBoss boss];
}} else {
target1 = [WeakAndFastBoss boss];
}
// Determine where to spawn the target along the Y axis
CGSize winSize = [[CCDirector sharedDirector] winSize];
int minY = target1.contentSize.height/2;
int maxY = winSize.height - target1.contentSize.height/2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the target slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
target1.position = ccp(winSize.width + (target1.contentSize.width/2), actualY);
[self addChild:target1 ];
// Determine speed of the target
int minDuration = target1.minMoveDuration;
int maxDuration = target1.maxMoveDuration;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
id actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-target1.contentSize.width/2, actualY)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveFinished:)];
[target1 runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
target1.tag = 1;
[_targets addObject:target1];
}
-(void)gameLogicboss:(ccTime)dt {
[self addTarget1];
iterations_++;
}
- (void)updateboss:(ccTime)dt {
CGRect projectileRect = CGRectMake(projectile.position.x - (projectile.contentSize.width/2), projectile.position.y - (projectile.contentSize.height/2), projectile.contentSize.width, projectile.contentSize.height);
BOOL bossHit = FALSE;
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target1 in _targets) {
CGRect target1Rect = CGRectMake(target1.position.x - (target1.contentSize.width/2), target1.position.y - (target1.contentSize.height/2), target1.contentSize.width, target1.contentSize.height);
if (CGRectIntersectsRect(projectileRect, target1Rect)) {
//[targetsToDelete addObject:target];
bossHit = TRUE;
Boss *boss = (Boss *)target1;
boss.hp--;
if (boss.hp <= 0) {
_score ++;
[targetsToDelete addObject:target1];
}
break;
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
_projectilesDestroyed++;
if (_projectilesDestroyed > 2) {
}
}
if (bossHit) {
//[projectilesToDelete addObject:projectile];
[[SimpleAudioEngine sharedEngine] playEffect:#"explosion.caf"];
}
[targetsToDelete release];
}
-(void)spriteMoveFinishedboss:(id)sender {
CCSprite *sprite = (CCSprite *)sender;
[self removeChild:sprite cleanup:YES];
GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:#"You Lose"];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
if (sprite.tag == 1) { // target
[_targets removeObject:sprite];
} else if (sprite.tag == 2) { // projectile
[_projectiles removeObject:sprite];
}
}
I Have found this, at CCParticleSystem.h
/** #typedef tCCPositionType
possible types of particle positions
/
typedef enum {
/* Living particles are attached to the world and are unaffected by emitter repositioning. */
kCCPositionTypeFree,
/** Living particles are attached to the world but will follow the emitter repositioning.
Use case: Attach an emitter to an sprite, and you want that the emitter follows the sprite.
*/
kCCPositionTypeRelative,
/** Living particles are attached to the emitter and are translated along with it. */
kCCPositionTypeGrouped,
you should set it like
myparticleSystem.positionType=kCCPositionTypeGrouped;
Hope it helps.
You don't need to update the particle emitter's position with the sprite.
You can add a particle system to the sprite as a child.
The particle system does need to be typed as such:
CCParticleSystem * booster = [CCParticleSystem particleWithFile:#"boosterParticles.plist"];
//customize your particles' options
//assuming you have a sprite defined as _motherShip
[_motherShip addChild:booster];
/*
* now that the particles are the _motherShip's child, you must remember
* to set the position relative to the mothership's origin...
*/
particles.position = ccp(15,0);
...So now whenever _motherShip.position changes, the booster will follow. It will even rotate with the ship.
Very simple logic without getting into code:
I spawn a sprite and give it a location (x, y).
For each sprite, I also spawn a CCParticleSystem, give it the required particle type, spawn rates etc.
The CCParticleSystem location is now set to be the same (x,y) location as the sprite, and it should get updated as the sprite's (x,y) location is update.
As the sprite and the CCParticleSystem move around, this location (x, y) is getting updates constantly at random in your schedule method per the interval step time.
Hope that makes sense.
I do this
vehicleParticleSystem = [CCParticleSystemQuad particleWithFile:#"vehicleParticle.plist"];
vehicleParticleSystem.position = ccp(_ship.position.x - _ship.contentSize.width/3, _ship.position.y - _ship.contentSize.height/3);
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
vehicleParticleSystem.scale = 0.5;
}
[self addChild:vehicleParticleSystem z:-1];
and update its position with this
- (void) updateParticleSystem:(ccTime)dt {
vehicleParticleSystem.position = ccp(_ship.position.x - _ship.contentSize.width/3, _ship.position.y - _ship.contentSize.height/3);
}
which is called in the -(void) update:(ccTime)dt method.
The ship is moved by the user via a joypad.
hope this helps. The positioning for the particle is slightly behind the vehicle for an engine effect.
Did you tried to update your particle position taking advantage of the update method?
I'm doing something like the following lines in one of my games and it works as I think you expect
-(void) update:(CCTime)delta{
_sprite.position = CGPointMake(newXPosition, newYPosition);
_fire.position = CGPointMake(_sprite.position.x, _sprite.position.y);
}
I hope it helps!

Sprite and its body don't act the same way to forces

So I created a body and filled it with a sprite. The problem is that if I apply a force on the body, the sprite goes higher than the body (which I can see from debug_draw). Any idea why this is happening?
UPDATE
- (void)tick:(ccTime) dt {
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *playerData = (CCSprite *)b->GetUserData();
playerData.position = ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
playerData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
And here's how I call this in my init method:
[self schedule:#selector(tick:)];
The for loop which gets the User_Data is kept on repeating now and then so to update the position of the creature would be difficult.. Please move the sprites position in the tick method....
-(void) moveCreature
{
// Please use the position to set the directions. This is used to fall from upside down in Portrait mode
[spriteCreature setPosition:ccp(spriteCreature.position.x,spriteCreature.position.y-5)];
}
Now in the init method schedule the sprite
-(id) init
{
if((self = [super init]))
{
// Create your World and other stuffs
[self schedule:#selector(moveCreature)];
}
return self;
}
The above stuff would carry the sprite in the body n fall down...

Cocos2d game termination problem when a moving sprite goes inside the bounding box of a animating still sprite(a ball get into a hole)

Let me explain it in depth, when ever if(CGRectContainsPoint([hole1 boundingBox], ball1.position)) condition goes true, i do lots of stuffs, like unscheduled, a selector, destroying a ball body calling an animation (please refer Code below)etc. This work properly most of the time. But sometimes when ball is really near to hole(just touching the hole but but not enough to make the above condition true), or is been throws towards the hole really fast speed, then application got terminated. I have checked, by commenting many actions which are been performed in this section, but got nothing helpful, application keep terminating when some efforts are been done to make it terminate.
if(CGRectContainsPoint([hole1 boundingBox], ball1.position))
{
ballBody->SetLinearVelocity(b2Vec2(0.0f,0.0f));
ballBody->SetAngularVelocity(0.0f);
[self unschedule:#selector(tick:)];
self.isTouchEnabled = NO;
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
// create the sprite sheet
CCSpriteSheet *spriteSheet;
GolfBallsAppDelegate *appDelegate = (GolfBallsAppDelegate *)[[UIApplication sharedApplication] delegate];
if([appDelegate.ballValue isEqualToString:#"cricketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"cricket_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"ironball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"iron_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"golfball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"golf_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"soccerball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"soccer_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"basketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"basket_ball_strip.png"];
}
spriteSheet.position = ccp(hole1.position.x,60);
[self addChild:spriteSheet];
float frameWidth = 96;
float frameHeight = 84;
CCSprite *sprite = [CCSprite spriteWithTexture:spriteSheet.texture rect:CGRectMake(0, 0, frameWidth, frameHeight)];
[spriteSheet addChild:sprite];
//if(animation)
{
// create the animation
CCAnimation *spriteAnimation = [CCAnimation animationWithName:#"potting" delay:0.1f];
int frameCount = 0;
for (int x = 0; x < 6; x++)
{
// create an animation frame
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:spriteSheet.texture rect:CGRectMake(x*frameWidth,0*frameHeight,frameWidth,frameHeight) offset:ccp(0,0)];
[spriteAnimation addFrame:frame];
frameCount++;
// stop looping after we've added 14 frames
if (frameCount == 6)
{
//[self removeChild:spriteSheet cleanup:YES];
break;
}
}
// create the action
CCAnimate *spriteAction = [CCAnimate actionWithAnimation:spriteAnimation];
//CCRepeatForever *repeat = [CCRepeatForever actionWithAction:spriteAction];
// run the action
[sprite runAction:spriteAction];
//[sprite runAction:repeat];
}
[self schedule:#selector(loading) interval:0.5];
[self schedule:#selector(holeFinish) interval:1];
//[self removeChild:spriteSheet cleanup:YES];
}
Any suggestion will be highly appreciated.
EDIT: What i found is, problem with folling lines [self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
(MAY be). but as it not occurs always, (as I mentioned), thus it's being ridiculous.
I think your problem will be that you are trying to delete a body when the b2World is 'locked', (When the world is busy working out collisions).
Try flagging the object as ready for deletion, and deleting it at the start of your next loop:
Replace:
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
with
ball1.isDead = YES;
And at the start of your next game loop:
for (Ball b in balls)
{
if (b.isDead)
world->DestroyBody(b.ballBody);
}