這個問題在許多遊戲中控制角度時都會遇到,但是在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自然的加速到一個指定速度,我將在下次詳細講下方法。