如何实例化场景并使其仅面向 x 或 z 方向?

How to instance a scene and have it face only an x or z direction?

我正在尝试创建一个“块”输入,在相机正在注视的位置召唤一堵墙,并让它面向相机的方向。它似乎正在使用对我来说没有意义的全局坐标,因为我使用相同的代码来毫无问题地生成子弹。这是我的代码:

    if Input.is_action_just_pressed("light_attack"):
        var b = bullet.instance()
        muzzle.add_child(b)
        b.look_at(aimcast.get_collision_point(), Vector3.UP)
        b.shoot = true
        print(aimcast.get_collision_point())
    if Input.is_action_just_pressed("block"):
        var w = wall.instance()
        w.look_at(aimcast.get_collision_point(),Vector3.UP)
        muzzle.add_child(w)
        w.summon = true

光攻击输入是用来召唤和定位子弹的代码。 muzzle 是生成位置(只是枪末端的一个空间节点),aimcast 是从相机中心发出的光线投射。所有这些都是 运行 在 get_input() 函数中。墙生成得很好,我就是无法定位它。我还需要防止在 y 轴上发生任何旋转。这个问题有点难问,所以我不能google。如果您需要任何说明,请告诉我。

新答案

提问的评论让我意识到有一个更简单的方法。在旧的答案中,我定义了一个 xz_aim_transform,可以这样做:

func xz_aim_transform(pos:Vector3, target:Vector3) -> Transform:
    var alt_target := Vector3(target.x, pos.y, target.z)
    return Transform.IDENTITY.translated(pos).looking_at(alt_target, Vector3.UP)

即:在相同的y值制作一个假目标,使旋转始终在同一平面上。

它完成与旧答案中的方法相同的事情。但是,它更短且更容易掌握。无论如何,我概括了旧答案中的方法,解释仍然有价值,所以我保留它。


旧答案

如果我没理解错的话,你想要类似 look_at 的东西,只是它只适用于 xz 平面。

在我们这样做之前,让我们确定 look_at 等同于此:

func my_look_at(target:Vector3, up:Vector3):
    global_transform = global_transform.looking_at(target, up)

要点在于它设置了 global_transform。我们不需要深入研究 look_at 的工作原理。相反,让我们开发新版本。

我们知道我们想要 xz 平面。坚持下去会让事情变得更简单。这也意味着我们 need/it 保留 up 向量没有意义。所以,让我们摆脱它。

func my_look_at(target:Vector3):
    # global_transform = ?
    pass

计划是创建一个新的全局变换,只是它围绕 y 轴旋转了正确的角度。我们稍后会弄清楚旋转。现在,让我们专注于角度。

在 2D 中计算角度很容易。让我们构建一些 Vector2:

func my_look_at(target:Vector3):
    var position := global_transform.origin
    var position_2D := Vector2(position.x, position.z)
    var target_2D := Vector2(target.x, target.z)
    var angle:float # = ?
    # global_transform = ?

如果使用任意 up 向量,那部分就不会那么容易了。

请注意,我们使用 2D y 作为 3D z 值。

现在,我们计算角度:

func my_look_at(target:Vector3):
    var position := global_transform.origin
    var position_2D := Vector2(position.x, position.z)
    var target_2D := Vector2(target.x, target.z)
    var angle := (target_2D - position_2D).angle_to(Vector2(0.0, -1.0))
    # global_transform = ?

由于我们将 2D y 用于 3D z 值,因此 Vector2(0.0, -1.0)(顺便说一句,与 Vector2.UP 相同)表示 Vector3(0.0, 0.0, -1.0)(即 Vector3.FORWARD)。因此,我们正在计算 xz 平面上 3D 前向矢量的角度。

现在,要创建新的全局变换,我们将首先从该旋转创建一个新的基础,并使用它来创建变换:

func my_look_at(target:Vector3):
    var position := global_transform.origin
    var position_2D := Vector2(position.x, position.z)
    var target_2D := Vector2(target.x, target.z)
    var angle := (target_2D - position_2D).angle_to(Vector2.UP)
    var basis := Basis(Vector3.UP, angle)
    global_transform = Transform(basis, position)

你可能想知道为什么我们不使用global_transform.rotated,原因是多次使用会累积旋转。如果每个对象只调用一次它可能没问题,但我宁愿这样做。

上述方法有一个注意事项。我们正在失去任何缩放比例。我们就是这样解决这个问题的:

func my_look_at(target:Vector3):
    var position := global_transform.origin
    var position_2D := Vector2(position.x, position.z)
    var target_2D := Vector2(target.x, target.z)
    var angle := (target_2D - position_2D).angle_to(Vector2.UP)
    var basis := Basis(Vector3.UP, angle).scaled(global_transform.basis.get_scale())
    global_transform = Transform(basis, position)

好了。这是在 xz 平面上工作的自定义“查看”功能。


哦,是的,如您所见,您的代码使用全局坐标。实际上,get_collision_point在全局坐标中。

因此,我建议不要将您的射弹添加为子级。 请记住,当父项移动时,子项也会随之移动,因为它们是相对于它放置的。

而是给它们相同的 global_transform,然后将它们添加到场景树中。 如果在给它们定位之前将它们添加到场景中,它们可能会触发碰撞。

例如,您可以将它们作为子节点直接添加到根(或者有一个节点专门用于存放射弹,另一个常见的选择是将它们添加到 owner)。

这样你做的一切都在全局坐标上,应该没有问题。

好吧,既然你要设置 global_transform,那么这个怎么样:

func xz_aim_transform(position:Vector3, target:Vector3) -> Transform:
    var position_2D := Vector2(position.x, position.z)
    var target_2D := Vector2(target.x, target.z)
    var angle := (target_2D - position_2D).angle_to(Vector2.UP)
    var basis := Basis(Vector3.UP, angle)
    return Transform(basis, position)

那么你可以这样做:

var x = whatever.instance()
var position := muzzle.global_transform.origin
var target := aimcast.get_collision_point()
x.global_transform = xz_aim_transform(position, target)
get_tree().get_root().add_child(x)
x.something = true
print(target)

顺便说一下,这将是 xz_aim_transform 不受 xz 平面约束的对应物:

func aim_transform(position:Vector3, target:Vector3, up:Vector3) -> Transform:
    return Transform.IDENTITY.translated(position).looking_at(target, up)

这花了我一些聪明才智,但这里是限制在任意平面的版本(有点,正如你所看到的,它不能处理所有情况):

func plane_aim_transform(position:Vector3, target:Vector3, normal:Vector3) -> Transform:
    normal = normal.normalized()
    var forward_on_plane := Vector3.FORWARD - Vector3.FORWARD.project(normal)
    if forward_on_plane.length() == 0:
        return Transform.IDENTITY

    var position_on_plane := position - position.project(normal)
    var target_on_plane := target - target.project(normal)
    var v := forward_on_plane.normalized()
    var u := v.rotated(normal, TAU/4.0)
    var forward_2D := Vector2(0.0, forward_on_plane.length())
    var position_2D := Vector2(position_on_plane.project(u).dot(u), position_on_plane.project(v).dot(v))
    var target_2D := Vector2(target_on_plane.project(u).dot(u), target_on_plane.project(v).dot(v))
    var angle := (target_2D - position_2D).angle_to(forward_2D)
    var basis := Basis(normal, angle)
    return Transform(basis, position)

这里w - w.project(normal)给你一个垂直于法线的向量。 w.project(u).dot(u) 给你多少次 u 适合 w,已签名。所以我们用它来构建我们的 2D 向量。