網格導航

返回:文件索引 / API 參考 / 開發指南 / 小地圖定位

本文只說明二維網格規劃、路線跟隨和遊戲輸入。座標來源、融合和校準見 小地圖定位。

1. 職責邊界

網格導航接收目標世界座標,消費可信的 world.pose,然後:

  • 載入當前地圖的 *.grid.npz;
  • 在可行走網格上做 A*;
  • 把路徑轉換成 TURN/WALK/ZIP_LINE/WAIT/STUCK/REPLAN/DONE;
  • 執行鍵盤、滑鼠和滑索操作;
  • 處理重規劃、卡住、脫困和週期校準。

它不直接讀取物品點位,也不啟動 WS、小地圖裡程計或融合器。

導航程式碼不匯入 src/localization/。位置、朝向和朝向控制通過 PoseProvider 介面獲得,具體實現由執行狀態適配層注入。

2. 模組

模組 職責 不負責
src/nav/grid_io.py 稠密網格、座標換算、*.grid.npz 讀寫 路徑規劃
src/nav/grid_planner.py A*、風險與牆距代價、起終點吸附、滑索圖接入 遊戲輸入
src/nav/route_follower.py 把航點轉換成動作狀態 直接操作遊戲
src/nav/nav_state.py 導航主迴圈每一拍的狀態、會話資料與輸入快照 判定與執行
src/nav/zip_line_graph.py 使用者滑索點解析和連線圖 網格規劃
src/tasks/navigation/mixin/grid_navigation_mixin.py 組織定位、規劃、動作、重規劃和脫困 純演算法實現
src/tasks/navigation/mixin/zip_line_mixin.py 登索、上下索 UI 和滑索執行 網格規劃

src/nav/ 是純 Python 模組,不依賴 ok 框架,可獨立測試。

3. 資料流

flowchart TD
    HUB[RuntimeStateHub: world.pose] --> NAV[GridNavigationMixin]
    G[*.grid.npz] --> P[A* Planner]
    Z[官方地图 saveMarks] --> ZG[用户滑索图]
    ZG --> P
    NAV -->|起点与终点| P
    P -->|cells / waypoints / zip_line_steps| RF[Route Follower]
    HUB -->|位置与朝向| RF
    RF -->|动作状态| NAV
    NAV -->|键盘、鼠标| GAME[游戏]

網格導航主迴圈必須先確認定位可信,再允許路線跟隨器繼續行走或重新規劃。

src/nav/ 只包含純演算法,不依賴定位實現;導航任務和導航 Mixin 也只依賴 world.pose 與 PoseProvider 介面。

4. 座標與網格

world = origin + (i, j) * cell_size
  • i 是行,沿世界 z;
  • j 是列,沿世界 x;
  • origin 是陣列下標 (0, 0) 格最小角的世界座標;
  • 當前導航網格是二維的,origin[1] 只作溯源記錄,不參與換算。

*.grid.npz 包含:

成員 內容
cells uint8 二維陣列,0=未知、1=可行走、2=阻挡
meta UTF-8 JSON,包含 origin、cell_size、map_name、zoom 和 schema 版本

讀取時會校驗 magic、schema_version、陣列維度和狀態值。

5. 配置

網格引數集中在全域性 Nav Config:

  • 地图id(留空自动);
  • 网格目录、网格文件(可选)、网格Zoom(可选);
  • 使用滑索路径;
  • 仅规划不移动;
  • 等待定位超时(秒);
  • 規劃、代價、轉向、衝刺、卡住和脫困引數。

MinimapNavigateToPoint 的任務配置只保留目標 X/Z,其餘引數從 Nav Config 讀取。

6. A* 規劃

