創建物理世界

什麼是物理世界,是對你遊戲的物理模擬. 使用前確認你的引擎包含了下載的Physics Extension包.

創建物理世界的時候,有兩類型供選擇, 標準(Standard)和FixedStepPhysicsWorld. 區別?

後者多了個參數,指定了每秒更新固定次數. 下例指示瞭如何創建物理世界:

private PhysicsWorld physicsWorld;

private void initPhysics()

{

    physicsWorld = new PhysicsWorld(new Vector2(0, SensorManager.GRAVITY_EARTH), false);

    yourScene.registerUpdateHandler(physicsWorld);

}

1. 如何控制重力

創建物理世界的時候必須傳遞Vector2(注:向量),來指定重力. 大多數時候第一個參數是0. 但是有時候你需要模擬其它力,比如橫向的風力,那X就不是0了.

2.使用FixedStepPhysicsWorld

FixedStepPhysicsWorld是PhysicsWorld的子類,所以用法上大體一致, 這個類使用固定的幀數, 這個由你的需要來定.但是過大的幀數設定在配置差的機器上就會有差的表現,所以你需要自己度量.

l 不同剛體間的區別:

在BOX2D中有以下三類剛體:

· 靜態剛體(Static)

· 運動剛體(Kinematic)

· 動態剛體(Dynamic)

1.靜態剛體

靜態剛體在物理模擬中不會移動,就好像有無限重力一樣(換言之,就是永遠在同一地方,不會受任何力的影響). 比如,滾動的遊戲中的地面.(注: 但是會和其它剛體發生作用的)

2.運動剛體

運動剛體在模擬中根據它自己的速度來運動, 但是不會對力的作用有反應. 通過設置它的速度來控制運動, 不會與其它剛體發生作用. 如,在跑酷遊戲中滾動的背景.

3.動態剛體

動態剛體是全模擬的,可以被使用者移動,也可以被力移動. 動態剛體可以與其它剛體發生碰撞.

重要說明:

總是選擇正確的剛體類型, 因爲 模擬是非常消耗性能的,顯然對性能最友好的靜態類型.

值得一提的是,在創建後也能改變剛體的類型.

l 創建剛體

創建剛體在在這個引擎中小菜一碟. 你可以使用基本形狀(也可以有複雜形狀):

· BoxBody- Rectangles, Squares

· LineBody

· CircleBody

1.例:

private void createBody()

{

    final FixtureDef objectFixtureDef = PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f);

    PhysicsFactory.createBoxBody(physicsWorld, sprite, BodyType.StaticBody, objectFixtureDef);

}

創建一個新的Body 時候,應當指定剛體的類型, 物理世界,和屬性fixture.  fixture有三個參數:

· 密度(Density)

· 張力(Elasticity)

· 摩擦(Friction)

這些值根據你的需要來.

2.創建移動的剛體

如果要創建一個移動的剛體,就要註冊物理關聯(Physics Connector),什麼意思? 就是讓你的形狀/精靈跟隨剛體運動, 不然精靈就不會移動.

physicsWorld.registerPhysicsConnector(new PhysicsConnector(sprite, body, true, true));

參數說明:被關聯的精靈和剛體,是否更新位置,是否旋轉。

3.設置用戶數據:

通過設置用戶數據爲剛體提供“全局標識”,通過這個簡單的方法,很容易區分剛體間的聯接。

yourBody.setUserData("Player");

這裏的參數類型爲Object, 所以不一定爲String, 甚至可以是Sprite,這樣你就可以檢查剛體是不是精靈的實例。

l 處理剛體間的碰撞和鏈接

基於物理模擬的遊戲,都需要處理剛體間的碰撞和鏈接,使用ContactListener就好了。假如我們要在Body A 和 Body B產生接觸後執行某種動作,使用ContactListener使工作變得簡單。

首先,創建新的ContactListener

private ContactListener createContactListener()

{

    ContactListener contactListener = new ContactListener()

    {

        @Override

        public void beginContact(Contact contact)

        {

            final Fixture x1 = contact.getFixtureA();

            final Fixture x2 = contact.getFixtureB();

        }

        @Override

        public void endContact(Contact contact)

        {

               

        }

        @Override

        public void preSolve(Contact contact, Manifold oldManifold)

        {

               

        }

        @Override

        public void postSolve(Contact contact, ContactImpulse impulse)

        {

               

        }

    };

    return contactListener;

}

