引擎工具开发的一些总结

 

参数编辑

可以说, 引擎工具在除了一些特定操作外, 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


以上就是工作以来的一些经验教训了. 下一步还有什么可以尝试的? 或许是多人协作编辑, 多进程通信, 插件机制......

 

 

你可能感兴趣的:(总结)