規劃器核心規則:

  • 可行走格基礎代價為 1,斜向為 sqrt(2);
  • 未知格可通行時乘以 risk_cost;
  • 阻擋格不可通行,斜向移動不能穿過阻擋牆角;
  • margin 和 wall_penalty 提供離牆偏好,但不會封死窄路;代價在 clearance >= margin 處飽和,因此只會把路線拉到離牆 margin 格以內(預設 3,可讓寬約 5 格以內的走廊 從正中穿過),開闊區域不受影響、不會為了居中繞遠;
  • frontier_margin 和 frontier_penalty 偏好已知自由區域內部;
  • 寬度只有一格的通路由 GridPlanner.is_narrow_corridor 識別(正對兩側被界內阻擋格夾住), 入口前後兩格會被強制保留為航點,入口航點下標寫入 PlanResult.narrow_entries;
  • 精確搜尋超過 max_expand 時自動加權重搜一次(權重取 max(risk_cost, 1/启发式缩放)), 降級只改擴充套件順序,路線依舊不穿牆、不切角,代價最優性不再保證並記入 notes;
  • 失敗原因區分搜尋觸頂、禁止穿越未知格和可達區域窮盡。

載入了使用者滑索時,規劃耗時會明顯上升,這是刻意付的代價:滑索邊的單價是 zip_line_cost_factor(預設 0.2)乘距離,比步行便宜得多,octile 啟發只有在整體乘上 同一係數之後才仍是代價下界,因此 A* 會退得更接近均勻代價搜尋。實測同一張 520 萬格 網格上一條 535m 路線,擴充套件節點數從 10.7 萬升到 34.7 萬;未開啟「允許穿越未知格」時搜尋 只覆蓋已知可行走區(本倉庫隨附網格最大約 3.7 萬格),影響有限,開啟後長路線可能頂到 max_expand。縮放係數記錄在 GridPlanner.zip_line_stats()["heuristic_scale"],導航日誌 會在接入滑索時列印它;觸頂診斷裡也會帶上這條說明,避免誤判成"網格資料有問題"。

7. 路線跟隨與執行

任務層主迴圈是顯式狀態機,每拍只做三步:_grid_nav_poll 取輸入快照、 _grid_nav_classify 判定這一拍的狀態、_GRID_NAV_HANDLERS 裡的處理器執行它。 狀態沒有跨拍記憶——每拍都按下面的固定優先順序重新判定一次,所以表裡的“狀態”是 “這一拍走哪個分支”。狀態、會話資料和快照型別都在 src/nav/nav_state.py; 狀態到處理器的登記必須一一對應,tests.TestGridNavigationMixin 會檢查。

7.1 每一拍的狀態

優先順序 狀態 進入條件 處理
1 WAIT_POSE 拿不到 world.pose 停車等下一拍
2 WAIT_ANCHOR x/z 未錨定 停車等下一拍
3 WAIT_TRUST position_trusted=False 停車;靜止且過了重試點則請求校準
4 WAIT_ODOM 本拍無有效位移樣本 停車等下一拍
5 CALIBRATE 定位服務要求校準且過了重試點 停車等一次真實重錨
6 WAIT_HEADING 朝向不可用或質量低於閾值 停車等下一拍
7 STOP_REACHED 已進入 stop_distance 停車並按成功結束
8 PLAN 沒有路線,或檢測到換圖 規劃;成功後同一拍繼續往下走
9 ZIP_LINE_APPROACH 跟隨器給出了待乘滑索段 先接近入口並登索,否則落回普通派發
10 REPLAN 跟隨器報 REPLAN 停車;按是否在已知格決定原地校準或回退
11 ZIP_LINE 跟隨器報 ZIP_LINE 執行滑索段
12 WAYPOINT_ARRIVED 到達一個航點 累計;到 航点校准间隔(个) 就停車校準
13 DONE 跟隨器報 DONE 還在回退目標上就先校準,否則判定到達
14 STUCK 跟隨器報 STUCK 按脫困序列嘗試;額度用完則失敗
15 NARROW_CALIBRATE 跟隨器報 NARROW_CALIBRATE 停車校準 + 對準入口,連續失敗到上限才繼續
16 TURN 跟隨器報 TURN 大角度或貼近航點時停車轉,否則邊走邊轉
17 WALK 跟隨器報 WALK 按住 W 前進
18 WAIT 跟隨器報 WAIT 鬆開 W 等狀態穩定
19 FAILED 跟隨器報 FAILED 鬆開 W 並按失敗結束

