網格導航
返回:文件索引 / 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 時按以下順序判斷:
anchor_set=True且本輪存在x/z;position_trusted=True;- 本拍存在有效里程計樣本;
- 以上條件滿足後,路線跟隨器才允許
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 重規劃
錯誤落點重規劃不變數:
- 錯誤落點一定是滑索架,無需先下索;
- 最新 WS 使用三維座標優先匹配最近滑索節點,避免同 X/Z、不同樓層誤認;
- 目標始終是原最終目標;
- 未對映網格的滑索節點可作為起點;
- 字首鏈和後續鏈合併為一次執行;
- 候選路線比較完整實際代價,不只看節點距離;
- 直接對準失敗時保留本段實際起終點的三維座標,只封禁該連線;
- 同一連線連續 5 次未觸發後,本次任務例項內標記不可用;
- 未對映起點的續接只嘗試代價下界最小的前
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
排查順序:
- 先確認
world.pose可信; - 檢查當前地圖、縮放與網格檔案是否唯一匹配;
- 判斷規劃失敗是搜尋觸頂、未知格策略還是連通性問題;
- 檢查跟隨器動作、滑索步驟、卡住判定和輸入許可權。