1. 程式人生 > >2.Libgdx擴充套件學習之Box2D_剛體和形狀

2.Libgdx擴充套件學習之Box2D_剛體和形狀

文章中涉及的很多概念,都是來自《Box2D中文手冊》。有統一的解釋方便理解。

物體(剛體/Body)

概念介紹

物體具有位置和速度。可以將力(forces)、扭矩(torques)、衝量(impulses)應用到物體上。 物體可以是靜態的(static)、運動但不受力的(kinematic)或動態的(dynamic)。這是物體的型別定義:
1.Static Body
static 物體在模擬時不會運動,就好像它具有無窮大的質量。在 Box2D 內部,會將 static 物體的質量和質量的倒數儲存為零。 static 物體可以讓使用者手動移動。它的速度為零,另外也不會和其它static 或 kinematic 物體相互碰撞。
2. Kinematic Body
kinematic 物體在模擬時以一定的速度運動,但不受力的作用。它們可以讓使用者手動移動,但通常的做法是設定一定的速度來移動它。 kinematic 物體的行為表現就好像它具有無窮大的質量,Box2D 將它的質量和質量的倒數儲存為零。
3. Dynamic Body
dynamic 物體被完全模擬。它們可以讓使用者手動移動,但通常它們都是受力的作用而運動。dynamic 物體可以和其它所有型別的物體相互碰撞。 dynamic 物體的質量總是有限大的,非零的。如果你試圖將它的質量設定為零,它會自動地將質量修改成一千克,並且它不會轉動。

物體定義

在建立物體之前你需要先建立物體定義(BodyDef)。物體定義含有建立並初始化物體所需的資料。Box2D 會從物體定義中複製資料,並不會儲存它的指標。這意味著你可以重複使用同一個物體定義去建立多個物體。
Libgdx也提供給我們一個Java版本的BodyDef,現在可以先簡單瀏覽一下程式碼:

public class BodyDef {
    /** The body type. static: zero mass, zero velocity, may be manually moved kinematic: zero mass, non-zero velocity set by user,
     * moved by solver dynamic: positive mass, non-zero velocity determined by forces, moved by solver */
public enum BodyType { StaticBody(0), KinematicBody(1), DynamicBody(2); private int value; private BodyType (int value) { this.value = value; } public int getValue () { return value; } }; /** The body type: static, kinematic, or dynamic. Note: if a dynamic body would have zero mass, the mass is set to one. **/
public BodyType type = BodyType.StaticBody; /** The world position of the body. Avoid creating bodies at the origin since this can lead to many overlapping shapes. **/ public final Vector2 position = new Vector2(); /** The world angle of the body in radians. **/ public float angle = 0; /** The linear velocity of the body's origin in world co-ordinates. **/ public final Vector2 linearVelocity = new Vector2(); /** The angular velocity of the body. **/ public float angularVelocity = 0; /** Linear damping is use to reduce the linear velocity. The damping parameter can be larger than 1.0f but the damping effect * becomes sensitive to the time step when the damping parameter is large. **/ public float linearDamping = 0; /** Angular damping is use to reduce the angular velocity. The damping parameter can be larger than 1.0f but the damping effect * becomes sensitive to the time step when the damping parameter is large. **/ public float angularDamping = 0; /** Set this flag to false if this body should never fall asleep. Note that this increases CPU usage. **/ public boolean allowSleep = true; /** Is this body initially awake or sleeping? **/ public boolean awake = true; /** Should this body be prevented from rotating? Useful for characters. **/ public boolean fixedRotation = false; /** Is this a fast moving body that should be prevented from tunneling through other moving bodies? Note that all bodies are * prevented from tunneling through kinematic and static bodies. This setting is only considered on dynamic bodies. * @warning You should use this flag sparingly since it increases processing time. **/ public boolean bullet = false; /** Does this body start out active? **/ public boolean active = true; /** Scale the gravity applied to this body. **/ public float gravityScale = 1; }
物體型別

前面介紹過,物體有3中型別: static、kinematic、和dynamic。在建立時就應該確定號物體型別,否則以後修改,代價很高
bodyDef.type = BodyType.Dynamic

