基於cocos2d-x引擎的遊戲框架設計

      文 / 李成,鄭鑫  轉載於:http://www.programmer.com.cn/10845/



移動互聯網浪潮正在徹底改變人們日常的生活習慣和生活方式。相應的,基於移動終端和感應交互的遊戲,也爲人們帶來了全新的遊戲體驗。本文,我們將結合目前流行的cocos2d-x引擎,使用C++語言,基於iOS平臺,和大家分享iPhone、iPad上游戲客戶端的構架與實現。

遊戲架構與實現

目前,很多基於cocos2d-x的代碼基本上僅是對引擎功能的使用,完全不能按照遊戲項目的標準來參考。作爲遊戲項目代碼,不僅需要實現遊戲的諸多功能,還需要從架構層面,從模塊設計的角度來思考和設計,使代碼具有更好的複用性和拓展性。

對於遊戲客戶端,按照功能模塊的區別可分爲:引擎封裝層模塊、遊戲數據管理模塊、應用程序配置模塊、日誌記錄模塊、網絡管理模塊、消息事件機制模塊、輸入輸出控制模塊、音效管理模塊、UI系統模塊、邏輯系統處理模塊、調試器控制模塊等。針對不同類型的遊戲,通常只需要單獨實現最上層的遊戲邏輯系統,而剩餘的模塊完全可以複用。下面將詳細講解各個模塊的職能與實現(暫不包含遊戲邏輯系統)。

引擎封裝模塊(EngineSystem)

爲了減少客戶端代碼對cocos2d-x引擎的依賴程度和降低耦合度,我們建立了引擎封裝層模塊,將引擎必要的初始化、邏輯更新、渲染和資源管理等操作全部交給引擎封裝層處理,使客戶端的其他模塊不需要過於依賴引擎層。同時,爲了避免客戶端代碼中頻繁、直接地調用平臺相關的諸多功能,我們還將一些平臺相關的功能全部封裝在引擎封裝層模塊內。

cocos2d-x功能很多很強大,但在開發時,需要根據項目需要有條件選擇引擎功能(當然,cocos2d-x本身設計實現的很好)。例如,在引擎封裝層內部,我們僅使用了一個CCScene對象,在設計之初就刻意避免處理多個CCScene之間的初始化、跳轉、銷燬、更新等操作,極大地簡化了邏輯層代碼,降低了複雜度,且到目前爲止,在表現效果上沒有什麼影響。

 

數據管理模塊(DBSystem)

在開發過程中經常會存儲和讀取大量的靜/動態數據,對比我們針對這部分設計了DBSystem模塊,專門進行遊戲數據層的管理。每種類型的遊戲數據都會派生出一個具體的類,如音效數據管理器、圖片數據管理器等。這些數據管理器都在DBSystem內部統一進行初始化、更新和銷燬,並各自使用單例模式。外層使用時,直接通過其類進行數據讀取即可,無須關心其初始化、邏輯更新、銷燬等操作。同時,爲了時刻對遊戲的靜態數據進行監控,所有的數據模塊都暴露了獲取其所包含數據的接口,這樣我們就可以在遊戲中隨時獲取數據層的信息,方便進行統計和監控。

應用配置系統(VariableSystem)

開發者一般需要對應用屬性進行可配置化處理:一方面可以方便開發者快速開啓/屏蔽某些功能;另一方面能更人性化地支持用戶偏好設置。目前,我們根據類型的不同,建立了賬號、網絡、日誌三種配置文件,分別對遊戲賬號、遊戲功能信息、遊戲網絡配置信息、遊戲日誌配置信息進行動態設置,全部使用XML進行數據存儲和讀取。

在開發中通常需要保存大量臨時數據,這些數據往往被放在各個模塊內部,如果其他模塊需要使用,就造成兩個模塊間強行依賴,增加了耦合度。所以,我們將所有臨時需要的數據統一定位爲內存配置數據,放在我們的應用配置系統中,其和賬號、網絡、日誌配置文件的區別在於:基於文件配置的屬性數據都需要在程序退出時強行寫回文件,而基於內存配置的屬性數據無須保存。

音效控制模塊(SoundSystem)

iOS平臺並沒有十分完善的音效引擎,而一般自行實現的音效庫都難以進行拓展和支持跨平臺,所以我們直接選取流行的FmodEx引擎,進行遊戲音效的播放和管理。同時,結合FmodEx提供的強大接口,可以很方便地實現聲音大小設置、暫停、循環、3D音效等操作,完全滿足一般遊戲的需求。

在遊戲中,一般都需要頻繁的播放多個音效,爲了提高效率並節省內存,我們在邏輯層對每一個音效文件都使用了引用計數技術,對同一種音效文件僅需通過計數的方式維持一份實例即可,同時播放的多個相同音效,實際上都是使用同樣的一份實例而已,無須單獨創建音效實例;另外,通過引用計數,很好地解決了音效資源回收的問題,當音效資源計數爲零時,即表示其可以被回收,對應的資源,佔用的內存也將被釋放。

日誌系統模塊(LoggerSystem)

爲了方便在開發、運營期對出現的問題及時進行定位和排查,對遊戲中關鍵的處理流程都需要進行日誌記錄。在客戶端,我們仿照Log4J的方式,實現了分級(Trace級、Info級、Error級等)、分文件、分輸出方式的強大日誌管理。遊戲的日誌模塊,結合了應用配置系統,完全實現了動態化配置,通過對日誌配置文件進行設置修改,開發者可以很方便地設置日誌的開啓等級、輸出方式、大小拆分、輸出名稱等。另外,對於客戶端日誌模塊,無須過多考慮其性能問題,所以我們的日誌模塊,完全是簡單地在主線程裏進行文件寫入,沒有多開線程進行文件操作。

消息事件系統模塊(EventSystem)

考慮到客戶端框架總體的拓展性,我們完全使用事件驅動模型(Event-driven)來設計和開發,將客戶端中事件的觸發時機和具體處理邏輯徹底分隔開。遊戲的各個模塊,僅需要註冊、監聽和實現其關心的消息事件,而無須關心事件何時被觸發,降低了總體耦合度。目前遊戲中所有UI面板的隱藏/顯示、事件響應、音效的播放/停止、遊戲流程的切換、遊戲角色狀態遷移等,完全通過事件驅動方式開發;同時這種基於事件的處理方式,爲項目使用動態腳本拓展提供了支持:腳本層省去對邏輯代碼的大量直接調用,通過消息事件完成腳本層和邏輯層的交互調度,大大簡化了開發的複雜度。

UI系統(GUISystem)

cocos2d、cocos2d-x這兩種引擎本身並沒有提供太多UI控件,僅提供了按鈕、進度條等基礎控件,如果想使用更多的UI控件,需要開發者借鑑或使用其他成熟的GUI引擎,如CEGUI等。

在我們的UI系統中,充分借鑑了CEGUI的設計思想。整體上,將遊戲中有關聯的UI控件集中到一個個單獨的CCLayer上,組成多個獨立的Layout,也就是我們在代碼中定義的IWindow類。每一個IWindow類,都包含其自身的根面板(CCLayer)和衆多依附在其上的子UI控件。通過IWindow,我們實現了對所有UI佈局Layout進行統一的接口調用和處理,如初始化加載、消息註冊與響應、隱藏/顯示、銷燬等。

雖然我們在之前的多個項目開發中提倡將窗口的邏輯實現全部交給動態語言(Python&Lua)來實現,但對於某一些UI功能,並不適合使用腳本來進行邏輯拓展。

對於這類難度比較大、複雜度特別高的UI處理,使用原生的C++開發可能更爲合適,所以,在UI系統中,針對這兩種不同的需求,我們對IWindow進行了拓展:對需要使用腳本來拓展邏輯的Layout,派生出UIWindowByScript類,其內部主要通過消息事件機制將對應窗口的初始化、加載、邏輯更新、事件處理、銷燬等操作傳遞給對應的腳本邏輯處理;對於需要使用C++來進行處理的Layout,直接根據功能需要,從IWindow上派生出各個具體的實現類。

目前的UI佈局還未完全通過外部配置文件動態實現,我們將借鑑CEGUI的處理方式,將所有UI的佈局信息、控件屬性與事件響應處理等全部使用外層配置文件實現,從而將這些靜態信息和程序分隔開,達到動態配置的目的。

網絡管理模塊(NetSystem)

考慮到傳統類型遊戲和即時競技類遊戲的差異性,在設計網絡模塊之初,我們就同時支持了UDP和TCP兩種通信方式。

爲了支持UDP通信方式,我們使用監聽器模型,在客戶端中設計了UDPAcceptor管理器,專門進行UDP通信方式的初始化、操作和銷燬。同時,考慮到UDP通信方式的特殊性,在UDPAcceptor內部,我們對收到的數據進行了雜亂包的過濾,並且使用序列號技術(Sequence Number),實現了UDP數據包先後順序的管理和糾正,確保最終交給邏輯層處理的數據包,都是完整可靠有序的。
對於TCP通信方式,我們使用連接器模型,在客戶端中封裝了TCPConnector管理器。爲了解決粘包、拼接、臨時數據拷貝等問題,我們在內部設計了特殊的MemNode存儲結構,將讀取的數據全部存儲到MemNode裏,其內部根據當前數據的讀、寫指針位置來進行有效數據定位,確保最終交給上層邏輯使用的數據包,都是完整獨立的數據。

網絡模塊面臨着頻繁的數據接受和發送,如果不進行控制,頻繁的new/delete對性能會有一定影響。因此,我們在網絡系統內部使用鏈表式內存池技術,對接收和發送的數據包都通過該內存池進行統一分配、回收和管理,從根本上解決了頻繁的new/delete。另外,在NetSystem內部,使用網絡編程中傳統的Selector模式進行網絡連接的監聽、輪詢、讀取和發送。目前市面上很多遊戲對網絡模塊都是單開線程進行處理,而在我們考慮到多線程同步、數據串行化等問題,所以儘量避免了多線程的方式,使用非阻塞式I/O,全部在邏輯主線程裏面進行網絡控制管理。

輸入控制系統(InputSystem)

因爲cocos2d-x本身提供的UI控件都有其自身的輸入響應機制,所以我們很難直接修改遊戲的輸入控制系統。此處我們討論的輸入控制系統,主要就是針對CCLayer進行的Touch和Accelerate事件控制與管理。

在遊戲中,爲了對Touch和Accelerate事件進行統一的管理和處理,在整個客戶端的窗口上,我們特意設計了一個最底層的UILayer(也就是所有UI佈局Layout的根窗口)。整個遊戲中,僅這個根窗口的CCLayer監聽了Touch和Accelerate事件,其內部對兩種事件進行捕獲和處理,然後將對應的事件,存儲在內部的輸入消息隊列中,通過消息事件,通知當前各個遊戲管理器調度和處理。

調試器系統(DebugerSystem)

在遊戲過程中,需要時刻對遊戲內的各項性能指標進行監控,判斷各個模塊、各個環節是否存在着重大的性能問題。因此,我們在客戶端中,徹底將調試、監控作爲一個核心模塊來設計開發,目前主要分爲網絡模塊調試器(NetDebuger)、渲染模塊調試器(RenderDebuger)、邏輯模塊調試器(LogicDebuger)三大模塊(可根據需要動態增加)。對於NetDebuger,主要監控當前網絡上下行數據量、接受和發送的數據包個數、網絡接受和發送的耗時信息等,並通過曲線圖展示;對於RenderDebuger,主要用於監控當前客戶端渲染的具體信息,例如DIP數量、像素填充率、渲染頂點數量等,開發者可以通過這些渲染引擎判斷當前渲染是否存在性能問題;對於LogicDebuger ,管理和統計所有邏輯對象個數、大小、邏輯處理耗時等。我們在實際開發中,通過自行開發的遊戲邏輯層調試器系統,結合XCODE本身提供的強大監控工具,可以非常完善仔細地監控各個模塊的詳細數據、內存、處理耗時等信息,完全滿足一般的開發監控需求。