這裏有兩個重要的函數:beginContact endContact。在beginContact 中得到兩個產生鏈接的剛體引用。

步驟二, 在物理系統中註冊創建的Listener

mPhysicsWorld.setContactListener(createContactListener());

通過設置的UserData, 可以在ContactListener中檢查是否是特定的剛體:yourBody.setUserData("player");

if (x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("monster"))

{

    Log.i("CONTACT", "BETWEEN PLAYER AND MONSTER!");

}

注意:這裏只檢查了一次,假設x2是玩家而x1是怪物,有時候你需要調換順序再檢查一次。

l 完全移除剛體

有時候你需要將剛體從系統中完全移動,一個常見的錯誤是沒有在UpdateThread線程中做,這可以導致錯誤和崩潰。正確的做法是:

mEngine.runOnUpdateThread(new Runnable()

{

    @Override

    public void run()

    {

        // Detete objects safely here.

    }  

}

1.完全移除剛體和形狀(shape):

如果已經關聯了PhysicsConnector, 應當先反註冊,再移除剛體,見代碼:

final PhysicsConnector physicsConnector =

physicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(shape);

mEngine.runOnUpdateThread(new Runnable() 

{

    @Override

    public void run() 

    {

        if (physicsConnector != null)

        {

             physicsWorld.unregisterPhysicsConnector(physicsConnector);

             body.setActive(false);

             physicsWorld.destroyBody(bbody);

             scene.detachChild(shape);

        }

    }

});

注:Shape是Entity的實例

l 物理系統的調試渲染器(Physics Debug Render)

這節介紹一個對每個開發者都非常有用的工具,特別是在創建不規則形狀的剛體的時候。你知道,剛體默認是不可見的,爲了更直觀,使用nazgee分享的調試渲染器吧。用來做什麼的?就是能讓你看到剛體的邊界!

1.如何使用:

 前提是AE的GLES2或者GLES2 AC版本。

 把源碼連接到你的工程

 與你的場景關聯

DebugRenderer debug = new DebugRenderer(mPhysicsWorld, getVertexBufferObjectManager());

pScene.attachChild(debug);

下載地址

使用這個東東後,你的遊戲速度會有明顯的下降,但是這僅僅作爲調試用,所以問題不是太大。

l 不規則的剛體形狀

有時候你發現有必要創建不規則的剛體形狀(不是簡單的矩形或圓),比如地面,有兩個辦法:

ü 在代碼中手動創建

ü 使用一些工具從精靈中生成多邊形剛體形狀

1.手動方式:

對於不太複雜的形狀(比如由幾個基本元素(fixture),矩形和感應器)這種方式很簡單,如果你想知道具體怎麼做,參見AE的這個例子: CLICK.

.使用工具

介紹兩個工作,一個免費一個付費的,但是可以免費試用。

Physics Editor by Andreas Löw (收費) - CLICK.

Vertex Helper by ZAN (免費) - CLICK

推薦第一個,更準備更容易。(注:打廣告?)

l 創建感應器或叫觸發器

先解釋到底什麼是感應器(sensor), 引用BOX2D手冊中的話:sensor 就是檢測碰撞但是不產生反應的裝置。你可以把任何裝置標記爲感應器,感應器可以爲靜態或者動態,記住一個剛體可以有多個裝置,因此是多個感應器和裝置的組合。

A sensor is a fixture that detects collision but does not produce a response.You can flag any fixture as being a sensor. Sensors may be static or dynamic. Remember that you may have multiple fixtures per body and you can have any mix of sensors and solid fixtures

1.用法:

假如遊戲中有一些區域,當玩家走進這些區域時會觸發一些事情(比如顯示消息)

final Sprite area = new Sprite(x, y, 100, 300, textureRegion, vbo);

FixtureDef areaFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 0);

areaFixtureDef.isSensor = true;

Body b = PhysicsFactory.createBoxBody(physicsWorld, area, BodyType.StaticBody, areaFixtureDef);

