为什么这个玩法不能只写成演示
轻竞技小游戏里,玩家控制小角色在地面留下颜色轨迹,围住一片区域后,该区域被填成自己的颜色。对手可以切断轨迹,也可以抢回边界。规则看起来直观,但区域判定和得分必须非常稳定。
涂色占地的核心是区域归属。不能只把画面涂上颜色就算分,因为边界、封闭、争夺和同步都会出问题。系统需要维护可计算的格子状态,再由 Phaser 表现颜色、粒子和地面过渡。 本文按一个可上线的小系统拆解,重点不是罗列 Phaser API,而是把输入、规则、表现、调试和内容配置的边界说明白。只要这些边界清楚,后续加关卡、加活动、加存档或加移动端适配,都不会反复推倒。
核心架构
flowchart TD
N1["PaintTrail"] --> N2["BoundaryDetector"]
N3["TileOwnership"] --> N2["BoundaryDetector"]
N2["BoundaryDetector"] --> N4["FloodFillResolver"]
N4["FloodFillResolver"] --> N3["TileOwnership"]
N3["TileOwnership"] --> N5["ScoreLedger"]
N6["ConflictRule"] --> N3["TileOwnership"]
N7["PaintRenderer"] --> N8["Phaser 表现层"]
这套结构的关键是让 PaintTrail、TileOwnership、BoundaryDetector、FloodFillResolver、ScoreLedger、ConflictRule、PaintRenderer 各司其职。输入层提交意图,规则层产出确定结果,Phaser 层负责把结果演出来。不要让 Tween 完成回调、Sprite 是否可见或某个音效是否播放成为规则事实。规则事实必须能被序列化、测试和回放。
格子是计分基础
即使画面是自由移动,计分也可以投射到网格。TileOwnership 记录每格归属、是否边界、是否临时轨迹。角色移动时 PaintTrail 标记经过格子。这样得分和同步都有明确单位,不依赖像素颜色读取。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
封闭区域要延迟判定
玩家画出闭环后,BoundaryDetector 判断轨迹是否连接到己方区域或自身轨迹。只有形成闭合边界时,FloodFillResolver 才从外部泛洪,找出被围住的内部格子。不要每走一步全图填充,性能会浪费。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
边界争夺有规则
对手踩到你的临时轨迹时,是切断、夺取还是让你失败?规则要明确。ConflictRule 处理轨迹碰撞、保护时间和无敌状态。若支持多人同步,冲突规则必须确定性,不能由两个客户端各判各的。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
得分账本避免重复
ScoreLedger 记录每次区域变化的 delta。一个格子从 A 变 B,A 扣一分,B 加一分;临时轨迹是否计分由规则决定。不要每帧重新统计全图,地图大时会浪费,也容易和动画不同步。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
表现要跟随状态补间
PaintRenderer 根据 TileOwnership 的变化播放颜色扩散。扩散可以有延迟和波纹,但分数已经在规则层结算。若动画被跳过,地面最终颜色仍然正确。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
地图边缘要特殊处理
泛洪通常从地图外部或边界开始,地图洞口、不可涂区域和障碍物都会影响封闭判断。关卡数据里要明确哪些格子阻挡填充,哪些格子只是不可占领。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
调试要显示填充过程
开发模式可以一步步显示外部泛洪、候选内部格子、边界格和最终归属。区域填充 bug 很难靠最终颜色判断,看到中间过程才能知道是边界断了还是障碍配置错了。
实现时建议先用调试图形把这部分规则跑通,再接正式美术。比如先画命中范围、路径、候选区域、分数来源或状态机阶段,确认数据没有问题后,再加入粒子、音效、镜头和 UI 动效。这样做看似慢,实际会减少大量返工。
TypeScript 实现骨架
type Owner = "none" | "red" | "blue";
interface Tile { owner: Owner; boundary?: Owner; blocked?: boolean }
function neighbors(x: number, y: number) { return [[x+1,y],[x-1,y],[x,y+1],[x,y-1]] as const; }
function floodOutside(grid: Tile[][]) {
const seen = new Set<string>();
const queue: Array<[number, number]> = [[0, 0]];
while (queue.length) {
const [x, y] = queue.shift()!;
const key = `${x},${y}`;
if (seen.has(key) || !grid[y]?.[x] || grid[y][x].blocked || grid[y][x].boundary) continue;
seen.add(key);
for (const n of neighbors(x, y)) queue.push(n);
}
return seen;
}
function scoreDelta(before: Owner, after: Owner) { return before === after ? 0 : 1; }
这段代码只展示核心边界。真实项目里还需要配置加载、错误码、事件总线、对象池、存档字段和测试夹具。原则是核心系统不依赖 Scene,Scene 只把玩家输入和系统结果连接到 Phaser 的显示对象。
落地步骤
- 第一步,先把 PaintTrail 和 TileOwnership 写成纯数据模型,准备两三个最小样例。
- 第二步,给 BoundaryDetector 增加调试可视化,确保中间状态能被看见。
- 第三步,把 Phaser 动画接到规则结果上,而不是让动画反过来提交规则。
- 第四步,补齐失败原因、暂停恢复、重复点击保护和读档恢复。
- 第五步,用正常流程、边界流程、错误配置三类夹具做校验。
常见坑
- 把画面当作状态来源。显示对象可能被对象池回收、被镜头隐藏或被动画临时改值,不能作为规则真相。
- 只为第一关写逻辑。第一关对象少、节奏慢,很多问题不会暴露;内容扩张后,重复触发和配置错误会一起出现。
- 失败反馈太笼统。玩家需要知道是条件不满足、资源不足、路径不可达、输入太晚,还是系统正在等待确认。
- 调试面板缺失。复杂玩法没有中间状态可视化,后期只能靠录屏和猜测定位。
运行时观测
记录每次封闭区域的格子数量、泛洪耗时、冲突次数和分数变化。若小区域填充也很慢,说明算法触发太频繁;若玩家经常质疑边界,说明视觉轨迹和规则网格没有对齐。 这些指标不一定都要上报到线上,但至少应该在开发版能导出。玩法系统越依赖手感和解释,越需要用数据区分规则问题、表现问题和关卡配置问题。
边界测试与移动端验证
涂色占地 在桌面浏览器里跑通,只能说明主流程成立,还不能说明它适合发布。建议为它准备一组专门的边界测试:轨迹刚闭合时被对手切断、边界贴着障碍物、低帧率下跨过多个格子、区域填充动画跳过、比分同步延迟。这些测试不用都做成复杂自动化,至少要有可重复的调试入口,让开发、策划和 QA 能在同一状态下观察同一个问题。每次测试都要记录 TileOwnership、FloodFillResolver、ScoreLedger 和冲突事件,否则失败只会变成“刚才好像不对”。如果玩法会出现在移动端,还要额外检查触控误差、浏览器切后台、低电量降频、横竖屏切换和音频恢复。很多 Phaser 小游戏不是输在核心规则,而是输在这些边界恢复上。
移动端验证还要关注触摸反馈和文字密度。按钮按下后要有立即响应,即使规则结果需要等待;长文本提示要能在窄屏换行;关键数值不能只靠颜色表达。若系统在低端机关闭粒子、阴影或轨迹后仍能保持同样的规则结果,就说明表现层和规则层边界清楚。发布前把这些用例整理成清单,后续每次改配置、换美术或加活动,都可以快速回归。
发布前检查
发布前至少确认四件事:第一,所有配置引用的 id 都存在;第二,核心状态能存档并恢复;第三,快速输入和跳过动画不会重复结算;第四,低端机可以关闭高成本表现但不改变规则。若系统涉及奖励、货币或排行榜,还要确认事件 id 幂等,避免重复发放或重复扣除。
视觉网格和规则网格对齐
涂色占地最怕“看起来围住了,系统却不算”。如果角色轨迹是曲线,而规则是格子,必须在视觉上暗示格子边界或吸附规则。可以让轨迹宽度覆盖完整格子,或者在角色经过时给格子短暂闪光。边界形成时,先高亮闭合线,再填充内部区域,玩家会更容易理解系统为何判定成功。若视觉轨迹很自由,规则网格很粗,争议会明显增加。
多人同步的确定性
如果玩法支持对战,区域填充必须确定性。同一条轨迹在不同客户端上要投射到同样的格子,冲突事件要按同一时间线排序。不要让客户端用本地帧率决定经过了哪些格子。可以按固定 tick 采样位置,并让权威端确认 TileOwnership 变化。客户端只预测颜色,最终以确认结果修正。
涂色玩法还要处理出生点保护。玩家刚复活时不应立刻被对手切断轨迹,也不应在出生点附近无限刷分。可以给出生区设置不可占领保护,并把保护范围画得清楚,避免玩家误判边界。
验收标准
这个系统的第一版不需要覆盖所有商业化变化,但至少要能回答三类问题:玩家为什么成功,玩家为什么失败,内容配置为什么非法。验收时可以让一名没有参与开发的人按测试清单操作,如果他能从画面反馈和调试面板里解释当前状态,就说明系统边界基本成立。若每次都需要开发者口头说明,说明 UI、日志或规则命名还不够清楚。
结语
涂色占地玩法:区域填充、边界判定和得分同步 的价值在于可解释。Phaser 可以把反馈做得很快,但真正决定项目能不能持续扩展的,是规则层是否稳定、表现层是否服从结果、调试层是否能讲清楚每一次失败。把这些边界立住,玩法才能从一个好看的 Demo 变成可维护的系统。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。