第 13-19 項在派發前會先記一次目標距離進展(有進展就歸還重規劃與脫困額度)。

stateDiagram-v2
    [*] --> Gates
    Gates --> Stopped: 已进入接近范围
    Gates --> Plan: 位置与朝向都可用
    Plan --> Follow: 规划成功
    Follow --> Plan: 未规划或换图
    Follow --> Gates: 下一拍重新判定
    Plan --> Failed: 规划失败
    Follow --> Failed: 执行失败或额度用完
    Follow --> Done: 到达目标
    Stopped --> [*]
    Done --> [*]
    Failed --> [*]

Gates 是 7.1 表裡的第 1-6 項,Follow 是第 9-19 項;兩者都只是“這一拍命中哪一組”, 不是可以停留在裡面的長期狀態。

7.2 跟隨器輸出

下面是 GridRouteFollower 每拍輸出的動作,其中執行類狀態與它們同名同值, 所以動作可以直接對映成狀態:

  • TURN:轉向;
  • WALK:按住 W 移動;
  • ZIP_LINE:執行滑索步驟;
  • NARROW_CALIBRATE:進入一格寬通路前停車校準;
  • WAIT:等待狀態穩定;
  • STUCK:進入脫困;
  • REPLAN:偏航或路徑失效後重新規劃;
  • DONE:到達目標。

普通行走開始後,如果當前規劃航段超過 15m,導航會在按住 W 時按一次 shift 進入衝刺。鬆開 W 後衝刺狀態清除。

7.3 靜止校準

靜止校準是導航裡唯一的主動停車點,入口統一在 _grid_nav_sync(ctx, reason), 六個觸發原因見 SyncReason:定位重新校準、定位服務請求、航點間隔、偏航後原地校準、 回到已知格後校準、窄道入口前。原因會寫進狀態提示與超時告警,便於回答“為什麼又停下來校準”。 等待本身由 _wait_for_minimap_sync 完成,上限 8 秒(覆蓋一個 WS 推送週期), 超時或“已對齊”都按未校準處理,呼叫方據此決定退避 30 秒還是繼續。

窄道入口是最特別的一個觸發點:噹噹前目標航點是一格寬通路的入口 (PlanResult.narrow_entries)、角色已接近到「窄道前校準距離(米)」以內時,跟隨器輸出 NARROW_CALIBRATE:主迴圈先停車校準,再對準入口航點方位,隨後才繼續走向入口, 避免帶著累積定位誤差進入沒有橫向餘量的通路。入口前最後一格也是捷徑保護點, 不會被 shortcut_radius 跳過;關閉「窄道前停車校準」即恢復舊行為。

7.4 卡住脫困

脫困動作序列是顯式資料(_GRID_STUCK_RECOVERY):後退 → 後退 + 側移 → 對準目標後 跳躍。嘗試次數超出序列長度時重複最後一步,所以把「卡住脫困次數」調大不會越界; 日誌裡的“第幾次/共幾次”跟隨配置,不再寫死 3/3。

7.5 轉向收斂

地面停車轉向首輪使用已標定的輸入比例,不把首輪角色朝向變化用於重新估計係數: 角色可能尚未朝向鏡頭,首輪變化包含兩者的初始偏差。按 W 後連續讀取新畫面,等朝向 穩定再判斷到位或按剩餘角度補轉;仍在變化時交回下一拍取樣,不連續追加滑鼠輸入。 穩定觀測複用原「角度重新整理等待」,正常轉向不額外疊加一次固定等待。

第二輪及之後才允許用同基準的穩定反饋微調本次轉向比例;小位移、反向或接近半圈的 角差不用於校準,避免取絕對值掩蓋方向或跨圈歧義。滑索純視角對準仍沿用其原有流程。