scene.attachChild(area);

FixtureDef 對象中設置isSensor屬性爲true.

問題來了,當玩家走進上面創建的區域時如何檢測呢?

處理剛體間的接觸就是使用Contact Listener ,參見前面的文章。

· serUserData 設置標識。 

· beginContact() 檢查標識 

· 聲明一個新類域 boolean isInsideZoone = false;

· 如果在beginContact中產生接觸將這個布爾值設爲 true 

·  endContact() 將值設爲false

private boolean isInsideZoone = false;

 

private ContactListener createContactListener()

{

    ContactListener contactListener = new ContactListener()

    {

        @Override

        public void beginContact(Contact contact)

        {

            final Fixture x1 = contact.getFixtureA();

            final Fixture x2 = contact.getFixtureB();

           

            if(x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("area"))

            {

                isInsideZoone = true;

                //action on enter

            }

        }

 

        @Override

        public void endContact(Contact contact)

        {

            final Fixture x1 = contact.getFixtureA();

            final Fixture x2 = contact.getFixtureB();

           

            if(x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("area"))

            {

                isInsideZoone = false;

                //進入指定區域,執行特定動作

            }

        }

 

        @Override

        public void preSolve(Contact contact, Manifold oldManifold)

        {

               

        }

 

        @Override

        public void postSolve(Contact contact, ContactImpulse impulse)

        {

               

        }

    };

    return  contactListener;

}

l 如何克服重力

假如有一個動態剛體,不時地需要克服重力,很簡單,只需將與重力相反的力加到剛體上。

private Body body;

private void createAntiGravityBody()

{

    Rectangle yourEntity = new Rectangle(0, 0, 100, 100, vbo)

    {

        @Override

        protected void onManagedUpdate(float pSecondsElapsed)

        {

            super.onManagedUpdate(pSecondsElapsed);

            body.applyForce(-physicsWorld.getGravity().* body.getMass(),

                            -physicsWorld.getGravity().* body.getMass(),

                             body.getWorldCenter().x, 

                             body.getWorldCenter().y);

        }

    };

    yourEntity.setColor(Color.RED);

    body = PhysicsFactory.createBoxBody(physicsWorld, yourEntity, BodyType.DynamicBody, fixtureDef);

    scene.attachChild(yourEntity);

}

關鍵就是使用applyForce 方法。

注:看了評論,還有一個更簡單的方法body.setGravityScale(0); 

l 銷燬物理系統和所有的對象

前面已經講了如何銷燬單個剛體對象,本節講如何銷燬整個物理系統。

記住,必須在update thread線程中做

public void destroyPhysicsWorld()

{

    engine.runOnUpdateThread(new Runnable()

    {

        public void run()

        {

            Iterator<Body> localIterator = physicsWorld.getBodies();

            while (true)

            {

                if (!localIterator.hasNext())

                {

                    physicsWorld.clearForces();

                    physicsWorld.clearPhysicsConnectors();

                    physicsWorld.reset();

                    physicsWorld.dispose();

                    System.gc();

                    return;

                }

                try

                {

                    final Body localBody = (Body) localIterator.next();

                    GameScene.this.physicsWorld.destroyBody(localBody);

                } 

                catch (Exception localException)

                {

                    Debug.e(localException);

                }

            }

        }

    });

}

通過getBodies 獲取對象,釋放完全,再將整個physicsWorld清除!

l BOX2D的連接體

什麼是連接體,你可能也猜得到是用來做什麼的,我們可以把不同的剛體通過接頭連起來,請先閱讀下邊的重要提示:

IMPORTANT大意就是要使用最新版本的AEBOX2D

環境配置如下:

1.最新的AE, GLES2 Anchor Center CLICK HERE

2.最新的Box2DCLICK HERE

3.Box2D debug draw  CLICK HERE

連接類型:

