Search code examples
cocos2d-iphonebox2dbox2d-iphonegravity

Box2d Radial Gravit Limitation coco2d


I have been tweaking with the following code to create radial gravity,

http://www.vellios.com/2010/06/06/box2d-and-radial-gravity-code/

now its working fine, but I wanted it so that for example this code only gets triggered if my current sprite is within a certain distance of the sprite,

I need some guides, I have given the world no gravity, so right now my sprites are floating, but I wanted to make like the following,

A sprite is traveling on a project path to X, there is no gravity applying to this since i have turned off gravity. If this body we will call D is within a certain limit on this path then the radial gravity code kicks into affect, other wise it continues on its projected path.

Does anyone know how to modify this code to do that, i have been mucking around with this for ages now and I can't seem to get anywhere close to creating a range and applying this code to it.

      // Import the interfaces  
    #import "HelloWorldLayer.h"

    // Needed to obtain the Navigation Controller
    #import "AppDelegate.h"

    #import "PhysicsSprite.h"

    enum {
kTagParentNode = 1,
    };

    CGPoint location;
    CGPoint nodePosition;

    #pragma mark - HelloWorldLayer

    @interface HelloWorldLayer()
    -(void) initPhysics;
    -(void) addNewSpriteAtPosition:(CGPoint)p;
    -(void) createMenu;
    @end

    @implementation HelloWorldLayer

    +(CCScene *) scene
    {
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];

// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];

// add layer as a child to scene
[scene addChild: layer];

// return the scene
return scene;
    }

    -(id) init
    {
if( (self=[super init])) {

    // enable events

    self.isTouchEnabled = YES;
    self.isAccelerometerEnabled = YES;
    CGSize s = [CCDirector sharedDirector].winSize;

    // init physics
    [self initPhysics];

    // create reset button
    [self createMenu];

    //Set up sprite

    #if 1
    // Use batch node. Faster
    CCSpriteBatchNode *parent = [CCSpriteBatchNode batchNodeWithFile:@"blocks.png"                      capacity:100];
    spriteTexture_ = [parent texture];
    #else
    // doesn't use batch node. Slower
    spriteTexture_ = [[CCTextureCache sharedTextureCache] addImage:@"blocks.png"];
    CCNode *parent = [CCNode node];
    #endif
    [self addChild:parent z:0 tag:kTagParentNode];


    [self addNewSpriteAtPosition:ccp(s.width/2 - 150, s.height/2)];


    [self scheduleUpdate];
}
return self;
            }

    -(void) dealloc
    {
delete world;
world = NULL;

delete m_debugDraw;
m_debugDraw = NULL;

[super dealloc];
    }   

    -(void) createMenu
    {
// Default font size will be 22 points.
[CCMenuItemFont setFontSize:22];

// Reset Button
CCMenuItemLabel *reset = [CCMenuItemFont itemWithString:@"Reset" block:^(id sender){
    [[CCDirector sharedDirector] replaceScene: [HelloWorldLayer scene]];
}];



CCMenu *menu = [CCMenu menuWithItems:reset, nil];

[menu alignItemsVertically];

CGSize size = [[CCDirector sharedDirector] winSize];
[menu setPosition:ccp( size.width/2, size.height/2)];


[self addChild: menu z:-1]; 
    }

    -(void) initPhysics
    {

CGSize s = [[CCDirector sharedDirector] winSize];

b2Vec2 gravity;
gravity.Set(0.0f, 0.0f);
world = new b2World(gravity);


// Do we want to let bodies sleep?
world->SetAllowSleeping(true);

world->SetContinuousPhysics(true);

m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);

uint32 flags = 0;
flags += b2Draw::e_shapeBit;
//      flags += b2Draw::e_jointBit;
//      flags += b2Draw::e_aabbBit;
//      flags += b2Draw::e_pairBit;
//      flags += b2Draw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);       


// Define the ground body.
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0); // bottom-left corner

// Call the body factory which allocates memory for the ground body
// from a pool and creates the ground box shape (also from a pool).
// The body is also added to the world.
b2Body* groundBody = world->CreateBody(&groundBodyDef);

// Define the ground box shape.
b2EdgeShape groundBox;      

// bottom

groundBox.Set(b2Vec2(0,0), b2Vec2(s.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);

// top
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO),            b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);

