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

问题描述

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

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

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

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

但我无法找出一个嵌套循环来查找要连接的邻居点

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

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

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

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


onready var rc = $RayCast
onready var aS = AStar.new()
var floor_size = Vector3(12,12)
var origin = Vector3(-5.5,0.5,-2.5)
var FRONT = Vector3(1,0)
var RIGHT = Vector3(0,1)
var BACK = Vector3(-1,0)
var LEFT = Vector3(-1,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,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,origin.z + value2) ) # Current
            rc.translation = Vector3(origin.x + value -1,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.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.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,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")

enter image description here

解决方法

在坐标和点 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)

说明

让我们从一维(一维)开始。我们将只有一个坐标。所以我们有一个假设的 Vector1,它有一个 x 属性。我们只是把事情排成一行。

然后映射是微不足道的:为了从坐标转换为 id,我们取 id = int(vector.x),如果我们想要坐标,我们只需执行 vector = Vector1(id)


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

因此,我们将制作一个具有一定宽度和高度的 2D 网格。 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 看起来非常类似于点积。

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

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)

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


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

让我们回顾一下 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.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,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,z)

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

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

func dimension_size(size:Vector3) -> Vector3:
    return Vector3(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),Vector3(0,1),Vector3(-1,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,vector)
    for offset in offsets:
        var adjacent = vector + offset

让我们也将它们添加到 AStar

for id in total_size(floor_size):
    var vector = id_to_vector(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 返回 true,则不要添加。
  • 如果相邻向量的 id 较小,则不对其进行处理。
  • 修改 offsets 以便您只检查尚未添加的相邻位置(从而防止出现前两种情况)。

让我们检查连接:

for id in total_size(floor_size):
    var vector = id_to_vector(id,adjacent)
        if check_connected(vector,adjacent):
            pass

并告诉AStar有关连接的信息:

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