原文連接 app
http://www.iforce2d.net/b2dtut/top-down-car 函數
最近討論'top down' car physics如何很實現的話題很愈來愈多,因此我想我能夠試一試而且展開一個話題。一個 td(top-down) car 被設計成在一個0重力的世界中,被一個爲底盤的身體和四個分離的輪子身體。它取決於僅僅用一個帶有底盤的身體 測試
和沒必要擔憂的分離的輪子。 問題的關鍵的另外一種狀況是阻止身體沿一條軸移動,然而仍然容許他在另一條軸上自由移動(輪子應該能夠來回移動。)這自己並非一個艱難的壯舉,訣竅是使得那些用戶來控制車有很好的感覺。若是水平速度被簡單的徹底種植,車子就會感受像是在鐵路上跑同樣,而且咱們想容許車子能夠在必定的狀況下大話,而且在不一樣的表面有不一樣的表現等等。在咱們開始之前你能夠看一下這個完美實現的top-down賽車遊戲.這種事情是咱們的目標。 基礎的步驟是去找到最近的身體的水平速度而且給他一個推理是的咱們能夠取消那個水平速度。咱們開始僅僅用一個身體來表明一個輪子,一會咱們用四個它來固定到另一個身體來實現更復雜的模擬。由於全部的輪子作一樣的事情,咱們能夠爲他作一個類。這是起始點,一個帶有b2Body指針自覺得成員變量而且帶着一個簡單的盒子形狀來初始化。 class TDTire { public: b2Body* m_body; TDTire(b2World* world) { b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; m_body = world->CreateBody(&bodyDef); b2PolygonShape polygonShape; polygonShape.SetAsBox( 0.5f, 1.25f ); m_body->CreateFixture(&polygonShape, 1);//shape, density m_body->SetUserData( this ); } ~TDTire() { m_body->GetWorld()->DestroyBody(m_body); } }; 這裏我是用了一個CreateFixture不須要fixture定義的簡單版本,注意到咱們也設置了數據來建立b2body,因此這個物理身體和遊戲邏輯會彼此涉及。 ---------------------------------------------------------------------------------- 消除水平速度。 爲了取消水平速度,咱們須要知道它是什麼。咱們能夠找到在最近時間內身體的水平速度在賽道方向的法線上的投影假。設輪子的當前座標是(0,1)將會向前走1,0將會向右走咱們可使用獲取世界向量來獲取最近的身體在世界座標中的朝向。 假設咱們的輪胎旋轉了一點點,向上移動以下圖所示。咱們想要使得藍色向量向紅色向量投影來看看他有多長若是他僅僅是以紅色方向移動。 咱們能夠增長下面這樣的方法到TDTire裏面: b2Vec2 getLateralVelocity() { b2Vec2 currentRightNormal = m_body->GetWorldVector( b2Vec2(1,0) ); return b2Dot( currentRightNormal, m_body->GetLinearVelocity() ) * currentRightNormal; } 而後給他一個推力,使得擺脫他的水平速度。這和最後一部分「一恆定速度前進」很類似,那裏咱們有一個渴望的速度。咱們應用一個推力乘以身體的質量,使其達到這一速度在一個時間步。咱們增長一個函數使得它在一個時間步讓他作這些。 void updateFriction() { b2Vec2 impulse = m_body->GetMass() * -getLateralVelocity(); m_body->ApplyLinearImpulse( impulse, m_body->GetWorldCenter() ); } 在舞臺上,若是你建立了一個輪子身體在世界中,若是使用鼠標推進他,將會注意到賽道方向的速度確實被推力取消了,若是朝前方推進輪子你會發現它更傾向於轉圓圈。幾乎他就像一個真的輪胎同樣當你滾動輪子而且他會慢慢停下來。 你也會注意到他能夠自由旋轉,使得它看起來有點不真實,一個真實的車輪子不是那樣的,因此讓咱們給用 類似的方式來消除側面速度。。旋轉更容易一些,由於咱們不須要作這個向量投影的東西——添加這個到updateFriction: m_body->ApplyAngularImpulse( 0.1f * m_body->GetInertia() * -m_body->GetAngularVelocity() ); 0.1是我決定的值,他有點像我上一次旋轉的輪胎。若是咱們徹底停掉輪子的旋轉,他將會像在鐵路同樣,哪裏都不能去,除了走直線。沒有徹底阻止輪胎的旋轉的另外一個緣由是咱們想讓玩家來使用這個身體來驅動它。 最後你可能注意到輪子會永遠朝前走,因此咱們給一個強力使得它最終中止。 b2Vec2 currentForwardNormal = getForwardVelocity(); float currentForwardSpeed = currentForwardNormal.Normalize(); float dragForceMagnitude = -2 * currentForwardSpeed; m_body->ApplyForce( dragForceMagnitude * currentForwardNormal, m_body->GetWorldCenter() ); 再次進入updateFriction和值2來自一些詐騙和調整。固然你聰明的人自動知道getForwardVelocity getLateralVelocity()是同樣的(),但與當地的一個向量(0,1)代替(1,0)對嗎? 控制一個輪子。 在咱們將要製做四輪車以前,咱們須要更多的考慮下一個輪子應該作的事情。咱們至少能讓他往前日後走。我也要使得它可以打滑,而且處理不一樣的表面。咱們先集中精力作好一個,剩下四個就會很容易。 要測試輪胎是受多種運動的狀況,咱們能夠先假設這個單一的輪胎自己就是一輛汽車,讓用戶直接旋轉它。這是一個基本的方式跟蹤的關鍵(W/A/S/D)的用戶是當前按下的按鍵: //global scope enum { TDC_LEFT = 0x1, TDC_RIGHT = 0x2, TDC_UP = 0x4, TDC_DOWN = 0x8 }; //testbed Test class variable int m_controlState; //testbed Test class constructor m_controlState = 0; //testbed Test class functions void Keyboard(unsigned char key) { switch (key) { case 'a' : m_controlState |= TDC_LEFT; break; case 'd' : m_controlState |= TDC_RIGHT; break; case 'w' : m_controlState |= TDC_UP; break; case 's' : m_controlState |= TDC_DOWN; break; default: Test::Keyboard(key); } } void KeyboardUp(unsigned char key) { switch (key) { case 'a' : m_controlState &= ~TDC_LEFT; break; case 'd' : m_controlState &= ~TDC_RIGHT; break; case 'w' : m_controlState &= ~TDC_UP; break; case 's' : m_controlState &= ~TDC_DOWN; break; default: Test::Keyboard(key); } } 注意: KeyboardUp 能夠在最新的box2d testbed中使用,可是若是你正在使用v2.1.2 從其餘的教程中找不到。你能夠獲取最新的box2d版本,或者在testbed中增長實現,他是很簡單的僅僅使用鍵盤函數。 讓咱們增長一個函數到輪子的clas中,使得他在輸入的狀態中作更聰明的事情。 float m_maxForwardSpeed; // 100; float m_maxBackwardSpeed; // -20; float m_maxDriveForce; // 150; //tire class function void updateDrive(int controlState) { //find desired speed float desiredSpeed = 0; switch ( controlState & (TDC_UP|TDC_DOWN) ) { case TDC_UP: desiredSpeed = m_maxForwardSpeed; break; case TDC_DOWN: desiredSpeed = m_maxBackwardSpeed; break; default: return;//do nothing } //find current speed in forward direction b2Vec2 currentForwardNormal = m_body->GetWorldVector( b2Vec2(0,1) ); float currentSpeed = b2Dot( getForwardVelocity(), currentForwardNormal ); //apply necessary force float force = 0; if ( desiredSpeed > currentSpeed ) force = m_maxDriveForce; else if ( desiredSpeed < currentSpeed ) force = -m_maxDriveForce; else return; m_body->ApplyForce( force * currentForwardNormal, m_body->GetWorldCenter() ); } 使用速度和力量來到使他變成你想要的那樣,話題的開始咱們有太多考慮過尺寸,而且個人輪子不是個不真實的一寬,因此這些速度也不是真實世界的值。 如今輪子能夠向前移動和向後移動,咱們也使得它運轉經過事假一些扭轉力,當a/d 按鍵被按下。由於咱們最後的目標是綁定這些輪子到一個車子的身上,程序的這部分將會被丟掉,因此他只是一個粗略的方式使得拐彎發生。因此咱們能夠測試下一部分--打滑和表面。另外一方面,若是你真的傾向於設計汽車做爲單體,你可能須要衝個這些使得它夠合乎情理的。e.g.不要讓車子拐彎除非他在移動。 void updateTurn(int controlState) { float desiredTorque = 0; switch ( controlState & (TDC_LEFT|TDC_RIGHT) ) { case TDC_LEFT: desiredTorque = 15; break; case TDC_RIGHT: desiredTorque = -15; break; default: ;//nothing } m_body->ApplyTorque( desiredTorque ); } Allowing skidding 這一點咱們有一個能夠控制的汽車身體,根據咱們最初的計劃來消除橫向速度,他表現的良好。這是很是好的,若是你想模擬的槽車,堅持本身的軌道像膠水。可是他感受更天然若是車子能夠再打滑一點。記得當咱們徹底橫向速度的時候,咱們怎樣徹底消除他的?咱們簡單的計算一下推力並給他加上去。因此全部咱們須要作的事情就是約束這個推力到最大值,而且輪子將會打滑當環境要求一個比容許值更大的修改。在updateFriction函數中這僅僅是一個另外的狀態。 b2Vec2 impulse = m_body->GetMass() * -getLateralVelocity(); //existing code if ( impulse.Length() > maxLateralImpulse ) impulse *= maxLateralImpulse / impulse.Length(); m_body->ApplyLinearImpulse( impulse, m_body->GetWorldCenter() ); //existing code 我發現maxLateralImpulse爲3的時候容許不多的數量的打滑 當以告訴度拐彎的時候,值爲2的時候就像一個溼擼,值爲1的時候是我想起一個行駛的小船在水上拐彎。這些值不管如何將會被適應當輪子鏈接到車子底盤。因此如今不要大驚小怪。