// left
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(0,0));
groundBody->CreateFixture(&groundBox,0);

// right
groundBox.Set(b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);

b2CircleShape shape;
shape.m_radius = 0.5f;
shape.m_p.Set(8.0f, 5.0f);
b2FixtureDef fd;
fd.shape = &shape;
planet = groundBody->CreateFixture(&fd);

    }

    -(void) draw
    {
//
// IMPORTANT:
// This is only for debug purposes
// It is recommend to disable it
//
[super draw];

ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );

kmGLPushMatrix();

world->DrawDebugData(); 

kmGLPopMatrix();
    }

    -(void) addNewSpriteAtPosition:(CGPoint)p
    {
CCLOG(@"Add sprite %0.2f x %02.f",p.x,p.y);
CCNode *parent = [self getChildByTag:kTagParentNode];

//We have a 64x64 sprite sheet with 4 different 32x32 images.  The following code is
//just randomly picking one of the images
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
PhysicsSprite *sprite = [PhysicsSprite spriteWithTexture:spriteTexture_ rect:CGRectMake(16 * idx,16 * idy,16,16)];
[parent addChild:sprite];

sprite.position = ccp( p.x, p.y);

// Define the dynamic body.
//Set up a 1m squared box in the physics world
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
b2Body *body = world->CreateBody(&bodyDef);

// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.3f, .3f);//These are mid points for our 1m box

// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox; 
fixtureDef.density = 2.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);

[sprite setPhysicsBody:body];

        b2Vec2 currentPosition = body->GetPosition();
        b2Vec2 desiredPosition;
        desiredPosition.Set(467.0f/PTM_RATIO, 169.0f/PTM_RATIO);

        b2Vec2 necessaryMovement = desiredPosition - currentPosition;
        float necessaryDistance = necessaryMovement.Length();
        necessaryMovement.Normalize();
        float forceMagnitude = b2Min(150.0f, necessaryDistance *PTM_RATIO /2);
        b2Vec2 force = forceMagnitude * necessaryMovement;
        body->ApplyForce( force, body->GetWorldCenter() );

    }

    -(void) update: (ccTime) dt
    {
//It is recommended that a fixed time step is used with Box2D for stability
//of the simulation, however, we are using a variable time step here.
//You need to make an informed choice, the following URL is useful
//http://gafferongames.com/game-physics/fix-your-timestep/

int32 velocityIterations = 8;
int32 positionIterations = 1;

// Instruct the world to perform a single step of simulation. It is
// generally best to keep the time step and iterations fixed.
world->Step(dt, velocityIterations, positionIterations);

for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
    b2Body* ground = planet->GetBody();
    b2CircleShape* circle = (b2CircleShape*)planet->GetShape();
    // Get position of our "Planet"
    b2Vec2 center = ground->GetWorldPoint(circle->m_p);
    // Get position of our current body in the iteration
    b2Vec2 position = b->GetPosition();
    // Get the distance between the two objects.
    b2Vec2 d = center - position;
    // The further away the objects are, the weaker the gravitational force is
    float force = 0.0f / d.LengthSquared(); // 150 can be changed to adjust the amount of force
    d.Normalize();
    b2Vec2 F = force * d;
    // Finally apply a force on the body in the direction of the "Planet"
    b->ApplyForce(F, position);

    if (b->GetUserData() != NULL) {
        CCSprite *myActor = (CCSprite*)b->GetUserData();
        myActor.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
        myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
    }   
}

    }

    - (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
    location = [touch locationInView: [touch view]];

    location = [[CCDirector sharedDirector] convertToGL: location];

    nodePosition = [self convertToNodeSpace: location];

    [self addNewSpriteAtPosition: location];
}
    }



    @end

Solution

  • You're not applying any force to your body.

    float force = 0.0f / d.LengthSquared();
    

    is always going to be zero no matter the value of d.

    Here's a simple formula for a linear gravity well. This is of course not how gravity really works but it might be good enough for what you want.

    If you want to apply zero force at some maximum distance maxDistance, and maximum force maxForce at zero distance, then the magnitude of force is simply:

    Force = maxForce * (maxDistance - d)/maxDistance;
    

    So when d = maxDistance the force is 0, and when d = 0 then force = maxForce.

    Like I said this is not how true gravity works because in reality the gravitational force is proportional to distance squared, so you may have to experiment if this doesn't work out for you.