自动寻路赠送

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
1