下载 App

地图编辑器「墙」全链路增强

07/272 浏览综合

horizontal linehorizontal line

经验 1:子编辑器"连续操作" vs "一次性操作"——提交后别清空激活态

现象:放完一段墙,提示"可继续拖拽放置",但再拖没反应。 根因CommitWall 提交后把 selectedItem=niltool="select",导致 IsActive() 返回 false,下次 HandlePointerDown 第一行就 return。提示语与行为矛盾。 模式:连续工具(墙、擦除)提交后只 ResetDrag()保留 selectedItem/tool;右键 Cancel() 才退出。
lua
-- 墙体拖拽编辑.lua CommitWall 末尾 self:ResetDrag() -- 保持 selectedItem 和 tool 不变,允许连续拖拽放置;右键由 Cancel 退出 self:RequestRebuild(context) return true
对比一次性工具(房间框选)提交后才清空。判断标准:提示语里写"可继续"就必须保持激活
horizontal linehorizontal line

经验 2:onClick 的 event 没有 StopPropagation(二次踩坑,必须入清单)

现象:属性面板"删除该墙"按钮点了无反应(实际是 nil 崩溃,removeSelectedWall 没执行到)。 根因:onClick 回调的 event 不是 PointerEvent,调 event:StopPropagation() 触发 attempt to call a nil value。onPointerDown/Move 才有该方法。 模式:所有 onClick 守卫用方法存在性检查
lua
-- 属性面板.lua 删除按钮 onClick = function(widget, event) if event and event.StopPropagation then -- ✅ 存在性检查 event:StopPropagation() end self.removeSelectedWall(context) -- 守卫之后才能执行 end, -- ❌ 错误写法:self:StopClickPropagation(widget, event) 在 onClick 里会崩
horizontal linehorizontal line

经验 3:拼接可能为 nil 的字段前先兜底

现象:点自由墙报 attempt to concatenate a nil value (field 'roomId')根因:自由墙 roomId=nil"..." .. segment.roomId 在 Lua 里 nil 不能拼接。 模式:用 or 给显示文本兜底,数值字段用 tostring()
lua
-- 地图编辑模式.lua SelectWall local ownerText = segment.roomId or "自由墙" editorState_.status = (segment.isDoor and "已选中门:" or "已选中墙体:") .. ownerText .. " / " .. tostring(segment.side)
horizontal linehorizontal line

经验 4:自由墙删除走"整条 wallId",房间墙不可逐段删

设计:自由墙是 MapData.data.walls[] 的独立对象,按 wallId 整条删;房间墙由 room.wallsEnabled 整体生成,逐段删会破坏闭合,应提示去关"启用墙体"。 数据层(单一职责,删除逻辑放数据模块):
lua
-- 校园主地图.lua function M.RemoveFreeWall(wallId) if not wallId or wallId == "" then return false end local walls = map.walls or {} for i = #walls, 1, -1 do if walls[i].id == wallId then table.remove(walls, i) return true end end return false end
编辑层(区分两类墙):
lua
-- 地图编辑模式.lua RemoveSelectedWall local segment = editorState_.selectedWall if not segment then ... return end if segment.roomId then -- 房间墙:拒绝逐段删 editorState_.status = "该墙体属于房间 " .. segment.roomId .. ",随房间整体生成。如需移除,请关闭房间的“启用墙体”。" return end if MapData.RemoveFreeWall(segment.wallId) then -- 自由墙:整条删 editorState_.selectedKind = nil editorState_.selectedWall = nil context.store.MarkDirty("mapEditorWallDelete") end
horizontal linehorizontal line

经验 5:框选擦除子编辑器——照抄 FloorAreaEditor/WallDragEditor 契约

