如何实例化场景并使其仅面向 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 向量。
我正在尝试创建一个“块”输入,在相机正在注视的位置召唤一堵墙,并让它面向相机的方向。它似乎正在使用对我来说没有意义的全局坐标,因为我使用相同的代码来毫无问题地生成子弹。这是我的代码:
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 向量。