容易混淆的问题
这些概念单独看都清楚,凑在一起就容易写反。
| 容易混淆 | 区别 |
|---|---|
| 状态机 / 状态栈 | 同时一个状态 vs 可以叠多层 |
update / render 范围 | 只更新栈顶,渲染全部 |
HP / currentHP | 上限 vs 当前值,血条两端分别取 |
mapX / x | 格子编号 vs 像素坐标,后者由前者推出 |
isDown / wasPressed | 按住持续为真 vs 只触发一帧 |
value / max | 会变 vs 不变 |
push / pop | 压入并 enter vs exit 后移除 |
| 遇敌判定时机 | 每走一格一次 vs 每帧一次 |
| 存档 / 存对象 | 存数据,读档重建实例 |
Panel 内缩 | 位置 +2,宽高 -4 |
更新与渲染
状态栈里 update 只发给栈顶,render 从底到顶全画。写反的两种后果:全更新则暂停菜单下面的角色继续走,全渲染只画栈顶则菜单背后是空白,看起来像换了场景。想把「只画栈顶」用在某个特殊状态上,别改 StateStack,让那个状态自己画一张不透明背景即可。
弹栈也不会自动恢复下层的状态。战斗状态压入时把场地音乐 pause 了,弹出时就得有人 resume。这个「有人」要么是战斗状态的 exit,要么是场地状态的某个回调,二者选一个,别两边都写,否则音乐会被恢复两次。
- 每次压栈新建实例;复用旧实例会带着光标位置、淡入进度、算好的对手
pop之后不要再持有那个状态的引用去改它,栈里已经没有它- 空栈时
update与pop都要能安全返回,否则states[0]是nil
两套坐标
mapX 在 tween 开始时就变成目标格,x 要 0.5 秒后才到。所以:
- 判断地形、遇敌、NPC 占位,用
mapX、mapY - 判断画面上的位置、和鼠标或摄像机换算,用
x、y
用 x 去查瓦片,会在移动途中查到两格之间的错误编号。
-- 正确:格子查询
local tile = level.grassLayer.tiles[entity.mapY][entity.mapX]
-- 危险:像素当格号
local tile = level.grassLayer.tiles[entity.y][entity.x]第二行不会报错,只会返回 nil,然后在下一行「索引 nil 的 id」时崩掉。崩点在判断地形的地方,原因在坐标混用,隔了好几层。
概率的写法
math.random(10) == 1 是十分之一。写成 math.random(10) < 1 则永远为假,因为返回的是 1 到 10 的整数。想改百分比,用浮点更不容易错:
if love.math.random() < 0.1 then
-- 10%
end还有一个隐藏假设:这段代码只在移动完成时调用一次。放进 update 里就变成每帧 10%,一秒能触发好几次。
随机源也值得分清:
love.math.random启动时已设种子,直接可用math.random在 Lua 5.1 下序列固定,要math.randomseed(os.time())- 两套随机混用,调参结果会对不上
概率判断也别和地形判断拆开写。if isGrass and random() < 0.1 与 if random() < 0.1 and isGrass 结果一样,但后者会先掷骰,调试时打印日志会误导你以为「每一步都判定过」。
面板的边界
Panel 画内框时位置加 2、宽高减 4。只减宽高不减位置,边框会跑到右下角;只加位置不减宽高,内框会溢出外框。
Textbox的maxLines用(height - 2 * padding)除以(lineHeight + linePadding)再取整- 少减一个 padding,最后一行会被切掉半截;不取整,行数会算多一行
getWrap的宽度传width - 2 * padding,传整个框宽会让文字压到边框上
战斗数值
血条上端取 max = HP,数值取 value = currentHP。两个都是数字,混用时不会报错,只会让进度条显示错误的长度。
- 伤害下限是 1,靠
math.max(1, attack - defense);去掉下限,防御高的双方永远打不完 - 速度相同先手属于对手,是
else分支的结果,不是显式规则 - 想改成玩家优先,在比较里用
>=,而不是在别处补判断 - 经验阈值按等级平方增长,觉得升级太快就调
expToLevel的系数
存档
table 里如果有函数或类实例,写不进文件。存档只存数字、字符串、布尔和嵌套表,读回来用定义表重建对象。忘了重建,读档后第一次调用 pokemon:calculateStats() 就会报「尝试索引 nil」。
调试坏档时先看文件本身:love.filesystem.read('save.lua') 打印出来,比在 pcall 里猜错误快。另外,love.filesystem 的读写是同步 IO,别把存档放进 love.update,一帧写一次会直接掉帧。