5
votes

In cocos2d, you can ease in CCSprites and move them around in all kinds of ways. Most importantly - they can have easing in/out. For most games this is desirable for smooth movement etc.

id action = [CCMoveTo actionWithDuration:dur position:pos];
move = [CCEaseInOut actionWithAction:action rate:2];
[self runAction: move];

When moving a box2d body, the sprite attached to it is updated after the box2d step(). Moving the sprite and then updating the body is not an option here, as it entirely defeats the purpose of the physics framework.

So the other option, which I have successfully implemented, is to calculate the displacement, velocity and acceleration of a sprite by treating it as a mechanics entity in its own right. Each time I call my update() on the sprite so the character can decide where to move etc, my superclass also stores the previous position and velocity. These are stored as box2d compliant values by dividing by the PTM_RATIO.

In the subclass of CCSprite, called FMSprite:

-(CGPoint) displacement {
    return ccpSub(self.position, lastPos);
}

-(b2Vec2) getSpriteVelocity:(ccTime)dt {
    return b2Vec2(self.displacement.x / dt / PTM_RATIO,
                  self.displacement.y / dt / PTM_RATIO);
}

-(b2Vec2) getSpriteAccel:(ccTime)dt {
    b2Vec2 currVel = [self getSpriteVelocity:dt];
    if (dt == 0) {
        return b2Vec2(0,0);
    } else {    
        float accelX = (currVel.x - lastVel.x)/dt;
        float accelY = (currVel.y - lastVel.y)/dt;
        return b2Vec2(accelX, accelY);
    }
}

// This is called each update()
-(void) updateLast:(ccTime)dt {
    // MUST store lastVel before lastPos is updated since it uses displacement
    lastVel = [self getSpriteVelocity:dt];
    lastPos = ccp(self.X, self.Y);
}

// Leave this method untouched in subclasses
-(void) update:(ccTime)dt {
    [self updateObject:dt];

    // Store previous update values
    [self updateLast:dt];
}

// Override this method in subclasses for custom functionality
-(void) updateObject:(ccTime)dt {

}

I have then subclassed "FMSprite" into "FMObject", which stores a b2Body etc.

In order to move the body, I must first move a sprite and track its acceleration, through which I can find the required force (using the mass) needed to follow the sprite's motion. Since I can't move the object's sprite (which is synchronized to the body), I make another sprite called a "beacon", add it as a child to the object, and move it around. All we need to do then is to have a function to synchronize the position of the box2d body with this beacon sprite using the forces I mentioned before.

-(void) followBeaconWithDelta:(ccTime)dt {
    float forceX = [beacon getSpriteAccel:dt].x * self.mass;
    float forceY = [beacon getSpriteAccel:dt].y * self.mass;
    [self addForce:b2Vec2(forceX, forceY)];
}

The result is brilliant, a smooth easing motion of the b2body moving where ever you want it to, without playing around with any of its own forces, but rather copying that of a CCSprite and replicating its motion. Since it's all forces, it won't cause jittering and distortions when colliding with other b2Body objects. If anyone has any other methods to do this, please post an answer. Thanks!

1
Hi, Thanks for this post. I haven't understood one thing though. Why do you need a beacon sprite? if an object of type FMObject(which is a CCSprite) is being moved using some custom action, why not make the b2Body in FMObject follow the parent sprite. I'm not sure what 'synchroinized to the body' means. Why do you separately need a beacon sprite and move it around?Aks
It's been nearly 3 years since I posted this, but I think you may be right. I can't see the reason for having a beacon sprite. I think that was just an implementation detail. You should be able to substitute the FMObject itself.Aram Kocharyan

1 Answers

4
votes

What I do is different from yours, but can also Moving Box2d Bodies Like CCSprite Objects and even use the CCAction. The most important thing is to create an object that contain ccSprite and b2body.

@interface RigidBody : CCNode {
    b2Body *m_Body;
    CCSprite *m_Sprite;
}

And then, rewrite the setPosition method.

-(void)setPosition:(CGPoint)position
{
    CGPoint currentPosition = position_;
    b2Transform transform = self.body->GetTransform();
    b2Vec2 p = transform.p;
    float32 angle = self.body->GetAngle();
    p += [CCMethod toMeter:ccpSub(position, currentPosition)];
    self.body->SetTransform(p, angle);  
    position_ = position;
}

The setPosition method calculate how much the position change,and set it to the b2body.

I hope I have understanding your question and the answer is helpful for you...