How to detect all the visible sprites in a layer? - cocos2d-iphone

Can I detect all the visible sprites in a layer in order to use them.

Here i assume u r using Box2d with cocos2d
//here create ccsprite and add them to ur scene
while create body with bodydef
bodydef.userData=ccspriteobject;
for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL) {
//if the userdata is not null then ur sprite
CCSprite *actor = (CCSprite*)b->GetUserData();

Here is a small function you can use:
-(void)getAllChildren {
CCArray *childrenArray = [self children];
for(CCNode *myNode in childrenArray)
{
}
}

Related

Collision with sprite having another sprite as parent

Seems impossible but there has to be a solution.
I have the following classes:
#interface EnemiesEntities : CCSprite {
bool isFunctional;
CCSprite * laserBeam; // <----------- !!!!! That's where I want to check the collision.
CCSprite * leftRingEffect;
CCSprite * rightRingEffect;
}
#interface ShipEntity : CCSprite
{}
And I simply want to verify the collision between the ShipEntity and the laserBeam sprite (laserBeam is a member variable and child of EnemiesEntities class).
The method [laserBeam boundingBox] doesn't work as the boundingBox converts the coordinates relative to the parent node.
I tried thend adding to CCNode a method computing the boundingBox relative to the world but also this one did not work:
- (CGRect) worldBoundingBox
{
CGRect rect = CGRectMake(0, 0, contentSize_.width, contentSize_.height);
return CGRectApplyAffineTransform(rect, [self nodeToWorldTransform]);
}
I checked online and found only unuseful (to me) answers to the same question.
I then tried a different approach and tried to start from the boudningBox and change the position of the rectangle so obtained in respect to the parent position as following:
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
[laserBeam worldBoundingBox];
CGRect laserBoundingBox = [laserBeam boundingBox];
CGRect laserBox = CGRectMake(laserBeam.parent.position.x, laserBeam.parent.position.y, laserBoundingBox.size.width, laserBoundingBox.size.height);
CGRect hitBox = [self hitBox];
if(CGRectIntersectsRect([self boundingBox], laserBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}
Unfortunately this does work only when the rotation of the parent sprite is set to 0.0 but when it actually changes then it doesn't work (is probably because the boundingBox is relative to the parent node and not world).
I am a bit lost and was wondering if any of you had better luck in solving this problem and which solution (code snippets please :)) you used.
EDIT in Response to #LearnCocos2D answer:
I followed the suggestion and added the following code which doesn't work properly (e.g. try with an EnemiesEntities object is rotated to -130.0f).
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CCLOG(#"rotation %f", laserBeam.rotation);
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = [self convertToWorldSpace:laserBeam.position];
CGRect shipBoundingBox = [self boundingBox]; //As we are in ShipEntity class
shipBoundingBox.origin = [self convertToWorldSpace:shipBoundingBox.origin];
//As this method is in the ShipEntity class there is no need to convert the origin to the world space. I added a breakpoint here and doing in this way the CGRect of both ShipEntity and gets misplaced.
if(CGRectIntersectsRect(shipBoundingBox, laserBoundingBox))
{
return TRUE;
}
else {
return FALSE;
}
}
The problem is in this line I think:
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
laserBeam isn't in laserBeam's space but laserBeams parent space. So the correct is:
CGPoint newPosition = [[laserBeam parent] convertToWorldSpace:laserBeam.position];
The whole code
-(BOOL) collidesWithLaser:(CCSprite*)laserBeam
{
CGPoint newPosition = [laserBeam convertToWorldSpace:laserBeam.position];
CGRect laserBoundingBox = [laserBeam boundingBox];
laserBoundingBox.origin = newPosition;
CGRect hitBox = [self boundingBox];
hitbox.origin = [[self parent] convertToWorldSpace:hitbox.origin];
if(CGRectIntersectsRect(hitbox, laserBoundingBox))
{
laserBeam.showCollisionBox=TRUE;
return TRUE;
}
else {
return FALSE;
}
}
for both boundingboxes do:
bbox.origin = [self convertToWorldSpace:bbox.origin];
now you can compare the rects...
Update to update:
The boundingBox is an axis-aligned bounding box.
If the entity is rotated, the bounding box size increases to encompass all of the sprite's corners. Therefore collision (intersection) may be detected even relatively far away from the node when testing axis-aligned bounding boxes.
In ccConfig.h there's an option you can turn on to draw sprite bounding boxes, you should set this to 1 to see the bounding boxes: #define CC_SPRITE_DEBUG_DRAW 1
For oriented rectangles you need a different data structure and different intersection test, see for example this tutorial.

