最近把box2d研究了一遍,整體上差很少是瞭解了,可是在運行那個小球下落的demo時候發現移動速度與物理狀況不同,仔細研究了半天才發現原來有這麼多細節概念要注意。這必須記錄下來,對之後有一樣問題的人確定會有所啓發。app
物理模型:ide
一個邊長爲1m的正方形盒子懸停在20m高的位置,而後自由落體。根據公式h=gt^2/2可知下落到0m高時應該是2s。post
對應box2d的模型:測試
一個形狀爲邊長爲1m的正四角型,不存在任何阻尼,放在20m高的空中,而後設置向下引力爲9.8m/s^2。動畫
注意一:單位換算 spa
這個地方是新手最容易出錯的。因爲libgdx中的距離單位都是px,但box2d的距離單位都是m,因此你程序中全部要計算的值都要確保單位正確。 rest
具體來講,好比個人顯示器分辨率是1440px*900px,那我建立一個遊戲界面爲200px*200px的,如new JoglApplication(new DemoGame(), "Demo Demo", 200, 200, false);。這個很好理解,你的整個遊戲界面就是這個大小,與顯示器的分辨率是正比關係。遊戲
而後,遊戲中會有一個camera來看具體的界面內容,這裏只考慮正投影攝像機(不會由於距離對界面放大或者縮小),那麼定義其能看到的也是200px*200px的畫面new OrthographicCamera(200, 200),這 樣就真的是畫面上的1px表明你顯示器的1px了。camera大小的變化是你遊戲中的1px與實際顯示器1px是成反比的。我以爲這個與遊戲界面設置同樣最好理解了。ci
那若是我但願10個像素表明1m(scale=10px/m),那計算出來的每一個物體位移1m,你對應的actor都要移動10個像素。就是這麼個換算關係。get
注1:box2d中的單位都是標準單位,好比長度就是m,重量就是kg。
注2:box2d的物理計算與畫面上的actor是分開的,須要把物理數據再經過同步來反應到actor上。可是在Box2DDebugRenderer能夠直觀的看這個物理世界。
注意二:最大速度限制
這個時候物體移動起來有點意思了,當把世界引力加到很大,詭異的事情發生了,開始仍是加速運動,忽然到後面明顯變成勻速運動了。查了一遍資料才發現,原來box2d把每幀位移限制在2m/frame,因此在60Hz的刷新率狀況下物體最大移動速度就是120m/s了。悲劇的是這個限制是hard coding,這多是讓物體看起來移動比較連續吧。因此要把速度加上去,那就只能提升幀數了,好比world.step(1/100f, 3, 3);能提升最大速度就是200m/s,但這麼修改會有一個嚴重的問題,就是讓物體位移的時間刻度和實際時間不一致了,畫面看上去會和人感知不同。
注:timestep是時間步長,這是動畫裏面一個重要概念,物理世界每次計算出來新的位置就必需要知道過了多久時間,這就讓離散的時間點計算變成一個連續的動畫。
注意三:初始化的影響
以上工做都作好了,發現下落時間怎麼統計都不對。弄了半天才發反應過來原來第一次render的時候就已經開始計算物體移動了,而且把這個deltaTime帶入進去計算了。因爲初始化速度會比較慢,程序遠達不到60Hz的刷新度,因此我把那個時間做爲第一次下落時間實際上是已經晚了的。因此乾脆把第一次render再讓每一個物體喚醒,這就獲得指望的結果了。
附上測試代碼:
- public class DemoGame implements ApplicationListener {
- protected OrthographicCamera camera;
- protected Box2DDebugRenderer renderer; // 測試用繪製器
- private World world;
- private float scale = 10f; // 屏幕的縮放比例,10像素/米
- private long start = 0;
- private Body body;
- private boolean isFinished = true;
- private int count = 0;
- private float totalTime = 0;
- @Override
- public void create() {
- Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
- camera = new OrthographicCamera(200/scale, 200/scale);// 這裏爲了展現物理世界,把視野也轉換成m
- camera.position.set(100/scale, 100/scale, 0); // 攝像機的postion是畫面的中心點
- renderer = new Box2DDebugRenderer();
- world = new World(new Vector2(0, -9.8f), true); // 通常標準重力場
- BodyDef bd = new BodyDef(); //聲明物體定義
- bd.position.set(10, 20);
- bd.type = BodyType.DynamicBody;
- body = world.createBody(bd); //經過world建立一個物體
- PolygonShape box = new PolygonShape();
- box.setAsBox(0.5f, 0.5f); // 注意單位是邊長的一半
- FixtureDef fd = new FixtureDef();
- fd.shape = box;
- fd.friction = 0;
- fd.restitution = 0;
- fd.density = 1;
- body.createFixture(fd); //將形狀和密度賦給物體
- body.setLinearDamping(0f); // 沒有線性阻尼
- body.setAngularDamping(0f); // 沒有旋轉阻尼
- body.setAwake(false);
- }
- @Override
- public void render() {
- world.step(Gdx.app.getGraphics().getDeltaTime(), 3, 3);
- if(start == 0) {
- count = 1;
- start = System.currentTimeMillis();
- System.out.println("start postion:" + body.getPosition().y);
- body.setAwake(true); // 讓渲染的第一幀再讓物體運動,這樣時間得到才準確,
- // 不然第一個deltaTime有初始化時間,會讓整個計算不許
- } else {
- if(isFinished){
- System.out.println("動畫渲染次數: " + count++);
- System.out.println("物體移動速度: " + body.getLinearVelocity().y);
- System.out.println("---------------------------------");
- totalTime += Gdx.app.getGraphics().getDeltaTime();
- }
- if(body.getPosition().y <= 0 && isFinished) {
- System.out.println("實際時間差:" + (System.currentTimeMillis() - start)/1000f);
- System.out.println("圖像增量統計時間差:" + totalTime);
- System.out.println("end postion:" + body.getPosition().y);
- isFinished = false;
- world.destroyBody(body);
- }
- }
- GL10 gl = Gdx.app.getGraphics().getGL10();
- gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
- camera.update();
- camera.apply(gl);
- renderer.render(world, camera.combined);
- }
- @Override
- public void dispose() {
- renderer.dispose();
- world.dispose();
- renderer = null;
- world = null;
- }
- @Override
- public void pause() {
- }
- @Override
- public void resize(int width, int height) {
- }
- @Override
- public void resume() {
- }
- }