下载 App

自动寻路赠送

07/281 浏览综合

核心功能

携带模式(点击物品跟鼠标)下:
  • ≤8格:点击小人直接赠送(金色边框)
  • >8格:点击远距离小人 → 自动寻路走向目标 → 到达8格内自动赠送
  • 取消:玩家按方向键/WASD/摇杆手动移动 → 立即取消,恢复物品

关键经验(3条踩坑教训)

经验1:类型标识不是中文,是英文枚举值
地图编辑器放置的角色 type/job 字段是英文标识(teacher_3doctorcleaner_2teacher_1),不是中文名("老师"/"保安"/"保洁")。用中文名硬编码过滤会导致所有工作人员角色漏检,表现为"鼠标在红衣服人物身上边框不变金色"。
正确做法
lua
-- ❌ 错误:硬编码中文名过滤 if c.type == "保安" or c.type == "老师" or c.type == "保洁" then ... -- ✅ 正确:MapData.data.characters 中有id的角色都是可见人物,全部检测 for i = 1, #(MapData.data.characters or {}) do local c = characters[i] if c.id then -- 参与距离检测 end end
经验2:帧顺序——SetPosition 写 volatile runtime,Ensure 不覆盖已有位置
每帧更新顺序:PlayerControlService.UpdateUpdateAutoGiftStudentPositionService.SyncAll(students)
担心 SyncAll 会覆盖 SetPosition 写入的位置?不会。ActorSpatialService.Ensure 只在 state 为 nil 时创建,已存在的 state 直接返回,不重置 x/y:
lua
function ActorSpatialService.Ensure(actorId, spec) local state = states_[id] if state == nil then -- 只在不存在时创建 state = { x = spec.x or 60, y = spec.y or 40, ... } states_[id] = state end return state -- 已存在直接返回,不覆盖 end
经验3:自动寻路直接复用 Navigation.CanMove + 贴墙滑行
不需要复杂的 A* 寻路,简单的"朝目标方向走 + CanMove 检测 + 单轴滑行贴墙"就够了(与手动操控完全一致的移动逻辑):
lua
local ndx = dx / dist; local ndy = dy / dist -- 归一化方向 local step = AUTO_WALK_SPEED * dt -- 6格/秒,与手动操控同速 local canMove, nx, ny = Navigation.CanMove(px, py, ndx, ndy, step, profile) if not canMove then -- 贴墙滑行:先试X轴,再试Y轴 if Navigation.CanMove(px, py, ndx, 0, step, profile) then nx, ny = px + ndx * step, py elseif Navigation.CanMove(px, py, 0, ndy, step, profile) then nx, ny = px, py + ndy * step else return false, false -- 完全卡住 end end ActorSpatialService.SetPosition(player.id, nx, ny, direction)

关键代码

