Sure, there are lots of ways it can be set up and lots of ways it can freak out. You might check out how Mitch sets thing up in Unity:
viewtopic.php?f=3&t=3318
He has a cape example somewhere where the cape from the "hero-mesh" project is controlled by physics.
Spine + Physics (box2d) - with more accurate collisions
Cool,
I'll check it out.
Thanks!
If you could share with me how you created the the fixtures and joints that would be amazing. If you could also share how you did the rotations that would also be amazing. Even if it is not really generic for every scenario I believe it will help a lot just being able to see how you did it.
Thank you!
Okay,
Warning - this is messy and unoptimized, but it could help you get started. Also, being that it's disorganized code, i'll be cutting out a lot of stuff that's specific to my game. I'm not the best with words, so hopefully my explanations below are not too convoluted.
So first...
Creating Fixtures and Joints
In order to avoid creating a fixture for every slot, we decide ahead of time which slots we want to map over to the physics world. This is good for performance optimization and for avoiding very mesh-based animation slots (such as wings or cloth).
Another important note - we designate one slot as the central slot. This will match up as the body part that we primarily will steer around and to which we would apply any necessary rotation forces for rotating the entire character. In this case our central slot is named "bodBounds."
Also, make sure to do all this after you've added the skeleton to the scene and placed it in its setup pose.
vector<string> slots = // place the slot names that you want to map to box2d
map<string,b2Body*> boneBodyMap; // to temporarily keep track of slot-to-body mappings
b2Body* bodyCenter = NULL;
struct imgInfo{...} // this is a class that holds the skeleton's bodies and joints for easy access later
for( string name : slots )
{
spSlot* slot = node->findSlot(name.data());
if( slot )
{
spSlot* slot = node->findSlot(name.data());
if( slot )
{
float worldVertices[1000];
float minX = FLT_MAX, minY = FLT_MAX, maxX = -FLT_MAX, maxY = -FLT_MAX;
if (slot->attachment)
{
// Create body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position = b2Vec2(position.x,position.y);
bodyDef.gravityScale = 0;
bodyDef.bullet = isBullet;
bodyDef.fixedRotation = isFixedRotation;
b2Body* body = m_world->CreateBody(&bodyDef);
body->SetUserData(imgInfo);
imgInfo->bodies.push_back(body);
boneBodyMap.insert(pair<string,b2Body*>(string(slot->bone->data->name), body));
int verticesCount = 0;
switch( slot->attachment->type )
{
/* ... removed code... */
case SP_ATTACHMENT_SKINNED_MESH:
{
spSkinnedMeshAttachment* mesh = (spSkinnedMeshAttachment*)slot->attachment;
spSkinnedMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
verticesCount = mesh->uvsCount;
break;
}
case SP_ATTACHMENT_REGION:
{
if( verticesCount == 0 )
{
spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
spRegionAttachment_computeWorldVertices( attachment, slot->bone, worldVertices );
verticesCount = 8;
}
// NOTE: pass-through here
}
case SP_ATTACHMENT_BOUNDING_BOX:
if( verticesCount == 0 )
{
spBoundingBoxAttachment* attachment = (spBoundingBoxAttachment*)slot->attachment;
spBoundingBoxAttachment_computeWorldVertices(attachment, slot->bone, worldVertices);
verticesCount = attachment->verticesCount;
}
// NOTE: pass-through here
case SP_ATTACHMENT_MESH:
if( verticesCount == 0 )
{
spMeshAttachment* mesh = (spMeshAttachment*)slot->attachment;
spMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
verticesCount = mesh->verticesCount;
}
b2Vec2 shapePoints[8];
b2Vec2 previousStart, previousEnd;
// box 2d doesn't like tons of vertices - so use
if( verticesCount > 16 )
{
/* we PolyPartition library for this - but there's a lot of room for optimization */
}
else
{
// create the fixture using the world vertices
b2FixtureDef fixtureDef;
b2PolygonShape polygonShape;
int iP = 0;
for (int ii = 0; ii < verticesCount; ) {
shapePoints[iP].x = worldVertices[ii]*node->getScaleX();
++ii;
shapePoints[iP].y = worldVertices[ii]*node->getScaleY();
++ii;
++iP;
}
polygonShape.Set( shapePoints, verticesCount/2 );
fixtureDef.shape = &polygonShape;
fixtureDef.density = density;
fixtureDef.friction = friction;
fixtureDef.restitution = restitution;
fixtureDef.filter.categoryBits = PHYSICS_FILTER_CATEGORY_ENEMY;
fixtureDef.filter.maskBits = PHYSICS_FILTER_MASK_ENEMY;
setFilterBits(fixtureDef, aiDefinition->type);
b2Fixture *fixture = body->CreateFixture(&fixtureDef);
}
break;
}
// sub-bodies get a low damping value so the whole character doesn't freak out when animating
body->SetAngularDamping(0.1);
body->SetLinearDamping(0.1);
// locate our central body and store it for use later
if( strcmp(slot->data->name,"bodBounds") == 0 )
{
bodyCenter = body;
}
}
}
}
//// Now to build the joints
// TODO - optimize - a second pass like this is not necessary
slots.erase(slots.begin()); // remove the central slot
for( string name : slots )
{
spSlot* slot = node->findSlot(name.data());
if( slot )
{
b2Body* body = boneBodyMap.at(string(slot->bone->data->name));
b2Body* parentBody = boneBodyMap.at(string(slot->bone->parent->data->name));
b2RevoluteJointDef jointDef;
jointDef.Initialize(body, parentBody, b2Vec2(position.x+slot->bone->worldX*node->getScaleX(), position.y+slot->bone->worldY*node->getScaleY()));
jointDef.collideConnected = false;
jointDef.enableMotor = true;
jointDef.maxMotorTorque = 10000.0f;
b2RevoluteJoint* joint = (b2RevoluteJoint*)m_world->CreateJoint(&jointDef);
joint->SetUserData(slot);
imgInfo->joints.push_back(joint);
imgInfo->initialJointAngles.push_back(slot->bone->rotation);
}
}
}
/** removed code */
// Now assign damping values you want for the central body
bodyCenter->SetAngularDamping(angularDamping);
bodyCenter->SetLinearDamping(linearDamping);
bodyCenter->SetUserData(imgInfo);
// adjust for current rotation
for( b2Body* b : imgInfo->bodies )
{
// note: negative rotation
b->SetTransform(bodyCenter->GetPosition(), -CC_DEGREES_TO_RADIANS(node->getRotation()));
}
Now you should have something like in the image below. This is a debug view of one of our bad guys. Outlines in blue are spine slot bounding boxes. In beige are the box2d fixtures. Note that we did not map all the body parts - just the torso, neck, and head.
Updating Animations - Rotations and Central Body Movement
At every frame, update the physics side to keep in sync with the animation:
vector<b2Joint*>& joints = imgInfo->joints;
vector<float>& initialJointAngles = imgInfo->initialJointAngles;
Node* node = imgInfo->sprite;
b2Body* body = imgInfo->body;
spSlot* bodySlot = imgInfo->bodySlot;
////////////// synchronize joints with animation bones
for( int i = 0; i < joints.size(); ++i )
{
b2Joint* j = joints.at(i);
b2RevoluteJoint* joint = (b2RevoluteJoint*)j;
spSlot* slot = (spSlot*)joint->GetUserData();
float32 angleError = joint->GetJointAngle() + slot->bone->rotation/180.0f*M_PI - initialJointAngles.at(i)/180.0f*M_PI;
if( node->getScaleY() < 0 )
angleError = joint->GetJointAngle() - slot->bone->rotation/180.0f*M_PI + initialJointAngles.at(i)/180.0f*M_PI;
if( angleError > M_PI*2 || angleError < M_PI*-2 )
{
// TODO - optimize
while (angleError <= -M_PI) angleError += M_PI*2.0f;
while (angleError > M_PI) angleError -= M_PI*2.0f;
}
// angular movement
joint->SetMaxMotorTorque(10000000);
joint->SetMotorSpeed(angleError*-FPS_SPINE_ANIMATIONS);
}
////////////// synchronize bod movement
spBone* bone = bodySlot->bone;
float nodeAngle = imgInfo->sprite->getRotation()/180.0f*M_PI;
float xx = bone->worldX*imgInfo->sprite->getScaleX() - imgInfo->previousX;
float yy = bone->worldY*imgInfo->sprite->getScaleY() - imgInfo->previousY;
float x = xx*sinf(nodeAngle) + yy*sinf(nodeAngle);
float y = xx*cosf(nodeAngle) + yy*cosf(nodeAngle);
imgInfo->previousX = bone->worldX*imgInfo->sprite->getScaleX();
imgInfo->previousY = bone->worldY*imgInfo->sprite->getScaleY();
// velocity to move the calculated distance in one frame
float dx = x*FPS_SPINE_ANIMATIONS;
float dy = y*FPS_SPINE_ANIMATIONS;
b2Vec2 v = body->GetLinearVelocity();
// don't exceed desired velocity
if( dx > 0 && v.x >= dx )
dx = 0;
else if( dx < 0 && v.x <= dx )
dx = 0;
if( dy > 0 && v.y >= dy )
dy = 0;
else if( dy < 0 && v.y <= dy )
dy = 0;
float mass = body->GetMass();
float impulseX = mass*dx;
float impulseY = mass*dy;
b2Vec2 linearImpulse(impulseX,impulseY);
// linear movement
body->ApplyLinearImpulse(linearImpulse, body->GetWorldCenter(), true);
Ragdoll
Once your animated character needs to ragdoll, stop updating the physics side. Let the physics take over and essentially apply its rotations to the animation instead.
Notes and stuff...
So this works well for us, but some characters' animations cause major stability problems. Synchronizing the central body is the cause of most of these issues. We like keeping that in though because it allows us to do things like synchronized hovering (synchronized with the wings of a character for example).
Okay, so that's all I have for now. I hope that this is remotely useful. If you have ideas for improvements, please let me know.
If time permits, I'll try to do this again with some video examples or something..
And while I'm at it, some self promotion :p
The game we're working on (almost at alpha): A Quiver of Crows - http://www.aquiverofcrows.com/
Hey...we're trying to follow your example, and we're getting close. One question: where do you get that FPS_SPINE_ANIMATIONS value from?
Cool, thanks man, yeah that's what I figured. We also have this weird offset problem...did you perhaps have anything like this?
Image supprimée en raison de l'absence de support de HTTPS. | Afficher quand même
No problem,
Glad to see others tackling this problem. Looks pretty cool what you got there btw..
I did have some offset issues while I was coding, but I don't remember the specific problem I had. It looks like your offset is off on your central body - maybe it's due to how you're using spMeshAttachment_computeWorldVertices? Maybe you're using the wrong slot as a reference point? Maybe your project setup in spine is different than mine (for example our root bone is centered in the central body).
Thanks man, both for the advice and the compliment! This is for sure the first problem that has us tearing out our hair on this game, haha. We'd be even further behind without your example to follow. It very well may be something to do with the root bone's location; I think I'll try experimenting with that to see if it cures the problem. When we figure out what's going wrong, I'll post up some tips for posterity...here's to likely days more of tweaking on this thing, haha! Gotta love Box2d!! (Which is clearly the bigger problem...Spine is awesome, and we love it! Just wish we could directly set the rotation angles of the Box2d joints without relying on the motor
hehe, yeah this problem has driven me crazy too (and still does sometimes) =]
good luck!
sheado, I tried your code; but when I see the values of shapePoints; everything has the same value
see my screenshot.
I used bounding box with 4 vertices.
still get error.
if (slotH)
{
if (slotH->attachment)
{
if (slotH->attachment->type == SP_ATTACHMENT_MESH)
{
mesh = (spMeshAttachment*)slotH->attachment;
spMeshAttachment_computeWorldVertices(mesh, slotH, worldVertices);
verticesCount = mesh->verticesCount;
log("Active: SP_ATTACHMENT_MESH, %d", mesh->verticesCount);
}
else if (slotH->attachment->type == SP_ATTACHMENT_BOUNDING_BOX)
{
boundBox = (spBoundingBoxAttachment*)slotH->attachment;
spBoundingBoxAttachment_computeWorldVertices(boundBox, slotH->bone, worldVertices);
verticesCount = boundBox->verticesCount;
log("Active: SP_ATTACHMENT_BOUNDING_BOX, %d", boundBox->verticesCount);
}
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
worldPhysics = new b2World(gravity);
worldPhysics->SetContinuousPhysics(false);
b2Body *boxBody;
b2BodyDef boxBodyDef;
b2FixtureDef boxFixture;
b2PolygonShape shapeConcave;
boxBodyDef.type = b2BodyType(b2_staticBody);
boxBodyDef.gravityScale = 0;
boxBodyDef.bullet = false;
boxBodyDef.fixedRotation = true;
boxBody = worldPhysics->CreateBody(&boxBodyDef);
b2Vec2 shapePoints[8] = {};
// create the fixture using the world vertices;
int32 iP = 0;
for (int32 ii = 0; ii < verticesCount;)
{
shapePoints[iP].x = worldVertices[ii] * skelNodeA->getScaleX();
++ii;
shapePoints[iP].y = worldVertices[ii] * skelNodeA->getScaleY();
++ii;
++iP;
log("Vertices count: %d, shapePoints: %d", ii, iP);
if (ii == verticesCount)
{
log("After setting all vertices! Try set a polygonShape!");
shapeConcave.Set(shapePoints, 8);
}
}
boxFixture.shape = &shapeConcave;
boxFixture.density = 10;
boxFixture.friction = 0.8;
boxFixture.restitution = 0.6;
boxBody->CreateFixture(&boxFixture);
boxBody->SetGravityScale(10);
boxBody->SetAngularDamping(0.1);
boxBody->SetLinearDamping(0.1);
}
}
It was error when creating the shapeConcave.
shapeConcave.Set(shapePoints, 8);
I just notice that sheado never use these variables:
float minX = FLT_MAX, minY = FLT_MAX, maxX = -FLT_MAX, maxY = -FLT_MAX;
some users besides sheado use another approach: helg, milos1290.
I am really confusing, which one I should trust in this forum.
and one more thing, the java code of I_Box2D.java seem very different with
spine library attached in cocos2d-x-3 above.
Is this forum alive?
Any admin who can guide cocos2d-x user?
Really, should we get a dead end like this?
I intend to buy your Spine Pro, but if the guides is nothing to read.
Then I'd not buy.
I'd try Creature.
hope they would give me free example and its library.
Hey Sheado, Mpmumau,
I'd love to get this working for ground based characters where the body moves as one. In case you guys have time:
- When let's say a foot hits a static object like a wall, won't box2D make an adjustment during the collision only for the foot?
- Assuming you only update the skeleton position based on central body forces, how would you relay the above back to the skeleton?
- Is there a way to lock the joints so that the body moves as one, and reacts to collisions as such?
Cheers!
Adrian
Hi Adrian,
Good questions - I wish I had time to implement a solution for each.
* When let's say a foot hits a static object like a wall, won't box2D make an adjustment during the collision only for the foot?
If you rig the box2d representation of your character with joints, then the forces do transfer from the foot to other connected body parts. Ideally, when a collision happens, you'd be able to detect it and have it override the animation - essentially flipping things around causing the physics to drive the animation. I haven't had time to implement this, but at least I've been able to gain the benefit of the characters responding to overall physical forces due to the joint-based setup.
* Assuming you only update the skeleton position based on central body forces, how would you relay the above back to the skeleton?
This part I haven't had time to implement. Theoretically you'd detect the collision and then instead of having the animation set the angle of the joints, you'd have the joints set the angles of the bones.
* Is there a way to lock the joints so that the body moves as one, and reacts to collisions as such?
Not sure what you mean here - but if you go with a joint based setup you get to have your character apply forces on physical objects, while still allowing physical objects to apply forces back onto the character.
:sun: