內容很多摘自
Aman JIANG(江超宇)翻譯的Box2D v2.0.1 使用者手冊
第02章 Hello Box2D
Box2D的發布包中有個Hello World程式。程式建立了一個大大的地面盒(ground box)和一個小小的動態盒(dynamic box)。盒子的位置隨著時間的變化而變化。代碼沒有涉及到圖形介面,你只能在控制台中看到文字輸出
這是個很好的例子, 展示了怎麼使用Box2D。
2.1 建立世界(Creating a World)
每個Box2D程式開始時都會建立一個b2World對象。b2World是物理樞紐(physics hub), 用於管理記憶體、對象和類比。根據自己的實際情況, 你可以在棧, 堆或資料區中建立出world。
建立Box2D的world很簡單。首先, 我們要定義重力向量,另外還要告訴world是否允許body在靜止時休眠。休眠中的body不需要任何類比。
b2Vec2 gravity(0.0f, -10.0f);
bool doSleep = true;
現在可以建立world對象了。注意,在這裡我們是在棧中建立world, 所以world不能離開它的範圍。
b2World world(gravity, doSleep);
我們已經有了自己的物理世界, 開始向裡面加東西了。
2.2 建立地面盒(Creating a Ground Box)
body用以下步驟來建立:
1. 用位置(position), 阻尼(damping)等來定義body
2. 通過world對象來建立body
3. 用形狀(shape), 摩擦(friction), 密度(density)等來定義fixture
4. 在body上來建立fixture
第一步,建立ground body。我們需要一個body定義。在定義中,我們指定ground body的初始位置。
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f,-10.0f);
第二步, 將body定義傳給world對象, 建立ground body。world對象並不保留body定義的引用。ground body是作為靜態物體(static body)建立的。靜態物體和其它靜態物體之間並沒有碰撞, 它們是固定的。當body的品質為零時, Box2D就認為它是靜態。物體品質的預設值就為零, 所以它們預設就是靜態。
b2Body* groundBody =world.CreateBody(&groundBodyDef);
第三步, 建立地面多邊形。我們用簡便函數SetAsBox使得地面多邊形構成一個盒子形狀,盒子的中心點就是父body的原點。
b2PolygonShape groundBox;
groundBox.SetAsBox(50.0f, 10.0f);
SetAsBox函數接收半個寬度和半個高度作為參數。因此在這種情況下,地面盒就是100個單位寬(x軸),20個單位高(y軸)。Box2D已被調諧到使用米,千克和秒做單位。你可以認為長度單位就是米。當物體的大小跟真實世界一樣時,Box2D通常工作良好。例如,一個桶約1米高。由於浮點演算法的局限性,使用Box2D類比冰川或沙塵的運動並不是一個好主意。
第四步, 我們建立shape fixture, 以完成ground body。這步中,我們有個簡便方法。我們並不需要修改fixture預設的材質屬性, 可以直接將形狀傳給body而不需要建立fixture的定義。隨後的教程中, 我們將會看到如何使用fixture定義來定製材質屬性。
groundBody->CreateFixture(&groundBox);
Box2D並不儲存shape的引用。它把資料複製到一個新的b2Shape對象中。
注意,每個fixture都必須有一個父body,即使fixture是靜態。然而,你可以把所有靜態fixture都依附於單個靜態body之上。之所以需要這個靜態body, 是為了保證Box2D內部的代碼更具一致性,以減少潛在的bug數量。
可能你已經注意到, 多數Box2D類型都有b2首碼。這是為了降低它和你的代碼之間名字衝突的機會。
2.3 建立動態物體(Creating a Dynamic Body)
現在我們已經有了一個地面body,我們可以使用同樣的方法來建立一個動態body。除尺寸之外的主要區別是, 我們必須為動態body設定品質屬性。
首先我們用CreateBody建立body。預設情況下,body是靜態, 所以在構造時候應該設定b2BodyType使得body成為動態
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body =world.CreateBody(&bodyDef);
注意
如果你想body受力的影響而運動, 你必須將body的類型設為b2_dynamicBody。
然後,我們建立一個多邊形shapde, 並將它附加到fixture定義上。我們先建立一個box shape:
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(1.0f, 1.0f);
接下來我們使用box建立一個fixture定義。注意, 我們把密度值設定為1,而密度值預設是0。並且,fixture的摩擦係數設定為0.3。
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
使用fixture定義, 我們現在就可以建立fixture。這會自動更新body的品質。要是你喜歡, 你可以為body添加許多不同的fixture。每個fixture都會增加物體的總品質。
body->CreateFixture(&fixtureDef);
這就是初始化過程。現在我們已經做好準備,可以開始類比了。
2.4 類比(Box2D的)世界
我們已經初始化好了地面box和一個動態box。該讓牛頓來接手了。我們只有少數幾個問題需要考慮。
Box2D使用了一個叫積分器(integrator)的數值演算法。 積分器在離散的時間點上類比連續的物理方程。 它與傳統的遊戲動畫迴圈一同運行。我們需要為Box2D選取一個時間步。通常來說用於遊戲的物理引擎需要至少 60Hz 的速度,也就是 1/60 的時間步。你可以使用更大的時間步,但是你必須更加小心地為你的世界調整定義。我們也不喜歡時間步變化得太大,所以不要把時間步關聯到幀頻(除非你真的必須這樣做)。直截了當地,這個就是時間步:
float32 timeStep = 1.0f / 60.0f;
除積分器外,Box2D代碼還使用了約束求解器(constraint solver)。約束求解器用於解決類比中的所有約束,一次一個。單個的約束會被完美的求解,然而當我們求解一個約束的時候,我們就會稍微耽誤另一個。要得到良好的解,我們需要多次迭代所有約束。
約束求解有兩個階段:速度、位置。在速度階段,求解器會計算必要的衝量,使得物體正確運動。而在位置階段,求解器會調整物體的位置,減少物體之間的重疊。每個階段都有自己的迭代計數。此外,如果誤差已足夠小的話,位置階段的迭代可能會提前退出。
對於速度和位置,建議的Box2D迭代次數都是10次。你可以按自己的喜好去調整這個數字,但要記得它是效能與精度之間的折中。更少的迭代會增加效能但降低精度,同樣地,更多的迭代會降低效能但能提高類比品質。對於這個簡單樣本,我們不需要多次迭代。這是我們選擇的迭代次數。
int32 velocityIterations = 6;
int32 positionIterations = 2;
時間步和迭代數是完全無關的。一個迭代並不是一個子步。一次迭代就是在時間步之中的單次遍曆所有約束,你可以在單個時間步內多次遍曆約束。
現在我們可以開始類比迴圈了, 在你的遊戲中, 類比迴圈和遊戲迴圈可以合并起來。每次遊戲迴圈你都應該調用b2World::Step, 通常調用一次就夠了, 這取決於幀頻以及物理時間步。步進後,你應當調用b2World::ClearForces清除你施加到body上的任何力。
這個Hello World程式設計得非常簡單, 它沒有圖形輸出。代碼會列印出動態body的位置以及旋轉角, 有文字輸出總比完全沒有輸出好。這就是類比 1 秒鐘內 60 個時間步的迴圈:
for (int32 i = 0; i < 60; ++i)
{
world.Step(timeStep,velocityIterations, positionIterations);
world.ClearForces();
b2Vec2 position =body->GetPosition();
float32 angle =body->GetAngle();
printf("%4.2f %4.2f%4.2f\n", position.x, position.y, angle);
}
輸出展示了動態box降落到地面的情況。你的輸出看起來應當是這樣:
0.00 4.00 0.00
0.00 3.99 0.00
0.00 3.98 0.00
...
0.00 1.25 0.00
0.00 1.13 0.00
0.00 1.01 0.00
2.5 清理
當world對象超出它的範圍,或通過指標將其 delete 時, 分配給body, fixture, joint使用的記憶體都會被釋放。這能使你的生活變得更簡單。然而,你應該將body, fixture或joint的指標都清零,因為它們已經無效了。
2.6 Testbed例子
一旦你征服了 HelloWorld 例子,你應該開始看 Box2D 的 testbed 了。testbed 是個單元測試架構,也是個示範環境, 這是它的一些特點:
• 可移動和縮放的攝像機
• 可用滑鼠選中依附在動態物體上的形狀
• 可擴充的測試集
• 通過圖形介面選擇測試,調整參數,以及設定調試繪圖
• 暫停和單步類比
• 文字渲染
在 testbed 中有許多 Box2D 的測試案例,以及架構本身的執行個體。我鼓勵你通過研究和修改它來學習 Box2D。
注意:testbed 是使用 freeglut 和 GLUI 寫成的,testbed 本身並不是 Box2D 庫的一部分。Box2D本身不知道如何渲染,就像 HelloWorld 例子一樣,使用 Box2D 並不一定需要渲染。