官术网_书友最值得收藏!

10.2 碰撞檢測

雖然說物體之間的碰撞Box2d已經很好地模擬出來了,但還是要了解一下Box2d的碰撞檢測,因為在很多時候需要知道碰撞的具體信息。例如,怪物被子彈碰到了,除了被打飛,或者打倒,還需要做很多其他的操作,如把怪物刪除,然后增加經驗、金幣之類的事情,這就需要用到碰撞監聽。而在玩CS之類游戲的時候,當把友軍傷害選項給屏蔽掉之后,我們發射的子彈只對敵軍產生影響,這樣的功能就涉及碰撞過濾,本節主要介紹這兩項內容。

10.2.1 碰撞監聽

Box2d里面使用接觸(Contact)來描述碰撞信息,在每次兩個物體的AABB出現重疊的時候,Box2d會產生相應的觸點,當物體的AABB分離的時候,又會把觸點刪除。使用World的GetContactList()函數可以獲取當前世界所有的接觸信息,通過這些接觸信息,可以獲取到接觸的物體以及接觸點等信息。但這并不是明智的做法,因為無法捕捉到所有的接觸。例如在一次循環中,在很多力的作用下,兩個物體短暫地接觸了,之后又快速地分開,在接觸列表中是不會找到這個接觸對象的。并且在每一幀輪詢這些接觸對象本身也是一個冗余的運算,就像坐公交車,每個站臺都問一句XX站到了沒?明智的做法是使用接觸監聽,它會在到達XX站臺時自動通知你。

使用監聽需要實現一個監聽者,叫作接觸監聽器,名字為b2ContactListener,位于b2WorldCallbacks.h內,可以實現它的幾個接觸相關的接口。

        ///在兩個fixtures開始碰撞的時候回調(當它們開始重疊的時候,只會在step中調用)
        virtual void BeginContact(b2Contact* contact) { B2_NOT_USED(contact); }

        ///在兩個fixtures結束碰撞的時候回調(當它們分開的時候,物體被銷毀的時候也會調用)
        virtual void EndContact(b2Contact* contact) { B2_NOT_USED(contact); }

        ///在接觸更新完成之后調用(碰撞檢測發生之后,碰撞沖突處理之前).此時可以禁用觸點,實現
        單面碰撞。例如,是男人就上100層這樣的小游戲的樓梯,在玩家跳上去時不發生碰撞,在玩家掉
        落的時候才碰撞。通過禁用觸點可以避免此次碰撞處理,并且不會調用到接觸處理完成的回調,但
        是可能會在一小段時間內連續收到PreSolve回調
        virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
        {
            B2_NOT_USED(contact);
            B2_NOT_USED(oldManifold);
        }

        ///在接觸被處理完之后調用,這時候物理模擬已經完成,在這里可以獲得接觸對象碰撞之后產生
        的力量、旋轉等信息
        virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
        {
            B2_NOT_USED(contact);
            B2_NOT_USED(impulse);
        }

Box2d不允許在碰撞回調中修改物理世界,因為上面可能發生在一個step回調之中,當出現多個對象同時碰撞,會依次處理每兩個對象之間的碰撞,而這時候會調用到回調函數,在回調函數中修改了對象,可能會導致其他對象的碰撞結果不正確,假設在回調中釋放了對象,還有可能導致程序崩潰。如果需要刪除或者做修改,可以將觸點信息保存起來,在step完成之后,再來處理。

上面的回調中可以做的修改僅僅是禁用觸點,來規避此次碰撞。例如超級瑪麗游戲中的單面墻,可以通過判斷碰撞的方向來決定是否規避此次碰撞,代碼如下。

          virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
          {
           b2WorldManifold worldManifold;
           contact->GetWorldManifold(&worldManifold);
           if (worldManifold.normal.y > 0.5f)
           {
              contact->SetEnabled(false);
           }
          }

在每次回調都會有一個b2Contact對象被傳進來,描述了發生碰撞的兩個物體的詳細信息,通過GetFixtureA()函數和GetFixtureB()函數可以分別獲取這兩個物體,但是需要你自己判斷這兩個物體是什么,可以根據指針來判斷,也可以設置UserData,根據UserData來判斷。在沖突處理之前的PreSolve回調中,調用b2Contact對象的SetEnabled()函數可以設置是否禁用接觸。

最后,在使用的時候,需要用new操作符創建一個這樣的監聽器對象,通過調用World->SetContactListener(myContactListener),把它設置給Wold。

10.2.2 碰撞過濾

在創建Fixture的時候,可以通過設置過濾標識,來控制Fixture之間的碰撞過濾,Fixture有兩種過濾標識,每個標識都是一個16位的int16變量(相當于短整型),可以表示16種不同類型的碰撞。

        b2Filter()
        {
            categoryBits = 0x0001;      //類別標志位
            maskBits = 0xFFFF;          //遮罩標志位
            groupIndex = 0;             //分組索引
        }

類別標志位定義了Fixture的類別,而遮罩標志位則定義了可以與之發生碰撞的類別,舉個例子:

? 我是一個公主a = 1。

? 我是一個戰士b = 2。

? 我是一個英雄c = 4。

我是一個公主,那么我的categoryBits |= a,我只能和英雄發生碰撞,那么我的maskBits|= c,這是我的邏輯,那么這個碰撞過濾,還得看英雄愿不愿意和我碰一下,也就是英雄的maskBits & a是否等于0,用一句代碼來解釋,就是:

        isCollid = A.maskBits & B.categoryBits ! = 0 && A.categoryBits & B.maskBits ! =0

在b2Filter的構造函數中,將categoryBits設置為1,而將maskBits設置為0xFFFF,表示我可以和所有的Fixture發生碰撞,而且所有的Fixture在創建的時候,categoryBits都是1。

那么分組索引是干什么的呢?其用來描述更加復雜的規則。

? 當A和B的分組索引相同且為正數,則產生碰撞。

? 當A和B的分組索引相同且為負數,則不產生碰撞。

? 在其他的情況下,都使用正常的類別/遮罩過濾規則。

簡單概括就是,如果是大于0的相同組,則一定碰撞,如果是小于0的相同組,則一定不碰撞。

在使用上面的標志和組無法解決問題的時候,還可以通過觸點過濾器來進行碰撞過濾,這有點類似于碰撞監聽,繼承一個b2ContactFilter對象,然后實現它的碰撞過濾方法,通過在World中調用SetContactFilter()函數,來傳入觸點過濾器,使其接受觸點消息。

      virtual bool ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB);

當這兩個Fixture的AABB包圍盒發生重疊的時候,就會調用這個回調,這時候它們可能還沒有真正發生碰撞,可以通過自己設置的UserData來判斷過濾,也可以根據Fixture的過濾標識結合自己定義的規則來判斷過濾。如果允許它們發生碰撞,則返回true;否則返回false,讓它們直接穿透,不做碰撞處理。另外,在游戲過程中也可以動態地修改Fixture的過濾標識,來達到想要的效果。

主站蜘蛛池模板: 自治县| 西安市| 思茅市| 湖州市| 永宁县| 利川市| 武功县| 焉耆| 科技| 兴国县| 黄陵县| 台湾省| 苍南县| 佳木斯市| 湘阴县| 资阳市| 饶河县| 卢湾区| 大竹县| 长兴县| 千阳县| 东乌| 娱乐| 五原县| 靖安县| 繁峙县| 仁化县| 蓬安县| 昌宁县| 灌南县| 宁都县| 湘乡市| 通江县| 乌拉特中旗| 十堰市| 乡宁县| 湖北省| 读书| 舞阳县| 陵川县| 屏边|