网格物理关卡
格子移动与碰撞:从规则到实现
用统一的“占用/可达”模型,处理推箱子、位移技能与阻挡。
0
玩法拆解
数据模型
- 占用表 occupancy[x,y] → entityId
- 层级:地形/单位/投射物
移动判定
- 先算意图,再做 resolve
- 支持链式推动(可选)
关键代码
占用表(简化)
ts· 18 行
Demo
最小可试玩:网格移动 + 墙体阻挡(用于演示占用/阻挡模型)。
文章
文章以 Markdown/MDX 文本子集渲染(不支持自定义组件)。
格子移动与碰撞:从规则到实现
这篇文章聚焦一个很常见但很容易“越写越乱”的系统:网格移动 + 阻挡 + 推动 + 多层碰撞。
目标是把所有判定收敛到一个统一模型里,让你后续加上:
- 推箱子/推动链(A 推 B 推 C…)
- 位移技能(冲刺/击退/牵引)
- 多层碰撞(地形 / 单位 / 投射物 / 机关)
- 关卡机制(门、开关、冰面、传送)
都不会变成“到处 if-else 补洞”。
---
1. 核心结论:用“占用表 + 解析器”统一规则
不要让每个系统各写各的判定(移动系统写一次、技能系统再写一次、怪物 AI 再写一次)。
推荐的中心结构:
- 占用表(Occupancy):网格上每个格子当前被谁占用(按层)。
- 移动意图(Intent):某个实体希望从 A → B,原因是什么(玩家输入/AI/技能/击退)。
- 解析器(Resolver):把意图在占用表上“结算”为一个可执行的结果(成功 / 被阻挡 / 推动链成功 / 失败回滚)。
> 你要做到的是:所有移动最终都走同一个 Resolver。
---
2. 数据模型:层(Layer)比“类型 if-else”更省命
一个格子可能同时存在:
- 地形(墙、地板、陷阱、传送带)
- 单位(玩家、敌人、箱子)
- 临时物(投射物、范围提示、特效占位)
因此占用表建议按层存:
terrain[x,y] -> terrainIdunit[x,y] -> unitIdprojectile[x,y] -> projectileId(可选,通常投射物更适合连续空间)
这样“能不能走进来”就是按层组合规则,而不是写成“如果是箱子且是冰面且是门…”。
---
3. 移动结算:两阶段(意图 → 结算)比“边走边改”安全
错误做法:玩家按键时直接改坐标,再检查碰撞,发现不行再改回来。 这会导致连锁推动、多人同时移动、以及回放/撤销时非常痛苦。
推荐做法:先构造意图,再统一结算:
- 输入阶段:收集意图
MoveIntent(entityId, from, dir) - 结算阶段:在“快照占用表”上运行 resolver,得到
MoveResult - 应用阶段:把
MoveResult变成一个“可回放”的事件序列(写入事件日志)
---
4. 推动链(Push Chain):递归/迭代都行,但要可回滚
推动链的本质:
- 我想进入格子 B,但 B 被箱子占用
- 如果箱子能被推到 C,则我可以进入 B
- 如果 C 被另一个箱子占用,则继续尝试
- 直到遇到空格(成功)或墙/不可推物(失败)
关键点:
- 一次结算里,不要反复读写真实占用表;在临时结构里模拟。
- 失败时能“整条链一起失败”,不出现半成功导致穿模。
一个实用的实现套路:
tryMove(entity, dir)返回{ ok, moves[] }moves[]是按顺序的位移列表(例如先推最后一个箱子,再推前一个,最后移动玩家)
---
5. 位移技能(冲刺/击退/牵引):把它当“多步移动”
很多人写技能位移时会绕过网格移动系统,直接改坐标,然后再补一堆特殊判定。
更稳的方式:
- 冲刺 = 连续 N 次
MoveIntent(每步都走 resolver) - 击退 = 由受击事件触发的
MoveIntent(方向由攻击者到受击者) - 牵引 = 由技能触发的
MoveIntent(方向相反)
好处:
- 技能不会“穿墙”
- 推动链自然生效
- 你还能统一做“碰到墙停止”“碰到机关触发”等规则
---
6. 常见坑与验收清单(写完就用来对照)
6.1 常见坑
- 顺序依赖:两个单位同时想进入同一格,先处理谁会影响结果。
- 幽灵占用:实体移动后忘记更新占用表,导致后续判定认为格子仍被占。
- 半成功:推动链中途失败但前面的箱子已经被移动了。
- 只在“移动”判定墙:技能位移/击退没有走同一套判定,导致穿墙。
6.2 最小验收(建议写成自动测试)
- [ ] 不能走进墙
- [ ] 推动 1 个箱子成功/失败都正确
- [ ] 推动链 2~3 个箱子成功/失败都正确
- [ ] 冲刺/击退不会穿墙,且与箱子交互正确
- [ ] 事件回放后状态一致(占用表与坐标一致)
---
7. 下一步扩展(建议优先顺序)
- 死局检测/可达性(关卡生成、提示系统会用到)
- 机关层(地形触发:开关、传送带、陷阱)
- 视野/碰撞形状(从网格扩展到“网格 + 子格/半格”)