关键词:Unity 2022.3.51f1、Sokoban、Level Editor、Level Validator、Level Analyzer、Solution Preview、Playtest、Editor Tooling


这是一次技术策划方向的笔试项目:题目要求做一个完整的推箱子(Sokoban)加上一个关卡编辑器。我没有停在「能摆格子」,而是继续追问了一个更实际的问题——设计师做完一个关卡之后,怎么知道它有没有问题、可不可解、最短解是什么、能不能直接试玩?

于是最后真正交出去的,不是「一个推箱子 + 一个画格子的窗口」,而是一条关卡内容生产链:

制作 → 校验 → 分析 → 解法预览 → 一键试玩 → 迭代

游戏实机

下面按这条链来讲,重点放在「为什么这么做」而不是「我做了哪些功能」。


1. 为什么不只做一个「能摆格子的编辑器」

推箱子本身的规则几十行就能写完:玩家移动、推箱子、箱子到目标点、胜利判定。真正值得花力气的地方不在玩法,而在于数据改动之后的反馈速度

一个关卡设计师的工作是反复改数据:加一堵墙、换一个箱子位置、改一下目标点。如果每次改完都要「打包 → 进游戏 → 手工走到终点」才能知道结果,这个循环慢得没法用。所以工具链的价值不在「能画格子」,而在每一个改动之后,能不能立刻得到可靠的反馈:有没有错、能不能解、怎么解、能不能直接试玩。

内容总览

内容总览会对整个正式关卡目录执行校验与分析,并直接进入编辑、解法预览或试玩。


2. 确定性的 Board

为了让「编辑器里的判断」和「游戏里的行为」完全一致,我把规则系统收敛到了一个确定性的整数网格 Board 上:

  • 格子用整数坐标索引,TryMove 决定每一次移动与推箱是否合法;
  • 物理从不决定推箱的合法性——动画只是视图层的缓动,棋盘状态永远是即时权威;
  • Undo / Restart / 步数与推箱计数,都直接建立在 Board 快照之上。

这样做有一个直接的好处:同一套规则可以被测试、被分析器、被解法预览反复复用,而不用担心「编辑器里跑的是简化版,游戏里跑的是物理版」这种两边对不上的问题。运行时代码也从不依赖 UnityEditorAssetDatabase,关卡通过显式序列化的 LevelCatalog 加载,而不是资源发现。


3. 关卡编辑器不是画格子工具

编辑器真正的对象是「工作副本」(Working Copy),而不是资源本身:

关卡编辑器

  • New / Load 得到一份可编辑的工作副本,资源本身不会被静默修改;
  • 画 / 擦 / 改尺寸:调色板提供墙、地板、目标、玩家、箱子、擦除,以及压力板/门 A·B 两组;
  • Undo / Redo 作用在编辑历史上,而不是直接改资源;
  • 校验问题可点击导航:点某条警告会直接跳到出问题的那个格子;
  • Save / Save As 才把工作副本写回 .asset
  • Analyze / Preview / Playtest 就在编辑器旁边,改完立刻能验证。

一句话:这里的设计目标是设计师的工作流,而不是「一个能涂格子的画板」。当存在阻断性错误时,Playtest 按钮会直接禁用——工具替设计师挡住了「保存一个肯定跑不起来的关卡」。


4. 提前发现错误:Error 与 Warning 的边界

校验器把问题按严重程度分成两类:

  • 阻断性错误(Error):箱子和目标点数量不一致、占据者落在墙上或门上、网格格式不对……这些会拦截保存和试玩。
  • 建议性警告(Warning):比如一个箱子停在非目标格、且两个正交方向都是墙/边界——这是「静态死角死锁」,只提示、绝不拦截。

校验与死锁警告

为什么把死角死锁做成警告而不是错误?因为有些死锁是设计师故意要的——它可能是一个「陷阱」式的谜题的一部分。工具越严格不一定越好,真正重要的是让「规则」和「设计判断」之间的边界清楚:硬性不变量由 Error 守,可争论的问题留给设计师自己决定。这是一个技术策划需要想清楚的判断,而不是「校验规则越多越好」。


