參數編輯可以說, 引擎工具在除了一些特定操作外, 80%的事情都是在進行參數的編輯與儲存等. 從我接觸工具開發開始, 就一直在學習如何簡化這麼部分的工作. 因為我見過很多業餘的編輯器, 大多都是每加一個參數就在UI層寫一些代碼, 在IO層加一些版本相容代碼等. 而這些代碼常常都是大同小異的, 很多都是Ctrl+C, Ctrl+V出來的. 說起來, 這個探索過程中我也走了不少的彎路, 順便寫出來當教訓吧最早是從java/.net轉來寫C++, 所以對於C++的UI開發十分的不滿. 但是對於3D引擎來說, 目前來說語言也沒有更好的的選擇. 所以, 也有很多引擎是多語言的架構, 如底層C++, 工具C#, 邏輯lua. 這是比較常見的一種選擇. .net在語言層次對於反射和序列化提供了非常好的支援, 可以參考我早期的文章: 強大的PropertyGrid. 為此, 我自學了C++/CLI, 把C++與.net的interop全部搞定了, 並且使用WPF試著做了一個工具. 結果呢? 沒有人能維護的了, 因為.net對於那些唯寫過C/C++的人來說, 太複雜了. 更何況, 又加上一個毀三觀的WPF. 所以說, 新技術並不一定就好, 就算它們吹的多麼玄乎, 也不要忘了自己團隊的實際情況. 雖然這次嘗試最終放棄了, 不過在此過程中積累的經驗讓我山寨成功了Unity3D的指令碼系統. 這條路不是說走不通, 因為很多國外的中小引擎就是這麼乾的. 只不過對於開發人員的要求會高一些. 做過指令碼系統的人都知道, 在兩種語言之間轉來轉去的要多噁心就有多噁心.當然, 上面這些嘗試, 多數是業餘的技術玩具. 工作中, 很多工具還是MFC的. 所以現實一點的話, 一般還是會在原有基礎上做. 所以那時也參考了一下同樣是MFC寫的Ogitor(後來改Qt了). 於是乎就有了這麼一篇: 基於屬性的編輯器架構. 這個思路經過驗證還是不錯的, 對於當時的我來說, 在一條沒有人走過的路上把東西做出來了, 算是一種自我突破. 一些編輯器常見的問題: Undo/Redo, 版本格式相容等也做了考慮. 不過有兩個問題沒有解決: 一是屬性的訪問效率, 二是代碼冗餘(手工重複添加的代碼太多).中間還試過把WPF的控制項放到MFC的工具裡, 雖說技術上的問題也都解決了, 但是只要出了問題別人都搞不定. 算是一條邪路, 哈哈目前階段, 在接觸了一些大牛和商業引擎後, 最終的選擇是: Qt + C++反射 + C++序列化. 這個雖然有對語言進行改造的嫌疑, 但是實際項目驗證下來, 是相對比較完美的解決方案. 因為一旦把底層搞定了, 後續開發可以節省程式至少三分之二的開發量, 一勞永逸. 參見: 關於遊戲引擎結構上的思考, C++的反射和序列化
Undo/Redo(撤消/重做)我們一大牛說過: "判斷一個工具是不是成熟, 就看它有沒有Undo/Redo的功能". 的確是這樣的, 因為在我維護過的編輯器裡, 只要沒有做到這一點的, 編輯器都是拿代碼堆出來的, 沒有一個整體上的設計, 然後換個人來維護就是死去活來的感覺. 其實看過設計模式的人都知道, Undo/Redo就是使用Command模式來解決. 但是, 從我面試過的人來看, 大多數都是知道這個模式, 真正做了的很少. 因為這個模式有一個弊端: 編碼量大. 因為很多操作只是改變一個變數的值而已. 所以呢, 一些偷懶的程式員, 就把這個功能給省了, 反正工具的使用者通常最低的要求是"先有這個功能, 再考慮易用性". 在基於屬性的編輯器架構裡, 我第一次嘗試了基於屬性的Undo/Redo. "編輯器"其實本質上來說, 就是"編輯資料". 所以呢, Undo/Redo本質上來說, 就是"資料"的備份/還原的過程. 按照這個思路來設計, 肯定是沒有錯的. 最近結合C++的反射序列化做了Undo/Redo, 其實就是通用的Undo/Redo操作. 這樣就解決了Undo/Redo需要每個操作都定義一個Command的問題, 因為資料抽象了, Command也簡化了.
檔案格式版本相容這也是一個很多項目面臨的問題. 對於二進位檔案來說, 低水平的人會直接把結構體寫進去, 加個版本號碼; 中水平的人會使用ChunkData, 讓格式可以擴充. 高水平的呢? 格式中儲存的參數可以改變類型, 增加/刪除屬性, 不但向下相容, 還向上相容. 所以說, 很多人會選擇XML/JSON來做開發時的資料儲存格式. 相對於二進位格式來說, XML/JSON為什麼可以實現新老版本之間的相容呢? 我覺得本質上來說, 還是因為它的屬性訪問是基於"名字"的. 也就是說, 把檔案格式設計成類似於map的方式, 通過key去尋找對應的值, 就可以實現版本之間的相容. 形象點說, 檔案裡儲存的是pair<name, value>的集合. 那麼, 二進位格式也要把"名字"字串儲存進去嗎? 雖說有這麼乾的, 更好的辦法是儲存字串的CRC值, 也就是pair<nameCRC, value>.
穩定性不會犯錯的程式員, 基本上是不存在的. 所以呢, 工具中新開發的功能, 99%是有BUG的. 嚴重一點, 就是崩潰了. 然後程式就會不停打噴嚏了. 如何改善工具的穩定性呢? 這個需要分三個方面來說. 預防, 容錯, 查錯, 三管齊下.把錯誤扼殺的搖籃裡是最理想的. 這個就需要引入自動化的測試. 但是呢, 我之前都沒有重視這個, 然後就不停地做善後工作. 做煩了回頭來想想, 原來是這個東西沒做到位...所以呢, 算是一個教訓, 還沒有經驗, 提醒自己改進容錯的話, 通常就是做異常處理了. 這個只是補救的辦法, 讓使用者體驗好一點而已. 如果之前使用了Command模式, 那就更好辦了, 只需要在Command執行的時候統一處理就行了. 雖說容錯可能會導致更加嚴重的錯誤, 但是多數是可以補救的, 利大於弊查錯. 如果經常在做這件事, 那就要從頭想想自己哪裡沒做到位. 這裡說的, 只是方便查錯的一些措施. 一, 多寫log, 代碼中多寫assert. 二, 產生dump, 讓使用者可以反饋崩潰. 順便推薦一個庫: CrashRpt以上就是工作以來的一些經驗教訓了. 下一步還有什麼可以嘗試的? 或許是多人協作編輯, 多進程通訊, 外掛程式機制......