前言
一年多没更新博客了,原因是疫情期间《骑马与砍杀2》发售,然后去写游戏MOD去了。
用C#大概写了7个月的游戏MOD,每天晚上肝到很晚,然后期间又因为介绍这个游戏MOD,学习了PR,然后做起了B站的UP主。
再到后面有了些别的想法和公司业务调整,也懒得写博客,不知不觉一年多也就过去了。
收获还是有的:
比如在断更这个MOD时,不论是在中文站还是3DM的MOD站,这个MOD的下载量都是排第一的,而且甩第二名相当远。如果有玩《骑砍2》MOD的朋友,应该猜出来我是谁了。
又比如在B站收获了五千多粉丝,从一开始说话结结巴巴,到最后也还是说得结结巴巴。不过因为自己的剪辑,观看效果也还不错。
又比如深刻认识到做个UP和主播有多麻烦,就我这拉胯的数据其实已经领先了B站很多UP主了。UP主中更多的不是头部UP,而是视频0播放的UP主。你可以看一下B站的最新视频,翻了几十页全是0播放,极为壮观。
有趣的人生体验增加了
好了,言归正传。
现在基本MOD断更,UP主也懒得继续认真做了。
更重要的是它可以直接修改你电脑上的的文件。
这是最终成品的代码仓库:https://gitee.com/vvjiang/mod-xml-editor
以及一张成品展示图:
本篇博客所涉及到的技术:
CodeMirror
react-codemirror2
xmldom
FileReader
IndexDB
Web Worker
File System Access
让我们从头开始讲起。
在做《骑砍2》的MOD时,需要经常写XML文件。
因为骑砍2的数据配置就是以XML的形式保存,然后MOD加载后,用MOD的XML去覆盖官方自己的XML。
通常我们做MOD数据这块,就是参考官方的XML自己去写XML文件。
但是这样会遇到一个问题,XML这东西没有代码提示和代码校验,写错一个字符也很难发现。
又或者有时候游戏更新,它的XML规则可能会改动。
官方是不会发布通知告诉你这些改动点的,所以如果你还是用的以前的元素和属性那就等于写错了。
写错的结果往往是游戏加载MOD时直接崩溃,也不会给你任何提示,你只能慢慢去寻找BUG。
而骑砍2作为一个大型游戏,每次启动时间都很长,导致你测试一个MOD数据是否配置正确的测试流程会非常长。
妈耶,多少个夜晚,游戏崩溃的那一瞬间,我人就崩溃了。
技术预研
可视化编程
你别说,我还真的做了一套初步方案出来,结果配置一个大型的XML这玩意拖拉拽无数次,心态逐渐爆炸,遂放弃此方案。
VSCODE插件
想看看有没有什么VSCode插件可以进行代码提示,有一个使用XSD进行代码校验的,貌似还是IBM提供的。
但是很可惜已经废弃,然后用不了了,放弃此方案。
然后我这边就尝试着做了一个,了解到了CodeMirror。
CodeMirror通过自己配置tags来支持xml的代码提示,但是并不支持xml的代码校验,所以需要自己去做xml的代码校验。
并且因为通常我们去校验xml用的是xsd,所以还需要将xsd转换成CodeMirror的tags配置。
这个不论是百度Google,还是说Github,都是查不到相对应的方案,所以只能自己写代码去实现。
在这个过程中,我对CodeMirror,xsd,htmllint都有了比较深的一个了解,最终完成了项目。
因为这是之前公司的代码,所以这里就不放出来了。
好了,废话不说,拿起键盘就是无脑干。
涉及到的技术就三个:
CodeMirror
FileReader
xmldom
CodeMirror这块主要使用的react的一个封装版react-codemirror2,反正就是看文档和Demo自己配。
总之你想玩的话最好还是看官方文档(https://codemirror.net/) 和文档上的Demo,然后自己研究下,抄别人配置的话水很深,你把握不住的。
里面的注释比较详尽了,包括常用的代码折叠,代码格式化都有,我就懒得一一讲了,你可以参考官网自己看看。
其中的一些引用代码我就不贴了,有兴趣的可以去上面提到的代码仓库看看。
学习XML,并提取出tags规则
很明显,不同的游戏有不同的XML规则,包括游戏更新之后XML规则也会更改。
所以我们必须要保证有一个机制去不断地学习这些XML规则,所以这里我做了一个学习XML文件规则的弹窗去做这个事情。
会弹出这样一个弹窗:
通过FileReader读取指定文件夹的XML文件,然后使用xmldom来依次解析这些xml文件的文本,生成文档对象。
再分析这些文档对象得到最终的tags规则。
这一步骤只需要对xml有所了解,其实也蛮基础的,所以不讲了。
涉及到的技术就两个:
FileReader
Web Worker
左侧文件树
左侧这个文件树使用Ant Design的Tree组件完成,这里配置什么的就不讲了。
同样使用FileReader来读取MOD文件夹中的文件。
但是FileReader获取到的是一个文件数组,要想生成我们左侧的树形结构需要自己手动解析每个XML文件的路径,并据此生成一个树形结构。
全文件校验功能
在打开文件夹的一瞬间,我们需要对全部的XML文件进行一次代码校验,如果校验有误,需要在左侧文件夹上将相关的文件及它父级祖级的一系列文件夹全部标红。
这个功能表面上很简单,其实坑点很大,因为校验的计算量实际上并不小,特别是你的MOD中有几百几千个文件的时候,非常容易搞得你js阻塞,页面无响应。
在这里我使用了Web Worker新开一个线程去处理这个校验过程,在校验完成后将结果返回给我。
在这个过程中,我对Web Worker的使用也有了更多的了解。
印象中一直以为是一个new Worker(某js文件)这样的方式去玩,感觉很难结合react的模块化开发来使用。
但是实际上现在在webpack里配置上worker-loader,可以很方便使用Web Worker。
首先我们的worker代码可以写成下面这样:
import { lintFileTree } from ‘@/utils/files’onmessage = ({ data }) => {lintFileTree(data.fileTree, data.currentTags).then(content => { postMessage(content)})}
然后我们使用这个Worker时,可以如下所示
import { useWebWorkerFromWorker } from ‘react-webworker-hook’import lintFileTreeWorker from ‘@/utils/webWorker/lintFileTree.webworker’const worker4LintFileTree = new lintFileTreeWorker()const [lintedFileTree, startLintFileTree] = useWebWorkerFromWorker(worker4LintFileTree)
然后你再用个useEffect依赖这个lintedFileTree,如果变动了就做某些操作,所以写起来就像用useState一样轻松。
非递归遍历树
大家可以看到上面我们用到的这些东西,很多都与树相关,比如遍历文件树去校验代码。
又或者我们切换了某个约束规则后,也是需要遍历整个文件树进行重新校验的。
遍历的过程中,之前我用的是递归遍历整个树,这样做不好的地方在于递归的时候内存得不到释放,所以后来我换了一种算法,采用非递归的方式遍历整个树。
IndexDB保存文件内容
因为我们的MOD文件内容比较多比较大,所以内存占用可能会很大,不可能一直把这些文件内容放到内存中。
只有在需要的时候,比如全文件校验或者切换文件时,才从IndexDB再次获取文件内容。
究极进化形态:突破浏览器沙盒限制,实现对电脑本地文件的增删改
但是它有一个致命缺点,就是受到浏览器沙盒环境的限制,我们在修改了文件后,没法直接保存到电脑上,而必须依靠手动将修改好的代码一一复制到对应的文件中。
之前我以为只能做到这种程度,但是后来我在知乎上偶然看了一个帖子,发现Chrome86 的版本多了一个功能API:FileSystemAccess。
另外,除非是本地localhost环境,否则这个API只在https环境下才能调用,也就是说你在一个http的网站上,即使你用的是Chrome86 或者是Edge86 ,那也是调用不了的。
这个API可以让我们直接操作本地电脑上的文件,而不是像FileReader一样只能读,或者像FileSystem一样只能在浏览器沙盒内操作。
通过FileSystemAccess我们不仅可以实现对文件夹中的文件进行读取修改,还能新增和删除文件。
所以我使用这个API全面替换了之前使用FileReader的各个点,实现了在文件树上右键进行文件夹和文件的新增和删除。(这里是不支持对文件进行重命名的,不过其实我们可以使用删除后再新增的方式来模拟重命名,但是我就懒得做了)
同时在按保存按钮或者按保存的快捷键Ctrl S后,就可以直接对文件进行保存操作。
下面是一个使用FileSystemAccess打开文件夹的组件代码:
import React from ‘react’ // 自定义的打开文件夹组件 const FileInput = (props) => { const { children, onChange } = props const handleClick = async () => { const dirHandle = await window.showDirectoryPicker() dirHandle.requestPermission({ mode : “readwrite” }) onChange(dirHandle) } return <span onClick={handleClick}> {children} </span> } export default FileInput
在打开文件夹后,通过获得的文件夹handle去请求文件夹写入权限,然后再把这个文件夹handle传到外部,获取文件树结构。
这里的操作是有瑕疵的,因为请求打开文件夹时浏览器会弹框向用户获取读取文件夹的权限,
打开完毕后又直接会弹第二次框获取写入权限,也就是说在打开文件夹时会弹两次框。
但是我也只能通过这种手法一次性请求到所有的权限,要不然等到要保存时再去请求权限也不太好。
不过瑕不掩瑜,通过这个API不仅实现了文件的增删改,还解除了对IndexDB的使用。
因为我们随时可以通过文件Handle获取到相应的文件内容,所以没必要将文件内容保存到IndexDB中。
更多的功能与细节
比如调整tags规则的面板,比如那些工具栏的按钮,比如对dva的简单封装处理,比如对xml进行分析时,如果属性值是数字,那么就不进行提示,而是直接忽略,因为数字往往没太大意义而且枚举值太大。
这一切的一切,都太多太多,但是它们的应用都比较基础,所以不想赘述细节,否则这篇博客就会变得非常长,而且难以突出核心思路。
不足与总结
这里的不足更多的是因为懒,比如之前说的文件夹和文件重命名功能,还有调整tags规则的自定义规则那里不支持修改删除。
可以实现,只是懒得做了。
这个东西前前后后做了几个月,也不是说每天晚上都在写这个,主要是有灵感了就来写一下,或者发现哪里可以更好地改进一下就再写一下。
合起来约摸着有两三周的每个晚上在做这个事情,然后当它愈加趋近于完善和可用时,就愈加懒得做了。
因为剩下的操作不太重要,且脑补一下就可以完成,没有太多有挑战性的地方了。
不过总体来说,这个东西现在的可用性还是很强的。
不仅仅可以用于《骑马与砍杀2》、《了不起的修真模拟器》、《文明6》等一系列游戏的XML文件的辅助编写,还可以用于那些没有XSD规则,又过于复杂的XML配置,甚至它还可以学习你自定义的XML规则。
本篇博客就到此结束了,如有疏漏之处,也希望大家不吝赐教。
源自:https://www.cnblogs.com/vvjiang/
感谢 · 转发欢迎大家留言