Quick-cocos2d-x3.3 Study (十六)--------- 碰撞檢測,事件監聽,設置掩碼


本章主要講解物體碰撞檢測之間的原理,以及具體的實現方法。

碰撞檢測

本游戲使用物理引擎的一個重要目的是為了讓碰撞檢測更方便,使用物理引擎可以進行精確的碰撞檢測,而且執行的效率也很高。

在 Quick 3.3final 版本中,所有事件均有事件派發器統一管理,物理引擎的碰撞事件也不例外。它由 cc.EventListenerPhysicsContact 的實例來監聽。

監聽事件分類

碰撞監聽事件有以下幾種:

  • cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN
    它是碰撞剛發生時觸發的事件,並且在此次碰撞中只會被調用一次。我們可以通過返回 true 或者 false 來決定物體是否發生碰撞。
    需要注意的是,當這個事件的回調函數返回 flase 時,EVENT_PHYSICS_CONTACT_PRESOLVEEVENT_PHYSICS_CONTACT_POSTSOLVE將不會被觸發,但EVENT_PHYSICS_CONTACT_SEPERATE必定會觸發。

  • cc.Handler.EVENT_PHYSICS_CONTACT_PRESOLVE
    它在碰撞接觸后的每幀都會調用。在該事件的回調函數中我們可以計算碰撞處理的一些屬性,比如彈力,速度等。同樣它可以通過返回 true 或者 false 來決定物體是否發生碰撞。

  • cc.Handler.EVENT_PHYSICS_CONTACT_POSTSOLVE
    發生在碰撞計算完畢的每個步驟(幀),你可以在此做一些碰撞的后續處理,比如安全的移除某個物體等。

  • cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE
    發生在碰撞結束兩物體分離時,同樣只會被調用一次。

下面我們來看看添加碰撞監聽的代碼,如下所示:

local contactListener = cc.EventListenerPhysicsContact:create() -- 1 contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN) -- 2 contactListener:registerScriptHandler(onContactSeperate, cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE) -- 3 local eventDispatcher = cc.Director:getInstance():getEventDispatcher() -- 4 eventDispatcher:addEventListenerWithFixedPriority(contactListener, 1) -- 5
  1. 創建物體碰撞檢測事件監聽器對象(contactListener);
  2. 設置監聽器的碰撞開始函數 onContactBegin;
  3. 設置監聽器的碰撞分離函數 onContactSeperate;
  4. 監聽器設置完畢,需要把它加入到引擎導演的事件分發器中。所以這里獲取游戲的事件分發器(eventDispatcher);
  5. 將檢測事件監聽器(contactListener)添加到事件分發器(eventDispatcher)中,這樣就可以觸發碰撞檢測事件。addEventListenerWithFixedPriority 方法是指定固定的事件優先級注冊監聽器,事件優先級決定事件響應的優先級別,值越小優先級越高。

掩碼屬性

在講解 onContactBegin 和 onContactSeperate 函數之前,這里我們需要清楚地一點。即在默認情況下,物理引擎中的物體是不會觸發碰撞回調事件的。也就是說,上面代碼中的 onContactBegin 和 onContactSeperate 方法永遠都不會調用到。

為什么啦? O(∩_∩)O~呵呵,咱不賣關子。因為每個 cc.PhysicsBody 都具有三個掩碼屬性,兩個剛體能不能碰撞,能不能發送接觸事件信息,都依靠於這三個參數的值。

所以,為了更好地解決剛才遇到的問題,下面我們先來了解下剛體的這三個 mask 屬性。

  • CategoryBitmask:32位整型,剛體的類別掩碼。它定義了一個物體所屬類別,每一個物體在場景中都能被分配到多達32位不同的類別。默認值為0xFFFFFFFF。

  • ContactTestBitmask:32位整型,剛體的接觸測試掩碼。當兩個物體接觸時,用一個物體的類別掩碼與另一個物體的接觸測試掩碼做“邏輯與”運行,如果結果為非零值,引擎才會新建 PhysicsContact 對象,發送碰撞事件。那么才發送碰撞事件消息。
    ContactTestBitmask 的設計是為了優化性能,並不是所有物體之間的碰撞我們都關心,所有這個 ContactTestBitmask 的默認值為0x00000000。

  • CollisionBitmask:32位整型,剛體的碰撞掩碼。當兩個物體接觸后,用一個物體的碰撞掩碼與另一個物體的類別掩碼執行“邏輯與”運算,如果結果為非零值,那么該物體能夠對另一個物體的碰撞發生反應。這里的“碰撞反應”會表現為一個物體受到另外物體的碰撞,而改變運動方向。默認值為0xFFFFFFFF。

總結:

  1. CategoryBitmask 是其它兩個掩碼比較的基礎。
  2. CategoryBitmask & ContactTestBitmask 決定是否發送事件消息。
  3. CategoryBitmask & CollisionBitmask 決定是否產生剛體反彈效果。
  4. ContactTestBitmask 和 CollisionBitmask 互相之間沒有聯系。

注:每個 mask 都有對應的 get 和 set 接口來獲取或者修改mask。
另外,發生碰撞和發送事件消息是不同的概念,前者是直觀地一種表現-碰撞反彈,后者是一種消息機制,就是說是否調用碰撞事件的回調函數。

回到我們的游戲,根據需求我們配置了一份各類 Node 的掩碼屬性表,如下所示:

節點 類別掩碼 接觸測試掩碼 碰撞掩碼
玩家:Player 0111 1111 1001
心心:Heart 0001 0100 0001
鳥:Bird 0010 0010 1000
飛艇:Airship 0100 0100 1000
地面 1000 0001 0011
天界 1000 0000 0001

首先,以玩家和心心為例,我們希望它們發生碰撞,並希望發送事件消息。所以,玩家的類別掩碼0111 邏輯&與 心心的碰撞掩碼0001結果為0001(不為0),發生碰撞;且玩家的類別掩碼0111 邏輯&與 心心的接觸測試掩碼0100結果為0100(不為0),發送事件消息。反之,心心的類別掩碼0001 & 玩家的碰撞掩碼1001結果為0001(不為0),發生碰撞;且心心的類別掩碼0001 邏輯&與 玩家的接觸測試掩碼1111結果為0001(不為0),發送事件消息。所以,玩家和心心兩個物體相互接觸時,它們會發生碰撞反彈,同時會發出事件消息。

我們再舉一例,這次以玩家和鳥為例,我們希望它們不發生碰撞,但希望發送事件消息。所以,玩家的類別掩碼0111 邏輯&與 鳥的碰撞掩碼1000結果為0000(為0),不發生碰撞;且玩家的類別掩碼0111 邏輯&與 鳥的接觸測試掩碼0010結果為0010(不為0),發送事件消息。反之,鳥的類別掩碼0010 & 玩家的碰撞掩碼1001結果為0000(為0),不發生碰撞;且鳥的類別掩碼0010 邏輯&與 玩家的接觸測試掩碼1111結果為0010(不為0),發送事件消息。所以,玩家和鳥兩個物體相互接觸時,它們不發生碰撞反彈,但會發出事件消息。

設置屬性

為了更方便碰撞檢測,我們給各個節點設置一個標簽。標簽的定義我們可以放在 config.lua 文件中。定義如下所示:

GROUND_TAG = 1 HEART_TAG = 2 BIRD_TAG = 3 AIRSHIP_TAG = 4 PLAYER_TAG = 5

根據以上的掩碼屬性和標簽屬性,我們在游戲項目中要設置好這些屬性,如對於 Player,我們要在綁定剛體的地方加上如下的一段代碼,即 Player:ctor()方法中:

body:setCategoryBitmask(0x0111) body:setContactTestBitmask(0x1111) body:setCollisionBitmask(0x1001) self:setTag(PLAYER_TAG)

其他節點同理,這里就不一一添加了。

碰撞實現

下面我們給出碰撞檢測的整段函數,程序中我們把它封裝在了 addCollision 方法中,如下所示:

function GameScene:addCollision() local function contactLogic(node) -- 4 if node:getTag() == HEART_TAG then -- 給玩家增加血量,並添加心心消除特效,下章會加上 node:removeFromParent() -- 5 elseif node:getTag() == GROUND_TAG then -- 減少玩家20點血量,並添加玩家受傷動畫,下章會加上 elseif node:getTag() == AIRSHIP_TAG then -- 減少玩家10點血量,並添加玩家受傷動畫 elseif node:getTag() == BIRD_TAG then -- 減少玩家5點血量,並添加玩家受傷動畫 end end local function onContactBegin(contact) -- 1 -- 2 local a = contact:getShapeA():getBody():getNode() local b = contact:getShapeB():getBody():getNode() -- 3 contactLogic(a) contactLogic(b) return true end local function onContactSeperate(contact) -- 6 -- 在這里檢測當玩家的血量減少是否為0,游戲是否結束。 end local contactListener = cc.EventListenerPhysicsContact:create() contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN) contactListener:registerScriptHandler(onContactSeperate, cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE) local eventDispatcher = cc.Director:getInstance():getEventDispatcher() eventDispatcher:addEventListenerWithFixedPriority(contactListener, 1) end

之前提到過的 onContactBegin 和 onContactSeperate 函數我們都寫在了 addCollision 方法中,而 contactLogic(node) 方法則是碰撞檢測的邏輯函數。

下面我們依次來看看這些代碼,請順着編號的順序來看:

  1. onContactBegin 方法是碰撞開始時觸發的回調函數。只有當場景中兩個碰撞的物體的CategoryBitmask & ContactTestBitmask 不為0時才調用。
  2. 獲取發生了碰撞的兩個節點。
  3. 調用 contactLogic 方法檢測碰撞邏輯。因為兩個物體發生碰撞時,可能是A碰了B,也可能是B來碰了A,所以這里我們調用了兩次 contactLogic 方法。
  4. 在 contactLogic 方法中,當被檢測節點的標簽為 HEART_TAG,即心心時,我們將在這里給玩家增加血量,添加心心消除特效,並且從屏幕中移除被撞的心心。
  5. 當被檢測節點的標簽為地面(GROUND_TAG)、飛艇(AIRSHIP_TAG)、鳥(BIRD_TAG)時,減少玩家相應地血量,並添加玩家受傷動畫。其中給玩家增加血量,添加心心消除特效等我們下章再講。
  6. onContactSeperate 方法是碰撞分離時觸發的方法,在這里檢測當玩家的血量減少是否為0,游戲是否結束。

在 GameScene 的 ctor 中調用 addCollision 方法后,你就可以實現碰撞檢測了,此時只有玩家與心心碰上了才有反應。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM