天天看點

cocos2d-x 建立自己的層級視窗消息機制

在開發一些視窗層次比複雜的cocos2d項目時,會發現一些由于沒有視窗層次而引起的bug。這些bug讓一些從windows平台過來的人覺得很無奈。比如,我們有一個清單控件,我們在其中放了一些菜單,當我們滑動清單控件使菜單選項(稱為A)滑出清單控件的可視範圍時,按理我們是無法再點選到A的,因為它滾動出了父控件可視範圍,不被使用者所看到。但是cocos2d的預設情況是能點選到的,這是因為cocos2d的消息管理是優先級消息機制,隻要控件登記接收消息,那麼cocos2d會一如既往的發給他。是以我們剛才講的情形在cocos2d看來,它無法根據A被遮擋而不給A發消息。究其根本,是沒有一個層級視窗消息機制(當然你能通過其他的方法幫助cocos2d,但我個人覺得有點不夠徹底)。

我想建立一個相對完整cocos2d的的層級視窗消息機制,且不改變cocos2d任何源碼(因為改變源碼的話,不知道以後更新起來是不是很麻煩)。基本思路有如下幾條:

在任何一個場景中,我們會有一個最底層的Layer(我稱為祖層),這個Layer将接受cocos2d的觸摸消息。并且這個Layer能将觸摸消息傳遞給其所有的子控件。

一個場景中除了祖層之外,所有其他的控件都将不接受任何觸摸消息,其觸摸消息的來源于父控件。

消息将從底層往上層傳遞。在每層中,節點根據order值從高到低排列(即order值高的表明該控件位于此層中的較上層的節點)并周遊,直到遇到消息感興趣的節點,并停止本層周遊,進入下一層。

盡可能相容已知和未知的cocos2d控件庫。

下面看一下類的組織架構圖:

cocos2d-x 建立自己的層級視窗消息機制

我們分别說一下各個類的大體作用:

BYTouchDelegate并非繼承于ccTouchDelegate或其他類(但消息處理函數名同ccTouchDelegate一樣)。它是一個消息傳播大使,所有繼承于該類的類都能自動地将消息傳播到所有的子控件。不繼承于ccTouchDelegate主要出于設計原則中避免多重繼承中的基類重複。

BYLayer繼承于BYTouchDelegate和CCLayer(圖中未指出),負責将消息處理函數轉接到BYTouchDelegate的消息處理函數,這是因為BYTouchDelegate的消息處理函數同ccTouchDelegate是一樣的,而CCLayer已經繼承了ccTouchDelegate,這樣如果不顯示的轉接處理函數,C++編譯器會提示有兩個版本選擇的錯誤。

此消息機制的目的有兩點:

讓大型Cocos2d-x網友有一個層級視窗管理機制。

讓所有的Cocos2d-x元素(即各類UI)能夠天生融入到這個機制中。

需要通過下面幾點修改達到這個目标的:

屏蔽所有子節點的消息注冊。通過實踐發現,為了不動源碼,必須寫一個新BYCocos::addChild函數,來代替原來的addChild函數族,這樣就要求項目裡面調用addChild的地方更改成BYCocos::addChild。這個函數内,會将子節點的所有子節點的消息都關閉掉。

Cocos2d-x源碼修改一處。

1.注釋掉CCLayer的ccTouchBegan中的CCAssert語句。這個語句隻是起警醒的作用,為了讓Cocos2d-x适應該機制,将此句注釋掉。并修改return true為return false。因為在這個機制裡面,傳回true表明此layer對視窗消息感興趣,這樣會阻止消息往兄弟節點傳遞。

重點:

由于此消息機制會判斷是否點選中視窗,是以視窗大小變得尤為重要。Cocos2d-x的CCControl的各類控件已經做好了這些。但自己寫的新的類,需要注意正确設定視窗大小。

下面來看源碼:

BYTouchDelegate:

BYTouchDelegate.h

BYTouchDelegate.cpp

其中比較重要的地方解釋一下:

1.首先是m_bDragging是為了判斷拖動用的,主要用于菜單處理,當點選了一下之後,菜單對此消息感興趣,我們記錄菜單,并傳遞後續消息,然後點選完之後傳來的是TouchMoved消息,那麼就停止菜單消息處理。

2.passMessage同ccTouchBegan一起來完成消息遞歸傳遞。passMessage還将幫助沒有繼承wmTouchDelegate的類(這些類的ccTouchBegan不具備消息傳遞功能)傳遞消息給其子控件。

BYLayer.因為主要功能都有wmTouchDelegate完成了,這些類隻是做了簡單功能和限制的的添加。

BYLayer.h

裡面通過一些宏,讓我編寫的更快一些。但希望沒能阻礙你閱讀。這裡面沒有太多需要說的,具體的請下載下傳示例工程檢視。

PS:

工程使用時,隻需包含BYCocos.h即可。

確定cocos2d-x中CCLayer.cpp内的CCLayer::ccTouchBegan()函數内的CCAssertion()語句被注釋并修改return true為return false。這是新層級視窗消息機制對cocos2d-x源碼的唯一修改。

這個體系中有一點需要注意,因為我們添加了窗體點選判斷,是以加入這個體系的視窗應該顯示地,正确地設定自己的窗體大小(這經常是一些bug的原因所在)。如果想屏蔽同級節點接受消息,重寫ccTouchBegan,并且傳回true。

添加子節點,必須使用BYCocos::addChild()。

另外由于使用了dynamic_cast,可能會讓部分讀者擔心效率問題。但個人認為dynamic_cast并不是C++的垃圾,其帶來的性能影響應該是很有限的。

代碼上有比較多的注釋,如果注釋有誤還請告訴我。習慣練習英文注釋了,希望我的注釋沒太大文法錯誤,能夠讓你了解。這裡就不多講述了。

任何後續修改都會列在下面的修訂記錄中。

修訂記錄:

2013-2-1, 修改BYTouchDelegate.cpp。

修訂原因:按理來說,一個Layer上應該隻有最上面的子節點才能相應消息。

修訂内容:将消息按節點的Zorder降序,隻傳遞給第一個對此消息感興趣的子節點,解決了一個Layer上,多個子節點互相重疊,都能響應消息的bug。

修複當沒有直接子節點對消息感興趣時BYTouchDelegate不接受消息的bug。此bug會是的當間接子節點對消息感興趣出問題。具體:修改BYTouchDelegate.cpp中passMessage(),修訂的部分以//revision# began開始,以//revision# end結束标記了(#表示第幾次修訂。我本想用顔色标記出來,但是csdn的代碼框内使用顔色格式有bug)。

2013-3-25,修改BYLayer.h

修訂原因:

1.原來的版本重載了onEnter(),并在其中将所有子節點的消息屏蔽掉。但是onEnter是在建立的時候調用,如果建立完成後,再調用addChild(),添加的節點的消息就沒有被屏蔽了,也就違背此消息機制。

2.由于我們在addChild中屏蔽了子節點的消息,所有BYLayerModal沒有存在的必要了(因為它原本設計的時候是通過自行注冊最高優先級别的消息來達到屏蔽消息的),如果想讓某視窗屏蔽消息,隻要確定該視窗order最高,并且重寫ccTouchBegan傳回true。另外BYLayerDescendant的存在增加的機制的複雜度,與其帶來的好處想比,還是去掉比較合理。

3.将BYLayerAncestor合并到BYLayer去,以減少新添加的類數量,讓系統精簡。

修訂内容:删除原來屏蔽子節點消息注冊的做法。添加BYCocos::addChild輔助函數,此函數将在内部屏蔽所有子節點的消息。

繼續閱讀