How to get b2Body of current CCSprite

In my code i've always used spriteA = (__bridge CCSprite *) bodyA->GetUserData(); //where spriteA is a CCSprite and bodyA is a b2Body. I use it to get whatever sprite is linked to bodyA. My problem is, how do I do this the other way around? I have a sprite and I want to find out what b2Body is linked to it. How do I do this?
Edit
I don't know wether I set it up right or not, but I'm trying to remove all b2bodies (and sprites) in an array called row4 once there are no more blue objects (objects in row4BlueArray)
Here is part of the code in my tick method:
//Find the sprite for the b2Bodies
else if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
spriteA = (__bridge CCSprite *) bodyA->GetUserData();
spriteB = (__bridge CCSprite *) bodyB->GetUserData();
contactPositionX = spriteA.position.x;
contactPositionY = spriteB.position.y;
//If sprite is a member of row 4 (tag 400)
if (spriteA.tag == 400 && spriteB.tag == 8)
{
[self createExplosionBlue];
[self addTileScore];
[self removeChild:spriteA cleanup:YES];
[self removeChild:spriteB cleanup:YES];
NSLog(#"row 4 count: %d",row4BlueArray.count);
//Remove object from another array
[row4BlueArray removeLastObject];
toDestroy.insert(bodyA);
toDestroy.insert(bodyB);
[self unschedule:#selector(tick:)];
ballCount = 0;
//if that array is empty, then remove all objects from this array (row4)
if (row4BlueArray.count == 0) {
for (b2Body * b = _world->GetBodyList(); b != NULL; b = b->GetNext()) {
Box2DSprite * sprite = (__bridge Box2DSprite*) b->GetUserData();
b2Body * spriteBody = sprite.body;
//not sure how to remove all bodies in an array (row4)`
}
}
}
One simple way to do this is to cache the body as a property of a class.
You could extend the CCSprite class with a class called Box2DSprite. In Box2DSprite, you include a property for the b2Body * you used to create the physics for your sprite. You can also store a reference to the world if you think you may need it.
#interface Box2DSprite : CCSprite {
b2Body * body;
b2World * world;
} // end ivars
#property (readwrite) b2Body * body;
#property (readwrite) b2World * world;
Also remember to rename your .m file to a .mm file
Edit
In your game loop, to obtain the Box2DSprite you simply need to cast it back to a Box2DSprite.
for (b2Body * b = world->GetBodyList(); b != NULL; b = b->GetNext()) {
Box2DSprite * sprite = (__bridge Box2DSprite*) b->GetUserData();
b2Body * spriteBody = sprite.body;
// Do stuff with body here
}
You just need to ensure that whenever you create a sprite with a physics body, you are creating a Box2DSprite.
Hope this helps

Cocos2d Box2d - assign generic userData to bodyDef

Most examples I see of assigning userData go something like this:
CCSprite *sprite = [CCSprite spriteWithFile:#"whatever.png" rect:CGRectMake(0, 0, screenSize.width, screenSize.height)];
sprite.tag = kWallTag;
[self addChild:sprite];
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
groundBodyDef.userData = (__bridge void*)sprite;
That's fine if you're using a sprite. But in my case, I don't want to create a sprite because I just want to test collisions with the screen edges. I could create a sprite the size of the screen with just a border but I don't want to use that much texture memory just for detecting walls. So my question is how to assign the kWallTag to the groundBodyDef, without assigning it a sprite. And how would I retrieve the tag value?
I've answered the first part:
GenericUserData *usrData = (GenericUserData*)malloc(sizeof(GenericUserData));
usrData->tag = kWallTag;
groundBodyDef.userData = usrData;
But I don't know how to test for the generic data:
if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
CCSprite *spriteA = (__bridge CCSprite *) bodyA->GetUserData();
CCSprite *spriteB = (__bridge CCSprite *) bodyB->GetUserData();
How do I test for generic user data instead of just assuming that it's a CCSprite?

expand a testPoint area in cocos2d when detecting a touch

i have many sprites in my game,which all have bodies in a b2world. and in order to detect a touch i do the next:
currentPosition = [[CCDirector sharedDirector] convertToGL: currentPosition];
b2Vec2 locationWorld = b2Vec2(currentPosition.x/PTM_RATIO, currentPosition.y/PTM_RATIO);
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
b2Fixture *bf1 = b->GetFixtureList();
if (bf1->TestPoint(locationWorld) )
{
CCSprite *tempSprite = (CCSprite *) b->GetUserData();
if (tempSprite.tag==2 )
{
now , because my sprite's body is too small,and he is moving, it is very hard to touch it while it moves, so i need to change this code, in order to detect a wide area around this sprite also.
how do i expand the testpoint to be +- more 50pixels ??
thanks a lot.
You could attach a larger fixture to the body and set the sensor flag of the fixture to true. The sensor fixture won't change any of the physics, but you can check if a point falls within its boundaries.
You can create a sensor fixture like this (from SensorTest.h):
b2CircleShape shape;
shape.m_radius = 5.0f;
shape.m_p.Set(0.0f, 10.0f);
b2FixtureDef fd;
fd.shape = &shape;
fd.isSensor = true;
body->CreateFixture(&fd);
See the Box2D manual, section 6.3 (PDF), and SensorTest.h included in the Testbed.

Box2d + CoCos2d: Moving an Object automatically to simulate computer movement in a game

I am working on a hockey game and I am implementing Single Player mode. I am trying to move the "computer's" paddle in offense mode (move towards the ball). I am using CoCos2d and Box2d. I am moving the paddle using MouseJoints. The problem is the Paddle doesnt move at all!
tick is called in init method
[self schedule:#selector(tick:)];
...
- (void)tick:(ccTime) dt
{
_world->Step(dt, 10, 10);
CCSprite *computer_paddle;
CCSprite *ball;
b2Body *computer_paddle_b2Body;
float32 direction;
b2Vec2 velocity;
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *)b->GetUserData();
if (sprite.tag == 1) { //ball
ball = sprite;
static int maxSpeed = 10;
velocity = b->GetLinearVelocity();
float32 speed = velocity.Length();
direction = velocity.y;
if (speed > maxSpeed) {
b->SetLinearDamping(0.5);
} else if (speed < maxSpeed) {
b->SetLinearDamping(0.0);
}
}
if (sprite.tag == 3){ // paddle
computer_paddle = sprite;
computer_paddle_b2Body = b;
}
// update sprite position
sprite.position = ccp(b->GetPosition().x * PTM_RATIO,b->GetPosition().y * PTM_RATIO);
sprite.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
// update the computer paddle in single player moving it towards the ball using MouseJoint
//move towards the ball
b2Vec2 b2Position = b2Vec2(ball.position.x/PTM_RATIO,ball.position.y/PTM_RATIO);
b2MouseJointDef md1;
md1.bodyA = _groundBody;
md1.bodyB = computer_paddle_b2Body;
md1.target = b2Position;
md1.collideConnected = true;
md1.maxForce = 9999.0 * computer_paddle_b2Body->GetMass();
_mouseJoint = (b2MouseJoint *)_world->CreateJoint(&md1);
computer_paddle_b2Body->SetAwake(true);
Check whether:
a) the body is sleeping
b) the body is a static body
If it is sleeping and you have no other bodies, disable sleeping entirely. Otherwise disable sleeping of the body: body->SetSleepingAllowed(NO)
Note: this is according to Box2D 2.2.1 API Reference which isn't the default in cocos2d 1.0 yet, so the function may be different in your Box2D version.
Also check that your body is dynamic by looking up the b2BodyDef's type member and, if necessary, set it to be dynamic (see the b2BodyType enumeration). I'm not sure what the default is, it should be dynamic but this may depend on the Box2D version.