位置和角度

物體定義提供了一個在建立時初始化位置的機會。 這比在World原點建立物體後再移動到某個位置更高效。

不要在原點建立物體後再移動它。如果在原點上同時建立了幾個物體,效能會很差。

阻尼

阻尼用於減小物體在世界中的速度。阻尼跟摩擦有所不同,摩擦僅在物體有接觸的時候才會發生。阻尼並不能取代摩擦,往往這兩個效果需要同時使用。
阻尼引數的範圍可以在 0 到無窮大之間, 0 表示沒有阻尼,無窮大表示滿阻尼。通常來說,阻尼的值應 該在 0 到 0.1 之間。通常我不使用線性阻尼, 因為它會使物體看起來有點漂浮。
bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;
阻尼類似穩定性與效能, 在值較小的時候阻尼效應幾乎不依賴於時間步,值較大的時候阻尼效應將隨著時間步而變化。如果你使用固定的時間步(推薦)這就不是問題了。

休眠引數

當 Box2D 確定一個物體(或一組物體)已停止移動時,物體就會進入休眠狀態。休眠物體只消耗很小的 CPU 開銷。如果一個醒著的物體接觸到了一個休眠中的物體,那麼休眠中的物體就會醒過來。當物體上的關節或觸點被摧毀的時候,它們同樣會醒過來。你也可以手動地喚醒物體。
通過物體定義,你可以指定一個物體是否可以休眠,或者建立一個休眠的物體。
bodyDef.allowSleep = true;
bodyDef.awake = true;

固定旋轉

想讓一個剛體,比如某個角色,具有固定的旋轉角。這樣物體即使在負載下,也不會旋轉。你可以設定 fixedRotation 來達到這個目的:
bodyDef.fixedRotation = true;
固定旋轉標記使得轉動慣量和它的倒數被設定成零。

子彈

遊戲模擬通常以一定幀率(frame rate)產生一系列的圖片。這就是所謂的離散模擬。在離散模擬中,在一個時間步內剛體可能移動較大距離。如果一個物理引擎沒有處理好大幅度的運動,你就可能會看見一些物體錯誤地穿過了彼此。這被稱為隧穿效應(tunneling)。
預設情況下, Box2D 會通過連續碰撞檢測(CCD)來防止動態物體穿越靜態物體。因此在 Box2D 中,高速移動的物體可以標記成子彈(bullet)。子彈跟 static 或者 dynamic 物體之間都會執行 CCD。你需要按照遊戲的設計來決定哪些物體是子彈。如果你決定一個物體應該按照子彈去處理,可使用下面的設定。子彈標記隻影響 dynamic 物體。
bodyDef.bullet = true;

活動狀態

你可能希望建立一個物體並不參與碰撞和動態模擬。這狀態跟休眠有點類似,但並不會被其它物體喚醒,它上面的 fixture 也不會 被放到 broad-phase 中。也就是說,物體不會參於碰撞檢測,光線投射(ray casts)等等。你可以建立一個非活動的物體,之後再啟用它。
bodyDef.active = true;
關節也可以連線到非活動的物體。但這些關節並不會被模擬。你要小心,當啟用物體時,它的關節不會被扭曲(distorted)。
注意,啟用一個物體和重新建立一個物體的開銷差不多。因此你不應該在流世界( streaming worlds)中使用啟用,而應該用建立和銷燬來節省記憶體。
(譯註: streaming worlds 是指該世界中的大多數物體是動態建立的,而不是一開始就有的。 )

重力因子

可以使用重力因子來調整單個物體上的重力。這需要足夠的細心,增加的重力會降低穩定性。
bodyDef.gravityScale = 0.0f;

使用物體

建立完一個物體之後,你可以對它進行許多操作。其中包括設定質量屬性,訪問其位置和速度,施加力,以及轉換點和向量,即BodyDef裡面定義的資料都可以後期更改。

質量資料

