概述
在Godot的CanvasItem
中没有办法直接绘制圆环,利用Geometry2D
进行两个圆的布尔运算也无法实现。所以只能另想办法。
这个问题其实想了有一段时间了,脑海中也早就有了解决方案,以及料想到可能存在的问题。昨天因为QQ群群友的提问,于是就直接试验了一下,结果就有了本文。
两种思路
对于在Godot中绘制圆环,我自己想到两种办法。办法1是将圆环理解为若干梯形围绕原点环形阵列:
- 这种思路的好处是,通过求一个小梯形,然后旋转变换,就可以绘制出圆环的形状。
- 而且这种方式可以很方便的用颜色插值做出渐变色环效果。
另一种思路是将整个圆环做成一个留有一条极小缝隙的多边形:
- 这种方法获得的圆环一体性强,可以直接作为单个多边形绘制,顶点数目也相较第一种方式少
- 但缺点是可能是做颜色渐变效果可能会有些不方便
两种方法各有优缺点吧。本文就介绍这两种思路下的圆环点求解和绘制思路。
整体圆环点求取函数
我首先实现了第2种方法的点求取函数,代码如下:
# 圆环点求取函数
func ring(r1:float,r2:float,edges:int = 3,start_angle:=0.0,end_engle:=360.0) -> PackedVector2Array:
var points:PackedVector2Array # 圆环的顶点
var v1 = (Vector2.RIGHT * r1).rotated(deg_to_rad(start_angle))
var v2 = (Vector2.RIGHT * r2).rotated(deg_to_rad(start_angle))
var ang = (end_engle - start_angle)/float(edges) # 单次旋转角度
var arc1:PackedVector2Array # 圆弧1
var arc2:PackedVector2Array # 圆弧2
# 通过向量旋转求不同半径的两条圆弧顶点
for i in range(edges+1):
arc1.append(v1.rotated(deg_to_rad( ang * i)))
arc2.append(v2.rotated(deg_to_rad( ang * i)))
var close_circle = fposmod((end_engle - start_angle),360.0) == 0 # 圆弧闭合形成圆
if close_circle:
arc1.set(arc1.size()-1,arc1[arc1.size()-1] + Vector2.UP * 0.01)
# 顺时针添加圆弧1的顶点
points.append_array(arc1)
arc2.reverse()
if close_circle:
arc2.set(arc2.size()-1,arc2[arc2.size()-1] - Vector2.UP * 0.01)
# 逆时针添加圆弧1的顶点
points.append_array(arc2)
return points
- 这就是利用简单的向量旋转在半径不同的两个外接圆上求多边形顶点的思路。
- 只不过将内部圆上求得的顶点顺序翻转,从而获得正确的多边形顶点顺序。
测试代码:
extends Node2D
func _draw() -> void:
draw_set_transform(Vector2(300,400)) # 设定绘图原点
# 求一个大圆半径100,小圆半径50,细分边数为100的圆环顶点
var ring = ring(100,50,100)
# 以绘制多边形形式绘制圆环
draw_colored_polygon(ring,Color.AQUAMARINE)
绘制效果:
其他边数下的效果:
设定起始和终止角度
弧线是圆的一部分,所以上面的函数不仅仅可以求圆环,也可以求圆环的一部分。
你只需要设定好圆弧的起始和终止角度即可。
extends Node2D
func _draw() -> void:
# 设定绘图原点
draw_set_transform(Vector2(300,400))
# 绘制圆环的一部分
var ring = ring(100,50,6,45,90)
# 以绘制多边形形式绘制圆环
draw_colored_polygon(ring,Color.AQUAMARINE)
- 45°到90°,6条边完成
绘制效果:
extends Node2D
func _draw() -> void:
# 设定绘图原点
draw_set_transform(Vector2(300,400))
# 绘制圆环的一部分
var ring = ring(100,50,6,45,180)
# 以绘制多边形形式绘制圆环
draw_colored_polygon(ring,Color.AQUAMARINE)
- 45°到180°,6条边完成
绘制效果:
extends Node2D
func _draw() -> void:
# 设定绘图原点
draw_set_transform(Vector2(300,400))
# 绘制圆环的一部分
var ring = ring(100,50,6,45,360)
# 以绘制多边形形式绘制圆环
draw_colored_polygon(ring,Color.AQUAMARINE)
- 45°到360°,6条边完成
绘制效果:
梯形旋转变换法
点求取函数如下:
# 圆环点求取函数
func ring2(r1:float,r2:float,edges:int = 3,start_angle:=0.0,end_engle:=360.0) -> Array[PackedVector2Array]:
var ts:Array[PackedVector2Array] # 所有梯形的集合
var v1 = (Vector2.RIGHT * r1).rotated(deg_to_rad(start_angle))
var v2 = (Vector2.RIGHT * r2).rotated(deg_to_rad(start_angle))
var ang = (end_engle - start_angle)/float(edges) # 单次旋转角度
print(ang)
# 第1个梯形
var t:PackedVector2Array
t.append(v1)
t.append(v1.rotated(deg_to_rad(ang)))
t.append(v2.rotated(deg_to_rad(ang)))
t.append(v2)
#t.append(v1)
var rot = Transform2D().rotated(deg_to_rad(ang)) # 旋转变换
# 通过旋转第一个梯形,获取其他的梯形
for i in range(edges):
ts.append(t)
t = rot * t
return ts
- 代码中求取了第一个梯形的所有顶点,然后构造了一个旋转变换,最后通过顺次应用到第一个梯形来获得其他位置的梯形。
- 函数直接返回包含所有梯形的数组。在绘制时,我们可以遍历取出每个单独梯形的顶点集合,来进行绘制。
测试代码:
extends Node2D
func _draw() -> void:
draw_set_transform(Vector2(300,400)) # 设定绘图原点
# 绘制圆环的一部分
var ring = ring2(100,50,100)
#print(ring)
# 以绘制梯形形式绘制圆环
for i in range(ring.size()):
draw_colored_polygon(ring[i],Color.AQUAMARINE.lerp(Color.BROWN,i/float(ring.size())))
各种参数下绘制的效果:
绘制同心环
通过插值一个长度范围,获得不同的半径,我们可以绘制多个同心环。
extends Node2D
var c1:= Color.RED
var c2:= Color.YELLOW_GREEN
var steps:= 30 # 步幅
var edges:= 20 # 边数
var max_r:= 100.0 # 最大半径
var min_r:= 0.0 # 最小半径
func _draw() -> void:
c1 = Color.AQUAMARINE
c2 = Color.BROWN
draw_set_transform(Vector2(300,400)) # 设定绘图原点
for w in range(0,steps+1):
c1 = c1.darkened(w/float(steps) * 0.1)
c2 = c2.darkened(w/float(steps) * 0.1)
var r2 = lerp(min_r,max_r,w/float(steps))
var r1 = lerp(min_r,max_r,(w+1)/float(steps))
# 绘制圆环的一部分
var ring = ring2(r1,r2,edges)
#print(ring)
# 以绘制梯形形式绘制圆环
for i in range(ring.size()):
draw_colored_polygon(ring[i],c1.lerp(c2,i/float(ring.size())))
各种参数下的效果:
extends Node2D
var c1:= Color.RED
var c2:= Color.YELLOW_GREEN
var steps:= 5 # 步幅
var edges:= 6 # 边数
var max_r:= 100.0 # 最大半径
var min_r:= 0.0 # 最小半径
extends Node2D
var c1:= Color.RED
var c2:= Color.YELLOW_GREEN
var steps:= 30 # 步幅
var edges:= 6 # 边数
var max_r:= 100.0 # 最大半径
var min_r:= 0.0 # 最小半径
extends Node2D
var c1:= Color.RED
var c2:= Color.YELLOW_GREEN
var steps:= 5 # 步幅
var edges:= 100 # 边数
var max_r:= 100.0 # 最大半径
var min_r:= 0.0 # 最小半径
extends Node2D
var c1:= Color.RED
var c2:= Color.YELLOW_GREEN
var steps:= 100 # 步幅
var edges:= 100 # 边数
var max_r:= 100.0 # 最大半径
var min_r:= 0.0 # 最小半径
同时减小c1、c2每次变暗的系数为0.01
总结
- 本文介绍和总结了在Godot求2D圆环图形顶点的两种思路和具体的实现办法,并测试了在不同参数设定下的效果。
- 可以看到,在求取一些特殊图形顶点时,我们必须要避开Godot的不足(比如无法绘制带孔洞的多边形)而另辟蹊径。
- 我们可以通过构造带有肉眼无法分辨的间隙的多边形,来绘制带有孔洞的图形,即可以骗过Godot正确实现三角化,也可以在视觉上骗过使用者和观看者。
- 通过将特殊图形分解为小的图形,再通过某些变换来拼凑,也是一种很好的思路。