1. pendingGift_ 状态机(底部物品栏.lua)
lua
local pendingGift_ = nil -- {targetId, targetName, charTarget, source, context} local AUTO_GIFT_RANGE = 8.0 local AUTO_WALK_SPEED = 6.0 -- 距离判定分支:ExecuteCarryPlacement 中的 charTarget 分支 if dist > AUTO_GIFT_RANGE then pendingGift_ = { targetId = charTarget.id, targetName = charTarget.name or tostring(charTarget.id), charTarget = charTarget, source = src, context = context, } success = true; action = "auto_walk" else -- 近距离直接赠送 interactSvc.GiveItemToTarget(context, src, charTarget) end
2. 每帧驱动 UpdateAutoGift(由 main.lua HandleUpdate 调用)
lua
function Hotbar.UpdateAutoGift(dt) if not pendingGift_ then return false, false end local pg = pendingGift_ local px, py = PlayerControlService.GetPlayerPosition(player.id) -- 与渲染同源 local tx, ty = ActorSpatialService.GetPosition(pg.targetId) -- 目标实时位置 local dist = math.sqrt((tx-px)^2 + (ty-py)^2) if dist <= AUTO_GIFT_RANGE then -- 到达:执行赠送 local ok, reason = PlayerInteractionService.GiveItemToTarget(ctx, pg.source, pg.charTarget) pendingGift_ = nil ExitCarryMode(not ok) return false, true -- needRebuild end -- 未到达:朝目标走一步(CanMove + 贴墙滑行,见经验3) ActorSpatialService.SetPosition(player.id, nx, ny, direction) return true, false -- moved=true end
3. main.lua 帧循环接入
lua
-- 手动操控 local playerChanged = false if PlayerControlService.IsManual() and router_:IsActive("campus") then playerChanged = PlayerControlService.Update(dt, playerCharId) end -- 自动寻路赠送:手动移动→取消,否则每帧驱动 if Hotbar.HasPendingGift() then if playerChanged then Hotbar.CancelAutoGift() -- 按了方向键→取消,恢复物品 context_.rebuildUIPending = true else local moved, needRebuild = Hotbar.UpdateAutoGift(dt) if moved then playerChanged = true end if needRebuild then context_.rebuildUIPending = true end end end
4. 金色锁定视觉(overlay pointermove)
lua
-- 自动寻路中:跳过正常颜色逻辑,保持金色 if pendingGift_ then carryState_.ghostWidget:SetBorderColor(GHOST_BORDER_TARGET) return end -- 正常四色逻辑:槽位蓝/红,丢地上绿,小人金色
5. CancelAutoGift(手动移动取消)
lua
function Hotbar.CancelAutoGift() if not pendingGift_ then return false end ExitCarryMode(true) -- true=恢复物品到来源槽,清除pendingGift_ return true end
右键拖拽平移
一、问题本质 不是平移公式写错,而是 UrhoX UI 库指针事件派发的两个不对称陷阱叠加: 陷阱 现象 根因文件 OnPointerMove 不向父冒泡 右键按下 isPanning_=true,但 move 事件永远到不了 mapView urhox-libs/UI/Core/Widget.lua(OnPointerDown/Up 有 parent 冒泡,OnPointerMove 没有) H
官方
4 赞
地图编辑器仿真 tick 重建抑制
UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。 关键代码(scripts/main.lua HandleUpdate) 四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。 -- ma
官方
4 赞
以鼠标为锚点缩放
核心数学原理 缩放前后,鼠标指向的那个世界坐标点必须保持在同一屏幕位置。 已知:旧缩放 oldZoom、新缩放 newZoom、鼠标锚点屏幕坐标 (ax, ay)、当前偏移 (panX, panY) 步骤1:算出旧缩放下鼠标指向的世界坐标 wx = (ax - panX) / oldZoom wy = (ay - panY) / oldZoom 步骤2:新缩放后反解新偏移,让 wx/wy
官方
1 赞
地图编辑器地面交互与视觉优化
一、问题链 用户截图反馈了三个递进的问题: 问题 1:点击地面类型(如食堂地砖),弹窗不关闭,挡住画布无法操作 ↓ 修复 v0.10.94 问题 2:地面贴图太大,一块地砖比小人还宽,比例失调 ↓ 修复 v0.10.96(改配置值) 问题 3:改了配置值后刷新仍然太大——旧存档的 tileSize=256 优先级高于新 config ↓ 修复 v0.10.97(反转渲染优先级) 二、关键代码
官方
4 赞
修复"地图编辑器刷新数据丢失"
问题诊断过程 用户报告:编辑器建好房间 → 保存 → 刷新页面 → 进编辑器 → 数据全没了。 关键线索:用户发现"校园日"模式刷新后能看到房间,但一进编辑器就没了。 这直接指向了编辑器页面自身的初始化逻辑,而非持久化链路。 根因 地图编辑模式.lua 的 EnsureBlankEditorLayout() 设计意图是"首次打开编辑器提供空白画布",用模块级布尔 editorLayoutClear
官方
4 赞
槽位变更只走原子方法
经验 下标 = 视觉格子,空槽是合法状态。定长 9 槽,inventory[i]=nil 即第 i 格空着,中间允许空。任何"把物品往前挪/压缩/重排"的操作都会让"你看到的格子"和"数组真实下标"脱节 → 物品消失。渲染直映 inventory[i],数据层绝不重排。 稀疏表禁用 #。带 nil 洞的表 #inv 是未定义的(可能=0 直接读成空包,或=洞前边界)。所有遍历上界一律 Player
官方
4 赞
图片加载三层优化设计
目标场景:地图编辑器"物品/墙体/地面/房间/人员"调色板弹窗的图片首帧显示 文档目的:沉淀"图片为什么慢、怎么治、发布后玩家体验如何、未来加资源时 preload_groups 怎么取舍"的完整结论,避免重复踩坑。 一句话结论 WASM 下图片"慢"不是单一问题,而是下载 / 解码 / 上传 GPU 三件事全堆在"玩家点弹窗那一刻"做导致的。优化的本质是把这三件事提前到启动期做(让玩家点的时候图
官方
1 赞
穿透层修复
经验一:CSS transform 穿透层修复模式 问题本质 当容器使用 CSS transform: translateX/Y/scale 平移/缩放子树时,即使父容器有 overflow = "hidden",引擎的命中检测可能不裁剪越界区域。如果 transform 后的子树有更高的 zIndex,它会在视觉范围外拦截其他兄弟节点的指针事件。 受影响的场景 地图编辑器(右键平移/滚轮缩放后)
官方
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 赞
围墙空气墙
问题根源 原系统用单点碰撞(Round(nx), Round(ny) 只检测中心1个点),角色精灵 36px(7.2格) 宽,中心停在 x=0.5 时精灵左半完全覆盖 5px 围墙,视觉"穿墙"。 1. AABB 矩形碰撞检测 — scripts/小人/导航.lua -- 角色碰撞体尺寸(格):脚底占据的地面矩形 -- 半宽 0.5 格(2.5px),半高 0.3 格(1.5px) local A
官方
1 赞