Pokémon
界面组件
Panel是两个圆角矩形的叠加- 外框白色,内框
56/255深灰,内缩 2px love.graphics.rectangle第五个参数是圆角半径Panel:toggle()翻转visibleTextbox在构造时内嵌一个Panelfont:getHeight()取行高font:getWrap(text, width)返回行宽表与分行表maxLines由框高、行高、行距算出chunkCounter记住下一屏从第几行开始endOfText与closed是两个独立标志Selection的每项含text与onSelectgapHeight = height / #items均分行距- 上下键在
1与#items之间回绕 - 回车只调用
items[currentSelection].onSelect() Menu是Panel与Selection的组合ProgressBar的填充宽度是value / max * width
状态栈
- 状态机同一时刻只有一个状态
- 状态栈用
states数组保存多层状态 - 栈顶是
states[#states] push追加到末尾并调用state:enter()pop先调exit()再table.removeupdate只更新栈顶render从底到顶全部绘制- 不更新就不动,因此下层状态自然冻结
BaseState提供四个空方法做基类- 状态可以自己检测关闭并
pop - 对话框、菜单、淡入淡出都靠压栈
- 多
pop空栈黑屏,少pop卡输入 - 实际操作栈深只有几层
地图与瓦片
Tile(x, y, id)只存网格坐标与贴图编号TileMap用tiles[y][x]存二维表TILE_IDS把地形名映射成编号- 地图按 50×50 瓦片生成
baseLayer铺满随机草地grassLayer只在高草区放高草- 高草贴图透明,必须靠底层草打底
- 层数不设上限,装饰与遮挡各占一层
- 渲染顺序决定谁盖住谁
- 地形判定只比较
id - 像素坐标由
(mapX - 1) * TILE_SIZE得到 - 生成式地图用
math.random选草地变体 - 地图数据也可以从文本或 Tiled 导出
网格移动与动画
- 实体同时有
mapX、mapY与像素坐标 - 网格坐标立刻改,像素坐标用 tween 追
Timer.tween(0.5, {[entity] = {x = ..., y = ...}}):finish回调里再判断是否继续走- 移动途中不接受新方向
direction取left、right、up、down- 动画名按
walk-<direction>拼出 changeState管walk与idle- 动画由 quad 表与帧间隔组成
love.graphics.newQuad切精灵图- 撞到边界只切动画不位移
- 0.5 秒一格是节奏参数
随机遇敌
- 遇敌判定写在
PlayerWalkState里 - 判定要走进的那一格是否为高草
math.random(10) == 1即每步一成- 停下不掷骰,站着不会遇敌
- 触发后先切
idle锁住移动 - 场地音乐
pause,战斗音乐play - 先压
FadeInState再压BattleState - 淡入淡出本身也是状态
FadeOutState负责反过来揭开画面- 战斗状态压在上面,
pop后回到原地 - 概率与步长共同决定期望步数
回合制战斗
BattleState持有双方party与两条血条- 血条
value用currentHP,max用HP BattleMessageState承担所有台词- 嵌套回调串起「野生的 X 出现了」
BattleMenuState给出 Fight 与 Run- 速度高者先手
- 未倒下时双方各攻击一次
- 战斗贴图滑入后才压台词
firstPokemon、secondPokemon指针简化调用- 伤害
math.max(1, attack - defense) Timer.every加:limit(6)做闪烁- 血条 tween 结束才写回
currentHP checkDeaths只看currentHP <= 0- 对手倒下进胜利结算,玩家倒下进复活
- 逃跑退出战斗,回到原地图位置
队伍与数据建模
POKEMON_DEFS以 id 为键存定义- 定义含
name、前后战斗贴图 - 定义含
baseHP、baseAttack、baseDefense、baseSpeed - 定义含四项
IV Pokemon(def, level)按定义与等级实例化HP是上限,currentHP是当前值currentExp与expToLevel分开记Party只是pokemon数组的包装- 队伍上限 6
getRandomDef()随机取野生种类- 野生等级
math.random(2, 6) Player与Opponent都有party字段- 战斗只取
party.pokemon[1] - 数值放数据表,逻辑放类
存档
t.identity决定存档目录名conf.lua里设t.identity = 'pokemon'love.filesystem.write(name, data)写入存档目录love.filesystem.read(name)读回字符串love.filesystem.getInfo判断文件是否存在love.filesystem.load执行存档里的 Lua 块- 存储的是数据,不是对象
- 只存可变的量:位置、等级、经验、HP
- 读档时用定义表重建
Pokemon - 存档里加版本号方便迁移
- 写入失败返回
false与原因 - 删除存档用
love.filesystem.remove
容易混淆的问题
- 状态机与状态栈什么时候用哪个
- 栈顶更新、全部渲染,为什么不是全部更新
Panel的边界与Textbox的 paddinggetWrap返回的两个表分别是什么value与max谁变谁不变HP与currentHP混用会怎样mapX与x差在哪- 遇敌概率是每帧还是每步
- 战斗中
pop一次会回到哪 - 为什么存档不能直接存 table