关键词: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快照之上。
这样做有一个直接的好处:同一套规则可以被测试、被分析器、被解法预览反复复用,而不用担心「编辑器里跑的是简化版,游戏里跑的是物理版」这种两边对不上的问题。运行时代码也从不依赖 UnityEditor 或 AssetDatabase,关卡通过显式序列化的 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 做出来,而是把一个玩法继续往前推了一步:如果今天坐在工具前的是关卡设计师,他能不能更快、更放心地做内容?
开发过程中,我把自动化验证、独立审查和可重复截图也纳入了工作流——但这些只是保证迭代质量的手段,项目本身的重点仍然是玩法、数据与设计工具之间的关系。