cocos2d-x Tests講解四:box2dTest(物理引擎)
一、box2d基礎知識
1、關於
Box2D 是一個用於遊戲的 2D 剛體模擬庫。從遊戲的視角來看,物理引擎就是一個程式性動畫(procedural animation)的系統,而不是由動畫師去移動你的物體。
1、核心概念
剛體(rigid body)一塊十分堅硬的物質,它上面的任何兩點之間的距離都是完全不變的。形狀(shape)一塊嚴格依附於物體(body)的 2D 碰撞幾何結構(collision geometry)。形狀具有摩擦(friction)和恢復(restitution)的材料性質。約束(constraint)
一個約束(constraint)就是消除物體自由度的物理連線。在 2D 中,一個物體有 3 個自由度。如果我們把一個物體釘在牆上(像擺錘那樣),那我們就把它約束到了牆上。這樣,此物體就只能繞著這個釘子旋轉,所以這個約束消除了它 2 個自由度。接觸約束(contact constraint)的。
3、 建立一個世界
每個 Box2D 程式都將從一個世界物件(world object)的建立開始。這是一個管理記憶體,物件和模擬的中心。
要建立一個世界物件,我們首先需要定義一個世界的包圍盒。Box2D 使用包圍盒來加速碰撞檢測。尺寸並不關鍵,但合適的尺寸有助於效能。這個包圍盒過大總比過小好。b2AABB worldAABB;worldAABB.lowerBound.Set(-100.0f, -100.0f);
worldAABB.upperBound.Set(100.0f, 100.0f); 接下來我們定義重力向量。b2Vec2 gravity(0.0f
bool doSleep = true; //當動態物體靜止時使它休眠,減少效能開銷
現在我們建立世界物件。
b2World world(worldAABB, gravity, doSleep);//在棧上建立world
那麼現在我們有了自己的物理世界,讓我們再加些東西進去。4、建立一個地面
第一步,我們建立地面體。要建立它我們需要一個物體定義(body definition),通過物體定義我們來指定地面體的初始位置。
b2BodyDef groundBodyDef;groundBodyDef.position.Set(0.0f, -10.0f);
第二步,將物體定義傳給世界物件來建立地面體。世界物件並不儲存到物體定義的引用。地面體是作為靜態物體(static body)建立的,靜態物體之間並沒有碰撞,它們是固定的。當一個物體具有零質量的時候 Box2D 就會確定它為靜態物體,物體的預設質量是零,所以它們預設就是靜態的。
b2Body* ground = world.CreateBody(&groundBodyDef); 第三步,我們建立一個地面的多邊形定義。我們使用 SetAsBox 簡捷地把地面多邊形規定為一個盒子(矩形)形狀,盒子的中點就位於父物體的原點上。b2PolygonDef groundShapeDef;groundShapeDef.SetAsBox(50.0f, 10.0f);其中,SetAsBox 函式接收了半個寬度和半個高度,這樣的話,地面盒就是 100 個單位寬(x 軸)以及 20 個單位高(y 軸)。Box2D 已被調諧使用米,千克和秒來作單位,所以你可以用米來考慮長度。
在第四步中,我們在地面體上建立地面多邊形,以完成地面體。
groundBody->CreateShape(&groundShapeDef);//建立形狀用於碰撞檢測等5、 建立一個動態物體首先我們用 CreateBody 建立物體。b2BodyDef bodyDef;bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);
接下來我們建立並新增一個多邊形形狀到物體上。注意我們把密度設定為 1,預設的密度是 0。並且,形狀的摩擦設定到了 0.3。形狀新增好以後,我們就使用 SetMassFromShapes 方法來命令物體通過形狀去計算其自身的質量。這暗示了你可以給單個物體新增一個以上的形狀。如果質量計算結果為 0,那麼物體會變成真正的靜態。
b2PolygonDef shapeDef;
shapeDef.SetAsBox(1.0f, 1.0f);
shapeDef.density = 1.0f;
shapeDef.friction = 0.3f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();
6、模擬(Box2D 的)世界
我們已經初始化好了地面盒和一個動態盒。現在我們只有少數幾個問題需要考慮。Box2D 中有一些數學程式碼構成的積分器(integrator),積分器在離散的時間點上模擬物理方程,它將與遊戲動畫迴圈一同執行。所以我們需要為 Box2D 選取一個時間步,通常來說遊戲物理引擎需要至少 60Hz 的速度,也就是 1/60 的時間步。你可以使用更大的時間步,但是你必須更加小心地為你的世界調整定義。我們也不喜歡時間步變化得太大,所以不要把時間步關聯到幀頻(除非你真的必須這樣做)。直截了當地,這個就是時間步:float32 timeStep = 1.0f / 60.0f;
除了積分器之外,Box2D 中還有約束求解器(constraint solver)。約束求解器用於解決模擬中的所有約束,一次一個。單個的約束會被完美的求解,然而當我們求解一個約束的時候,我們就會稍微耽誤另一個。要得到良好的解,我們需要迭代所有約束多次。建議的 Box2D 迭代次數是 10 次。你可以按自己的喜好去調整這個數,但要記得它是速度與質量之間的平衡。更少的迭代會增加效能並降低精度,同樣地,更多的迭代會減少效能但提高模擬質量。這是我們選擇的迭代次數:
int32 iterations = 10;//一個時間步遍歷10次約束
現在我們可以開始模擬迴圈了,在遊戲中模擬迴圈應該併入遊戲迴圈。每次迴圈你都應該呼叫 b2World::Step,通常呼叫一次就夠了,這取決於幀頻以及物理時間步。
這就是模擬 1 秒鐘內 60 個時間步的迴圈
for (int32 i = 0; i < 60; ++i){
world.Step(timeStep, iterations);
}
7、API 設計
單位Box2D 使用浮點數,所以必須使用一些公差來保證它正常工作。這些公差已經被調諧得適合米-千克-秒(MKS)單位。尤其是,Box2D 被調諧得能良好地處理 0.1 到 10 米之間的移動物體。這意味著從罐頭盒到公共汽車大小的物件都能良好地工作。
• 注意:Box2D 已被調諧至 MKS 單位。移動物體的尺寸大約應該保持在 0.1 到 10 米之間。你可能需要一些縮放系統來渲染你的場景和物體。Box2D 中的例子是使用 OpenGL 的視口來變換的。使用者資料
b2Shape,b2Body 和 b2Joint 類都允許你通過一個 void 指標來附加使用者資料。這在你測試 Box2D 資料結構,以及你想把它們聯絡到自己的引擎中的時候是較方便的。舉個典型的例子,在角色上的剛體中附加到角色的指標,這就構成了一個迴圈引用。如果你有角色,你就能得到剛體。如果你有剛體,你就能得到角色。GameActor* actor = GameCreateActor();b2BodyDef bodyDef;
bodyDef.userData = actor;
actor->body = box2Dworld->CreateBody(&bodyDef);這是一些需要使用者資料的案例: • 使用碰撞結果給角色施加傷害• 當玩家進入一個包圍盒時播放一段指令碼事件• 當 Box2D 通知你一個關節即將摧毀時訪問一個遊戲結構記得使用者資料是可選的,並且能放入任何東西。然而,你需要保持一致性。例如,如果你想在一個物體中儲存一個角色的指標,那你就應該在所有物體中都儲存一個角色指標。不要在一個物體中儲存角色指標,卻在另一個物體中儲存一個其它指標。這可能會導致程式崩潰。
8、世界
b2World 類包含著物體和關節。它管理著模擬的方方面面,並允許非同步查詢(就像 AABB 查詢)。你與 Box2D 的大部分互動都將通過 b2World 物件來完成。
要建立或摧毀一個世界你需要使用 new 和 delete:
b2World* myWorld = new b2World(aabb, gravity, doSleep);// ... do stuff ...delete myWorld;
世界類用於驅動模擬。你需要指定一個時間步和一個迭代次數。例如:
float32 timeStep = 1.0f / 60.f;int32 iterationCount = 10;
myWorld->Step(timeStep, iterationCount);
在時間步完成之後,你可以調查物體和關節的資訊。最可能的情況是你會獲取物體的位置,這樣你才能更新你的角色並渲染它們。你可以在遊戲迴圈的任何地方執行時間步,但你應該意識到事情發生的順序。例如,如果你想要在一幀中得到新物體的碰撞結果,你必須在時間步之前建立物體。推薦使用固定的時間步。使用大一些的時間步你可以在低幀率的情況下提升效能。1/60 的時間步通常會呈現一個高質量的模擬。
掃描世界:
世界就是一個物體和關節的容器。你可以獲取世界中所有物體和關節並遍歷它們。例如,這段程式碼會喚醒世界中的所有物體:for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext()){
b->WakeUp();
}
AABB 查詢:
有時你需要求出一個區域內的所有形狀。b2World 類為此使用了 broad-phase 資料結構,提供了一個 log(N) 的快速方法。你提供一個世界座標的 AABB,而 b2World 會返回一個所有大概相交於此 AABB 的形狀之陣列。這不是精確的,因為函式實際上返回那些 AABB 與規定之 AABB 相交的形狀。例如,下面的程式碼找到所有大概與指定 AABB 相交的形狀並喚醒所有關聯的物體。b2AABB aabb;aabb.minVertex.Set(-1.0f, -1.0f);
aabb.maxVertex.Set(1.0f, 1.0f);
const int32 k_bufferSize = 10;
b2Shape *buffer[k_bufferSize];
int32 count = myWorld->Query(aabb, buffer, k_bufferSize);
for (int32 i = 0; i < count; ++i)
{
buffer[i]->GetBody()->WakeUp();
}
9、 物體
物體具有位置和速度。你可以應用力,扭矩和衝量到物體。物體可以是靜態的或動態的,靜態物體永遠不會移動,並且不會與其它靜態物體發生碰撞。物體是形狀的主幹,物體攜帶形狀在世界中運動。在 Box2D 中物體總是剛體,這意味著同一剛體上的兩個形狀永遠不會相對移動。通常你會儲存所有你所建立的物體的指標,這樣你就能查詢物體的位置,並在圖形實體中更新它的位置。另外在不需要它們的時候你也需要通過它們的指標摧毀它們。
質量性質:
1)在物體定義中顯式地設定
bodyDef.massData.mass = 2.0f;//物體的質量是2kg
2)顯式地在物體上設定(在其建立之後)
3)基於物體上的形狀來進行密度設定
b2PolygonDef shapeDef;
shapeDef.SetAsBox(1.0f, 1.0f);shapeDef.density = 1.0f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();//這個函式成本較高,所以你應該只在需要時使用它。你可以在執行時調整一個物體的質量,這通常是在新增或移除物體上之形狀時完成的。可能你會根據物體上的當前形狀來調整其質量。可能你也會直接設定質量。例如,你可能會改變形狀,但你只想使用自己的質量公式。void SetMass(const b2MassData* massData);通過以下這些函式可以獲得物體的質量資料:float32 GetMass() const;
float32 GetInertia() const;
const b2Vec2& GetLocalCenter() const;
位置和角度:
bodyDef.position.Set(0.0f, 2.0f); // the body's origin position.bodyDef.angle = 0.25f * b2_pi; // the body's angle in radians.你可以訪問一個物體的位置和角度,這在你渲染相關遊戲角色時很常用。你也可以設定位置,儘管這不怎麼常用。bool SetXForm(const b2Vec2& position, float32 angle);const b2XForm& GetXForm() const;
const b2Vec2& GetPosition() const;
float32 GetAngle() const;
你可以訪問線速度與角速度,線速度是對於質心所言的。
void SetLinearVelocity(const b2Vec2& v);b2Vec2 GetLinearVelocity() const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity() const;
阻尼:
阻尼用於減小物體在世界中的速率。阻尼與摩擦是不同的,因為摩擦僅在物體有接觸的時候才會發生,而阻尼的模擬要比摩擦便宜多了。然而,阻尼並不能取代摩擦,往往這兩個效果需要同時使用。阻尼引數的範圍可以在 0 到無窮之間,0 的就是沒有阻尼,無窮就是滿阻尼。通常來說,阻尼的值應在 0 到 0.1 之間,我通常不使用線性阻尼,因為它會使物體看起來發飄。bodyDef.linearDamping = 0.0f;bodyDef.angularDamping = 0.01f;阻尼相似於穩定性與效能,阻尼值較小的時候阻尼效應幾乎不依賴於時間步,而阻尼值較大的時候阻尼效應將隨著時間步而變化。如果你使用固定的時間步(推薦)這就不是問題了。
休眠引數:
模擬物體的成本是高昂的,所以如果物體更少,那模擬的效果就能更好。當一個物體停止了運動時,我們要停止去模擬它。 當 Box2D 確定一個物體(或一組物體)已經停止移動時,物體就會進入休眠狀態,消耗很小的 CPU 開銷。如果一個醒著的物體接觸到了一個休眠中的物體,那麼休眠中的物體就會醒來。當物體上的關節或觸點被摧毀的時候,它們同樣會醒來。你也可以手動地喚醒物體。通過物體定義,你可以指定一個物體是否可以休眠,或者建立一個休眠的物體。bodyDef.allowSleep = true;bodyDef.isSleeping = false;
子彈:
高速移動的物體在 Box2D 被稱為子彈(bullet),你需要按照遊戲的設計來決定哪些物體是子彈。如果你決定一個物體應該按照子彈去處理,使用下面的設定。bodyDef.isBullet = true;子彈開關隻影響動態物體。有的時候,在一個時間步內可能會有大量的剛體同時運動。如果一個物理引擎沒有處理好大幅度運動的問題,你就可能會看見一些物體錯誤地穿過了彼此。這種效果被稱為隧道效應(tunneling)。預設情況下,Box2D 會通過連續碰撞檢測(CCD)來防止動態物體穿越靜態物體,這是通過從形狀的舊位置到新位置的掃描來完成的。引擎會查詢掃描中的新碰撞,併為這些碰撞計算碰撞時間(TOI)。物體會先被移動到它們的第一個 TOI,然後一直模擬到原時間步的結束。如果有必要這個步驟會重複執行。一般 CCD 不會應用於動態物體之間,這是為了保持效能。在一些遊戲環境中你需要在動態物體上也使用 CCD,譬如,你可能想用一顆高速的子彈去射擊薄壁。沒有 CCD,子彈就可能會隧穿薄壁。 CCD 的成本是昂貴的,所以你可能不希望所有運動物體都成為子彈。所以 Box2D 預設只在動態物體和靜態物體之間使用 CCD,這是防止物體逃脫遊戲世界的一個有效方法。然而,可能你有一些高速移動的物體需要一直使用 CCD。狀態資訊:
物體的狀態含有多個方面,通過這些函式你可以訪問這些狀態資料:bool IsBullet() const;void SetBullet(bool flag);
bool IsStatic() const;
bool IsDynamic() const;
bool IsFrozen() const;
bool IsSleeping() const;
void AllowSleeping(bool flag);
void WakeUp();
力和衝量:
你可以對一個物體應用力,扭矩,以及衝量。當應用一個力或衝量時,你需要提供一個世界位置。這常常會導致對質心的一個扭矩。void ApplyForce(const b2Vec2& force, const b2Vec2& point);void ApplyTorque(float32 torque);
void ApplyImpulse(const b2Vec2& impulse, const b2Vec2& point);應用力,扭矩或衝量會喚醒物體,有時這是不合需求的。例如,你可能想要應用一個穩定的力,並允許物體休眠來提升效能。這時,你可以使用這樣的程式碼:if (myBody->IsSleeping() == false)
{
myBody->ApplyForce(myForce, myPoint);
}
座標轉換:
物體類包含一些工具函式,它們可以幫助你在區域性和世界座標系之間轉換點和向量。如果你不瞭解這些概念,請看 Jim Van Verth 和 Lars Bishop 的“Essential Mathematics for Games and Interactive Applications”。這些函式都很高效,所以可放心使用。b2Vec2 GetWorldPoint(const b2Vec2& localPoint);b2Vec2 GetWorldVector(const b2Vec2& localVector);
b2Vec2 GetLocalPoint(const b2Vec2& worldPoint);
b2Vec2 GetLocalVector(const b2Vec2& worldVector);
列表
你可以遍歷一個物體的形狀,其主要用途是幫助你訪問形狀的使用者資料。for (b2Shape* s = body->GetShapeList(); s; s = s->GetNext()){
MyShapeData* data = (MyShapeData*)s->GetUserData();
... do something with data ...
}
你也可以用類似的方法遍歷物體的關節列表。
10、 形狀
形狀就是物體上的碰撞幾何結構。另外形狀也用於定義物體的質量。也就是說,你來指定密度,Box2D 可以幫你計算出質量。形狀具有摩擦和恢復的性質。形狀還可以攜帶篩選資訊,使你可以防止某些遊戲物件之間的碰撞。形狀永遠屬於某物體,單個物體可以擁有多個形狀。形狀是抽象類,所以在 Box2D 中可以實現許多
型別的形狀。如果你有勇氣,那便可以實現出自己的形狀型別(和碰撞演算法)。形狀定義 :
形狀定義用於建立形狀。通用的形狀資料會儲存在 b2ShapeDef 中,特殊的形狀資料會儲存在其派生類中。
1)摩擦和恢復
摩擦可以使物件逼真地沿其它物件滑動。Box2D 支援靜摩擦和動摩擦,但使用相同的引數。摩擦引數經常會設定在 0 到 1 之間,0 意味著沒有摩擦,1 會產生強摩擦。當計算兩個形狀之間的摩擦時,Box2D 必須聯合兩個形狀的摩擦引數,這是通過以下公式完成的:
float32 friction;friction = sqrtf(shape1->friction * shape2->friction);
恢復可以使物件彈起,想象一下,在桌面上方丟下一個小球。恢復的值通常設定在 0 到 1 之間,0 的意思是小球不會彈起,這稱為非彈性碰撞;1 的意思是小球的速度會得到精確的反射,這稱為完全彈性碰撞。恢復是通過這樣的公式計算的:
float32 restitution;restitution = b2Max(shape1->restitution, shape2->restitution);
當一個形狀發生多碰撞時,恢復會被近似地模擬。這是因為 Box2D 使用了迭代求解器.
2) 密度
Box2D 可以根據附加形狀的質量分配來計算物體的質量以及轉動慣量。直接指定物體質量常常會導致不協調的模擬。因此,推薦的方法是使用b2Body::SetMassFromShape 來根據形狀設定質量。3) 篩選
碰撞篩選是一個防止某些形狀發生碰撞的系統。
Box2D 支援 16 個種群,對於任何一個形狀你都可以指定它屬於哪個種群。你還可以指定這個形狀可以和其它哪些種群發生碰撞。例如,你可以在一個多人遊戲中指定玩家之間不會碰撞,怪物之間也不會碰撞,但是玩家和怪物會發生碰撞。這是通過掩碼來完成的,例如:playerShapeDef.filter.categoryBits = 0x0002;monsterShapeDef.filter.categoryBits = 0x0004;
playerShape.filter.maskBits = 0x0004;
monsterShapeDef.filter.maskBits = 0x0002;
碰撞組可以讓你指定一個整數的組索引。你可以讓同一個組的所有形狀總是相互碰撞(正索引)或永遠不碰撞(負索引)。組索引通常用於一些以某種方式關聯的事物,就像自行車的那些部件。在下面的例子中,shape1 和 shape2 總是碰撞,而 shape3 和 shape4 永遠不會碰撞。
shape1Def.filter.groupIndex = 2;shape2Def.filter.groupIndex = 2;
shape3Def.filter.groupIndex = -8;
shape4Def.filter.groupIndex = -8;
不同組索引之間形狀的碰撞會按照種群和掩碼來篩選。換句話說,組篩選比種群篩選有更高的優選權。
注意在 Box2D 中的其它碰撞篩選,這裡是一個列表: • 靜態物體上的形狀永遠不會與另一個靜態物體上的形狀發生碰撞• 同一個物體上的形狀之間永遠不會發生碰撞• 你可以有選擇地啟用或禁止由關節連線的物體上的形狀之間是否碰撞有時你可能希望在形狀建立之後去改變其碰撞篩選,你可以使用 b2Shape::GetFilterData 以及 b2Shape::SetFilterData 來存取已存在形狀之 b2FilterData 結構。Box2D 會快取篩選結果,所以你需要使用 b2World::Refilter 手動地進行重篩選。4)感測器
有時候遊戲邏輯需要判斷兩個形狀是否相交,但卻不應該有碰撞反應。這可以通過感測器(sensor)來完成。感測器會偵測碰撞而不產生碰撞反應。你可以將任一形狀標記為感測器,感測器可以是靜態或動態的。記得,每個物體上可以有多個形狀,並且感測器和實體形狀是可以混合的。
myShapeDef.isSensor = true;
5) 圓形定義
b2CircleDef 擴充了 b2ShapeDef 並增加一個半徑和一個區域性位置。b2CircleDef def;def.radius = 1.5f;
def.localPosition.Set(1.0f, 0.0f);
6)多邊形定義
b2PolyDef 用於定義凸多邊形。要正確地使用需要一點點技巧,所以請仔細閱讀。最大頂點數由 b2_maxPolyVertices 定義,當前是 8。如果你需要更多頂點,你必須修改 b2Settings.h 中的 b2_maxPolyVertices。當建立多邊形定義時,你需要給出所用的頂點數目。這些頂點必須按照相對於右手座標系之 z 軸逆時針(CCW)的順序定義。在你的螢幕上可能是順時針的,這取決於你的座標系統規則。多邊形必須是凸多邊形,也就是,每個頂點都必須指向外面。最後,你也不應該重疊任何頂點。Box2D 會自動地封閉環路。這裡是一個三角形的多邊形定義的例子:
b2PolygonDef triangleDef;triangleDef.vertexCount = 3;
triangleDef.vertices[0].Set(-1.0f, 0.0f);
triangleDef.vertices[1].Set(1.0f, 0.0f);
triangleDef.vertices[2].Set(0.0f, 2.0f);
7)形狀工廠
初始化一個形狀定義,而後將其傳遞給父物體;形狀就是這樣建立的。
b2CircleDef circleDef;circleDef.radius = 3.0f;
circleDef.density = 2.5f;
b2Shape* myShape = myBody->CreateShape(&circleDef);
11、關節
關節的作用是把物體約束到世界,或約束到其它物體上。在遊戲中的典型例子是木偶,蹺蹺板和滑輪。關節可以用許多種不同的方法結合起來,創造出有趣的運動。
有些關節提供了限制(limit),以便你控制運動範圍。有些關節還提供了馬達(motor),它可以以指定的速度驅動關節,直到你指定了更大的力或扭矩。1)關節定義各種關節型別都派生自 b2JointDef。所有關節都連線兩個不同的物體,可能其中一個是靜態物體。如果你想浪費記憶體的話,那就建立一個連線兩個靜態物體的關節 你可以為任何一種關節指定使用者資料。你還可以提供一個標記,用於預防相連的物體發生碰撞。實際上,這是預設行為,你可以設定 collideConnected 布林值來允許相連的物體碰撞。很多關節定義需要你提供一些幾何資料。一個關節常常需要一個錨點(anchor point)來定義,這是固定於相接物體中的點。在 Box2D 中這點需要在區域性座標系中指定,這樣,即便當前物體的變化違反了關節約束,關節還是可以被指定 —— 在遊戲存取進度時這經常會發生。另外,有些關節定義需要預設的物體之間的相對角度。這樣才能通過關節限制或固定的相對角來正確地約束旋轉。初始化幾何資料可能有些乏味。所以很多關節提供了初始化函式,消除了大部分工作。然而,這些初始化函式通常只應用於原型,在產品程式碼中應該直接地定義幾何資料。這能使關節行為更加穩固。其餘的關節定義資料依賴於關節的型別。下面我們來介紹它們。2)距離關節
距離關節是最簡單的關節之一,它描述了兩個物體上的兩個點之間的距離應該是常量。當你指定一個距離關節時,兩個物體必須已在應有的位置上。隨後,你指定兩個世界座標中的錨點。第一個錨點連線到物體 1,第二個錨點連線到物體 2。這些點隱含了距離約束的長度。
這是一個距離關節定義的例子。在此我們允許了碰撞。
b2DistanceJointDef jointDef;jointDef.Initialize(myBody1, myBody2, worldAnchorOnBody1,
worldAnchorOnBody2);
jointDef.collideConnected = true;
3)旋轉關節
一個旋轉關節會強制兩個物體共享一個錨點,即所謂鉸接點。旋轉關節只有一個自由度:兩個物體的相對旋轉。這稱之為關節角。
要指定一個旋轉關節,你需要提供兩個物體以及一個世界座標的錨點。初始化函式會假定物體已經在應有位置了。在此例中,兩個物體被旋轉關節連線於第一個物體的質心。
b2RevoluteJointDef jointDef;jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter());
這裡是對上面旋轉關節定義的修訂;這次,關節擁有一個限制以及一個馬達,後者用於模擬摩擦。
b2RevoluteJointDef jointDef;jointDef.Initialize(body1, body2, myBody1->GetWorldCenter());//使用 Initialize() 建立關節時,旋轉關節角為 0,無論兩個物體當前的角度怎樣。
jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees最小角度jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees最大角度jointDef.enableLimit = true;
jointDef.maxMotorTorque = 10.0f;//馬達
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;
你可以訪問旋轉關節的角度,速度,以及扭矩。
float32 GetJointAngle() const;
float32 GetJointSpeed() const;
float32 GetMotorTorque() const;
你也可以在每步中更新馬達引數。
void SetMotorSpeed(float32 speed);void SetMaxMotorTorque(float32 torque);關節馬達有一些有趣的能力。你可以在每個時間步中更新關節速度,這可以使關節像正弦波一樣來回
移動,或者按其它什麼函式運動。// ... Game Loop Begin ...myJoint->SetMotorSpeed(cosf(0.5f * time));// ... Game Loop End ...你還可以使用關節馬達來追蹤某個關節角度。例如:// ... Game Loop Begin ...float32 angleError = myJoint->GetJointAngle() - angleTarget;
float32 gain = 0.1f;
myJoint->SetMotorSpeed(-gain * angleError);
// ... Game Loop End ...通常來講你的增益引數不應過大,否則你的關節可能會變得不穩定。
4)移動關節
移動關節(prismatic joint)允許兩個物體沿指定軸相對移動,它會阻止相對旋轉。因此,移動關節只有一個自由度。
移動關節的定義有些類似於旋轉關節;只是轉動角度換成了平移,扭矩換成了力。以這樣的類比,我們來看一個帶有關節限制以及馬達摩擦的移動關節定義: b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter(),
worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.motorForce = 1.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;
旋轉關節隱含著一個從螢幕射出的軸,而移動關節明確地需要一個平行於螢幕的軸。這個軸會固定於兩個物體之上,沿著它們的運動方向。就像旋轉關節一樣,當使用 Initialize() 建立移動關節時,移動為 0。所以一定要確保移動限制範圍內包含了 0。移動關節的用法類似於旋轉關節,這是它的相關成員函式:
float32 GetJointTranslation() const;float32 GetJointSpeed() const;
float32 GetMotorForce() const;
void SetMotorSpeed(float32 speed);
void SetMotorForce(float32 force);
5)滑輪關節
滑輪關節用於建立理想的滑輪,它將兩個物體接地(ground)並連線到彼此。這樣,當一個物體升起時,另一個物體就會下降。滑輪的繩子長度取決於初始時的狀態。
length1 + length2 == constant
你還可以提供一個係數(ratio)來模擬滑輪組,這會使滑輪一側的運動比另一側要快。同時,一側的約束力也比另一側要小。你也可以用這個來模擬機械槓桿(mechanical leverage)。length1 + ratio * length2 == constant 舉個例子,如果係數是 2,那麼 length1 的變化會是 length2 的兩倍。另外連線 body1 的繩子的約束力將會是連線 body2 繩子的一半。當滑輪的一側完全展開時,另一側的繩子長度為零,這可能會出問題。此時,約束方程將變得奇異。因此,滑輪關節約束了每一側的最大長度。另外出於遊戲原因你可能也希望控制這個最大長度。最大長度能提高穩定性,以及提供更多的控制。
這是一個滑輪定義的例子:b2Vec2 anchor1 = myBody1->GetWorldCenter();b2Vec2 anchor2 = myBody2->GetWorldCenter();
b2Vec2 groundAnchor1(p1.x, p1.y + 10.0f);
b2Vec2 groundAnchor2(p2.x, p2.y + 12.0f);
float32 ratio = 1.0f;
b2PulleyJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2,
anchor1, anchor2, ratio);
jointDef.maxLength1 = 18.0f;
jointDef.maxLength2 = 20.0f;
滑輪關節提供了當前長度:
float32 GetLength1() const;float32 GetLength2() const;6) 齒輪關節
如果你想要建立複雜的機械裝置,你可能需要齒輪。原則上,在 Box2D 中你可以用複雜的形狀來模擬輪齒,但這並不十分高效,而且這樣的工作可能有些乏味。另外,你還得小心地排列齒輪,保證輪齒能平穩地齧合。Box2D 提供了一個建立齒輪的更簡單的方法:齒輪關節。
齒輪關節需要兩個被旋轉關節或移動關節接地(ground)的物體,你可以任意組合這些關節型別。另外,建立旋轉或移動關節時,Box2D 需要地(ground)作為 body1。類似於滑輪的係數,你可以指定一個齒輪系數(ratio),齒輪系數可以為負。另外值得注意的是,當一個是旋轉關節(有角度的)而另一個是移動關節(平移)時,齒輪系數是長度或長度分之一。coordinate1 + ratio * coordinate2 == constant這是一個齒輪關節的例子:
b2GearJointDef jointDef;jointDef.body1 = myBody1;
jointDef.body2 = myBody2;