7.6 偏航回退

偏航時若當前位置不在已知可行走格,先規劃回最近走過的已知格(session.recovery_goal), 走到之後由 DONE 觸發一次靜止校準,校準完成再清空回退目標、繼續原目標。 真重錨會整體平移世界座標,因此每次 sync_seq 變化都會清空“走過的已知格”。

8. 定位協作

網格導航消費 world.pose 時按以下順序判斷:

  1. anchor_set=True 且本輪存在 x/z;
  2. position_trusted=True;
  3. 本拍存在有效里程計樣本;
  4. 以上條件滿足後,路線跟隨器才允許 WALK 或重新規劃。

普通重錨後必須停車等待靜止校準。too_long_dt 屬於良性換錨,不撤銷信任。

達到指定航點數量或累計移動距離後,導航會主動停車請求重新校準;定位服務完成校準後, 導航再繼續原路線。

9. 使用者滑索

9.1 資料

滑索點來自官方地圖介面:

GET /web/v1/game/endfield/map/mark/list?mapId=...&roleId=...&serverId=...

只讀取 data.saveMarks,通過 data.markTemplates 對映:

模板名 最大連線距離
滑索架 80m
长距滑索架 110m

連線約束:

  • 僅連線同一 mapId;
  • 兩端都有 levelId 時必須同層;
  • 使用三維歐氏距離;
  • 距離不超過兩端連線半徑的較大值。

9.2 規劃

每個滑索點會吸附到最近的可通行格。可連線滑索對生成雙向轉移邊:

普通滑索边:distance * zip_line_cost_factor + zip_line_boarding_cost

規劃器支援把中間滑索鏈合成一條 A* 邊。例如 A -> B -> C 中若 B 位於未知格, 只要 A/C 可到,仍可規劃成 A -> C,執行時依次乘坐兩段。若 B 本身能對映到 可行走格,正常情況下仍保持 A -> B -> C 連續執行,不在中間點重複按 F。只有 後續連線確認失敗並封禁後,重規劃才會改為在 B 下索並使用普通網格路線。

9.3 執行

執行分兩段:先把角色弄上滑索架(登索),再按段輪詢騎乘狀態。兩段各有獨立的判定, 共用「登上滑索架」模板(_zip_line_on_rack_visible)與最新 WS 座標。

9.3.1 登索階段

_grid_zip_line_enter_rack 負責“按「登上滑索架」按鈕並用 fL.esc 確認”, 一次嘗試的結果是 ZipLineBoardAttempt:

結果 判定 動作
BOARDED fL.esc 已消失 記「已成功登上滑索」,進入騎乘階段
BUTTON_MISSING 沒找到「登上滑索架」按鈕 直接失敗:重新對準解決不了按鈕不存在
UNCONFIRMED 點了按鈕但 fL.esc 判定超時 直接失敗,不重試
NOT_BOARDED fL.esc 仍在 重新對準入口滑索架後重試一次,再失敗才放棄

重試只有一次,且只有 NOT_BOARDED 才值得重試。重試輪的失敗文案與首輪不同 (「重新對正後仍未找到…」「重新對正後仍檢測到 fL.esc…」),日誌裡能直接看出是第幾次。

走向入口滑索架的過程由 _approach_grid_zip_line_for_boarding 負責:

狀態 判定 動作
待登索 距計劃上索點 <=5m 按一次 ctrl 切步行;保持 W 連續接近,在移動中讀取最新 WS 並持續搜尋模板;模板出現立即停車並按 F
待續乘 錯誤落點重規劃後仍在同一滑索架 跳過登索,直接對準下一段
接近搜尋超時 連續接近 20s 仍未檢測到模板 先在入口附近短距 W/A/D/S 搜尋;多輪仍失敗才短暫後退約 0.6s,不直接長距離遠離
按 F 後未登索 fL.esc 在約 1.5s 後仍存在 短暫按 S 後退約 0.6s,下一輪從最新 WS 重新計算入口方位後再走

