下载 App

右键拖拽平移

07/26综合

一、问题本质

不是平移公式写错,而是 UrhoX UI 库指针事件派发的两个不对称陷阱叠加:
陷阱现象根因文件
OnPointerMove 不向父冒泡右键按下 isPanning_=true,但 move 事件永远到不了 mapViewurhox-libs/UI/Core/Widget.lua(OnPointerDown/Up 有 parent 冒泡,OnPointerMove 没有)
HandlePointerUp 无视按键触发 OnClick右键抬起在物件/同学身上会误弹菜单/拾取urhox-libs/UI/Core/UI.lua:2078-2103(同 widget 松开就调 pressedWidget:OnClick(event),不看 button)
加上 worldLayer 默认 pointerEvents="auto" + backgroundColor 使 findWidgetAt 在空白区命中 worldLayer 而非 mapView,三件套叠加 → 右键完全失效。
horizontal linehorizontal line

二、关键代码(4 处改动,scripts/页面/校园地图.lua

1. worldLayer 改为 pointerEvents = "box-none"(核心修复)

lua
local worldLayer = UI.Panel { position = "absolute", left = worldOffsetX_ or 0, top = worldOffsetY_ or 0, width = canvasMetrics.worldWidth, height = canvasMetrics.worldHeight, backgroundColor = { 48, 52, 55, 255 }, overflow = "visible", -- v0.10.80:必须 box-none;否则空白区域右键时 findWidgetAt 返回 worldLayer -- (default "auto" + backgroundColor 使其 HitTest 命中), -- Widget:OnPointerMove 不像 OnPointerDown/Up 那样向父冒泡,mapView.onPointerMove -- 就收不到拖拽事件,表现为右键平移无效。 pointerEvents = "box-none", scale = zoomLevel_, transformOrigin = "top-left", children = { ... }, }
原理box-none 让 worldLayer 自身不 HitTest(子节点仍可命中)。空白区右键时 pressedWidgets_[0] = mapView,HandlePointerMove 的 pressed-widget 拖拽回退路径(UI.lua:2035-2042)会在光标离开 mapView 时克隆 dragEvent 并用 screenToWidgetLocal(mapView, x, y) 继续派发给 mapView,所以即使拖拽途中划过学生/物件标记,平移也不会中断

2. 三个 onClick 加左键守卫(防右键误触)

物件拾取(CreateObjectDot):
lua
onClick = interactionText and function(widget, event) -- v0.10.80:仅左键触发拾取;右键是地图拖拽,不能触发交互 if event and event.button and event.button ~= MOUSEB_LEFT then return end if event then event:StopPropagation() end local ok, reason = PlayerInteractionService.PickupAny(context, object.id) ...
主角菜单(playerMarker):
lua
playerMarker.onClick = function(widget, event) -- v0.10.80:仅左键打开主角菜单;右键是地图拖拽 if event and event.button and event.button ~= MOUSEB_LEFT then return end if event then event:StopPropagation() end ...
同学菜单(nearbyStudents marker):
lua
onClick = function(widget, event) -- v0.10.80:仅左键打开同学菜单;右键是地图拖拽 if event and event.button and event.button ~= MOUSEB_LEFT then return end if event then event:StopPropagation() end ...
注意守卫条件是 ~= MOUSEB_LEFT 而不是 == MOUSEB_RIGHT。这样中键(MOUSEB_MIDDLE)和未来扩展按键也不会误触交互,只有左键才触发业务逻辑。

3. Pan 逻辑(v0.10.78 写的,v0.10.80 原样保留,现在才能真正跑通)

lua
-- 模块级状态 local isPanning_ = false local panStartX_, panStartY_ = 0, 0 local panOrigOffsetX_, panOrigOffsetY_ = 0, 0 local mapView = UI.Panel { ... onPointerDown = function(widget, event) if event.button == MOUSEB_RIGHT then isPanning_ = true panStartX_ = event.x panStartY_ = event.y panOrigOffsetX_ = worldOffsetX_ panOrigOffsetY_ = worldOffsetY_ end end, onPointerMove = function(widget, event) -- 缓存光标供滚轮锚点缩放用 lastMouseX_, lastMouseY_ = event.x, event.y -- v0.10.80:isPanning_ 为 true 平移(event.button 在 MOVE 事件不可靠,必须靠状态位) if isPanning_ then worldOffsetX_ = panOrigOffsetX_ + (event.x - panStartX_) worldOffsetY_ = panOrigOffsetY_ + (event.y - panStartY_) context.RebuildUI() end end, onPointerUp = function(widget, event) isPanning_ = false -- 防粘手:在控件内松开 end, onPointerCancel = function(widget, event) isPanning_ = false -- 防粘手:拖出控件外/系统中断 end, }
horizontal linehorizontal line

三、可复用的经验规则(已写入 工具准则 避雷清单)

  1. 父容器要接 onPointerMove 拖拽 → 子容器必须 pointerEvents="box-none"。默认 auto + 任何背景色都会让空白区 HitTest 命中子层;而 OnPointerMove 不冒泡,父层永远收不到拖拽。
  2. 任何会弹出菜单/拾取/提交的 onClick 都必须加左键守卫,因为 HandlePointerUp 不区分按键:
  3. MOVE 事件上的 event.button 不可靠,拖拽状态必须用模块级 isPanning_ 这类状态位判断,onPointerUp/onPointerCancel 都必须复位(onPointerCancel 覆盖拖出控件外松开、系统中断等场景)。
  4. pan 公式坐标空间event.x/event.y 在 onPointerDown/Move 回调里已由 UI 库通过 screenToWidgetLocal(widget, x, y) 覆写为 widget-local 基准像素,与 worldLayer 的 left/top(position="absolute")同空间,直接相减即可,不要再扣 viewRect.x/y(v0.10.78 已修正)。
horizontal linehorizontal line

四、约束遵守验证

用户约束实现
左键绝对不能拖地图onPointerDown 只在 event.button == MOUSEB_RIGHT 时置 isPanning_;左键行为(拾取/菜单/背包)完全保留
只有右键按住才拖onPointerDown 入口 + 状态位驱动
松开右键立即停onPointerUp onPointerCancel 都复位 isPanning_
左键原有交互不破坏三个 onClick 守卫仅过滤非左键,左键路径一行没改
LSP 41 文件 0 Error,官方 Build 一次通过。
地图编辑器地面交互与视觉优化
一、问题链 用户截图反馈了三个递进的问题: 问题 1:点击地面类型(如食堂地砖),弹窗不关闭,挡住画布无法操作 ↓ 修复 v0.10.94 问题 2:地面贴图太大,一块地砖比小人还宽,比例失调 ↓ 修复 v0.10.96(改配置值) 问题 3:改了配置值后刷新仍然太大——旧存档的 tileSize=256 优先级高于新 config ↓ 修复 v0.10.97(反转渲染优先级) 二、关键代码
官方
4 赞
图片加载三层优化设计
目标场景:地图编辑器"物品/墙体/地面/房间/人员"调色板弹窗的图片首帧显示 文档目的:沉淀"图片为什么慢、怎么治、发布后玩家体验如何、未来加资源时 preload_groups 怎么取舍"的完整结论,避免重复踩坑。 一句话结论 WASM 下图片"慢"不是单一问题,而是下载 / 解码 / 上传 GPU 三件事全堆在"玩家点弹窗那一刻"做导致的。优化的本质是把这三件事提前到启动期做(让玩家点的时候图
官方
1 赞
槽位变更只走原子方法
经验 下标 = 视觉格子,空槽是合法状态。定长 9 槽,inventory[i]=nil 即第 i 格空着,中间允许空。任何"把物品往前挪/压缩/重排"的操作都会让"你看到的格子"和"数组真实下标"脱节 → 物品消失。渲染直映 inventory[i],数据层绝不重排。 稀疏表禁用 #。带 nil 洞的表 #inv 是未定义的(可能=0 直接读成空包,或=洞前边界)。所有遍历上界一律 Player
官方
4 赞
精灵平滑移动、墙碰撞体积
一、 渲染层用连续坐标(scripts/页面/校园地图.lua,三处统一): -- NPC 定位(第408-410行) local contLeft = (character.x or 0) * 5 - 18 - charLeft local contTop = (character.y or 0) * 5 - 36 - charTop -- 主角定位(第504-506行) local con
官方
1 赞
自动寻路赠送
核心功能 携带模式(点击物品跟鼠标)下: ≤8格:点击小人直接赠送(金色边框) >8格:点击远距离小人 → 自动寻路走向目标 → 到达8格内自动赠送 取消:玩家按方向键/WASD/摇杆手动移动 → 立即取消,恢复物品 关键经验(3条踩坑教训) 经验1:类型标识不是中文,是英文枚举值 地图编辑器放置的角色 type/job 字段是英文标识(teacher_3、doctor、cleaner_2、tea
官方
1 赞
修复"地图编辑器刷新数据丢失"
问题诊断过程 用户报告:编辑器建好房间 → 保存 → 刷新页面 → 进编辑器 → 数据全没了。 关键线索:用户发现"校园日"模式刷新后能看到房间,但一进编辑器就没了。 这直接指向了编辑器页面自身的初始化逻辑,而非持久化链路。 根因 地图编辑模式.lua 的 EnsureBlankEditorLayout() 设计意图是"首次打开编辑器提供空白画布",用模块级布尔 editorLayoutClear
官方
4 赞
地图编辑器仿真 tick 重建抑制
UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。 关键代码(scripts/main.lua HandleUpdate) 四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。 -- ma
官方
4 赞
地面样式配置归一化
1. 唯一真相源 scripts/数据/地图/地面样式配置.lua local FloorStyleConfig = {} -- ★ 所有地面样式集中定义在这里 FloorStyleConfig.STYLES = { ["木地板"] = { texture = "image/地面/木地板_无缝.png", tileSize = 40 }, ["草地"] = { texture = "imag
官方
4 赞
UI动画的技巧
悬停动画(缩放+旋转) 技巧:分离X/Y轴缩放时间,配合随机旋转方向,使用EASE_OUT + TRANS_BACK曲线。 关键代码: func hover() -> void: if disabled: return pivot_offset = size / 2.0 var scale_ratio = clampf(width_full_rot / size.x, 0.5, 1.0) var
官方
3 赞
编辑器实体 zIndex 层级
问题 编辑器中墙段被房间面板覆盖不可见。原因:给房间设 zIndex=1 会创建 stacking context,其内部带背景色/TiledImage 的 floorLayer 作为 paint layer 在合成时盖过了同层级 zIndex=2 的墙段——差距太小不够可靠。 解决:底层不设 zIndex,上层用明显间距 错误做法(zIndex 间距太小 + 给底层也设 zIndex): --
官方
1 赞