In my Box2d game I found out that I must update my CCSprite position in the game loop so it works properly. I have looked through the Box2D docs to see what APIs there are to get the b2Body coordinates but I didn't see anything and this seems unclear.
Should I be moving each CCSprite manually or is there an easy way to move all CCSprite on screen to the b2Bodys associated with them?
Does anyone have any idea on how to do this?
Thanks!
Edit1:
Would this code just update every sprite in my world to the b2Body corresponding to them? And this will work with the x and y axis and rotation correct?
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);
}
}
simply do this:
In your init class schedule the tick method like this
[self schedule: #selector(tick:)];
-(void)tick: (ccTime) dt
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL)
{
CCSprite *spr= (CCSprite*)b->GetUserData();
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());
}
}
Related
I am in the process of making a game and I need an object to move only when the buttons are pressed. I have a method that begins the movement, and so far I am ending the movement of an object by having destroying the body of the object. The problem that I have is that I can't move the object again since the program will now crash. I'm wondering if there is a way to recreate the body once its destroyed since my current method of checking if the body still exists isn't working.
Here is my code.
NSMutableArray *spaceObjectsArray;
#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;
}
-(void)gameLogic:(ccTime)delta
{
[self addSpaceObjects];
}
-(void) addSpaceObjects
{
_spaceObject = [CCSprite spriteWithFile:#"blueDot.jpg"];
//create spaceObject body
b2BodyDef spaceObjectbBodyDef;
spaceObjectbBodyDef.type=b2_dynamicBody;
spaceObjectbBodyDef.userData = _spaceObject;
//make the location of spaceObject
CGSize winSize = [CCDirector sharedDirector].winSize;
int minX= _spaceObject.contentSize.width/2;
int maxX = winSize.width - _spaceObject.contentSize.width/2;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
_spaceObject.position = ccp(actualX, winSize.height + _ship.contentSize.height);
spaceObjectbBodyDef.position.Set(actualX/PTM_RATIO, (winSize.height+_ship.contentSize.height)/PTM_RATIO);
_spaceObjectBody= _world->CreateBody(&spaceObjectbBodyDef);
//create spaceObject shape
b2PolygonShape spaceObjectShape;
spaceObjectShape.SetAsBox(_spaceObject.contentSize.width/PTM_RATIO/2, _spaceObject.contentSize.height/PTM_RATIO/2);
//create spaceObject fixture
b2FixtureDef spaceObjectShapeDef;
spaceObjectShapeDef.shape= &spaceObjectShape;
spaceObjectShapeDef.density = 2;
spaceObjectShapeDef.restitution =0;
spaceObjectShapeDef.friction=0;
_spaceObjectFixture = _spaceObjectBody->CreateFixture(&spaceObjectShapeDef);
[self addChild:_spaceObject];
_spaceObject.tag=1;
[spaceObjectsArray addObject:_spaceObject];
//aply force on the object
int randomValue = ((arc4random() % 5) *-1);
b2Vec2 force = b2Vec2(0,randomValue);
_spaceObjectBody ->ApplyLinearImpulse(force, _spaceObjectBody->GetPosition());
}
init method that contains the body creations and definitions
-(id) init
{
if( (self=[super init])) {
CGSize s = [CCDirector sharedDirector].winSize;
//create spaceShip sprite and add it to the layer
_ship = [CCSprite spriteWithFile:#"theShip.gif" ];
_ship.position = ccp(s.width/2, 1.25*_ship.contentSize.height);
[self addChild:_ship];
//create the world
b2Vec2 gravity = b2Vec2_zero;
_world = new b2World(gravity);
//create ship body
b2BodyDef shipBodyDef;
shipBodyDef.type = b2_dynamicBody;
shipBodyDef.position.Set((s.width/2)/PTM_RATIO, (1.25*_ship.contentSize.height)/PTM_RATIO);
shipBodyDef.userData = _ship;
if(_shipBody == NULL){
_shipBody =_world->CreateBody(&shipBodyDef);
}
//create ship shape
b2PolygonShape shipShape;
shipShape.SetAsBox(_ship.contentSize.width/PTM_RATIO/2, _ship.contentSize.height/PTM_RATIO/2);
//create Ship definition and add to body
b2FixtureDef ShipShapeDef;
ShipShapeDef.shape = &shipShape;
ShipShapeDef.density = 3;
ShipShapeDef.friction =0;
ShipShapeDef.restitution =0;
_shipFixture = _shipBody->CreateFixture(&ShipShapeDef);
//make the paddles
//bottom left one
_paddle1 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomOfScreenX = 0 + _paddle1.contentSize.width/2;
int bottomOfScreenY = 0+_paddle1.contentSize.height/2;
_paddle1.position = ccp(bottomOfScreenX,bottomOfScreenY);
[self addChild:_paddle1];
//bottom right one
_paddle2 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomRightOfScreenX = s.width - _paddle2.contentSize.width/2;
_paddle2.position = ccp(bottomRightOfScreenX, bottomOfScreenY);
[self addChild:_paddle2];
//continuously spawn spaceObjects
[self schedule:#selector(gameLogic:) interval:1];
// enable events
self.touchEnabled = YES;
// init physics
[self schedule:#selector(tick:)];
}
return self;
}
-(void)tick:(ccTime) delta
{//this method is to simulate physics and to test for the position of where objects should be if force has been applied to them
_world->Step(delta, 8, 8);
for (b2Body *b=_world->GetBodyList(); b; b=b->GetNext()){
if (b->GetUserData() != NULL){
CCSprite *shipData = (CCSprite *)b->GetUserData();
shipData.position = ccp(b->GetPosition().x *PTM_RATIO, b->GetPosition().y *PTM_RATIO);
}
}
}
The paddles to move the ship are touched logic
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Set up a way for touches to be turned into locations onscreen
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location ];
//check to see if Left Paddle is being pressed
if (CGRectContainsPoint([_paddle1 boundingBox], location)){
b2Vec2 force = b2Vec2(-5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody ->GetPosition());
}
if (CGRectContainsPoint([_paddle2 boundingBox], location)){
b2Vec2 force = b2Vec2(5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody->GetPosition());
}
}
The paddle box is no longer being touched logic
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_world->DestroyBody(_shipBody);
}
-(void) dealloc
{
delete _world;
_world = NULL;
[super dealloc];
}
#end
Destroying the body to end the movement of it is not the best solution. You should only remove the body if you really don't want it to be part of the simulation any more.
There are several options for stopping the body's movement:
1 - Set its linear velocity to 0. This will bring it to an immediate stop. If something else is pushing on it (e.g. contact wit body), you will have to decide what to do.
body->SetLinearVelocity(b2Vec2(0,0)));
2 - Set its linear/angular damping to 0. This will dissipate the momentum it has so it will slowly stop. The factor you use should be greater than 0. The body will come to a halt more quickly with larger values and it will be resistant to movement from other bodies (if they bump it, it will slow down and stop again). Remember to turn the linear/angular damping back to 0 when you want the body to start moving.
body->SetLinearDamping(0.2);
body->SetAngularDamping(0.2);
3 - Give it a target position to seek to and set the position as the place you want it to be. This is basically a feedback control loop where you are applying a force to get it to move towards where you want the body to stay. It can be used to make a body follow paths, etc. The code below is part of a larger code base (you can see it here), but you should be able to get the general idea. This function applies thrust to the object so that it pushes towards a target.
void MovingEntity::ApplyThrust()
{
// Get the distance to the target.
b2Vec2 toTarget = GetTargetPos() - GetBody()->GetWorldCenter();
toTarget.Normalize();
b2Vec2 desiredVel = GetMaxSpeed()*toTarget;
b2Vec2 currentVel = GetBody()->GetLinearVelocity();
b2Vec2 thrust = GetMaxLinearAcceleration()*(desiredVel - currentVel);
GetBody()->ApplyForceToCenter(thrust);
}
I have an box2d object which i am throwing from top to bottom and i have set its speed constant but when i run it , that object has different speed sometimes and how can i make this object more smoother.
Following are some methods to show how i have created box2d world and box2d body object.
#pragma -mark Box2D World
-(void)createWorld
{
// Define the gravity vector.
b2Vec2 b_gravity;
b_gravity.Set(0.0f, -9.8f);
// Do we want to let bodies sleep?
// This will speed up the physics simulation
bool doSleep = true;
// Construct a world object, which will hold and simulate the rigid bodies.
world = new b2World(b_gravity);
world->SetAllowSleeping(doSleep);
world->SetContinuousPhysics(true);
}
-(void) createWeb
{
freeBodySprite = [CCSprite spriteWithFile:#"web1.png"];//web_ani_6_1
//freeBodySprite.position = ccp(100, 300);
[self addChild:freeBodySprite z:2 tag:6];
CGPoint startPos = CGPointMake(100, 320/1.25);
bodyDef.type = b2_staticBody;
bodyDef.position = [self toMeters:startPos];
bodyDef.userData = freeBodySprite;
float radiusInMeters = ((freeBodySprite.contentSize.width * freeBodySprite.scale/PTM_RATIO) * 0.5f);
shape.m_radius = radiusInMeters;
fixtureDef.shape = &shape;
fixtureDef.density = 0.07f;
fixtureDef.friction = 0.1f;
fixtureDef.restitution = 0.1f;
circularObstacleBody = world->CreateBody(&bodyDef);
stoneFixture = circularObstacleBody->CreateFixture(&fixtureDef);
freeBody = circularObstacleBody;
}
-(b2Vec2) toMeters:(CGPoint)point
{
return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO);
}
-(b2Body *) getBodyAtLocation:(b2Vec2) aLocation {
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
b2Fixture* bodyFixture = b->GetFixtureList();
if (bodyFixture->TestPoint(aLocation)){
return b;
}
}
return NULL;
}
-(void) tick: (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 = 3;
// 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);
//Iterate over the bodies in the physics world
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL) {
//Synchronize the AtlasSprites position and rotation with the corresponding body
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());
}
}
}
This is my touch Event where i am getting angle and speed to throw .
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//get the location of the end point of the swipe
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
//CCLOG(#"Start -> %0.f || End -> %0.f",startPoint.x,location.x);
if (freeBody) {
//[self calcAngleAndRotateObjectStartingAtPoint:startPoint endingAtPoint:location];
self.isTouchEnabled = NO;
freeBody->SetType(b2_dynamicBody);
//this is the maximum force that can be applied
const CGFloat maxForce = 20;
//get the rotation b/w the start point and the end point
CGFloat rotAngle = atan2f(location.y - startPoint.y,location.x - startPoint.x);
//the distance of the swipe if the force
CGFloat distance = ccpDistance(startPoint, location) * 0.5;
//if (distance>maxForce)
distance = maxForce;
//else
// distance = 10;
//apply force
freeBody->ApplyForce(b2Vec2(cosf(rotAngle) * distance, sinf(rotAngle) * distance), freeBody->GetPosition());
//lose the weak reference to the body for next time usage.
freeBody = nil;
}
}
This is code i am using to throw , but sometimes its speed is faster and some time slower , and i have set maxForce = 20 for constant speed.
As the comment above world->Step() dictates, you should use fixed dt. Verify that dt is fixed and world->Step() is being called at regular interval.
Finnally i have solved this problem. i changed ApplyForce with SetLinearVelocity..
here is the code.
float spd = 10;
b2Vec2 velocity = spd*b2Vec2(cos(rotAngle), sin(rotAngle));
freeBody->SetLinearVelocity(velocity);
Hello good people of stack overflow
I stand before you today with a (not very great) cocos2d issue.
How can I check collisions WHILE in an action, instead of checking the target tile?
I am working with a tilemap that has a meta layer where there are collidable tiles. The following code works when I click the collidable tile, but not when I click beyond it, if that happens he will just walk straight through.
Currently, I detect collisions with this code:
-(void)setPlayerPosition:(CGPoint)position {
CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid) {
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties) {
NSString *collision = properties[#"Collidable"];
if (collision && [collision isEqualToString:#"True"]) {
return;
}
}
}
float speed = 10;
CGPoint currentPlayerPos = _player.position;
CGPoint newPlayerPos = position;
double PlayerPosDifference = ccpDistance(currentPlayerPos, newPlayerPos) / 24;
int timeToMoveToNewPostition = PlayerPosDifference / speed;
id moveTo = [CCMoveTo actionWithDuration:timeToMoveToNewPostition position:position];
[_player runAction:moveTo];
//_player.position = position;
}
By the way, this gets called from this method:
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
[_player stopAllActions];
CGPoint touchLocation = [touch locationInView:touch.view];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
CGPoint playerPos = _player.position;
CGPoint diff = ccpSub(touchLocation, playerPos);
int diffx = diff.x;
int diffy = diff.y;
int adiffx = diffx / 24;
int adiffy = diffy / 24; //Porque my tile size is 24
if ( abs(diff.x) > abs(diff.y) ) {
if (diff.x > 0) {
playerPos.x = playerPos.x + adiffx * 24;
} else {
playerPos.x = playerPos.x + adiffx * 24;
}
} else {
if (diff.y > 0) {
playerPos.y = playerPos.y + adiffy * 24;
}else{
playerPos.y = playerPos.y + adiffy * 24;
}
}
[self setPlayerPosition:playerPos];
}
I tried
[self schedule:#selector(setPlayerPosition:) interval:0.5];
Without any luck, that just instantly crashed the app at this
NSAssert( pos.x < _layerSize.width && pos.y < _layerSize.height && pos.x >=0 && pos.y >=0, #"TMXLayer: invalid position");
And i can't really blame it for crashing there.
How do I constantly check for collisions with the collidable meta tiles while a CCMoveTo is running?
In your init, schedule a collision detecting method:
[self schedule:#selector(checkCollisions:)];
Then define it as follows:
-(void)checkCollisions:(ccTime)dt
{
CGPoint currentPlayerPos = _player.position;
CGPoint tileCoord = [self tileCoordForPosition:currentPlayerPos];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid)
{
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties)
{
NSString *collision = properties[#"Collidable"];
if (collision && [collision isEqualToString:#"True"])
{
[_player stopAllActions];
return;
}
}
}
}
I using a similar meta layer. I personally skipped using MoveTo, and created my own update code that both moves the sprite and does collision detection.
I figure out what the potential new position would be, then I find out what tile position is in all four corners of the sprite at that new position, then I cycle through all tiles that the sprite would be on and if any are collidable, I stop the sprite.
I actually go a step further and check if moving only the x, or only the y changes the results, then move to that position instead if it does.
This might sound quite straightforward. I want to keep track of a sprite body position only AFTER it has been moved by a mouseJoint so I can limit it's movement by comparing it's position (at any given time after mouseJoint is released) with a given position. Please help.
UPDATED
Here's what I did. I made a method that returns the sprite's position, which I called in the ccTouchesEnded method:
- (CGPoint)spritePositionRelease {
for(b2Body *b = mouseJoint->GetBodyB(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL)
{
CCSprite *mySprite = (CCSprite*)b->GetUserData();
if (mySprite.tag == 1) {
mySprite.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
spritePosition = mySprite.position;
CCLOG(#"the sprite position is x:%0.2f , y:%0.2f", spritePosition.x, spritePosition.y);
return spritePosition;
}
}
}
}
ccTouchesEnded:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (mouseJoint)
{
[self spritePositionRelease];
world->DestroyJoint(mouseJoint);
mouseJoint = NULL;
}
}
In the tick method I added the following code:
for(b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL)
{
CCSprite *mySprite = (CCSprite*)b->GetUserData();
if (mySprite.tag == 1) {
mySprite.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
CGPoint spriteCurrentPosition = mySprite.position;
if ( spritePosition.x != spriteCurrentPosition.x &&
spritePosition.y == spriteCurrentPosition.y) {
CCLOG(#"the sprite limit for y is y:%0.2f has been reached", spriteCurrentPosition.y);
}
}
}
}
I initialized the spritePosition in the HelloWorldLayer.h class. I know I've done something wrong. I don't think the spritePosition I am accessing in the tick method has the same value as the spritePosition in the ccTouchesEnded method, hence the condition in the tick method never gets satisfied. I am not sure how to get this corrected. Please help
You can use ccpdistance(X2 , X1); to find the distance between two points. And from that distance you can limit the range that sprite can move.
I have created a weldJoint between two sprites in my code (see update method below) and I also created a method that returns the exact position of a sprite when a mouseJoint is released. I want to compare the current position of the sprite to the spritePositionRelease value and destroy the weldJoint if the y values are the same and x values are different. Please help.
spritePositionRelease:
- (CGPoint)spritePositionRelease {
for(b2Body *b = mouseJoint->GetBodyB(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL)
{
CCSprite *mySprite = (CCSprite*)b->GetUserData();
if (mySprite.tag == 1) {
mySprite.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
CGPoint spritePosition = mySprite.position;
CCLOG(#"the sprite position is x:%0.2f , y:%0.2f", spritePosition.x, spritePosition.y);
return spritePosition;
}
}
}
}
ccTouchesEnded:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (mouseJoint)
{
[self spritePositionRelease];
world->DestroyJoint(mouseJoint);
mouseJoint = NULL;
}
}
update:
-(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);
// using the iterator pos over the set
std::set<BodyPair *>::iterator pos;
for(pos = bodiesForJoints.begin(); pos != bodiesForJoints.end(); ++pos)
{
b2WeldJoint *weldJoint;
b2WeldJointDef weldJointDef;
BodyPair *bodyPair = *pos;
b2Body *bodyA = bodyPair->bodyA;
b2Body *bodyB = bodyPair->bodyB;
weldJointDef.Initialize(bodyA,
bodyB,
bodyA->GetWorldCenter());
weldJointDef.collideConnected = false;
weldJoint = (b2WeldJoint*) world->CreateJoint(&weldJointDef);
// Free the structure we allocated earlier.
free(bodyPair);
// Remove the entry from the set.
bodiesForJoints.erase(pos);
}
}
Are you sure that mouseJoint->GetBodyB() will always return the right body? Maybe you should check mouseJoint->GetBodyA() to?
Anyway your check would be quite simple:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (mouseJoint)
{
CGPoint releasePoint = [self spritePositionRelease];
CGPoint touchPoint = [[touches anyObject] locationInView:[[touches anyObject] view]];
if((releasePoint.y==touchPoint.y) &&(releasePoint.x!=touchPoint.x))
{
//Destroy weld joint
}
world->DestroyJoint(mouseJoint);
mouseJoint = NULL;
}
}