每個物體都有質量(標量)、質心(二維向量)和轉動慣性(標量)。對於 static 物體,它的質量和轉動慣性都被設為零。當物體設定成固定旋轉(fixed rotation),它的轉動慣性也是零。

public class MassData {
    /** The mass of the shape, usually in kilograms. **/
    public float mass;  // 質量(標量)

    /** The position of the shape's centroid relative to the shape's origin. **/
    public final Vector2 center = new Vector2(); // 質心(二維向量)

    /** The rotational inertia of the shape about the local origin. **/
    public float I; // 轉動慣性(標量)
}

通常情況下,當 fixture 新增到物體上時,物體的質量屬性會自動地確定。你也可以在執行時(runtime)調整物體的質量。當你有特殊的遊戲方案需要改變質量時,可以這樣做。
setMassData (MassData data)

形狀(碰撞檢測)

形狀描述了可相互碰撞的幾何物件,它的使用獨立於物理模擬。知道如何建立shape,並將之附加到剛體上。
Shape 是個基類, Box2D 的各種形狀都實現了這個基類。此基類定義了幾個函式:

  • 判斷一個點與形狀是否有重疊(碰撞)
  • 在形狀上執行光線投射(ray cast)
  • 計算形狀的AABB
  • 計算形狀的質量
    每個形狀都有成員變數:型別(type)和半徑(radius)。對於多邊形,半徑也是有意義的。

    注意 shape並不知道body,也與力學系統無關。Shape 採用一種緊湊格式來進行儲存,這種格式經過尺寸和效能的優化。因此, shape 並不方便移動,你必須通過手動的設定形狀頂點來移動 shape。然而,當使用 fixture 將 shape 新增到 body 上之後, shape 就會和他的宿主 body 一起移動

  • 當一個shape沒有新增到body上時,它的頂點用世界座標系來表示。

  • 當一個shape新增到body上時,它的頂點用區域性座標系來表示。
        // Body位置為(1, 1), 多邊形和Body繫結在一起,那邊座標都要+1
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(1, 1);
        Body body1 = world.createBody(bodyDef);

        PolygonShape polygonShape = new PolygonShape();

        Vector2[] vector2s = new Vector2[4];
        vector2s[0] = new Vector2(0, 0);
        vector2s[1] = new Vector2(0.0f, 1.0f);
        vector2s[2] = new Vector2(1.0f, 1.0f);
        vector2s[3] = new Vector2(1.0f, 0.0f);
        polygonShape.set(vector2s);
        body1.createFixture(polygonShape, 0);
圓形

圓形有位置和半徑。圓形是實心的,沒有辦法使圓形變成空心。
CircleShape circleShape = new CircleShape();
circleShape.setRadius(0.5f);
circleShape.setPosition(new Vector2(3.0f, 3.0f));

多邊形

Box2D 的多邊形是實心的凸(Convex)多邊形。多邊形是實心的,而不是空心的。一個多邊形必須有 3 個或以上的頂點。多邊形的頂點以逆時針( counter clockwise winding, CCW)的順序儲存。
Vector2 vetices[3]
vertices[0] = new Vector2(0.0f, 0.0f)
vertices[1] = new Vector2(1.0f, 0.0f)
vertices[2] = new Vector2(0.0f, 1.0f)
PolygonShape polygon = new PolygonShape()
polygon.set(vetices)
另外,多邊形有一些方便的方法來建立box
public void setAsBox (float hx, float hy)
這裡寫圖片描述

邊框形狀(EdgeShape)

邊框形狀由一些線段組成。可輔助為你的遊戲建立一個形狀自由的靜態環境。邊框形狀的主要限制在於它們能夠與圓形和多邊形碰撞,但它們之間卻不會碰撞。 Box2D 使用的碰撞演算法要求兩個碰撞物體中至少有一個有體積。邊框形狀沒有體積。所以邊框形狀之間的碰撞是不可能的。
EdgeShape edgeShape = new EdgeShape();
edgeShape.set(5.4f, 3.0f, 7.3f, 2.0f);

連結形狀(ChainShape)

