我如何使用 A* (Astart) 寻路系统连接点?在戈多

How I connect dots using A* (Astart) pathfinding system? in GODOT

我正在尝试做一些与往常不同的事情。

我有一个 3D 网格图节点设置,我正在尝试使用 A* 自动生成点和连接 我没有创建障碍物瓦片,而是在瓦片之间创建墙,所以瓦片仍然可以行走,你只是不能穿过墙。我已经想通了

但我不知道如何编码如何以简单的方式连接点,而不是连接中间有墙的点...

我正在使用 RaycastCast 节点检测墙,以及它穿过每个网格时的位置

但我想不出一个嵌套循环来找到要连接的邻居点

这就是我尝试做的(显然 get_closest_point() 没有按照我想要的方式工作)。 如果我可以只使用 Vector3 坐标得到一个点,我想我可以让它工作。

额外:如果你们能告诉我一种清理代码的方法,尤其是在“FORs”语法上,因为我有点不知道自己在做什么

任何其他干净的代码建议都会很棒并且非常受欢迎

最后有一个想法逻辑的视觉图(图像)。


onready var rc = $RayCast
onready var aS = AStar.new()
var floor_size = Vector3(12,0,12)
var origin = Vector3(-5.5, 0.5, -2.5)
var FRONT = Vector3(1,0,0)
var RIGHT = Vector3(0,0,1)
var BACK = Vector3(-1,0,0)
var LEFT = Vector3(-1,0,0)


func set_walkable():
    var value = 0
    var value2 = 0
    var i = 0
    for _length in range (origin.x, origin.x + floor_size.x + 1):
        value += 1 
        value2 = 0
        for _width in range(origin.z, origin.z + floor_size.z):
            i += 1
            value2 += 1
            aS.add_point(i,  Vector3(origin.x + value -1 , 0.5, origin.z + value2 -1) , 1.0)
    value = 0       
    for _u in range(origin.x, origin.x + floor_size.x + 1):
        value += 1 
        value2 = 0
        for _v in range(origin.z, origin.z + floor_size.z):
            value2 += 1
            var from = aS.get_closest_point(Vector3(origin.x + value ,0.5,  origin.z + value2) ) # Current
            rc.translation = Vector3(origin.x + value -1 ,0.5,  origin.z + value2 -1)
            draw_points()
            print(rc.translation)
            rc.cast_to = FRONT
            var to = aS.get_closest_point(rc.translation) # Front
            if from != -1 and !rc.is_colliding():
                aS.connect_points(from, to)
                draw_connections(Vector3(rc.translation.x + 0.5,rc.translation.y,rc.translation.z))
            rc.cast_to = BACK
            to = aS.get_closest_point(rc.translation) # back
            if from != -1 and !rc.is_colliding():
                aS.connect_points(from, to)
                draw_connections(Vector3(rc.translation.x + -0.5,rc.translation.y,rc.translation.z))
            rc.cast_to = RIGHT
            to = aS.get_closest_point(rc.translation) # right
            if from != -1 and !rc.is_colliding():
                aS.connect_points(from, to)
                draw_connections(Vector3(rc.translation.x,rc.translation.y,rc.translation.z + 0.5))
            rc.cast_to = LEFT
            to = aS.get_closest_point(rc.translation) # left
            if from != -1 and !rc.is_colliding():
                aS.connect_points(from, to)
                draw_connections(Vector3(rc.translation.x + 0.5,rc.translation.y,rc.translation.z + -0.5))


func draw_points(): # Make points visible
    var cube = MeshInstance.new() 
    cube.mesh = CubeMesh.new()
    cube.translation = rc.translation
    cube.scale = Vector3(0.25,0.25,0.25)
    add_child(cube)
    print("Cubo adicionado")

func draw_connections(position): # Make connections visible
    var line = MeshInstance.new()
    line.mesh = PlaneMesh.new()
    line.scale = Vector3(0.03,0.03,0.03)
    line.translation = position
    add_child(line)
    print("Cubo adicionado")

坐标和点 id 之间的转换

让我们在坐标和点id之间建立一个映射。鉴于我们有 floor_size,这很容易:

func vector_to_id(vector:Vector3, size:Vector3) -> int:
    return int(int3(vector).dot(dimension_size(size)))

func id_to_vector(id:int, size:Vector3) -> Vector3:
    var s:Vector3 = dimension_size(size)
    var z:int = int(id / s.z)
    var y:int = int((id % int(s.z)) / s.y)
    var x:int = id % int(s.y)
    return Vector3(x, y, z)

func int3(vector:Vector3) -> Vector3:
    return Vector3(int(vector.x), int(vector.y), int(vector.z))

func dimension_size(size:Vector3) -> Vector3:
    return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))

可能的优化:

  • 存储 dimension_size(floor_size) 并直接使用。
  • 跳过调用 int3,前提是您传递给 vector_to_id 的值保证为整数。

我们需要一个函数来获取总点数:

func total_size(size:Vector3) -> int:
    return int(size.x + 1) * int(size.y + 1) * int(size.z + 1)

说明

让我们从 1D(一维)开始。我们将只有一个坐标。所以我们有一个假设的 Vector1 有一个 x 属性。我们只是把东西排成一行。

那么映射就很简单了:要从坐标转换为 id,我们采用 id = int(vector.x),如果我们想要坐标,我们只需 vector = Vector1(id).


现在,让我们转到二维。我们有 Vector2xy。值得庆幸的是我们有一个大小(当不知道大小时有一些方法可以进行映射,但是有一个大小很方便)。

因此,我们将制作一个具有一定宽度和高度的二维网格。 y 坐标告诉我们所在的行,x 告诉我们在行中的位置。