5. 让工具承认「我不知道」

分析器是一个有预算上限的广度优先搜索,它遍历的是真实的 Board 状态,并给出三种判定:

判定 含义
可解 在预算内找到了解,并报告最短移动解和步数/推箱数
无解 在预算内穷尽了可达状态空间,且不存在目标状态
未确定 还没得出结论就触发了预算(状态数或时间)

分析器

最值得强调的一点是:预算耗尽绝不等于无解。对一个足够大的关卡,分析器会如实说「我不知道」,而不是把「我算不动了」包装成「这关无解」。这个「未确定」状态是我认为整个工具链里最有价值的一个设计——它让工具从不说谎。

找到解之后,解法预览会在一张独立的预览棋盘上回放最短解:

解法预览

预览棋盘独立于 LevelDefinition 和工作副本,回放的过程绝不会污染任何已保存的资源。


6. 用 Multi-group Door 验证整条链能不能扩展

为了证明这条流水线能承载「新规则」而不只是「新瓦片」,我把压力板 + 门端到端地加了进来:A 压力板控制 A 门,B 压力板控制 B 门,两组互不影响。

多组压力板与门

重点不是「多加了一种玩法」,而是这个机制同时要求数据、棋盘、撤销、校验器、分析器、解法预览、编辑器、内容总览、运行时、测试一起跟着扩展。如果链条里任何一环是写死只为「箱子 + 目标」服务的,这个机制就加不进来。

其中有一个关键的设计决策:门的状态是派生的,从不存储——每次移动/撤销/重新开始后,它都是玩家与箱子格子的纯函数。正因为如此,撤销、重新开始、以及分析器的已访问状态集都自动保持正确,不需要为门单独维护状态。


7. 最终关卡生产流程

把这些工具串起来,一个关卡的完整生命周期是:

内容总览 → 找到关卡 → 编辑 → 校验 → 分析 → 看最短解 → 保存 → 一键试玩 → 再迭代

一键试玩会把工作关卡直接送进 Play Mode,用和正式游戏完全相同的运行时加载器跑起来——不是「编辑器里有个模拟器」,而是「你玩到的就是将来玩家玩到的规则」。

关卡完成


8. 验证与自动化

最终状态是用测试钉住的,而不是靠「我试了一下能跑」:

  • EditMode 196/196、PlayMode 4/4 全部通过;
  • Windows x64 Release 构建通过;
  • 目录审计:8 个已发行关卡(从入门推箱到压力板/门综合),0 错误、0 不可解。

顺手做的一件工程小事是自动截图:一条 PowerShell 命令会启动 Unity、自动准备 Runtime 和 Editor 的十个场景状态、截图、做 PNG 质检(黑图/空图/重复检测)、再把结果写进 README。它保证了文章里这些截图是可重复生成的,而不是某一次手工截下来之后再也复现不了。


9. 如果重新做,我会改什么

  • 运行时美术仍然是轻量程序化表现——用 OnGUI 而不是 UIElements,够用但不精致;如果继续做,优先换掉它。
  • 分析器有预算上限,大关卡会落到「未确定」;这是刻意的取舍,但意味着它不是一个完整的求解器。
  • 内容规模有限:8 个关卡覆盖了从基础到压力板/门的弧线,但谈不上「内容丰富」。
  • 如果继续投入,我会优先打磨关卡质量,而不是继续往上堆系统。

这些取舍写出来,比「项目很完美」更接近真实。


结尾

对我来说,这个项目真正有意思的部分,不是把 Sokoban 做出来,而是把一个玩法继续往前推了一步:如果今天坐在工具前的是关卡设计师,他能不能更快、更放心地做内容?

开发过程中,我把自动化验证、独立审查和可重复截图也纳入了工作流——但这些只是保证迭代质量的手段,项目本身的重点仍然是玩法、数据与设计工具之间的关系。