最近在自己第一个游戏项目里面碰到一个看似简单却花了我2天时间才解决的问题
特地发出来分享一下
在BOX2D 中如何控制body 自然的旋转到一个指定角度?
这个问题在许多游戏中控制角度时都会遇到,但是在BOX2D中,你必须考虑到如果转动中与其他body碰撞等物理因素。
能够想到的解决方案有三种:
1 在update方法里不断更改body的角度,使他接近于要设定的角度。
b2vec2 clickedPoint;//设定点的向量 float bodyAngle = body->GetAngle();//取得物体自身的弧度 b2Vec2 toTarget = clickedPoint - body->GetPosition();//计算角度差 float desiredAngle = atanf( -toTarget.x, toTarget.y );//计算设定的弧度 float totalRotation = desiredAngle - bodyAngle;//计算需要旋转的弧度 while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;//调整设定角度到-180到180度之间 while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD; float change = 1 * DEGTORAD; //每次间隔允许最大旋转角度 float newAngle = bodyAngle + min( change, max(-change, totalRotation)); body->SetTransform( body->GetPosition(), newAngle );
很明显,第一种方法并不适合在物理引擎中使用,因为不断设定body的角度会打乱BOX2D中的仿真效果。
2 在update方法里不断给body施加一个能使body转到设定角度的力矩。
刚开始肯定会想到这样做:
float totalRotation = desiredAngle - bodyAngle; while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD; while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD; body->ApplyTorque( totalRotation);
但是运行看看?你会发现一个问题,物体始终受到一个朝向设定点角度方向的力矩,直到物体转到这个角度,乍看好像没问题,但是实际上,物体在到达设定角度时角速度并不是零,所以物体将继续转过这个角度,并受到一个反向的力矩,然后到达设定角度后又一次超过设定角度,这样永远循环摆动,却永远到达不了设定角度。
总结了下原因没有考虑到自身本身的角速度影响。
真的非常难解释的非常清楚,可能我也理解的不太够吧,直接给出解决方案。
方法是假设一定时间间隔dt内body不受任何转矩,计算出dt间隔后的body角度,用来替换现在的body角度:
float dt = 1.0f / 60.0f; float nextAngle = bodyAngle + body->GetAngularVelocity() *dt; float totalRotation = desiredAngle - nextAngle; while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD; while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD; float desiredAngularVelocity = totalRotation / dt; float torque = body->GetInertia() * desiredAngularVelocity / dt; body->ApplyTorque( torque );
但是这样还不是最好的方法,body仍然需要来回晃动数个来回才能最终停下来。
3 在update方法里不断给body施加一个能使body转到设定角度的惯性冲量。
最终的解决方案是通过施加一个冲量,和上面的方法相似:
float dt = 1.0f / 60.0f; float nextAngle = bodyAngle + body->GetAngularVelocity() *dt; float totalRotation = desiredAngle - nextAngle; while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD; while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD; float desiredAngularVelocity = totalRotation / dt; float impulse = body->GetInertia() * desiredAngularVelocity;// disregard time factor body->ApplyAngularImpulse( impulse );
此外,如果你想在旋转过程中设定一个最大旋转速度,可以添加一个change值
float dt = 1.0f / 60.0f; float nextAngle = bodyAngle + body->GetAngularVelocity() *dt; float totalRotation = desiredAngle - nextAngle; while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD; while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD; float desiredAngularVelocity = totalRotation / dt; float change = 1 * DEGTORAD; //allow 1 degree rotation per time step desiredAngularVelocity = min( change, max(-change, desiredAngularVelocity)); float impulse = body->GetInertia() * desiredAngularVelocity;// disregard time factor body->ApplyAngularImpulse( impulse );
至此,基本完美的解决了这个问题,并且可以通过调整dt的值,可以实现不同精度的旋转body到指定角度,通过调整change的值,改变旋转的最大速度。
对应的,此方法还可以实现控制body自然的加速到一个指定速度,我将在下次详细讲下方法。