M6 追加复盘:UI/UX 批 × promote003 批 × 地图程序化随机生成 × promote004 批¶
本文是 magic-plant-demo 在 m6_review.md(M4–M6 合成/战斗/死亡捡尸)之后的多批追加工作的复盘。 覆盖:M6.5 UI/UX 拍板批 → promote003 批(M6.6) → 地图程序化随机生成批 → promote004 批(大地图定向分区重设计 + 数值重平衡 + 系统层),以及穿插的 bug 修复与数值平衡。 依据:dev_manual.md §2/§4/§5/§7/§9 · content_manual.md §1–§2 · map_progress.md。 地图设计专篇见 m6_map_design.md。 日期:2026-09-03 · 版本:0.1-preview
0. 一图总览(这批改了什么)¶
三批工作沿「先调体验 → 再修数值/细节 → 最后重构地图」的路径推进,每批都让 preview 更接近「可玩、可看、可调」:
| 批次 | 主题 | 目标 |
|---|---|---|
| M6.5 | UI/UX 一轮拍板 | 地图从"太小怪贴脸"→四向扩大;开局 9 点;种植 3→6 坑;蘑菇按色区分;背包升级为对话框+拖拽;左上角三格便捷栏(Q/E/R) |
| M6.6 (promote003) | 种植交互 + UI 打磨 + 数值平衡 + 地图场景化 | 播种取当前选中;便捷栏重排(中央大/两侧小);UI 包边/背景/字体;数值集中配置;地图改手绘场景先跑通 |
| 地图程序化批 | 地图程序化随机生成 | 简单色块 + 三层 TileMapLayer + FastNoiseLite/Poisson Disk/Drunkard's Walk;固定 seed + 内存态 delta;按 map_progress 重划进度区域 |
| promote004 批 | 大地图定向分区重设计 + 数值重平衡 + 系统层 | 400×320 定向多区域(p-轴分区、水做外缘、河/墙门带);合成/升级材料大幅上调;系统层三件套:法术升级 UI(魔药祭坛) + 装备入口(穿戴光明晶石) + T3 无光洞窟 |
| promote005 批 | 快进指令 × 能力栏背包 UX × T2/T3 密度 × 二批(回蓝/复活/昼夜怪) | dev 台新增时间快进(白天/夜晚/HH:MM/分钟);背包「物品栏 ↔ 便捷栏」间加能力栏(5 格、可拖、按 R 施放)+ 3px 金边 + 内部不透明底 + Esc 关三面板;T2 障碍/怪下调、T3 怪上调;二批:青露伞回蓝(灼心芝保持回血) + 死亡复活三维重置 + T2 昼夜刷怪(腐根虫昼/夜行兽夜)+昼夜密度分化(昼 0.3 / 夜 1.7) |
结论:preview 已从「能玩的最小闭环」推进到「能玩 + 体验到位的完整闭环」,地图从"一张死地图"升级为"每次开局都能确定性复现的随机地图",再到"沿主轴定向分区、面积精确、门序无环的 400×320 大地图";同时种田=变强(材料需求上调)、法术升级、装备穿戴、T3 黑暗门槛四条系统线全部串通。promote005 再补上开发者快进、背包能力栏、T2/T3 区域密度三处体验与调试打磨。
1. 里程碑对照¶
| 里程碑(本次) | 目标 | 状态 | 说明 |
|---|---|---|---|
| M6.5 UI/UX | 体验一轮拍板 | ✅ | 见 §2 表 |
| M6.6 promote003 | 交互/打磨/数值三件套 | ✅ | 见 §3 表 |
| 地图程序化批 | 三层 TileMapLayer 随机地图 + 进度分区 | ✅ | 见 §4 表 |
| M7 | Boss + 结算 + 得分/成就 | ⬜ | 下一步(封闭地牢 Boss + 入场封门 + 结算累计得分) |
这批没有新增里程碑编号,而是在 M6 与 M7 之间做了一次集中的「体验/数值/地图"三条线"的打磨冲刺」,故称 M6 追加。
2. M6.5 UI/UX 拍板批¶
围绕「开局体验不爽 → 改」的七处:
| # | 诉求 | 落地 |
|---|---|---|
| 1 | 地图四向扩大(怪贴脸) | WORLD_RECT := Rect2(-520,-300,1040,600),BASE_RECT(-340,-80,240,170);边缘三怪外移到显式坐标,最近距离 >150px 触发仇恨范围;蘑菇从集中撒到 ZONE_MID/ZONE_DEEP |
| 2 | 开局上午 9 点 | game_clock.gd game_min := 9*60;DAY_START_MIN=06:00 仍用于睡觉/昼夜分界 |
| 3 | 种植坑 3→6 | 基地 3×2 网格 6 处 Plot |
| 4 | 蘑菇按色区分 | ItemData.color/CropData.color 双端 @export color;野生/种植/背包图标颜色一致(银光=白、绯泪=粉、月伞=淡蓝),全套程序化 draw_colored_polygon |
| 5 | 背包升级为对话框 | B 开合;ui/inventory_dialog.gd:图标网格 ui/bag_cell.gd + 悬停显示描述 + 6 格便捷栏;拖拽走 Godot 4 _get_drag_data/_can_drop_data/_drop_data(mouse_filter=STOP) |
| 6 | 左上角三格便捷栏 | ui/quick_slot.gd 轮播 [上一格][当前高亮][下一格];Q/E 左右循环(空槽跳过)、R 食用当前、移动端点中间=用、点两端=切换 |
| 7 | UI 布局 / 施法键 | 施法从 E 改 C;HUD/三维条/槽位在代码建(hud.tscn 仅 Control+脚本);三条状态条开局即满的 bug 修复(_ready 补 _fill) |
额外补了一个交互与背包的联动:食用(
Inventory.use_selected)应用heal_hp/restore_mp/restore_satiety并扣 1。
3. promote003 批(M6.6)¶
3.1 种植交互修正¶
原「种植时随机播种」是 bug——播种应取当前选中。修复:
- plot.gd 删 _pick_seed,_try_plant 改用 Inventory.selected_item()(选中为 null 或不可种 → toast)。
- 新增 Inventory.prune_selected_if_empty()(use_selected 末尾复用):播种后数量归零自动清格、跳格。
3.2 UI 打磨¶
| 项 | 改动 |
|---|---|
| 便捷栏重排 | SLOT=30(中间不变)/SLOT_SIDE=24(左右缩小)/SLOT_GAP=6 区分主次 |
| 背包对话框 | StyleBoxFlat 边框 + 圆角 8 + 边框 2;关键:_panel = PanelContainer.new() 必须在加 stylebox 前,否则 null 崩溃 |
| 炼金合成台 | 同样的包边/背景,标题 13 / 行 11 / 按钮 12;字体调小 |
| 右上角 | day/time 从左上移到右上角;三格重排 _mk_top_right |
3.3 数值集中配置(回答"哪里调")¶
所有数值集中在一处 .tres + 对应资源,不散在脚本:
| 文件 | 内容 | 本次调整 |
|---|---|---|
resources/game_config.tres |
玩家基值/初始蓝/初始饱食度 | start_mp=30(原 20) |
resources/abilities/bloom.tres |
绽华 AoE 伤害/耗蓝 | cost 12→8、power 2→6 |
resources/abilities/star_thorn.tres |
星刺远程伤害/耗蓝 | cost 8→4、power=4.0 |
resources/monsters/night_beast.tres |
夜行兽 HP | hp=10 |
resources/monsters/spore_witch.tres |
孢子巫 HP | hp 12→6 |
resources/monsters/root_rot.tres |
腐根虫 HP | hp 16→10 |
平衡原则:远程耗蓝减半(两次施法仍有余蓝)、Boss 与精英怪 HP 回落(不再"打十几次才死"),普通小怪伤害保持不变——所有改动集中在资源层,改 .tres 即可,不碰逻辑脚本。
3.4 地图场景化(先跑通,后被程序化批取代)¶
把地图从"代码生成"改成手绘 .tscn,方便直接拖拽调整——
- 新建 level_map.gd(仅存活 CanvasModulate 昼夜 _process/_daylight + _spawn_wild/_spawn_monsters/_rand_in,结构不再代码生成)+ level_map.tscn(手写,13 load_steps:4 色块 ZONES/BASE 负 z、3 房、4 围墙 StaticBody2D、6 plot、bed、crafting_station、3 Gate)。
- gate.gd 加 @export data/rect,_ready 自初始化。
- 删除 level_prototype.gd/.tscn/.uid、tools/build_map_scene.gd。
- 为什么放弃生成器:--script 模式不识别 autoload EventBus/Inventory 致 Identifier not found,生成器无法 headless 验证。
注意:这一步的"手绘 .tscn"后来被本批的程序化随机地图整体替换(见 §4)。
level_map.tscn现只保Plot×6 / Bed / CraftingStation,地形/边界/门改由生成器产出。
4. 地图程序化随机生成批(本会话)¶
4.1 需求与两个拍板决策¶
按 promote003 末条:简单色块 + 三层 TileMapLayer,生成算法 = FastNoiseLite(阈值定草地/农田/水) + Poisson Disk(撒树和矿) + Drunkard's Walk(挖路径),固定 seed,存档只存 seed + delta,批量写入不逐格 set_cell,生成后按 map_progress.md 重划进度区域。
两个关键拍板(AskUserQuestion):
- 存档范围 = 内存态 MapState(seed + delta 数据结构),不落盘(符合 dev_manual §9.1「preview 无存档」,为 demo 预留)。
- 分区方式 = 噪声只决定每格地表外观;进度分区用确定性空间规则叠加(基地居中安全区 + 按距基地距离/方位的径向带划分林缘/中环/夜森/洞窟),三块门放分区边界 gap —— 保留 hub-and-spoke 骨架与门序无环。
4.2 新文件(纯数据层可 headless 测 → 运行期层)¶
| 文件 | 职责 |
|---|---|
scenes/map_generator.gd |
class_name MapGenerator extends RefCounted,纯数据:generate(seed)->{ground,obstacle,deco,region_map,gates,seed_used},零 autoload 依赖 |
resources/region_map.gd |
RegionMap,进度分区网格(PackedByteArray,BASE/EDGE/MID/DEEP/CAVE) |
resources/map_state.gd |
MapState extends Resource,seed + 玩家改动 delta(内存态不落盘) |
tools/map_tileset_builder.gd |
程序化色块 atlas → TileSet + 碰撞 + 自定义数据层 |
scenes/level_map.gd |
运行期:建三层 TileMapLayer + 批量写入 + spawn 门/怪/菇 + 昼夜 |
scenes/level_map.tscn |
删手绘地形/边界/固定 Gate,仅保 Plot×6/Bed/CraftingStation |
tools/map_gen_test.gd |
headless 断言(24 checks / 0 fails) |
映射细节:TILE=16,GRID=(66,38)(x∈[-33,33)、y∈[-19,19),世界≈1056×608px),BASE_CENTER=(-14,1) 包住 spawn(-180,40),SAFE_R=8。区域阶梯(§1 map_progress):d≤SAFE_R→BASE;≤EDGE_R=12→EDGE;≤MID_R=19→MID;≤DEEP_R=26→DEEP;更远按方位扇区分 CAVE(东 0/1 区)。
4.3 生成算法细节¶
- 地表:FastNoiseLite(
TYPE_SIMPLEX、frequency≈0.03、fractal_octaves=4)→v<-0.35→WATER、v>0.45→FARM、否则GRASS;再消碎水/成湖(水连通块 <3 回填)、基地安全区强制草地。 - 路径:Drunkard's Walk 从
BASE_CENTER挖主路径穿三处门 gap(wobble=0.25),每段末端再散支路(wobble=0.45)。 - 障碍:Poisson Disk 撒树(
density=0.10, min=2,仅 EDGE/MID/DEEP)与矿(density=0.035, min=5,仅 MID/DEEP,避开路径两侧)。 - 装饰:草点/花/小石
density=0.08。 - 进度分区:径向带 + 8 方位扇区填进
RegionMap;三扇门 gap(magic_gate@EDGE_R /bridge@MID_R /darkness@DEEP_R)铺 PATH 在分区边界缺口。 - 连通性:BFS 自基地 flood,校验门 gap 可达、每区有可达入口;失败则
seed+1重试(MAX_TRIES=24),超限走保底图(100% 连通)。
4.4 运行时写入(level_map.gd)¶
- 用
MapTilesetBuilder.build()建 tileset → 创建三层TileMapLayer(Ground z=-20、Deco z=-12、Obstacle z=0),三层共用同一 tileset。 - spawn:门
Gate.setup(gd, rect)(按GATE_DEFS边界 gap rect);怪按MonsterData.region分带、远离基地安全半径;野生菇按day_night派生子(夜菇→DEEP/CAVE,昼菇→EDGE/MID,高阶夜菇仅 DEEP/CAVE)。 - 玩家改动 delta(挖矿/砍树/割草等)在
_ready用MapState.replay_into回放。
5. 关键技术点:TileMapLayer 取代 TileMap 的三个坑¶
本次把整张地图从 Node2D 色块重构到 TileMapLayer,踩到 4.7.2 的三个关键差异(均已修正并写进代码注释):
坑一:TileMapLayer 没有批量 set_cells¶
老 TileMap 的 set_cells 在 TileMapLayer 不存在(只有 set_cell 单格 / set_cells_terrain_connect autotile)。
修正:按 atlas 坐标分组后逐格 set_cell。分组保证色调一致、顺序确定、可复现,且只在 _ready() 集中写一次(非逐帧)。与需求"批量写入"略有偏差——是引擎 API 限制,非偷懒。
坑二:RandomNumberGenerator 没有 shuffle()¶
Array.shuffle() 用的是全局 RNG(真随机 seed),会破坏 seed 复现。
修正:手动确定性 Fisher-Yates _shuffle(用 _rng.randi_range)。
坑三:碰撞是 TileSet 物理层的事,不是 TileMapLayer 节点的¶
TileMapLayer 没有 collision_layer/collision_mask(那是 CollisionObject2D 的属性)。赋 layer.collision_layer=... 直接报 Invalid assignment 启动失败。
修正:碰撞由 tile 在 TileSet 哪个物理层建了多边形决定——builder 建两个物理层(PHYS_OPEN=0 可走 / PHYS_BLOCK=1 阻挡),阻挡 tile(water/wall/tree/ore/bush)多边形建在层 1,set_physics_layer_collision_layer(1,1)+...mask(1,1),玩家 mask=3 bit0 命中即挡。另注意 set_collision_polygons_count(0,…) 前必须先 TileSet.add_physics_layer(),否则物理层数为 0 越界。
以上三条都在
map_tileset_builder.gd/map_generator.gd/level_map.gd注释里留了"为什么这样写",避免未来重踩。
6. 验证方法¶
- 纯数据冒烟:
<Godot>_console.exe --headless --path d:/Repo/waste_aesthetic/magic-plant-demo --script res://tools/map_gen_test.gd→== map_gen_test DONE: 24 checks, 0 fails ==、EXIT=0。覆盖:网格尺寸 / 基地安全区无危险 tile 且 region=BASE / 每门 gap 连通且为 PATH / 每区可达入口 / seed 复现两版逐格一致 / delta 往返 + replay_into 覆盖格子。 - 启动冒烟:
--headless --path <proj> res://scenes/main.tscn --quit-after 30→[LevelMap] seed=20260902、EXIT=0零报错;level_map.tscn单独跑同样干净。 - 全项目注册:
--headless --import→EXIT=0。
关键经验:headless 跑场景必须加
--quit-after N,否则 GUI/窗口模式会阻塞不退(本次已踩:直接--path会挂起转后台)。
7. 本次踩的坑(含修正)¶
| 坑 | 现象 | 修正 |
|---|---|---|
| #1 架构矛盾 | map_generator.gd 同一文件混纯数据 generate() 和引用 Gate/Monster/Content 的运行时 spawn → 整个文件在 headless --script 编译失败 |
运行期写层/spawn 全搬进 level_map.gd,map_generator.gd 回归纯数据(零 autoload) |
#2 TileMapLayer 无 set_cells |
layer.set_cells(...) 报 nonexistent function |
按 atlas 分组逐格 set_cell |
#3 RandomNumberGenerator.shuffle |
_rng.shuffle() 报 nonexistent |
手动 Fisher-Yates + Array.shuffle 破坏复现的坑已规避 |
| #4 物理层未建就设碰撞 | set_collision_polygons_count(0,…) 报 physics.size()=0 越界 |
先 TileSet.add_physics_layer() |
#5 TileMapLayer 无 collision_layer |
layer.collision_layer=... 报 Invalid assignment,启动失败 |
碰撞由 TileSet 物理层承载,节点不设;构建器 PHYS_BLOCK=1 层承载阻挡多边形 |
#6 _probe 泄漏 RID |
SceneTree 冒烟构建 TileSet/TileMapLayer 未释放 → 退出报泄漏 | 属已知良性(quit() 提前,不走正常 teardown);测试件不用带进关卡 |
#7 --path 直接跑挂起 |
窗口模式不退出转后台 | 加 --quit-after N |
8. 遗留与下一步¶
| 优先级 | 项 | 说明 |
|---|---|---|
| 高 | M7 Boss + 结算 + 得分/成就 | 封闭地牢 Boss(多模式/会躲/入场动画封门)+ 结算累计得分 + 成就——preview 核心闭环仅剩这一环 |
| 中 | 随机地图的门 rect 是否精确落在分区缺口 | GATE_DEFS 的 rect 由 _gap_center 推导,需窗口 F6 人工核对每扇门 gap 是否在对应分区边界、是否明显错位 |
| 中 | 物理阻挡确认 | 玩家能否被树/墙/水挡住、能否顺畅走到三门——headless 验不了 2D 物理,需窗口 F6 |
| 中 | 移动端动作按钮 + 手感 | 摇杆已可移动;攻击/施法/交互触控按钮、安全区(M8) |
| 低 | 美术换皮 | 占位色块 → Sprite2D/AnimatedSprite2D,只换纹理不碰玩法(§7.4);程序化 TileMap 的 atlas 换正式美术即可 |
| 指标 | draw calls / script time | 暂未实测,M8 打磨时按 §8 预算(draw calls<100、script<4ms)核 |
9. promote004 批(大地图定向分区重设计 + 数值重平衡 + 系统层)¶
本批是 promote004.md 需求的落地,分两部分:先数据/功能(低风险),后地图(大改)。地图设计专篇见 m6_map_design.md。
9.1 地图重设计(400×320 定向多区域)¶
- 网格:66×38 → 400×320(世界 6400×5120px),
BASE_CENTER=(0,0)、SAFE_R=10。region_map.gd的 W/H/OX/OY 对齐(400/320/-200/-160)。 - 定向分区:不用"基地居中径向带",改沿 NE↔SW 主轴 p = x − y 做手摆模板 + 程序化填充。
q = x + y的斜带|q|≤qmax内才算陆地;qmax 由_calibrate_partition()二分使陆地格数 = LAND_TARGET,再按 p 降序把陆地切成 T2(NE)→T1→T3→BOSS(SW),面积精确 = 12/6/9/4 × 当前图(Base 兜底吸收剩余)。俯视呈45° 菱形(shots/map_regions.png)。 - 水做整图外缘:
_sample_terrain里所有未被 zone 认领的格 →WATER;另在带缘 NW/SE 撒噪声水袋。不再画径向石墙外缘。 - 门带:
GATE_DEFS建bridge(T1↔T2, 河, dir NE)、magic_gate(T1↔T3, 石墙, dir -NE)、final(T3↔BOSS, 石墙, dir -NE);GATE_TARGETS定方向落点(右上最多 T2、左下最少 T3/Boss)。_place_gates_and_features先铺河/墙交界带,_punch_gate沿 dir 穿 PATH 门洞并记录 rect 供Gate.setup。 - 路径:
_carve_paths从基地沿 Drunkard's Walk(wobble=0.25)挖主路穿三处门 gap,门内直线(wobble=0.0)。 - 内容规则:
_spawn_monsters按MonsterData.region分带且成群 3-5(_nearest_n);_spawn_wild按CropData.region分 zone 且稀疏(cells/400, 3..28);都避开基地安全半径与障碍格。
9.2 数值重平衡("种田=变强")+ 数据从哪改¶
合成/升级所需蘑菇数大幅上调,采集几下不够、必须回家种:
| 项 | 改动 | 现场 |
|---|---|---|
| 配方材料 | resources/recipes/*.tres inputs[].count |
桥钥=8银芒菇+1魔牙、魔法门钥=8晶铠蕈+1孢粹、光明晶石=8灼心芝+1腐脂、新增 final 钥=8绯泪茸+1洞窟之核 |
| 法术升级 | resources/abilities/*.tres upgrade_cost |
第1级基数=本系菇×4 + 虹髓芝×1;cost_for_rank(rank) 按 rank 递增=越升越贵 |
| 作物产量 | resources/crops/*.tres yields[].count |
3→5 |
关键:改数值一律改
.tres(Content 懒加载自取),别信tools/generate_content.gd(已与 .tres 脱节)。详解见 m6_map_design §11。
9.3 系统层三件套(promote004 "直接继续做剩余系统")¶
① 法术升级 UI(魔法书·魔药祭坛)
- ui/ability_upgrade_ui.gd(class_name AbilityUpgradeUI extends Control):只列已解锁且带 upgrade_cost 的法术,显示 Lv/max、分类标签(攻击/疗愈/辅助)、下一级效果(effect_per_level 加成)、升级消耗(含拥有量,如 灼心芝 10/4);点「升级」→ PlayerState.upgrade_ability。
- features/progression/altar.gd(基地紫色祭坛,F 交互)挂进 level_map.tscn;EventBus 新增 altar_ui_toggled 信号(逻辑层触发、表现层监听)。
- player_state.gd 新增 upgrade_ability(id):读 cost_for_rank(lvl) 逐项扣 Inventory 素材、ability_levels[id]+1;upgrade_cost.is_empty() 直接返回 false——绽华/星刺等基础攻击无素材、不可升(避免免费升级 bug)。
② 装备入口(穿戴光明晶石)
- ui/bag_cell.gd 点击 EQUIP 物品穿/脱,已穿戴=绿色方框(_draw 加绿色外框),悬停信息行 [装备] 已穿戴(点击穿/脱)/未穿戴。
- ui/inventory_dialog.gd 监听 PlayerState.equipment_changed 重绘;crafting_ui.gd 合成出 EQUIP 物品时 toast 提示「打开背包(B)穿戴」。
- player_state.gd 已有 equip/unequip/is_equipped(equipped=Array[StringName],重复穿戴返回 false)。
③ Stage3-T3 无光洞窟
- features/lighting/darkness.gd(class_name DarknessCave extends CanvasLayer,放 Main 的 UI CanvasLayer 之前)。玩家 region==T3 且未戴光明晶石 → 全屏近全黑(DARK_T3=0.965);戴上 → DIM_WORN=0.22 微弱洞窟调。
- level_map.gd 增 region_at_world()/region_cells() + add_to_group("level_map");map_generator.gd 增 world_cell()(cell_world 的逆)。
9.4 修复(promote004.md 末尾两处 + bug_notes_002)¶
| issue | 根因 | 修正 |
|---|---|---|
| T1→T2 桥解锁后无法通行(路缺区块) | _punch_gate 只穿到第一个 after 格就停——该格仍落在河/墙带内(被水/墙裹住),与 after 侧干地之间隔一格水/墙 → 门洞是"水中小岛",开锁后依旧走不过去;map_gen_test 的 flood 走的是草地而非 PATH,故在纯数据层仍"通过" |
_punch_gate 改穿到 _is_dry(nxt)(非 WATER/WALL 的干地) 才停,门洞两侧都落在可走地面;_carve_path 主路 + Gate 的 StaticBody2D 挡板作唯一阻碍(先连通、用需解锁的门卡住)。新增回归校验:cell→across 沿 dir 全程 PATH 且 across 非水 |
| 采集蘑菇无"解锁法术"提示 | HUD 的 toast 是单行 Label,on_interact() 先 emit("解锁能力:%s") 再 emit("采集了「%s」"),同帧后发覆盖先发 → 玩家只看到"采集了" |
_try_unlock() 改为返回新解锁的能力名,on_interact() 把它拼进同一条文案:采集了「X」,解锁能力:Y(基础攻击绽华/星刺开局已解锁,故只对首次采集的对应蘑菇法术提示) |
9.5 踩的坑 / 关键点¶
--script编译期不注册 autoload 全局名(同 m6 追加 §4 首条):tools/sys_shot.gd/sys_verify.gd里不能写c is InventoryDialog或直接引用Inventory.xxx(会把依赖脚本拉进编译而Identifier not found: Inventory)。改走 Variant 鸭子类型:root.get_node("/root/Inventory")、_hud._dialog;且先 load 主场景再get_nodeautoload;动态方法一律 Variant,:=推断 Variant 会报错要显式标类型。- autoload 单例首帧后才可用:在
_ready/_initialize取会为 null,需在_process首帧才_resolve。 root是 Window:get_first_node_in_group在 Window 上不存在,用self(SceneTree)的。- 免费升级 bug(截图发现):基础法术无
upgrade_cost在祭坛显示空消耗且可白升——双重修复:upgrade_ability对空 upgrade_cost 返回 false +_refresh跳过。 - 窗口截图别用
--headless:headless 渲染不到纹理,root.get_texture().get_image()为空。
9.6 验证¶
--importexit 0(无解析错);--headless主场景引导 exit 0。tools/map_gen_test.gd:35 检查 0 败(网格/面积比/基地安全/各门 gap 连通/各区可达/seed 复现/成群 + 新增:门洞穿出交界带落在干地(across 非水且 cell→across 全程 PATH))。- 多 seed 复检:12 个 seed × 3 门洞,across 均落干地、桥洞跨河连续(防运行时随机 seed 复现"缺区块")。
tools/sys_verify.gd(headless):22 检查 0 败——升级扣 4 风耳蕈+1 虹髓、材料不足 false、无素材基础法术不可升、穿脱/重复穿 false、T3 黑暗未戴 0.96↔戴上 0.33 恢复。tools/sys_shot.gd(窗口模式):shots/sys_upgrade.png(只列蘑菇法术+可负担)、sys_bag.png(绿框穿戴)、sys_dark_no.png(近全黑)、sys_dark_yes.png(恢复视野)目视核验。
10. promote005 批(快进指令 × 能力栏背包 UX × T2/T3 密度 × 二批:回蓝/复活/昼夜怪)¶
本批是 promote005.md 需求的落地,三组独立改动:① dev 快进时间(开发调试用)→ ② 背包能力栏 + 背包外边框 + Esc 关三面板(content_manual §2.2 治疗 + 4 buff)→ ③ T2/T3 区域密度(T2 怪/障碍过密、T3 怪偏少)。
10.1 快进时间指令(dev 代码)¶
DevConsole 新增 快进 命令(同样支持 advance / ff,大小写不敏感),底层是 GameClock 两个新方法;核心难点是跳帧也要让 HUD / 昼夜层一并刷新(m3_review 曾踩:只改 game_min 不发射号):
| 方法 | 行为 |
|---|---|
advance_mins(mins) |
mins<=0 直接 return;否则走 _advance,跨天自动进位并发出 day_started / minute_ticked |
set_game_min(target) |
posmod(target,1440);若该时刻今天已过(t<=game_min)→ 落次日同一时刻 + day_changed;发出 day_started(仅跨天)/minute_ticked,昼夜翻转时发 night_started |
_fast_forward(s) 按参数分派:白天/昼/day/晨→set_game_min(DAY_START_MIN);夜晚/夜/night/晚→set_game_min(NIGHT_START_MIN);HH:MM→set_game_min(h*60+m);整数→advance_mins(n);空/解析失败给出用法提示。命令已写入 dev_code.md 命令表。
关键点:
set_game_min用t <= game_min判断"今天已过",例如当前 20:00(夜)时快进 白天会落到次日 06:00 而非法回退——这正是"跨天"语义;posmod处理负值取模(posmod而非%,避免负数错位)。
10.2 能力栏 + 背包 UX¶
① 能力栏(物品栏 ↔ 便捷物品栏 之间的一行)
- ui/inventory_dialog.gd 新增 _ability_row,_collectable_abilities() 固定 5 个可习得能力(顺序固定):soil_heal(治疗) / wind_gather / chase_light / rock_guard / ember_will —— 治疗 + 4 buff(content_manual §2.2)。
- 每格 ui/ability_cell.gd(AbilityCell):已解锁 → 程序化图标 ui/ability_icon.gd + 金边(可拖 {"ability_id": id});未解锁 → 暗格 + 锁形占位(图钉锁,提示"首次采集对应蘑菇后可用")。悬停经 show_ability_hover 显示等级/描述/解锁来源。
- ui/ability_icon.gd(纯静态画):取 ab.grant_mushroom.color 做主题色,HEAL→圆+白十字,BUFF/AOE→菱形。
② 拖到便捷栏 + R 施放(道具/能力 id 不重叠 → 同一 quickbar 数组)
- 设计拍板:物品 id 与能力 id 不重叠,故便捷栏可混存二者,Inventory 解析顺序为 Content.item(id) 先、Content.ability(id) 后。
- inventory.gd 新增 quick_slot_ability(i) / selected_ability();caret() 的 _caret_at→_caret_id 改为返回格子 id。
- R 施放:use_selected() 先判断是否为能力,命中 → EventBus.ability_requested.emit(ab.id) → Player._cast_ability(与 C 键同一条施放链,不扣材料、返回 true);否则走原食用路径。event_bus.gd 新增信号 ability_requested(id)。
- ui/quick_slot.gd 新增 ability 字段;_draw 在 item==null && ability!=null 时渲染 AbilityIcon;_can_drop_data/_drop_data 同时收 item_id 与 ability_id(ATTACK 类自动索敌被排除,仅 HEAL/BUFF 可拖入)。
- ui/hud.gd _refresh_quick 按 caret 返回的格子 id 分别解析 item / ability 填入。
③ 背包外边框 + 内部不透明背景
- 用户反馈「原边框太弱/半透明看不清」:inventory_dialog.gd 边框 2→3px 暖金 Color(0.78,0.66,0.40),bg_color alpha 0→1.0(Color(0.15,0.15,0.21,1.0))——边框内部彻底不透出地图。
④ Esc 关闭三个对话框
- 背包 / 合成台 / 魔法升级(inventory_dialog / crafting_ui / ability_upgrade_ui)统一在 _unhandled_input 里 InputManager.menu_pressed()(back 动作,映射 Esc)→ close()/_close()。背包标题改为 背包(B / Esc 关闭)。
10.3 T2 / T3 区域密度¶
| 层 | 文件 | 改动 |
|---|---|---|
| 障碍物 | scenes/map_generator.gd _plant_obstacles |
改为区域感知:T2 树密度 0.06→0.03、矿 0.08→0.04;T1/T3 保持默认;矿仍只落 T2/T3 |
| 怪物 | scenes/level_map.gd _spawn_monsters |
按区域缩放集群数:T2 首版 ×0.5(减半:原 2 只小怪×满集群 → 过密)、T3 ×1.5(上浮:原 1 只小怪 → 偏少)、T1 ×1.0。用集群数而非包大小,以一个参数同时处理"减 T2 / 增 T3"两个方向(clusters = clampi(int(round(min(10, cells/5)*scale)), 1, 15))。二批再细化:T2 内部改按昼夜分化(昼 0.3 / 夜 1.7,见 §10.5),T2 不再用固定 0.5 |
10.4 验证¶
tools/verify_promote005.gd(headless,--script模式需root.get_node("/root/..")动态取 autoload +_process首帧再跑):16 检查 0 败——advance_mins前进/0 无操作、set_game_min白天/夜晚/跨天(20:00→次日 06:00)、便捷栏能力格命中/quick_slot_item为 null、use_selected施放返回 true 且不扣材料、道具格仍走原路、caret 返回格子 id。tools/map_gen_test.gd:35 检查 0 败(含 promote004 的门洞回归)。--import:exit 0;主场景引导 exit 0。tools/shot_promote005.gd(windowed,勿--headless):shots/promote005_bag.png(解锁 5 能力后能力栏 5 格金边、便捷栏第 2 格渲染chase_light能力图标、金边 + 不透明底)、shots/promote005_hud.png目视核验。
踩坑:
verify_promote005里曾把「caret 返回能力 id」测成 fail——是断言写错(set_quick_slot会把选中格跳到 index 1,再cycle_prev又移回 0),非代码 bug;删掉cycle_prev直接查 caret 即 16/16。
10.5 二批:回蓝道具 × 死亡复活重置 × T2 昼夜刷怪¶
本批是 promote005.md 后半段三类改动:① 没有回蓝道具 → 青露伞改回蓝(灼心芝保持回血)、② bug:死亡复活状态栏没重置、③ T2 昼夜刷怪(腐根虫昼出 / 夜行兽夜出)+ 昼夜密度分化(白天怪少、晚上更凶)。
① 青露伞改回蓝(数据层,.tres)
- 起因:吃类道具里没有回蓝的——moon_umbrella(月纹伞) 有 restore_mp=8,但按需求把 dew_umbrella(青露伞) 也改成回蓝:heal_hp 1→0、restore_mp 0→8、标签 食_回血→食_回蓝(resources/items/dew_umbrella.tres)。ember_heart(灼心芝) 保持 heal_hp=1/食_回血(改回血不变)。
- 食用逻辑 autoload/inventory.gd use_selected() 读 heal_hp/restore_mp/restore_satiety → 调 PlayerState.restore_mp,改 .tres 即生效,不碰逻辑脚本。
② 死亡复活状态栏重置(bug)
- 根因:autoload/game_manager.gd _respawn_player() 只 heal_hp(max_hp),复活后蓝/饱食度条仍空。
- 修正:复活时三维全部回满——heal_hp(mhp) + restore_mp(mmp) + restore_satiety(msat),三者各自发出 health/mana/satiety_changed → HUD 状态条一并刷新。
③ T2 昼夜刷怪 + 昼夜密度分化
- 谁昼谁夜(MonsterData.day_night:0 昼 / 1 夜 / 2 双):root_rot(腐根虫) 保持 0(昼出);night_beast(夜行兽) 从 2(双/始终)→ 1(夜出)。配合 features/combat/monster.gd _phase_active():is_night()==(day_night==1),非活动相直接 visible=false+collision_layer=0+移出 enemies 组 → 昼只见腐根虫、夜只见夜行兽。
- 昼夜密度(scenes/level_map.gd _spawn_monsters):T2 内部再按怪物 day_night 分档——昼怪(腐根虫=0) scale 0.3(下调,白天怪少)、夜怪(夜行兽=1) scale 1.7(上调,晚上怪多、难度增强);T3 仍 ×1.5、T1 ×1.0(此改动取代 T2 首版固定 0.5)。
10.6 二批验证¶
tools/verify_promote005b.gd(headless):12 检查 0 败——青露伞restore_mp=8/heal_hp=0、灼心芝heal_hp=1/restore_mp=0、夜行兽day_night=1、腐根虫day_night=0、食用青露伞回蓝 +8 且计数 3→2、复活后 HP/MP/饱食全回满、多 seed 用 Generate 纯数据调_spawn_monsters→ T2 夜怪总数 > 昼怪总数(scale 0.3 vs 1.7生效)。- 头less 主场景引导 exit 0;
--importexit 0。 - 踩坑:脚本里
load(main.tscn)在--headless --script下会卡住本 script 的_process(不触发后续帧)——改不入树即可:_respawn_player只在current_scene==null时提前 return,重置三维的语句不在if p is Player块内,故用 dummycurrent_scene即可测复活;T2 密度则用lm_script.new()(不入树)+_spawn_monsters(...)直接计数,规避_ready的 autoload 依赖。
11. 一句话结论¶
M6 之后的五批工作把 preview 从「能玩」推进到「体验到位 + 地图可复现随机 + 大地图定向分区 + 系统层成型」:UI/UX 一轮解决"怪贴脸/背包太简/无法食用";promote003 解决"播种随机/UI 糙/数值硬";程序化随机地图把「一张死地图」升级为「空地上用噪声/泊松/醉汉踏步长出的三层 TileMap 地图」;promote004 再把它升级为"沿主轴定向分区、面积精确、门序无环的 400×320 大地图",并补齐"种田=变强(材料上调)+法术升级+装备穿戴+T3黑暗门槛"四条系统线;promote005 补上"dev 快进 + 背包能力栏(治疗+4buff) + T2/T3 密度 + 回蓝道具/复活重置/昼夜怪密度"数处调试与体验打磨。至此 preview 核心闭环只剩 Boss+结算(M7) 一环即可收官。