9.3.2 騎乘階段

_wait_zip_line_motion 每拍輪詢一次,按下面五種情況處理。呼叫方負責點選並按一次 E, 本函式只輪詢狀態、不重複傳送按鍵:

狀態 判定 動作
MOVING 兩個移動模板都不存在 等待,不重複傳送 E
ARRIVED 模板存在且 WS 進入目標範圍 進入下一段
WAITING_TEMPLATE 已停穩在目標附近但到達模板還沒出現 繼續等模板,既不算到達也不算失敗
NOT_TRIGGERED 已停穩且 WS 仍在起點範圍 返回失敗,交給外層重新對準後重試
WRONG_RACK 停穩後 WS 既不在起點也不在目標附近 不下索,從實際滑索架重新規劃
stateDiagram-v2
    [*] --> EnterRack
    EnterRack --> Ride: BOARDED
    EnterRack --> [*]: BUTTON_MISSING / UNCONFIRMED
    EnterRack --> Reaim: NOT_BOARDED
    Reaim --> EnterRack: 重新对准后重试一次
    Reaim --> [*]: 仍失败
    Ride --> Ride: MOVING / WAITING_TEMPLATE
    Ride --> Reaim: NOT_TRIGGERED
    Ride --> Ride: ARRIVED 且还有下一段
    Ride --> [*]: 全部完成
    Ride --> [*]: WRONG_RACK 重新规划

模板是“是否在滑索架上”的視覺依據;網格登索只使用最新 WS 與入口滑索座標計算接近方位。 下索前必須檢查滑索節點的實際座標是否落在可通行格:可通行時 A* 會比較“原地下索步行”和“繼續乘坐滑索”;不可通行時不能下索,只能繼續沿滑索圖移動。滑索鏈的目標節點也必須是可通行格,否則不能把該節點作為實際下索終點。

9.4 重規劃

錯誤落點重規劃不變數:

  1. 錯誤落點一定是滑索架,無需先下索;
  2. 最新 WS 使用三維座標優先匹配最近滑索節點,避免同 X/Z、不同樓層誤認;
  3. 目標始終是原最終目標;
  4. 未對映網格的滑索節點可作為起點;
  5. 字首鏈和後續鏈合併為一次執行;
  6. 候選路線比較完整實際代價,不只看節點距離;
  7. 直接對準失敗時保留本段實際起終點的三維座標,只封禁該連線;
  8. 同一連線連續 5 次未觸發後,本次任務例項內標記不可用;
  9. 未對映起點的續接只嘗試代價下界最小的前 zip_line_start_max_candidates(預設 4)個可對映落點:每個候選都要跑一次完整 A*, 在 map02 這種 520 萬格的圖上單次就是 1~2 秒。被截斷時 PlanResult.notes 會寫明, 按提示調大上限即可;截斷的代價是可能選到更繞的那條(例如先滑到一個落點再原路滑回)。

10. 除錯與測試

導航除錯任務:

  • MinimapNavigateToPoint:按世界座標規劃;開啟「僅規劃不移動」可只檢查路徑;
  • 滑索日誌重點檢視:执行滑索链、已到达下一滑索附近、到达了非目标滑索架、 滑索连接不可用、错误落点已匹配滑索架节点。

常用測試:

uv run --locked python -m unittest tests.TestNavGridIO tests.TestNavGridPlanner tests.TestNavZipLineGraph tests.TestNavRouteFollower tests.TestGridNavigationMixin tests.TestFindZipLineBoardButton -v

排查順序:

  1. 先確認 world.pose 可信;
  2. 檢查當前地圖、縮放與網格檔案是否唯一匹配;
  3. 判斷規劃失敗是搜尋觸頂、未知格策略還是連通性問題;
  4. 檢查跟隨器動作、滑索步驟、卡住判定和輸入許可權。
在 GitHub 查看來源 ↗ · 頁面產生時間: 2026年10月23日