问题
I am trying to adjust the position and rotation of a sprite based on that of a b2body in box2d.
After I have created the body im setting the userData property to that of my body object that holds the sprite and position etc. The problem is that in the tick method b->GetUserData never retrieves the object I put in there. Can you see anything wrong with the following?
Here is my add player method:
-(void) addPlayerShip
{
CCSpriteSheet *sheet = [CCSpriteSheet spriteSheetWithFile:@"PlayerShip.png" capacity:1];
//Get sprite sheet
CCSprite *sprite = [CCSprite spriteWithSpriteSheet:sheet rect:CGRectMake(0,0,64,64)];
[sheet addChild:sprite];
CGSize screenSize = [CCDirector sharedDirector].winSize;
sprite.position = ccp( screenSize.width/2, screenSize.height/2);
[self addChild:sheet];
// Define the dynamic body.
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(sprite.position.x/PTM_RATIO, sprite.position.y /PTM_RATIO);
bodyDef.angularVelocity=0;
b2Body *body = world->CreateBody(&bodyDef);
b2PolygonShape dynamicTriangle;
dynamicTriangle.m_vertexCount=3;
dynamicTriangle.m_vertices[0].Set(0,-1);
dynamicTriangle.m_vertices[1].Set(1,1);
dynamicTriangle.m_vertices[2].Set(-1,1);
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicTriangle;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.0f;
fixtureDef.restitution=0.0f;
b2Fixture *fixture = body->CreateFixture(&fixtureDef);
b2Vec2 *vector = new b2Vec2;
BodyObject *bodyObject = [[BodyObject alloc] initWithBody:body
andFixture:fixture
andVelocity:vector
andSprite:sprite];
bodyDef.userData = bodyObject;
}
and the tick method that is always returning null on b->GetUserData
-(void) tick: (ccTime) dt
{
int32 velocityIterations = 8;
int32 positionIterations = 1;
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 Sprites position and rotation with the corresponding body
GameObject *myObject = (GameObject*)b->GetUserData();
myObject.Sprite.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
myObject.Sprite.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
and finally the bodyObject
#import "BodyObject.h"
@implementation BodyObject
@synthesize Body;
@synthesize Fixture;
-(id) initWithBody:(b2Body*) body
andFixture:(b2Fixture*) fixture
andVelocity:(b2Vec2*) velocity
andSprite:(CCSprite*) sprite
{
self = [super initWithSprite:(CCSprite*)sprite
andVelocity:(b2Vec2*)velocity];
self.Body=body;
self.Fixture=fixture;
return self;
}
@end
im new to objective c so if im doing anything majorly wrong please let me know!
EDIT: I have tried another bit of code here and i seem to be able to get a bit further.
if I set bodyDef.UserData = sprite;
just after the line
bodyDef.position.Set(sprite.position.x/PTM_RATIO, sprite.position.y /PTM_RATIO);
then GetUserData returns the sprite. if however I set the user data to the sprite at the end of the addPlayerShip method then it returnns null again. Is the sprite / body object being disposed of somewhere?
回答1:
You are assigning the userdata to the bodyDef AFTER you call CreateBody(), so it is never being added to the actual body.
b2BodyDef is a struct that holds settings to create a body. Once you've called CreateBody(), changes to this struct have no effect.
You found the solution yourself by moving the bodyDef.UserData = sprite; line somewhere before CreateBody() is called. This is the correct place to put it. If you think you need it at the end of the function for some reason (other variables?), then you need to refactor your code so that this is not the case.
I would change this line (and the BodyObject class):
BodyObject *bodyObject = [[BodyObject alloc] initWithBody:body
andFixture:fixture
andVelocity:vector
andSprite:sprite];
so that you can instantiate it without needing the box2d elements (i.e. before they have been defined), then use the properties to assign these values after they have been created. This way you can pass this object as the body's userdata, create the body, then assign the b2Body* using [bodyObject setBody:body];
Don't forget to ensure your Body and Fixture properties are not readonly.
The final code might look like this:
-(void) addPlayerShip
{
CCSpriteSheet *sheet = [CCSpriteSheet spriteSheetWithFile:@"PlayerShip.png" capacity:1];
//Get sprite sheet
CCSprite *sprite = [CCSprite spriteWithSpriteSheet:sheet rect:CGRectMake(0,0,64,64)];
[sheet addChild:sprite];
CGSize screenSize = [CCDirector sharedDirector].winSize;
sprite.position = ccp( screenSize.width/2, screenSize.height/2);
[self addChild:sheet];
b2Vec2 *vector = new b2Vec2;
BodyObject *bodyObject = [[BodyObject alloc] initWithSprite:sprite
andVelocity:vector];
//MOVES THE BODY DEFINITION AFTER THE BODYOBJECT DEFINITION
// Define the dynamic body.
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(sprite.position.x/PTM_RATIO, sprite.position.y /PTM_RATIO);
bodyDef.angularVelocity=0;
bodyDef.userData = bodyObject;
b2Body *body = world->CreateBody(&bodyDef);
b2PolygonShape dynamicTriangle;
dynamicTriangle.m_vertexCount=3;
dynamicTriangle.m_vertices[0].Set(0,-1);
dynamicTriangle.m_vertices[1].Set(1,1);
dynamicTriangle.m_vertices[2].Set(-1,1);
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicTriangle;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.0f;
fixtureDef.restitution=0.0f;
b2Fixture *fixture = body->CreateFixture(&fixtureDef);
[bodyObject setBody:body];
[bodyObject setFixture:fixture];
}
The bodyObject class would then simply look like this:
#import "BodyObject.h"
@implementation BodyObject
@synthesize Body;
@synthesize Fixture;
-(id) initWithSprite:(CCSprite*) sprite
andVelocity:(b2Vec2*) velocity
{
self = [super initWithSprite:(CCSprite*)sprite
andVelocity:(b2Vec2*)velocity];
return self;
}
@end
回答2:
If anyone has the same problem that GetUserData() returns null, even though you SetUserData. Then don't make my mistake and assume than b2World.QueryShape() gives bodies in callback. Nope, it gives you Fixtures. You have to fixture.GetBody().GetUserData() (my case was: box2dweb in javascript; this is the question I googled for answer, I assume others will come this way)
来源:https://stackoverflow.com/questions/5071470/box2d-b2body-getuserdata-always-returns-null