好的,我在主场景中有一个这样的节点树(简化)
root
|- LevelHolder
|- Player
|- Camera2D
|- CanvasLayer
|- SomeUI
|- ...
|- Extents
|- TopLeft
|- BottomRight
我的情况是,我的画布层有一个完整的矩形布局在屏幕上伸展,然后我的SomeUI元素包含了一堆UI元素,组成了一个HUD和一个帧。我有一个Camera2D,允许我放大和缩小我的球员和关卡,所以编辑器的Node2D元素的位置不匹配游戏内视图,因为相机移动和缩放。
我不希望我的播放器能够在我的HUD元素下面移动,所以我在CanvasLayer中有这些范围对象,我可以将它们锚定在我的HUD的左上角和右下角,我实际上希望玩家能够在其中走动。
所以我认为我需要做的是把Canvas的位置变成我可以为我的播放器设置的位置。
经过大量的尝试和错误,这是我的工作:
var tl = $CanvasLayer/Extents/TopLeft
var tlgp = tl.get_viewport_transform() * tl.get_global_transform()
$Player.topleft = self.get_viewport_transform().affine_inverse().xform(tlgp.origin)
var br = $CanvasLayer/Extents/BottomRight
var brgp = br.get_viewport_transform() * br.get_global_transform()
$Player.bottomright = self.get_viewport_transform().affine_inverse().xform(brgp.origin)
这是有效的,但似乎需要很多工作,我花了很多时间才找到这一切。在这种情况下我可以省略self.get_global_transform()
因为我知道root
的全局变换是恒等函数,但通常情况下会更复杂。
你所做的是正确的。
是过度杀伤吗?也许。假设UI没有以扩展需要移动的方式改变,您可以事先计算屏幕坐标……或者仅在必要时计算它们。
另一件要考虑的事情是如何移动Camera2D
。如果摄像机跟随玩家,你可以使用"拖拽边缘"。你用它来指定在视图开始移动之前,它与屏幕边界的距离有多近。尽管这可能不可行,取决于游戏。
对于另一种选择,你可以把游戏世界放在Viewport
,也许使用ViewportContainer
,这将是UI的一部分。尽管Viewport
是性能方面的考虑因素——尤其是在移动设备上——但您很有可能负担得起这个解决方案。这种方法还允许为游戏世界和UI设置不同的分辨率。参见如何在Godot
中为pixelart游戏制作光滑的相机这个答案的其余部分是对你正在做的事情的确认,并希望使它更容易理解。
官方文档有一个屏幕位置的公式:
var screen_coord = get_viewport_transform() * (get_global_transform() * local_pos)
参见Viewport和canvas转换。
首先,我们不需要使用get_global_transform()
,因为我们有它的属性:
var screen_coord = get_viewport_transform() * (global_transform * local_pos)
如果我们知道我们正在处理Control
并且我们想要它的位置(即Vector2.ZERO
在本地空间中的位置),我们可以写一个更短的版本:
var screen_coord = get_viewport_transform() * rect_global_position
另一方面,对于Node2D
(同样需要它的位置),我们可以这样做:
var screen_coord = get_viewport_transform() * global_position
等于:
var screen_coord = get_viewport_transform() * global_transform.origin
顺便说一下,你得到了同样的结果,就像这样(这就是你正在做的):
var screen_coord = (get_viewport_transform() * global_transform).origin
不同之处在于这里我们组合了转换,这是额外的工作。
然而,由于我们希望将Node2D
放置在其他内容的屏幕坐标中,我们需要反向过程。
给定这个方程:
screen_coord = get_viewport_transform() * global_position
我们可以在两边的开始处组合其中一个变换的逆,像这样:
get_viewport_transform().affine_inverse() * screen_coord
=
get_viewport_transform().affine_inverse() * get_viewport_transform() * global_position
和-当然-get_viewport_transform().affine_inverse() * get_viewport_transform()
抵消(到单位变换,我们可以省略),所以我们有:
get_viewport_transform().affine_inverse() * screen_coord = global_position
翻转它,你就可以将Node2D
放置在给定的屏幕位置:
global_position = get_viewport_transform().affine_inverse() * screen_coord
现在我们可以结合屏幕位置的计算和我们如何在给定的屏幕位置定位Node2D
。例如:
var screen_coords := control.get_viewport_transform() * control.rect_global_position
node2D.global_position = node2D.get_viewport_transform().affine_inverse() * screen_coords
如果您愿意,可以使用xform
代替*
。但是,要注意xform
返回Variant,因此您会丢失类型信息。