I spent my entire Sunday getting the player's gun to attach in front of the player. You see, I'm using Box2D for my physics and pushing my entities (player, bullets, enemies, etc.) around by setting a linear velocity every frame. That works great in most cases, except when you want something with a parent-child relationship, like a gun entity hovering in front of the player entity.
To make something physically-enabled with Box2D, you give it a body and a fixture. The body defines the type (static, dynamic, or kinematic) and the position in the world. The fixture defines the shape, the density, the friction, and all that good stuff. The separation of concerns is confusing, but I'm sure the library authors had their reasons. One advantage is that you can make a new fixture that attaches to an existing body. This was my first attempt, and it works pretty well. Box2D will automatically match the new collision shape with the existing body's position. Unfortunately, you can't do offsets with this technique, so you can't make the gun hover in front of the player. Which is, again, what I'm really looking to do.
Next, I looked into joints, which was a very deep rabbit hole indeed. These objects are used to attach fixtures in some physically-accurate manner. This overview page was a huge help because joints are one hell of a confusing mess. "Alright," says Box2D, "so you're looking for a joint? We've got weld joints, line joints, distance joints, revolute joints, prismatic joints, pulley joints, gear joints, and mouse joints. Good luck!"
Long story short, none of these joint types worked for me. Because the gun doesn't have a velocity of its own, all the joints can do is drag the gun behind the player. But if I need to match the velocity exactly, why bother using a joint? I can use the parent's world transform to offset the child object directly, which is what I ultimately ended up doing:
if (auto anchorCom = params.registry.try_get<AnchorComponent>(entity))
{
if (auto parentPhysicsCom = params.registry.try_get<PhysicsComponent>(anchorCom->parent))
{
auto parentBody = parentPhysicsCom->body;
glm::mat3 model = glm::rotate(
glm::mat3{ 1.0f },
glm::radians(transformCom.rotationDegrees)
);
glm::mat3 view = math::toGlmMat3(parentBody->GetTransform());
glm::mat3 mvp = m_projection * view * model;
transformCom.position = glm::xy(mvp * anchorCom->parentLocalAnchor);
physicsCom.velocity = math::toGlm(parentBody->GetLinearVelocity());
}
}
If it's ugly, but it works, ship it.
