序章
单字模型做完之后,手里有一个 ONNX 格式的识别模型——输入 64×64 灰度图,输出 7356 个汉字中概率最高的那个,内部 Top-1 约 96.5%。模型放在那,自己跑 benchmark 没什么意思,需要一个能让人实际体验的展示平台。
做网页 demo 是最直接的选择,但太常规了。当时想的是,能不能让展示本身变成一种玩法——不是在屏幕上点按钮,而是在一个 3D 世界里真正”写字”?写出来的字不是显示在文本框里,而是触发某种效果?
于是想到了 Minecraft。Minecraft 有成熟的 Mod 生态、3D 世界交互、以及大量可扩展的游戏系统。在 MC 里实现”手写汉字 → 识别 → 施法”,这个想法本身就很有趣。
这篇文章记录了从 ONNX 模型到可玩 Mod 的完整开发历程,涉及三个阶段、五个子项目。中间有一次重要的架构转向,以及一套在纯服务端实现手写识别的方案。
第一章:第一版原型——Fabric + Paper
技术选型
最初的方案是 Fabric 客户端 Mod + Paper 服务端插件配合运行。
客户端 Mod(在 Fabric 1.21.1 上开发)负责所有视觉和交互:用 Blaze3D 底层渲染 3D 书写平面,鼠标射线与平面求交获取 2D 笔迹坐标,笔迹走 ONNX Runtime 本地推理识别,识别后显示太极旋转法阵和确认环动画。
服务端插件(Paper 1.21.1)通过 Plugin Channel 接收客户端传来的识别结果,执行对应的法术效果——冷却管理、经验消耗、伤害计算、实体召唤等。
客户端做了什么
书写体验花了不少功夫打磨。笔迹渲染分了三层:外层金色光晕、中层棕色轮廓、内层深棕色墨芯,叠加之后有毛笔的质感。抬笔后 700ms 自动识别,配合一个逐渐收敛的确认环动画,给玩家”正在思考”的反馈。
做了两个书写模式:施写圈(G 键),在玩家面前的空中展开书写平面,写完对自己施法;感应圈(V 键),用来”发现”新字——写一个陌生汉字,服务端通过 SSE 流式调用外部 AI 服务,实时返回这个字的技能定义(名称、元素、稀有度、效果、费用、描述),客户端用打字机动画逐字展示。
内置了金木水火土五个基础元素法术,外加 spells.json 里预生成了七千多个汉字技能定义。发现后的字可以反复施放,冷却和时间消耗都有追踪。
服务端这边做了完整的技能执行引擎:30 多种效果类型(伤害、治疗、控制、位移、防御、召唤、环境),空间瞄准系统支持 self/sphere/cone/wall_plane 等多种目标形状,还有自定义魔杖物品和模型。
问题
效果确实好——在 3D 世界里手写汉字、看确认环转完、然后火球飞出去,体验很独特。但问题也很直接:玩家必须下载对应的 Fabric 客户端 Mod 才能玩。
这对推广是硬伤。你没法在服务器列表里写”来玩汉字施法服,先装这个 Mod”然后指望有人来。这个认知直接驱动了后续两个方向的调整。
第二章:架构转向——底座与玩法分离
设计反思
第一版原型验证了”手写汉字施法”这件事做得到、体验好。但在接着往下做之前,重新想了几个问题。
第一,不要把 AI 框死。 之前试过在一个小框架下让 AI 批量设计全部 7356 个字的效果,结果大量同质化,人工审核不过来。与其批量生产平庸效果,不如精选少量字、每个都单独设计。
第二,底座和玩法必须分开。 识别是基础设施——输入笔迹、输出识别结果——不应该和任何具体玩法绑定。底座只提供 API,玩法 Mod 基于 API 扩展。这样既约束自己不在底座里堆功能,也为以后开发者接入留了清晰的接口。
aozainkmc:纯底座,不做玩法
底座 Mod 定了一个硬规矩:不做任何玩法。不放火球、不回血、不召唤怪物、不生成技能 JSON、不连接数据库。只做三件事——手写输入、ONNX OCR 识别、把结果变成可查询/可监听的状态。
输入系统实现了两种书写模式:施写阵(G 键,地面附着)和铭刻阵(V 键,世界标记点附着)。书写面是 3D 的,太极纹理 45 秒周期旋转。采样用了原始 GLFW 鼠标输入而非 Minecraft 的输入系统,避免了 MC 自身输入处理的延迟和丢失。最小距离 0.012、最小间隔 22ms、最多 512 点/笔画、2048 总点。抬笔 700ms 自动识别,三层确认环动画(暗辉→琥珀→亮黄)给反馈。
OCR 管线:Java2D 把笔迹渲染为 64×64 灰度图,自动居中缩放加 18% 填充,然后进 ONNX Runtime 推理,输出 Top-K 候选,过滤只保留汉字。
API 层的设计花了最多心思:
InkMarkStore(内存级 ConcurrentHashMap)作为核心状态存储,一个识别结果就是一个 InkMarkInkGlyphRegistry:静态线程安全注册表,addon mod 注册自己要处理的汉字InkGlyphDefinition:支持 castOnly/anchorOnly、最低魔杖等级、识别后自动关闭等行为配置- 三个事件系统:
InkMarkBeforeAttachEvent(可取消,控制资源消耗)、InkMarkAttachedEvent、InkBlockTargetSelectedEvent - 语义标签:约 50 个汉字预映射到 element/quality/operator 标签,addon 可以基于标签做组合逻辑而非穷举每个字
魔杖分了七个等级:木→石→铜→铁→金→钻石→下界合金,每级不同耐久和 power 倍率(1.00→1.80)。还做了笔势(cultivation momentum)进度系统——写字积累进度,满后标记”渡劫”,写”劫”字触发升级配方。
一个关键的设计决策:底座不注册任何网络包。 单人模式下,客户端通过 MinecraftServer.execute(Runnable) + CompletableFuture 直接调度到服务端线程。没有网络层,减少了大量同步复杂性。这个设计在多人阶段会变,但在单人阶段非常干净。
第三章:glyphbound——23 个字的 RPG 玩法
底座只提供 API,真正的玩法在 glyphbound。它是底座的第一个官方 addon,注册了 23 个汉字,每一个都单独设计、单独实现,与 Minecraft 原版系统深度交互。
体字:不是 buff,是状态改造
14 个体字全部通过 AttributeModifier 和伤害管线实现,不是简单的药水效果。举几个例子:
命是最核心的设计——致命伤害被截断、锁住 1 点血,附带短暂金身无敌。每个周期只能用一次,用后有疲劳冷却。这不是”加防御”,而是改变了死亡的判定逻辑。
忍引入了”墨伤”机制——受到伤害的一部分被延迟结算到一个墨伤池里,之后缓慢以魔法伤害形式释放。配合命使用可以撑过致命一击:先忍把伤害拖住,命触发后金身时间里墨伤慢慢消化。这两个字的交互产生了一套小型战斗策略。
隐的设计让怪物在 5 格外丢失仇恨。不是简单的隐身药水——攻击、交互、破坏方块都会打破隐身,所以玩家必须真的”藏起来”,不能一边隐身一边挖矿。
明和脉是信息类技能。明周期性扫描周围敌对生物并以文字报告数量和类型,高阶附加发光效果,还附带矿石、箱子、陷阱、刷怪笼的探测试探。脉是明的升级版——持续夜视加大范围透视,用 BlockDisplay 实体标记被掩埋的矿石,暴露面用粒子标记。
魄是死亡回收——死亡时保留 50% 经验和部分掉落物,复活后限时写”魄”可以牵回。这是专门为单人生存设计的容错机制,不是简单的 keepInventory。
事件字与封印:组合深度
8 个事件字和 1 个封印字提供了世界交互和据点建设玩法。
山和裂是地形技能。山在指定位置生成 5 格宽临时石墙,材质从石头到黑曜石逐级升级,高阶带荆棘伤害。裂是延迟地陷——1 秒后塌陷半径 1-2 格深 2 格,推动敌对生物。两者都有完整的方块保存和回滚系统,不会永久破坏地形。
墨是 toggle 型技能,也是整个玩法里最复杂的设计——展开后进入墨染场状态:玩家造成的伤害翻倍但受到的治疗打折,场内敌对生物获得属性增幅并主动攻击玩家,周期性在边缘刷怪。敌人在场内被击杀有概率掉落墨核。这是一个典型的高风险高回报模式——你变强了,但敌人也变强了,要不要开需要策略判断。
封印系统是组合玩法的核心。两步操作:先用铭刻阵在方块上写”印”,定下方碑位置;再用施写阵写入印之字(心/命/救/息/忍/坚/稳/隐/力/怒/净/泉 中选一个)。方碑成为持续效果区域,进入区域的玩家自动享受对应效果。心印加生命上限和持续回血,命印给濒死保护,泉印群体治疗。
每个玩家每维度最多 3 个封印。方碑被破坏或玩家离开维度则消散。效果半径从 5 格到 24 格,持续时间从 3 分钟到 12 分钟,按魔杖等级递增。
墨界:自定义维度
写”入”字可以在面前生成临时传送门(墨砖框架 + 传送方块),步入后传送到墨界——一个自定义维度的前庭平台。墨界有自己的方块生成器、方块保护规则、传送门生命周期管理。
墨界里有个墨海——低于海平面持续受伤,深度超过阈值被推回岸上。不是简单的”掉下去就死”,而是一个有边界的危险区域。死亡、登出、换维度都有正确的状态清理和回传逻辑。
为什么每个字都单独做
有人问为什么不写一套通用的效果模板然后往里填参数。答案是在做的过程中发现——真正好玩的效果,恰恰是那些无法被模板化的细节。
命的”锁血 1 点”、忍的”墨伤延迟结算”、隐的”5 格外丢失仇恨”、墨的”toggle 双向增强”、封印的”两步组合”——每一个都是针对特定体验场景设计的,不是”攻击力 +X%“能概括的。23 个字不多,但每个都在原版的属性系统、伤害管线、AI 系统或方块状态上做了特定的 hack。质量比数量重要得多。
第四章:多人版——纯服务端手写识别
动机
glyphbound 的单人体验做完了,但多人怎么办?不能让每个玩家都装 NeoForge。这次的目标更激进:玩家不需要安装任何 Mod,用原版客户端直连。
服务端视角采样
这是整个项目里技术上最有趣的部分。客户端 Mod 的输入很直接——监听鼠标事件,拿到屏幕坐标。纯服务端没有这个条件。解决方案是反过来用 Minecraft 已有的信息:
- 玩家手持魔杖,右键切换”落笔/抬笔”状态
- 落笔状态下,服务端每个 tick 采样玩家的视角方向(yaw/pitch)
- 视角射线与虚拟书写平面求交(2.2 格距离,1.35 格半径),得到 2D 坐标
- 最小距离和点数过滤后,Shift 结束书写触发识别
- OCR 管线在服务端跑(同一个 ONNX 模型,同一个光栅化器)
视觉反馈全部用原版粒子实现:金色粒子圆环标记书写边界(96 个 dust 粒子,每 5 tick 刷新),深色粒子标记采样点位置,Action Bar 实时显示笔状态和点数。
还有一个关键改进:允许集识别。 不是从 7365 个字里做全量分类,而是根据玩家当前所在的模式和可用技能,只对允许集内的汉字做分类。类别数从 7000+ 压缩到十几个,准确率大幅提升。这个设计在之前的单字研究阶段从来没想过——分类不是越多越好,场景约束本身就是一种特征。
双模式
插件按玩家所在世界自动切换法术集。
生存模式 12 个字,有等级解锁门槛:泉(团队治疗区)、山(临时石墙)、裂(地陷陷阱)、盾(伤害吸收)、影(隐形诱饵吸引怪物)、门(短距传送)、稳(取消速度变化)、灯(夜视加矿石发光标记)、净(清负面效果)、怒(受击积累攻击加成加冲击波)、坚(伤害减免)、兵(召唤铁傀儡盟友)。
地牢模式 8 个字,针对 dungeon 场景做了差异化:水(灭火加治疗)、火(扇形攻击,对普通怪/精英/Boss 伤害不同)、土(泥浆减速场)、风(AOE 击退加速)、日(解除失明加夜视)、石(射线石化——普通和精英怪 AI 禁用,Boss 减速)。
地牢模式的字和生存模式不重叠,因为场景需求完全不同——你在 dungeon 里不需要”命”和”魄”,但”石化”和”灭火”非常有用。
全部走原版协议
魔杖外观用 Stick + CustomModelData + 资源包 ItemModel 覆盖。书写可视化用 Particle.DUST,只发给对应的玩家。法术反馈用 Title subtitle 和 Action Bar。持续时间用 BossBar,每人独立。资源包通过 setResourcePack() 下发,SHA-1 验证。技能菜单用雪球物品打开 AuraSkills GUI。
没有任何一个功能要求玩家安装 Mod。写字的体验当然不如客户端 Mod 版——粒子描点没有真正的 3D 笔迹渲染那么流畅——但胜在零门槛。这个取舍在多人在线场景下是对的。
第五章:项目全貌与设计复盘
三个阶段的演进逻辑
第一阶段(Fabric + Paper)证明了”手写汉字施法”可行,但依赖客户端 Mod 导致无法推广。
第二阶段(NeoForge 单人)转向深度玩法,通过底座/玩法分离的架构解决了可扩展性问题。aozainkmc 只做识别 API,glyphbound 基于 API 做 23 个字的 RPG 玩法。
第三阶段(Paper 服务端)将单人经验反哺到多人场景,技术上将整个输入和识别管线从客户端搬到了服务端,配合允许集分类保证了准确率。
三个阶段共用一个技术基石:cup_ocr_64.onnx 模型和同一套笔迹→光栅化→推理管线,在客户端和服务端分别独立运行。
底座/玩法分离架构
这是我在这轮开发中学到的最重要的一课。把识别底座和具体玩法拆开,有几个好处:
- 约束自己。底座不放任何玩法效果,就不会忍不住往里堆功能
- API 即文档。
InkGlyphRegistry的注册接口就是给未来的开发者用的,glyphbound 自己是第一个使用者 - 独立迭代。底座升级 ONNX 模型或输入采样逻辑,不影响玩法;玩法加新字或改效果,不需要动底座
- 开放扩展。理论上任何人都可以基于 aozainkmc 写自己的 addon,注册自己要处理的汉字
用原版特性做 Mod
多人插件的经验表明,大多数”看起来需要 Mod”的功能,其实用原版协议就能搞定。粒子系统做可视化、BossBar 做计时器、资源包做模型覆盖、聊天拦截做快捷绑定——这些都不是新东西,但组合起来能做出很完整的体验。关键是知道原版都有什么、以及每个特性的边界在哪。
允许集比全量分类更重要
之前做单字模型研究了几个月怎么从 7356 个类中挑对那一个,但在实际产品里,场景本身就把选择空间压缩到了十几个。这不是说全量分类的研究白做了——模型必须有能力在全部 7356 个字中区分——而是说在工程落地时,场景约束是最便宜的特征。
写在最后
从”识别模型需要一个展示平台”到”一套完整的 3D 法术系统”,整个项目横跨了大半年。三个阶段、五个子项目、两个 Mod 平台、一个插件平台,累计几千行 Java。
最有意思的不是技术本身,而是写代码的过程中不断发现的”这件事原来可以这样做”——服务端识别不是一开始就想到的,底座/玩法分离是被第一版原型的问题逼出来的,允许集分类是上线前临时想到的优化。
目前三个阶段的代码都已归档到 GitHub,墨蕴的 alpha.1 版本在 NeoForge 和 Paper 上都可以跑。后续的方向取决于玩的反馈——如果某个字太强或太弱、某个组合没人用、某个场景缺一个合适的技能,这些都会比闷头多做几十个字更有价值。
本项目借助了 AI 工具辅助代码实现和思路梳理。