新增子编辑器必须实现同一组方法,主编辑器才能统一接线。核心是拖拽矩形 + AABB 命中 + 提交删除
lua
-- 墙体框选擦除.lua 关键片段 function WallEraseEditor:IsActive() return self.editorState.tool == "erase_wall" end function WallEraseEditor:HitTestFreeWalls(left, top, right, bottom) local hits = {} for i = 1, #(self.mapData.data.walls or {}) do local w = self.mapData.data.walls[i] if not w.broken then local x0, x1 = math.min(w.ax,w.bx), math.max(w.ax,w.bx) local y0, y1 = math.min(w.ay,w.by), math.max(w.ay,w.by) if not (x1 < left or x0 > right or y1 < top or y0 > bottom) then -- AABB 相交 hits[#hits + 1] = w.id end end end return hits end function WallEraseEditor:Commit(context) local left, top, right, bottom = self:GetRect() if not left then self:ResetDrag(); return false end local hits = self:HitTestFreeWalls(left, top, right, bottom) local count = 0 for i = 1, #hits do if self.mapData.RemoveFreeWall(hits[i]) then count = count + 1 end end self:ResetDrag() -- 保持 erase_wall 激活,可连续框选 self:RequestRebuild(context) return true end
主编辑器接线清单(每新增一个子编辑器都要补齐这 7 处,漏一处就事件不通):
  1. require + 模块级 wallEraseEditor_ = nil
  2. SelectPaletteItemitem.tool == "erase_wall" 路由
  3. CreateCanvasWall 的 HandleClick 加 erase_wall 守卫(避免单段点击抢事件)
  4. mapSurface onPointerDown:右键 Cancel 链 + 左键 HandlePointerDown
  5. onPointerMove / onPointerUp / onPointerCancel 各加一行
  6. mapSurface onClickerase_wall 守卫
  7. Create 实例化 + CreateMapCanvasCreatePreview()
horizontal linehorizontal line

经验 6:编辑器能画 ≠ 运行时能画——两页渲染必须对齐

现象:编辑器保存后,运行时地图(图2)墙不出现,房间/地面却正常。 根因:运行时 CreateMapView 遍历了 rooms/floorAreas/objects/characters,唯独没有遍历 data.walls。房间靠 CreateRoomCard 的边框、地面靠 CreateFloorAreaView 各画各的,墙没有对应渲染代码。 模式:凡是编辑器能创建/修改的实体,运行时页必须有同源的渲染分支;优先复用 MapData 的 getter,配色与编辑器保持一致。
lua
-- 校园地图.lua 自由墙整条渲染(zIndex=2 压在地面上、物件/人物下;pointerEvents=none 不挡点击) local function CreateFreeWallView(wall) if not wall or wall.broken then return nil end local x0, x1 = math.min(wall.ax, wall.bx), math.max(wall.ax, wall.bx) local y0, y1 = math.min(wall.ay, wall.by), math.max(wall.ay, wall.by) local style = wall.style or "普通墙" local color = style == "围墙" and {110,88,60,255} or style == "栅栏" and {120,150,95,230} or style == "破损墙" and {120,95,80,200} or {94,96,89,255} return UI.Panel { position = "absolute", left = x0*5, top = y0*5, width = math.max(5,(x1-x0+1)*5), height = math.max(5,(y1-y0+1)*5), backgroundColor = color, pointerEvents = "none", zIndex = 2, } end -- CreateMapView 内,地面之后、物件之前: for i = 1, #(data.walls or {}) do local v = CreateFreeWallView(data.walls[i]) if v then campusChildren[#campusChildren + 1] = v end end
horizontal linehorizontal line

改动文件清单