· 旋轉接頭(Revolute joint 旋轉接頭強迫兩個剛體共享一個錨點通常叫做鉸點,旋轉接頭有一個旋轉自由度,即兩個剛體的相對旋轉角度。

· Distance joint - it maintains distance between two bodies, by specifying anchor points for those joints, some advantages and possibilities: you can manipulate distance on the runtime, you can also adjust damping ratio and mass-spring-damper frequency in Hertz to achieve different behaviour (you will often need to "play" with its values before achieving result you were thinking of) 

· Prismatic joint -  Prismatic joint allows for relative translation of two bodies along a specified axis. A prismatic joint prevents relative rotation. Therefore, a prismatic joint has a single degree of freedom. Still do not understand how does it work? Lets imagine slider in your pants.

· Weeld joint - It attempts to constrain all relative motion between two bodies, in another words, it bounds two bodies together

· Rope joint - Restricts the maximum distance between two points. This can be useful to prevent chains of bodies from stretching, even under high load, usually we use this joint to connect multiple small bodies to each other, to simulate proper rope, thought it usually its not performance friendly, because requires many dynamic bodies.

· Wheel joint - The wheel joint restricts a point on bodyB to a line on bodyA. The wheel joint also provides a suspension spring, as you can conclude from joint name, we can use it to simulate wheel of the car for instance - with realistic suspension effect.

B2裏還有更多類型的裝置,比如gear, 更詳細地請參見B2官方手冊 :box2d manual.

l 旋轉接頭

下圖展示了一個RJ:

如何實現下圖的效果呢?

簡明起見,有兩個剛體,動態的紅色矩形和靜態的綠色矩形。錨點在綠色矩形的中心:

// Create green rectangle

final Rectangle greenRectangle = new Rectangle(400, 240, 40, 40, getVertexBufferObjectManager());

greenRectangle.setColor(Color.GREEN);

scene.attachChild(greenRectangle);

// Create red rectangle

final Rectangle redRectangle = new Rectangle(460, 240, 120, 10, getVertexBufferObjectManager());

redRectangle.setColor(Color.RED);

scene.attachChild(redRectangle);

// Create body for green rectangle (Static)

final Body greenBody = PhysicsFactory.createBoxBody(physicsWorld, greenRectangle, BodyType.StaticBody, PhysicsFactory.createFixtureDef(0, 0, 0));

// Create body for red rectangle (Dynamic, for our arm)

final Body redBody = PhysicsFactory.createBoxBody(physicsWorld, redRectangle, BodyType.DynamicBody, PhysicsFactory.createFixtureDef(5, 0.5f, 0.5f));

physicsWorld.registerPhysicsConnector(new PhysicsConnector(redRectangle, redBody, true, true));

// Create revolute joint, connecting those two bodies 

final RevoluteJointDef revoluteJointDef = new RevoluteJointDef();

revoluteJointDef.initialize(greenBody, redBody, greenBody.getWorldCenter());

revoluteJointDef.enableMotor = true;

revoluteJointDef.motorSpeed = -1;

revoluteJointDef.maxMotorTorque = 100;

physicsWorld.createJoint(revoluteJointDef);

// Attach box2d debug renderer if you want to see debug.

scene.attachChild(new DebugRenderer(physicsWorld, getVertexBufferObjectManager()));

1.創建綠色的矩形

2.創建紅色矩形,設置正確的位置。

3.爲兩個矩形創建正確類型剛體。

4.爲紅色剛體註冊物理連接器,讓紅色矩形隨着剛體運動。

5.定義一個連接頭,使用initialize 初始化連接頭:第一個剛體,第二個剛體,錨點

6.關聯box2d debug renderer

設定剛體位置時,注意使用的單位是米(Meters)而不是像素(Pixels

float pixelsInMeters = pixelsValue / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT;

常見問題:

1.如何改變轉動方向? 將MotorSpeed值乘以-1

2.最大的扭轉力(Motor Torque)是多少? 設爲你想達到的效果即可

3.運行時如何調整連接體的屬性?

一個很愚蠢的錯誤是對RevoluteJointDef 的引用修改屬性還期望會得到正確的結果,其實什麼都不會發生!因爲DEF只是用來創建Joint用的。正確的做法是直接修改對RevoluteJoin的t引用:

// Our reference

private RevoluteJoint revoluteJoint;

// While creating joint

revoluteJoint = (RevoluteJoint) physicsWorld.createJoint(revoluteJointDef);

然後對RevoluteJOint進行修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章