跳到正文

容易混淆的问题 ​

这一讲把坐标、时间和数据结构各拆成两套,混淆几乎都发生在两套之间的边界上。

下标与坐标 ​

Lua 的下标从 1 起,屏幕像素从 0 起,于是 gridX = 1 对应 x = 0。所有换算集中在 (gridX - 1) * 32,只写这一处。

lua
-- 网格:1..8
local gridX = 3
-- 棋盘像素:0..224
local px = (gridX - 1) * 32      -- 64
-- 屏幕像素:棋盘偏移 240 之后
local screenX = px + (VIRTUAL_WIDTH - 272)  -- 304
说法取值用途
网格坐标1–8数组下标、匹配、相邻判断
棋盘像素0–224补间目标、Tile:render
屏幕像素240–496鼠标命中、调试
虚拟分辨率512×288交给 push 放大

gridX 是逻辑,改它不会动画面;x 是画面,改它不会动逻辑。交换时先改 gridX 再补间 x,两者短暂不一致正是动画存在的空间。

棋盘偏移也是常错的一处:Board:init 收的 240 和 16 只加在渲染与命中判断里,Tile 自己不存这两个数。漏加时整块棋盘贴着左上角,看起来像「图集切错了」。

尺寸与缩放 ​

newQuad 的宽高是裁剪窗口,love.graphics.draw 的 sx/sy 才是缩放。把 32 写进缩放参数,方块会变成 32 倍大小。

数字出处含义
32newQuad 的宽高从图集里裁出的一格
32(gridY - 1) * 32两格之间的像素间距
512×288VIRTUAL_WIDTH逻辑分辨率,push 放大
1280×720窗口尺寸实际像素,不参与布局

像素画必须先用 setDefaultFilter('nearest', 'nearest') 再加载图片。顺序反了,32×32 的图会被线性插值糊掉,看起来像「缩放没对齐」,其实是采样方式错了。

另一个混淆是「一格」和「一次绘制」:棋盘 8 格宽只有 256 像素,屏幕上看着大是因为 push 把 512×288 拉到了 1280×720,乘的是 2.5 倍,不是 32 倍。

时间与顺序 ​

Timer.every 是周期,Timer.after 是一次性,Timer.tween 是插值。三者都进同一个默认组,由同一次 Timer.update(dt) 推进。

lua
Timer.every(1, tick)                    -- 每秒一次,直到移除
Timer.after(1, function() end)          -- 一秒后一次
Timer.tween(0.25, def):finish(nextFn)   -- 补间结束才跑 nextFn
混淆点区别
:finish 与 Timer.after前者跟着本段结束,后者从创建时起算
Timer.update 调两次所有动画与倒计时双倍速
暂停时清空计时器应停止调用 Timer.update,而不是 clear
Timer.clear() 的位置必须在 change 之前,否则新状态计时器被清

自己累加 dt 时记得取模 % interval,直接清零会让每帧的余数丢掉,倒计时越走越慢。

补间和计时器共用同一个组,Timer.clear() 会连正在跑的补间一起清掉。只想停倒计时,用 :remove() 单独摘,别动整组。

  • 判定在 update 开头、推进在 update 末尾,归零到切状态差一帧
  • 条件写 timer <= 0,== 0 会被跨帧的小数跳过

返回值与状态 ​

calculateMatches 返回 false 而不是空表,调用方才能写 if matches then。空表在 Lua 里是真值,if {} then 永远成立——这是最容易踩的一句话。

名字看着像实际是
false空结果「没有匹配」
{}空结果真值,条件永远成立
self.matches临时变量removeMatches 的输入
canInput输入锁从没被置 false,等于没锁
pauseInput与上面重复真的在挡输入

removeMatches 依赖 self.matches 这个字段,所以算匹配与删匹配必须按顺序调用。先删再算,删的是上一轮的结果;matches 又是「组的数组」,十字交叉进两组、中心格计两次分,以为是重复计数而去重,反而少给玩家分数。

  • empty 状态的方法全是空的,它只兜住第一帧,不代表「没有状态」
  • 状态之间不共享变量,分数与关卡号靠 params 传,params.score or 0 兜住 nil
  • 目标是 score >= scoreGoal,写成 > 会让踩线的分数卡住一关

选中与相邻 ​

同一格再按一次 Enter 是取消选中,不是交换。距离为 0 走取消分支,为 1 才尝试交换,其余情况播 error 并清空。

曼哈顿距离含义行为
0同一格取消选中
1上下左右相邻补间后尝试交换
2斜角或隔一格播 error 清空

判定顺序写反,最典型的症状是「按同一格反而报错」:先查距离、后查相等,距离 0 永远过不了相邻判断。

消除后的棋盘 ​

removeMatches 之后棋盘是半空的,tiles[y][x] 里混着 nil。这时任何遍历都可能出事:# 不可靠、ipairs 提前停、calculateMatches 读到 nil.color 直接崩。

getFallingTiles 在返回补间表之前就把数据填满了,画面才慢慢追。所以「掉落中」只是视觉状态,数据始终是完整的 8×8,递归判分才敢直接重入。分数则必须在置空之前结算,顺序写错就会一直加 0 分。