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!
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...
Related
I'm building a game with Cocos2d-x version 3.13.1 and I've decided to go with the built-in physics engine (Chipmunk 2D) to accomplish animations and collision detection. I have a simple projectile called BulletUnit that inherits from cocos2d::Node. It has a child sprite that displays artwork, and a rectangular physics body with the same dimensions as the artwork.
The BulletUnit has a method called fireAtPoint, which determines the angle between itself and the point specified, then sets the initial velocity based on the angle. On each update cycle, acceleration is applied to the projectile. This is done by applying impulses to the body based on an acceleration variable and the angle calculated in fireAtPoint. Here's the code:
bool BulletUnit::init() {
if (!Unit::init()) return false;
displaySprite_ = Sprite::createWithSpriteFrameName(frameName_);
this->addChild(displaySprite_);
auto physicsBody = PhysicsBody::createBox(displaySprite_->getContentSize());
physicsBody->setCollisionBitmask(0);
this->setPhysicsBody(physicsBody);
return true;
}
void BulletUnit::update(float dt) {
auto mass = this->getPhysicsBody()->getMass();
this->getPhysicsBody()->applyImpulse({
acceleration_ * mass * cosf(angle_),
acceleration_ * mass * sinf(angle_)
});
}
void BulletUnit::fireAtPoint(const Point &point) {
angle_ = Trig::angleBetweenPoints(this->getPosition(), point);
auto physicsBody = this->getPhysicsBody();
physicsBody->setVelocityLimit(maxSpeed_);
physicsBody->setVelocity({
startingSpeed_ * cosf(angle_),
startingSpeed_ * sinf(angle_)
});
}
This works exactly as I want it to. You can see in the image below, my bullets are accelerating as planned and traveling directly towards my mouse clicks.
But, there's one obvious flaw: the bullet is remaining flat instead of rotating to "point" towards the target. So, I adjust fireAtPoint to apply a rotation to the node. Here's the updated method:
void BulletUnit::fireAtPoint(const Point &point) {
angle_ = Trig::angleBetweenPoints(this->getPosition(), point);
// This rotates the node to make it point towards the target
this->setRotation(angle_ * -180.0f/M_PI);
auto physicsBody = this->getPhysicsBody();
physicsBody->setVelocityLimit(maxSpeed_);
physicsBody->setVelocity({
startingSpeed_ * cosf(angle_),
startingSpeed_ * sinf(angle_)
});
}
This almost works. The bullet is pointing in the right direction, but the trajectory is now way off and seems to be arcing away from the target as a result of the rotation: the more drastic the rotation, the more drastic the arcing. The following image illustrates what's happening:
So, it seems that setting the rotation is causing the physics engine to behave in a way I hadn't originally expected. I've been racking my brain on ways to correct the flight path, but so far, no luck! Any suggestions would be greatly apprecitated. Thanks!
In my scene class I've overridden the update function and I call scheduleUpdate() in the onEnter() method as suggested here. So in the update function I call the update of my layer class which should update my sprites:
void View::update(float dt)
{
world->Step(dt, 10, 10);
for(b2Body *b = world->GetBodyList(); b; b=b->GetNext())
{
if(b->GetUserData() != NULL && b->GetType() != b2_kinematicBody))
{
cocos2d::CCSprite *data = (cocos2d::CCSprite*)b->GetUserData();
data->setPosition(ccp(b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO));
data->setRotation(-1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
}
}
}
At the moment, the only movement is falling down because of gravity. However, the character moves so fast. It almost instantaneously hits the floor. Is there a way to slow this down?
When I used this tutorial for objective c, the ball was falling down much slower.
Did you correctly set the mass of your character? Try to reduce it. Also I recommend you to use a small wrapper which makes life with box2d and cocos2d-x much easier.
Found the problem. I set the position of my sprite to the center of my screen, but I set the position of the body to the bottom. So in the first frame the sprite immediately appeared on the bottom side.
So if anyone has the same problem: check the position of your body. Not just the sprite's position.
I want to build a platform game with cocos2d/Box2D. I use CCFollow to follow the player sprite but CCFollow constantly puts it in the center of the screen. I want CCFollow to follow more naturally, like a human turning a camcorder with an acceptable lag, a small overshoot ...etc.
Here is a method of mine that didn't work: I attached (via a distance joint) a small physics body to the player that doesn't collide with anything, represented by a transparent sprite. I CCFollow'ed the transparent sprite. I was hoping this ghost body would act like a balloon attached to the player, hence a smooth shift in view. The problem is distance joint breaks with too heavy - too light objects. The balloon moves around randomly, and of course, it pulls the player back a little no matter how light it is.
What is a better way of following a moving sprite smoothly?
Try add this to CCActions in cocos2d libs.
-(void) step:(ccTime) dt
{
#define CLAMP(x,y,z) MIN(MAX(x,y),z)
CGPoint pos;
if(boundarySet)
{
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(boundaryFullyCovered) return;
CGPoint tempPos = ccpSub(halfScreenSize, followedNode_.position);
pos = ccp(CLAMP(tempPos.x,leftBoundary,rightBoundary), CLAMP(tempPos.y,bottomBoundary,topBoundary));
}
else {
// pos = ccpSub( halfScreenSize, followedNode_.position );
CCNode *n = (CCNode*)target_;
float s = n.scale;
pos = ccpSub( halfScreenSize, followedNode_.position );
pos.x *= s;
pos.y *= s;
}
CGPoint moveVect;
CGPoint oldPos = [target_ position];
double dist = ccpDistance(pos, oldPos);
if (dist > 1){
moveVect = ccpMult(ccpSub(pos,oldPos),0.05); //0.05 is the smooth constant.
oldPos = ccpAdd(oldPos, moveVect);
[target_ setPosition:oldPos];
}
#undef CLAMP
}
i get this from cocos2d forums.
Perhaps this http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:actions_ease can help you get an "acceleration" effect with CCFollow.
I've got a problem with my current project.
What I'd like to do is make a b2Body move up and down repeatedly. I already know how to do this with a CCSprite:
[paddle runAction:[CCRepeatForever actionWithAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:1.0 position:ccp([paddle position].x,[paddle position].y+40)],
[CCMoveTo actionWithDuration:1.0 position:ccp([paddle position].x,[paddle position].y)],
nil
]]];
Can anybody help me do the same thing with a b2Body?
Thanks in advance!
You will have to implement the sequence yourself, which involves:
keeping track of the current target position
from the current position of the body, detect whether it has reached the target
if it has, change the target position
apply force, impulse or set velocity as necessary to move the body
You might be able to extend CCMoveTo to make your own class to do this... I would look into that first.
i've got it dude,
in almost explanation, every CCsprite move depend on b2body movement -that movement is placed at 'tick' method-.
in my case, i reverse that way, i move b2body according to CCsprite movement on tick method, so i give these code on tick method:
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *)b->GetUserData();
if (sprite.tag == 4 || sprite.tag == 5) {
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);
}
}
}
I have one question when infinite background scrolling is done, is the object remain fixed(like doodle in doodle jump, papy in papi jump) or these object really moves.Is only background move or both (background and object )move.plz someone help me.I am searching for this solution for 4/5 days,but can't get the solution.So plz someone help me. And if object does not move how to create such a illusion of object moving.
If you add the object to the same layer as the scrolling background, then it will scroll as the background scrolls.
If your looking for an effect like the hero in doodle jump, you may want to look at having two or more layers in a scene.
Layer 1: Scrolling Background Layer
Layer 2: Sprite layer
SomeScene.m
CCLayer *backgroundLayer = [[CCLayer alloc] init];
CCLayer *spriteLayer= [[CCLayer alloc] init];
[self addChild:backgroundLayer z:0];
[self addChild:spriteLayer z:1];
//Hero stays in one spot regardless of background scrolling.
CCSprite *squidHero = [[CCSprite alloc] initWithFile:#"squid.png"];
[spriteLayer addChild:squidHero];
If you want objects to scroll with the background add it to the background layer:
//Platform moves with background.
CCSprite *bouncePlatform= [[CCSprite alloc] initWithFile:#"bouncePlatform.png"];
[backgroundLayer addChild:bouncePlatform];
Another alternative is to use a CCFollow action. You would code as if the background is static (which it will be) and the player is moving (which it will be), but add a CCFollow action to the player. This essentially moves the camera so that it tracks your player.
You can also modify the classes so that you can get the CCFollow action to follow with an offset (i.e., so the player is not in the middle of the screen) as well as to have a smoothing effect to it, so that when the player moves, the follow action is not jerky. See the below code:
*NOTE I am using cocos2d-x, the c++ port. The methods are similar in cocos2d, and you should be able to modify these to fit the cocos2d syntax. Or search around -- I found these for cocos2d and then ported to c++.
//defines the action to constantly follow the player (in my case, "runner.p_sprite is the sprite pointing to the player)
FollowWithOffset* followAction = FollowWithOffset::create(runner.p_sprite, CCRectZero);
runAction(followAction);
And separately, I have copied the class definition for CCFollow to create my own class, CCFollowWithAction. This also has a smoothing effect (you can look this up more online) so that when the player moves, the actions are not jerky. I modified "initWithTarget," to take into account an offset, and "step," to add a smoothing action. You can see the modifications in the comments below.
bool FollowWithOffset::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCAssert(pFollowedNode != NULL, "");
pFollowedNode->retain();
m_pobFollowedNode = pFollowedNode;
if (rect.equals(CCRectZero))
{
m_bBoundarySet = false;
}
else
{
m_bBoundarySet = true;
}
m_bBoundaryFullyCovered = false;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);
//m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f);
m_obHalfScreenSize = CCPointMake(m_obFullScreenSize.x/2 + RUNNER_FOLLOW_OFFSET_X,
m_obFullScreenSize.y/2 + RUNNER_FOLLOW_OFFSET_Y);
if (m_bBoundarySet)
{
m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x);
m_fRightBoundary = -rect.origin.x ;
m_fTopBoundary = -rect.origin.y;
m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y);
if(m_fRightBoundary < m_fLeftBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;
}
if(m_fTopBoundary < m_fBottomBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2;
}
if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) )
{
m_bBoundaryFullyCovered = true;
}
}
return true;
}
void FollowWithOffset::step(float dt)
{
CC_UNUSED_PARAM(dt);
if(m_bBoundarySet){
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(m_bBoundaryFullyCovered)
return;
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary),
clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary)));
}
else{
//custom written code to add in support for a smooth ccfollow action
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
CCPoint moveVect = ccpMult(ccpSub(tempPos,m_pTarget->getPosition()),0.25); //0.25 is the smooth constant.
CCPoint newPos = ccpAdd(m_pTarget->getPosition(), moveVect);
m_pTarget->setPosition(newPos);
}
}