You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用内置AStar算法生成转向最少的最短路径?

如何让Godot内置AStar选择转向最少的最短路径

这个问题我之前也碰到过——Godot内置的AStar(3.x)确实不支持直接设置边权重,只能给节点设权重,这就导致默认寻路只会优先最短距离,容易出现频繁转向的路径。不过我们可以通过扩展节点状态的方式模拟边的转向成本,让AStar优先选择转向少的路径,同时保证总长度最短。

核心思路

既然没法给边加权重,我们就把「网格坐标+进入方向」组合成一个唯一的节点。这样:

  • 当移动方向和之前一致时,路径权重保持基础值(比如1)
  • 当需要转向时,给这条路径加一个极小的惩罚值(比如0.1),让转向多的路径总权重更高,AStar就会自动避开这类路径

这种方法的好处是:惩罚值远小于基础权重,不会让路径变长,只会在长度相同的路径中优先选择转向最少的。

具体实现代码

下面是适配你现有GridMap代码的完整实现:

extends GridMap
var _astar = AStar.new()

# 定义我们用到的两个移动方向(对应你代码里的right和above)
const DIRS = [Vector3.RIGHT, Vector3.FORWARD]
# 额外增加一个"无方向"状态,用于起点
const DIR_NONE = -1

func _ready():
    var id = 0
    # 第一步:为每个可通行网格点的所有方向状态创建节点
    for c in get_used_cells():
        var cell_pos = Vector3(c.x, c.y, c.z)
        # 判断当前网格是否可通行
        var is_passable = not _get_cover(c.x, c.y, c.z)
        if not is_passable:
            continue
        
        # 为"无方向"和两个移动方向分别创建节点
        for dir_state in [DIR_NONE, 0, 1]:
            _astar.add_point(id, cell_pos, 1.0)
            id += 1
    
    # 第二步:连接节点,处理转向惩罚
    for c in get_used_cells():
        var cell_pos = Vector3(c.x, c.y, c.z)
        var is_passable = not _get_cover(c.x, c.y, c.z)
        if not is_passable:
            continue
        
        # 连接到右侧(X+方向)的网格
        var right_pos = cell_pos + Vector3.RIGHT
        if _is_cell_passable(right_pos):
            # 遍历当前节点的所有方向状态,连接到右侧节点的X方向状态
            for from_dir in [DIR_NONE, 0, 1]:
                var from_id = _get_node_id(cell_pos, from_dir)
                var to_id = _get_node_id(right_pos, 0) # 0对应X方向
                # 计算权重:转向时加惩罚
                var weight = 1.0
                if from_dir != DIR_NONE and from_dir != 0:
                    weight += 0.1
                _astar.connect_points(from_id, to_id, true, weight)
        
        # 连接到上方(Z+方向)的网格
        var above_pos = cell_pos + Vector3.FORWARD
        if _is_cell_passable(above_pos):
            for from_dir in [DIR_NONE, 0, 1]:
                var from_id = _get_node_id(cell_pos, from_dir)
                var to_id = _get_node_id(above_pos, 1) # 1对应Z方向
                var weight = 1.0
                if from_dir != DIR_NONE and from_dir != 1:
                    weight += 0.1
                _astar.connect_points(from_id, to_id, true, weight)

# 辅助函数:根据网格坐标和方向状态获取节点ID
func _get_node_id(cell_pos: Vector3, dir_state: int) -> int:
    # 先获取网格点在used_cells中的索引,再结合方向状态计算唯一ID
    var cell_idx = get_used_cells().find(Vector3i(cell_pos.x, cell_pos.y, cell_pos.z))
    # 每个网格点对应3个节点(无方向+两个移动方向)
    return cell_idx * 3 + (dir_state + 1) # 把DIR_NONE转成0索引

# 辅助函数:判断网格点是否可通行
func _is_cell_passable(pos: Vector3) -> bool:
    var cell = Vector3i(pos.x, pos.y, pos.z)
    if not is_cell_used(cell):
        return false
    return not _get_cover(cell.x, cell.y, cell.z)

# 自定义寻路函数,返回去重后的路径坐标
func find_optimized_path(start_pos: Vector3, end_pos: Vector3) -> Array[Vector3]:
    if not _is_cell_passable(start_pos) or not _is_cell_passable(end_pos):
        return []
    
    # 起点用"无方向"状态的节点
    var start_id = _get_node_id(start_pos, DIR_NONE)
    # 遍历终点的所有方向状态,找到成本最低的路径
    var min_cost = INF
    var best_end_id = -1
    for dir_state in [DIR_NONE, 0, 1]:
        var end_id = _get_node_id(end_pos, dir_state)
        if _astar.has_point(end_id):
            var cost = _astar.get_point_cost(start_id, end_id)
            if cost < min_cost:
                min_cost = cost
                best_end_id = end_id
    
    if best_end_id == -1:
        return []
    
    # 提取路径并去重(连续节点可能是同一坐标不同方向)
    var path_ids = _astar.get_id_path(start_id, best_end_id)
    var final_path = []
    for id in path_ids:
        var pos = _astar.get_point_position(id)
        if final_path.empty() or pos != final_path[-1]:
            final_path.append(pos)
    return final_path

方案说明

  1. 节点扩展:每个可通行网格点对应3个节点,分别代表「无方向(起点)」「从X方向进入」「从Z方向进入」,这样转向操作会对应不同的节点跳转。
  2. 转向惩罚:当从一个方向转向另一个方向时,给这条连接边加0.1的权重,让转向多的路径总权重更高,AStar会优先选择转向少的路径。
  3. 路径去重:因为路径中会出现同一坐标的不同方向节点,所以最后需要去重得到干净的坐标路径。

如果你使用的是Godot 4.x,也可以尝试用AStar3D的set_heuristic_function自定义启发式,但这种方法需要跟踪路径方向,实现起来更复杂,上面的节点扩展方案兼容性和可靠性更好。

内容的提问来源于stack exchange,提问作者rcorre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:23:31