然后如果我们有一些id,我们需要弄清楚我们需要多少行才能到达那里,然后我们在该行中的什么位置。找出行很容易,我们除以网格的宽度。而在行中的位置就是提醒。一个警告:我们从 0 开始测量(因此宽度 0 实际上意味着每行 1 个元素)。

我们有:

func id_to_vector(id:int, size:Vector2) -> Vector2:
    var y:int = int(id / (size.x + 1))
    var x:int = id % int(size.x + 1)
    return Vector2(x, y)

反过来怎么样?好吧,我们用 y 乘以一行的长度(宽度),然后加上 x:

func vector_to_id(vector:Vector2, size:Vector2) -> int:
    return int(vector.x) + int(vector.y) * int(size.x + 1)

通知:

  • 我们不需要 size.y
  • 两个函数都需要size.x + 1
  • vector_to_id 看起来非常类似于点积。

因此,让我们创建一个新函数,returns 我们将用来制作点积的向量:

func dimension_size(size:Vector2) -> Vector2:
    return Vector2(1, int(size.x + 1))

并使用它:

func vector_to_id(vector:Vector2, size:Vector2) -> int:
    return int(vector.dot(dimensional_size(size)))

func id_to_vector(id:int, size:Vector2) -> Vector2:
    var s = dimensional_size(size)
    var y:int = int(id / int(s.y))
    var x:int = id % int(s.y)
    return Vector2(x, y)

注意如果不能保证vectorvector_to_id中只有整数,点积make中的小数部分会导致错误的结果.这就是为什么我有一个函数让它只有整数。


3D 时间到了。我们有 Vector3xyz。我们正在制作一个 3D 网格。现在 z 会告诉我们图层,每个图层都是一个二维网格。

让我们回顾一下 dimensional_size,我们有:

func dimension_size(size:Vector2) -> Vector2:
    return Vector2(1, int(size.x + 1))

也就是一个元素的大小(1),一行的大小(size.x + 1),我们需要加上一层的大小。即二维网格的大小,就是宽度乘以高度。

func dimension_size(size:Vector3) -> Vector3:
    return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))

我们如何从 id 中获取 z?我们除以网格的大小(所以我们知道我们在什么网格上)。然后从那个分区的提示中我们可以找到y:

func vector_to_id(vector:Vector3, size:Vector3) -> int:
    return int(vector.dot(dimensional_size(size)))

func id_to_vector(id:int, size:Vector3) -> Vector3:
    var s = dimensional_size(size)
    var z:int = int(id / int(s.z))
    var y:int = int(int(id % int(s.z)) / int(s.y))
    var x:int = id % int(s.y)
    return Vector2(x, y, z)

事实上,从技术上讲,所有这些坐标都是以相同的形式计算的:

func id_to_vector(id:int, size:Vector3) -> Vector3:
    var s = dimensional_size(size)
    var tot = total_size(size)
    var z:int = int(int(id % int(tot)) / int(s.z))
    var y:int = int(int(id % int(s.z)) / int(s.y))
    var x:int = int(int(id % int(s.y)) / int(s.x))
    return Vector2(x, y, z)

除此之外,不需要带总大小的提醒,因为 id 应该总是小于那个。而且不需要除以s.x,因为单个元素的大小总是1而且我还删除了一些多余的 int 转换。

什么是total_sizedimensional_size的下一个元素,当然是:

func dimension_size(size:Vector3) -> Vector3:
    return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))

func total_size(size:Vector3) -> int:
    return int(size.x + 1) * int(size.y + 1) * int(size.z + 1)

正在检查连接

以及检查连通性的方法:

func check_connected(start:Vector3, end:Vector3) -> bool:
    rc.transform.origin = start
    rc.cast_to = end
    rc.force_update_transform()
    rc.force_raycast_update()
    return !raycast.is_colliding()

你对 FRONTRIGHTBACKLEFT 的想法是正确的,但将它们放在一个数组中:

var offsets = [Vector3(1,0,0), Vector3(0,0,1), Vector3(-1,0,0), Vector3(-1,0,0)]

注意 我正在调用 force_update_transformforce_raycast_update 因为在同一帧上进行多次光线投射检查。


填充AStar

好了,足够的设置,我们现在可以迭代:

for id in total_size(floor_size):
    pass

在每次迭代中,我们需要得到向量:

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)

可能的优化:直接迭代向量坐标以避免调用id_to_vector.

我们可以将向量添加到AStar:

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)
    aS.add_point(id, vector)

接下来我们需要相邻向量:

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)
    aS.add_point(id, vector)
    for offset in offsets:
        var adjacent = vector + offset

让我们也将它们添加到 AStar

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)
    aS.add_point(id, vector)
    for offset in offsets:
        var adjacent = vector + offset
        var adjacent_id = vector_to_id(adjacent, floor_size)
        aS.add_point(adjacent_id, adjacent)

可能的优化:

  • 不添加如果has_point returns true.
  • 如果相邻向量的id较低,则不处理。
  • 修改 offsets 以便您只检查尚未添加的相邻位置(从而防止前两种情况)。

让我们检查连接:

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)
    aS.add_point(id, vector)
    for offset in offsets:
        var adjacent = vector + offset
        var adjacent_id = vector_to_id(adjacent, floor_size)
        aS.add_point(adjacent_id, adjacent)
        if check_connected(vector, adjacent):
            pass

并告诉 AStar 连接情况:

for id in total_size(floor_size):
    var vector = id_to_vector(id, floor_size)
    aS.add_point(id, vector)
    for offset in offsets:
        var adjacent = vector + offset
        var adjacent_id = vector_to_id(adjacent, floor_size)
        aS.add_point(adjacent_id, adjacent)
        if check_connected(vector, adjacent):
            connect_points(id, adjacent_id)