連結形狀提供了一種有效的方式,來將許多邊框連線在一起,用以構建你的靜態遊戲世界。連結形狀自動消除幽靈碰撞,並提供兩側的碰撞。
這裡寫圖片描述

/**
 * 主要介紹剛體(rigid body)和形狀(shap)
 *
 * 剛體主要有3類 1. Static Body  靜態剛體.零質量,沒有運動,不接受反作用力等,可以人為移動
 *  2. Kinematic Body 運動剛體,零質量,求解器(solver)控制速度
 *  3. Dynamic Body 動態剛體,有質量,速度受力影響,求解器控制速度
 *
 *  Shape 主要用作碰撞檢測,可以在任何時候建立。不再使用時要銷燬 dispose(),比如可以在呼叫body.createFixture() 之後銷燬
 *   1. Circle  圓形
 *   2. Edge
 *   3. Polygon 多邊形
 *   4. Chain 鍊形
 */
public class BodyShape extends ApplicationAdapter {

    World world;
    Box2DDebugRenderer box2DDebugRenderer;
    Body rectBody, circleBody, chainBody, edgeBody;

    OrthographicCamera camera;

    float scene_width = 12.8f;
    float scene_height = 7.2f;

    @Override
    public void create() {
        world = new World(new Vector2(0.0f, -9.8f), true);
        box2DDebugRenderer = new Box2DDebugRenderer();

        BodyDef bodyDef = new BodyDef();
        // 預設是Static Body,只有定義Dynamic 才會受重力影響
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set(1f, 3f);
//        bodyDef.angularVelocity = 1.0f;
        rectBody = world.createBody(bodyDef);
        circleBody = world.createBody(bodyDef);
        chainBody = world.createBody(bodyDef);
        edgeBody = world.createBody(bodyDef);

        // 正方形
        PolygonShape polygonShape = new PolygonShape();
        polygonShape.setAsBox(0.6f, 0.3f);
        Gdx.app.log("BodyShape", "VertexCount = " + polygonShape.getVertexCount());

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = polygonShape;

        fixtureDef.density  = 2;
        fixtureDef.restitution = 1.0f;
        rectBody.createFixture(fixtureDef);
        polygonShape.dispose();

        CircleShape circleShape = new CircleShape();
        circleShape.setRadius(0.5f);
        circleShape.setPosition(new Vector2(3.0f, 3.0f));

        fixtureDef.shape = circleShape;
        fixtureDef.isSensor = false;
        circleBody.createFixture(fixtureDef);
        circleShape.dispose();

        EdgeShape edgeShape = new EdgeShape();
        edgeShape.set(5.4f, 3.0f, 7.3f, 2.0f);

        fixtureDef.shape = edgeShape;
        edgeBody.createFixture(fixtureDef);
        edgeShape.dispose();

        ChainShape chainShape = new ChainShape();
        float[] ver = {8.0f, 2, 8.9f, 3, 10.4f, 3, 11.4f, 4};
        chainShape.createChain(ver);
//        chainShape.createLoop(ver);

        fixtureDef.shape = chainShape;
        chainBody.createFixture(fixtureDef);
        chainShape.dispose();

        camera = new OrthographicCamera(scene_width, scene_height);
        camera.position.set(scene_width / 2, scene_height / 2, 0);
        camera.update();

        /** 建立測試地面盒子 **/
        createGround();
    }

    @Override
    public void dispose() {
        world.dispose();
        box2DDebugRenderer.dispose();
    }

    @Override
    public void render() {
        world.step( 1/ 60f, 6, 2);


        Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1.0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        box2DDebugRenderer.render(world, camera.combined);
    }

    private void createGround() {
        BodyDef goundBodyDef = new BodyDef();
        goundBodyDef.position.set(scene_width * 0.5f, 0.5f);

        Body groundBody = world.createBody(goundBodyDef);

        PolygonShape groundBox = new PolygonShape();
        groundBox.setAsBox(scene_width * 0.5f, 0.5f);

        groundBody.createFixture(groundBox, 0);
        groundBox.dispose();
    }
}

這裡寫圖片描述