cocos2d-x模型載入的重構,我眼中的物件導向,cocos2d-x眼中
這兩天在寫cocos2d-x載入ogre的mesh模型的東西,完成了一半了,還差動畫檔案沒接進來。這篇文章並不是教如何載入模型檔案的,因為那種東西沒什麼可教的,無非就是負載檔案,然後解析出自己想要的頂點、主要畫面格等資料,最後給coco2d-x中的MeshData賦好值。這裡我想表達一些我有關代碼格式、代碼設計、代碼重構的想法,我會分一些有關或者無關的問題,來逐一分析。
一、為什麼要支援多種模型格式?cocos2d-x自己定義一種新的格式c3d、c3t這樣好不好?
首先,我認為,一個成熟的遊戲引擎不需要支援多種模型格式,至少從渲染和內部代碼上是不需要的,因為那樣沒有意義。支援的少了,總會有遺漏的;支援的多了又會顯得非常臃腫。我記得之前見過一個解析模型格式的開源庫,靜態庫就有一百多兆。對一個遊戲引擎來說這是完全沒有意義的。
但是這裡有一個問題,就是cocos2d-x的3D部分並不成熟,並不具備競爭力。所以從引擎的發展角度上說,能夠支援幾種常見的格式是非常有必要的,這會讓人感覺到簡單易上手。比如fbx格式,支援這個就意味著unity很多模型可以直接用了;比如mesh格式,支援這個意味著《火炬之光》《天龍八部》的模型可以直接顯示了。倒不是說鼓勵盜用資源來說商業遊戲,但是這種支援可以讓新手和福士更容易接受這個引擎的3D能力。作為一個不成熟的引擎,這方面的妥協才意味著成熟的表現。
最後,我不得不說,cocos2d-x的3D部分,至少是模型載入部分設計的很醜陋,很淩亂,而且寫的很不靈活。我一直很好奇,那些做開源遊戲引擎或者是公司內部商業遊戲引擎的開發人員難道都喜歡閉門造車嗎? 我們很容易就可以找到值得參考借鑒的解決方案。我也很好奇,為什麼一個公司可以花費三五年時間來研發一個遊戲引擎,而且一直給人一種半成品的感覺,一直人罵?要是我的話,直接把OGRE封裝一下,用一個月時間商討需要提供的介面和特性,基礎都擺在那裡了,要是一年時間還開發不出一個渲染引擎的話,那我就不做程式員了。誠然,但凡有些技術的,都會認為自己的設計才是漂亮的,其他人寫的東西都跟屎一樣。但是沒有成功的產品來支撐的話,一些吐槽都是大言不慚。
我自己也很喜歡吐槽這個吐槽那個,包括這篇文章說白了也是吐槽,但是我承認自己是個好高騖遠的屌絲。而那些拿著百萬年薪的大牛,也做同樣的事情,那他們是否值得欽佩?
總結一下,這部分說的就是,其實cocos2d-x沒有必要定義新的模型格式的,直接支援流行的三五種格式更加有利於推廣,這樣就不用操心和維護相應的工具鏈了。而且我確實沒有在其3D部分看到什麼技術優勢,所以更應該放下身段走平民路線。
二、項目開發中的勢與術
這個是我在看網路小說中看到的。所謂勢就是兵勢、國勢,所謂術就是戰術、奇術。 很多名將都有經典的戰術,而且這些“計謀”確實引導了勝利。但是這些經典戰術之所以流芳百世就是因為他們罕見,九死一生,以弱勝強。然而真正偉大的統帥永遠會讓自己站在優勢的一方而不是弱勢的一方。“勢”上的失敗需要用更多的“術”上的成功來彌補,而這其中只要有一個“術”失敗了,那就全盤皆輸。
引申到遊戲開發中,我的觀點就是:方向要選對,目標要明確,否則再牛的人也無法力挽狂瀾。
很多牛人,技術上無可指摘,但是看不清楚方向。遇到問題總會想著用自己的技術來解決,或者總是認為自己的解決方案才是最好的,這也是很多牛人喜歡自己造輪子的原因,因為他們確實可以在其他輪子上找到無數缺點。他們造出了完美的輪子,成為了“專家”,獲得了名望,但是卻沒有讓他們造的汽車大賣。這在我看來就是方向的錯誤。 當然,再如何說,他們的成就也是我輩凡人高瞻仰止的。
三、關於物件導向和設計模式
這個是學c++的基礎,也是面試中最常問的東西。這裡並不是說要寫一篇論文《物件導向-從理論到實踐》,而是說說我對於漂亮代碼的看法。
在上大學的時候我問過一個牛人學長,“經常看書中寫優美的代碼,那究竟什麼樣的代碼是優美的?”。他沒有回答出來。 這個問題應該是看得代碼多了,寫的代碼多了,才會有一些體會,而且還是自己的體會。 每個人的審美觀不同,有的人喜歡引用各種模式,來讓自己的設計無比靈活;有的人喜歡炫耀各種技術,來讓代碼顯得無比高端;有的人喜歡展示自己的基礎多麼紮實,寫的代碼非常符合教科書的條款。
(待續)
四、這就是重構
下面這個例子就是標題說的重構,看著很簡單,但是這就是我們實際開發中需要的技能。 我們需要的不是對照著108個設計模式,套到我們的項目中,而是看到“代碼的壞味道”,然後修正它們。
比如現在cocos2d-x的模型載入是在一個Bundle3D類裡面,載入時根據副檔名和檔案頭調用對應的函數,比如loadMeshData loadMeshDataJson等等。Sprite3D檔案中也有根據副檔名來判斷是用Bundle3D來讀取,還是用ObjLoader來讀取。
實際上一個小的改變就可以讓這塊兒的代碼變得靈活起來。
設計一個Bundle3DLoader,然後支援哪種模型的載入就對應實現一個Loader,比如Bundle3DMeshLoader,Bundle3DFbxLoader等等。一個Factory負責管理Loader。外部使用Factory擷取到對應的loader,然後調用loadMesh、loadMaterial等函數來完成實際模型的載入。
樣本如下:
class Bundle3DLoader{public:virtual bool isSupportFile(const std::string filePath) = 0;virtual void reset();virtual bool load(const std::string& path);virtual bool loadMeshData(const std::string& id, MeshData* meshdata);virtual bool loadSkinData(const std::string& id, SkinData* skindata);virtual bool loadMaterialData(const std::string& id, MaterialData* materialdata);virtual bool loadAnimationData(const std::string& id, Animation3DData* animationdata);virtual bool loadMeshDatas(MeshDatas& meshdatas);virtual bool loadNodes(NodeDatas& nodedatas);virtual bool loadMaterials(MaterialDatas& materialdatas);static std::string getFileExt(const std::string filePath);};
這樣,添加一個新的模型格式就直接實現一個對應的Loader就可以了,而且還可以在引擎外部實現,然後通過Factory來添加管理。
這裡沒有什麼進階的模式,但是確確實實的在改變代碼的可讀性。對應一個檔案一個模型載入的實現,要比3000行的Bundle3D可讀性大大提高。 這就是物件導向,這就是重構,它不神秘,它很簡單,它為工程服務。凡是寫了十幾個類,一大坨繼承關係來實現一個所謂的物件導向,都不是好對象。