文件内容
scripts/页面/地图编辑/墙体拖拽编辑.luaCommitWall 保持激活(连续拖放)
scripts/页面/地图编辑/墙体框选擦除.lua新建框选擦除子编辑器
scripts/页面/地图编辑/编辑器目录.lua调色板 + TOOLS 加「擦除墙」
scripts/页面/地图编辑/属性面板.lua删除按钮 + 修 onClick 崩溃
scripts/页面/地图编辑模式.lua修 SelectWall nil + RemoveSelectedWall + 擦除接线 7 处
scripts/数据/地图/校园主地图.lua新增 RemoveFreeWall
scripts/页面/校园地图.lua新增 CreateFreeWallView 运行时墙渲染
贯穿性教训:① onClick 永远用存在性检查 StopPropagation;② 子编辑器的"提交后是否清空激活态"要和提示语一致;③ 编辑器与运行时的实体渲染要同源对齐,新增实体时两页同步加;④ 新增子编辑器照统一契约写、主编辑器补全 7 处接线。
穿透层修复
经验一:CSS transform 穿透层修复模式 问题本质 当容器使用 CSS transform: translateX/Y/scale 平移/缩放子树时,即使父容器有 overflow = "hidden",引擎的命中检测可能不裁剪越界区域。如果 transform 后的子树有更高的 zIndex,它会在视觉范围外拦截其他兄弟节点的指针事件。 受影响的场景 地图编辑器(右键平移/滚轮缩放后)
官方
4 赞
右键拖拽平移
一、问题本质 不是平移公式写错,而是 UrhoX UI 库指针事件派发的两个不对称陷阱叠加: 陷阱 现象 根因文件 OnPointerMove 不向父冒泡 右键按下 isPanning_=true,但 move 事件永远到不了 mapView urhox-libs/UI/Core/Widget.lua(OnPointerDown/Up 有 parent 冒泡,OnPointerMove 没有) H
官方
4 赞
弹窗内做"搜索 + 分类 + 网格"
经验 1:弹窗内做"搜索 + 分类 + 网格",用局部刷新保住输入焦点 为什么有效:搜索框是 UI.TextField,如果每次输入都 RebuildUI 重建整棵树,焦点会丢、光标乱跳。正确做法是只把"网格容器"这一棵子树的子节点 ClearChildren + AddChild,搜索框和分类标签不动。模块级变量 objectCategory_ 跨弹窗开关不重置,天然实现"记住上次分类"。 关键
官方
【日常更新】支持2人、3人、4人对战截图
【日常更新】支持2人、3人、4人对战
优化了排行榜显示玩家昵称
3 赞
图片加载三层优化设计
目标场景:地图编辑器"物品/墙体/地面/房间/人员"调色板弹窗的图片首帧显示 文档目的:沉淀"图片为什么慢、怎么治、发布后玩家体验如何、未来加资源时 preload_groups 怎么取舍"的完整结论,避免重复踩坑。 一句话结论 WASM 下图片"慢"不是单一问题,而是下载 / 解码 / 上传 GPU 三件事全堆在"玩家点弹窗那一刻"做导致的。优化的本质是把这三件事提前到启动期做(让玩家点的时候图
官方
1 赞
槽位变更只走原子方法
经验 下标 = 视觉格子,空槽是合法状态。定长 9 槽,inventory[i]=nil 即第 i 格空着,中间允许空。任何"把物品往前挪/压缩/重排"的操作都会让"你看到的格子"和"数组真实下标"脱节 → 物品消失。渲染直映 inventory[i],数据层绝不重排。 稀疏表禁用 #。带 nil 洞的表 #inv 是未定义的(可能=0 直接读成空包,或=洞前边界)。所有遍历上界一律 Player
官方
4 赞
修复"地图编辑器刷新数据丢失"
问题诊断过程 用户报告:编辑器建好房间 → 保存 → 刷新页面 → 进编辑器 → 数据全没了。 关键线索:用户发现"校园日"模式刷新后能看到房间,但一进编辑器就没了。 这直接指向了编辑器页面自身的初始化逻辑,而非持久化链路。 根因 地图编辑模式.lua 的 EnsureBlankEditorLayout() 设计意图是"首次打开编辑器提供空白画布",用模块级布尔 editorLayoutClear
官方
4 赞
地图编辑器仿真 tick 重建抑制
UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。 关键代码(scripts/main.lua HandleUpdate) 四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。 -- ma
官方
4 赞
地图编辑器地面交互与视觉优化
一、问题链 用户截图反馈了三个递进的问题: 问题 1:点击地面类型(如食堂地砖),弹窗不关闭,挡住画布无法操作 ↓ 修复 v0.10.94 问题 2:地面贴图太大,一块地砖比小人还宽,比例失调 ↓ 修复 v0.10.96(改配置值) 问题 3:改了配置值后刷新仍然太大——旧存档的 tileSize=256 优先级高于新 config ↓ 修复 v0.10.97(反转渲染优先级) 二、关键代码
官方
4 赞
以鼠标为锚点缩放
核心数学原理 缩放前后,鼠标指向的那个世界坐标点必须保持在同一屏幕位置。 已知:旧缩放 oldZoom、新缩放 newZoom、鼠标锚点屏幕坐标 (ax, ay)、当前偏移 (panX, panY) 步骤1:算出旧缩放下鼠标指向的世界坐标 wx = (ax - panX) / oldZoom wy = (ay - panY) / oldZoom 步骤2:新缩放后反解新偏移,让 wx/wy
官方
1 赞