引擎改進與公共代碼庫遊戲中的UI面板,難免會使用到模態窗口,但cocos2d-x引擎並沒有提供類似的功能,爲了避免在邏輯層編寫大量冗餘代碼來實現該功能,我們需要對cocos2d-x引擎進行必要的修改:在cocos2d-x消息事件處理(CCTouchDispatcher)內部,存在一個事件響應隊列,對於所有關心Touch事件的對象,按照優先級從小到大排序,優先級越小,則越優先調度。所以,如果我們需要實現模態窗口的功能,需要自己管理所有UI控件的消息響應級別,並且按照控件之間的父子依賴關係,實現一個類似樹的優先級結構,每次需要實現模態窗口時,只要確保其對應的事件優先級在最頂層,並且在處理完消息之後,屏蔽掉該消息,避免繼續傳遞到下面的窗口。

考慮到不同開發者對操作系統、底層接口等熟悉程度不同,爲了降低開發成本,我們開發了一套基於iOS平臺的基礎代碼庫cobra_ios,其內部封裝了開發中常用的各種接口:整套完善的線程安全容器,如數組、單鏈表、雙向循環列表、隊列、二叉樹等;各種線程控制模型和串行化接口、各種內存管理技術、數據解包器DPacket和數據組包器EPacket等。在開發中,所有開發者都統一使用該基礎庫,使用相同的接口處理,方便開發且提升效率。

拓展

cocos2d-x VS. Cocoa

前文提到,cocos2d-x引擎本身沒有提供太多UI控件,除開發者自行實現外,我們還可以使用iOS標準的UI控件。熟悉Win32、MFC的朋友都知道,Win32標準控件很難與DirectX結合,因爲兩者是完全相同的渲染機制,但在iOS平臺,cocos2d-x與Cocoa自帶的UI控件完全兼容,並可以相互調用,例如在遊戲登錄界面,我們就可以使用Cocoa自帶的NSTextField控件來實現賬號和密碼輸入框。因此,對於遊戲開發者來說,學好Cocoa很有必要。

其他

由於iOS平臺不支持以動態鏈接庫的方式使用第三方庫,所以我們不可能選擇Boost+Python的方式進行腳本拓展,不過我們還可以選擇Lua等其他可行性比較高的動態語言。“工欲善其事,必先利其器”,對於中大型項目,除開發客戶端外,一般還需要單獨開發編輯器進行關卡、場景等編輯處理。對於編輯器的開發,由於引擎cocos2d-x本身完全跨平臺,所以我們完全可以使用自己熟悉的語言和平臺來開發,選擇使用Cocoa、MFC、C#等熟悉的語言,只要確保所有最終的關卡場景數據可被跨平臺讀取即可。

總結

移動時代,遊戲的操作方式,已不僅僅限於傳統的鼠標鍵盤模式,隨着觸摸、攝像、語音、重力感應等更多操作形式的普及,也爲遊戲帶來了前所未有的機遇和挑戰。

作者李成,網名關中刀客,曾參與制作過多款端類MMO、Web Game等遊戲項目,目前熱衷於移動設備的遊戲製作。

作者鄭欣,遊戲客戶端程序員,參與制作多款遊戲項目,曾供職於GameLoft北京手遊部門、盛大遊戲老牌遊戲工作室,現任職於騰訊遊戲,負責拓展國際業務。


發佈了7 篇原創文章 · 獲贊 7 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章