概述
今天(2024年4月13日)在群里收到群友的提问,它想实现子节点的环形排布,当然按他的说法,它实际想要实现的是一个自动环形排列子控件的自定义容器。
好在我之前有自定义容器的经验,也马上想到可以用向量旋转获取圆上的定位。
实现2D节点环形阵列
在实现环形阵列容器之前,我首先测试了2D节点的子节点环形阵列。
创建如下场景:
- 以一个
Node2D
为根节点,添加6个Sprite2D
,并赋予不同的图片,这里我以以前编写骰子组件使用的骰子6面图片+问号图片举例 - 将根节点尽量移动到视口矩形的中心,对所有
Sprite2D
进行了适当的等比缩放
根节点添加代码如下:
extends Node2D
var r:= 100.0 # 圆半径
func _ready() -> void:
var step = get_child_count() # 阵列的元素数目
var t = Transform2D().translated(Vector2.RIGHT * r) # 初始向量
var ang = TAU/step # 间隔角度
for i in range(step):
t = t.rotated(ang) # 旋转向量
get_child(i).transform = t * get_child(i).transform
上面代码中:
- 用根节点的一级子节点数目作为阵列的步骤数
step
,并计算出每次要旋转的角度ang
- 构造了一个变换
t
,它实际上是将某个点的位置向右移动到Vector2.RIGHT * r
距离。 - 然后
for
循环遍历step
次,每次对变换t
施加旋转ang
的操作 - 然后将当前变换实施到对应顺序的子节点上。
完成后的效果:
环形阵列容器
如果要应用于Control
节点,则用上述方法则可能无法实现想要的结果。而且Control节点一般不需要旋转,只需要将矩形中心位移到相应的圆上就行。
所以这里我们自定义一个环形阵列容器CircleArrayContainer
,用来自动化的环形排列其一级子控件。
完整代码如下:
@tool
extends Container
class_name CircleArrayContainer # 环形阵列容器
var r:= 100.0: # 圆半径
set(val):
r = val
queue_sort()
func _init() -> void:
# 容器尺寸发生变化时
connect("resized",func():
set("r",get_rect().size.y/2.0) # 修改半径为1/2的高
)
func _notification(what):
var center = get_rect().get_center() # 容器中心点
var a = Vector2.RIGHT * r
match what:
NOTIFICATION_SORT_CHILDREN:
var step = get_child_count() # 阵列的元素数目
var ang = TAU/step # 间隔角度
for i in range(step):
var b = a.rotated(ang * i) # 旋转向量
get_child(i).position = center + b - get_child(i).size/2.0
效果:
- 你可以通过“添加节点”对话框,实例化
CircleArrayContainer
容器,并直接在其下添加子控件。 - 容器会自动根据子控件的数目,环形排列它们,圆的半径等于容器矩形尺寸的一半。
- 修改容器尺寸时会自动更新圆的半径
总结
- 这是一次很好的自定义容器尝试,证明了Godot可以实现特殊的布局容器
- 当然这种做法可能不算符合Godot容器的定义规范,对于子控件将无法再使用
size_flags
来实现动态大小,控件将自动被压缩至最小尺寸,除非定义了custom_minimum_size