2D 中的自定义绘图
前言
东西 的节点, 而在恐惧, 焦虑和愤怒中哭泣之前… 有知道, 让任何2D节点(不管它是继承自 Control 还是 Node2D ) 绘制一个自定义命令是很轻松的. 而且这 真的非常 容易操作. 非常 有用。下面是一些用例:
- 绘制现有节点类型无法完成的形状或逻辑,例如带有轨迹或特殊动态多边形的图像。
- 与节点不太兼容的呈现,如俄罗斯方块棋盘。俄罗斯方块的例子,使用一个自定义绘制函数来绘制方块。
- 绘制大量简单的对象。自定义绘制避免了使用大量节点的开销,能降低内存占用,并提高性能。
- 制作自定义的UI控件,以满足很多可用的控件之外的特别需求。
绘制
CanvasItem 的派生节点, 如 Control 或 Node2D. 然后重载_draw()函数. GDScript C#extends Node2Dfunc _draw(): # Your draw commands here pass
CanvasItem 类型引文中有所描述, 有不少绘制命令.public override void _Draw(){ // Your draw commands here}
更新
_draw()函数只调用一次, 然后绘制命令被缓存并记住, 因此不需要进一步调用. CanvasItem.update() ,触发新的_draw()调用。 这是一个更复杂的示例. 这是一个被修改就会重新绘制的纹理变量: GDScript C#extends Node2Dexport (Texture) var texture setget _set_texturefunc _set_texture(value): # If the texture variable is modified externally, # this callback is called. texture = value # Texture was changed. update() # Update the node's visual representation.func _draw(): draw_texture(texture, Vector2())
public class CustomNode2D : Node2D{ private Texture _texture; public Texture Texture { get { return _texture; } set { _texture = value; Update(); } } public override void _Draw() { DrawTexture(_texture, new Vector2()); }}
_process()回调调用update(), 如下所示: GDScript C#extends Node2Dfunc _draw(): # Your draw commands here passfunc _process(delta): update()
public class CustomNode2D : Node2D{ public override void _Draw() { // Your draw commands here } public override void _Process(float delta) { Update(); }}
一个示例: 绘制圆弧
draw_circle()函数, 它会绘制一个完整的圆. 但是, 画一个圆的一部分怎么说?你必须编写一个函数来执行此操作, 自己绘制它.弧函数
弧由其所在的圆的参数定义. 即: 中心位置和半径. 弧本身由开始的角度和停止的角度来定义. 这些是我们必须为绘图提供的4个参数. 我们还将提供颜色值, 因此我们可以根据需要绘制不同颜色的圆弧. 多层次细节(Level of Detail, LoD) . 在我们的示例中, 无论半径如何, 我们都只使用固定数量的点. GDScript C#func draw_circle_arc(center, radius, angle_from, angle_to, color): var nb_points = 32 var points_arc = PoolVector2Array() for i in range(nb_points + 1): var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90) points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) for index_point in range(nb_points): draw_line(points_arc[index_point], points_arc[index_point + 1], color)
public void DrawCircleArc(Vector2 center, float radius, float angleFrom, float angleTo, Color color){ int nbPoints = 32; var pointsArc = new Vector2[nbPoints]; for (int i = 0; i < nbPoints; ++i) { float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90f); pointsArc[i] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius; } for (int i = 0; i < nbPoints - 1; ++i) DrawLine(pointsArc[i], pointsArc[i + 1], color);}
nb_points变量中的这个数字修改为32. 然后, 我们初始化一个空的PoolVector2Array, 就是一个Vector2数组. 下一步包括计算构成弧的这32个点的实际位置. 这是在第一个for循环中完成的: 我们迭代我们想要计算位置的点的数量, 后面+1来包括最后一个点. 我们首先确定起点和终点之间每个点的角度.cos()和sin()使用弧度, 而不是度数作为参数. 虽然我们想在12点钟位置开始计数, 但0°(0弧度)的角度从3点钟位置开始. 因此我们将每个角度减小90°, 以便从12点位置开始计数.angle(单位是弧度)位于圆上的点的实际位置由Vector2(cos(angle), sin(angle))给出. 由于cos()和sin()返回介于-1和1之间的值, 因此位置位于半径为1的圆上. 要将此位置放在我们的半径为radius的辅助圆上, 我们只需要将那个位置乘以radius. 最后, 我们需要将我们的辅助圆定位在center位置, 这是通过将其与我们的Vector2相加来实现的. 最后, 我们在之前定义的PoolVector2Array中插入这个点. 现在, 我们需要实际绘制我们的点. 你可以想象, 我们不会简单地画出我们的32个点: 我们需要绘制每一点之间的所有内容. 我们可以使用前面的方法自己计算每个点, 然后逐个绘制. 但这太复杂和低效了(除非确实需要). 因此, 我们只需在每对点之间绘制线条. 除非我们的辅助圆的半径很大, 否则一对点之间每条线的长度永远不会长到足以看到它们. 如果发生这种情况, 我们只需要增加点的个数就可以了.在屏幕上绘制弧形
_draw()函数中调用它了: GDScript C#func _draw(): var center = Vector2(200, 200) var radius = 80 var angle_from = 75 var angle_to = 195 var color = Color(1.0, 0.0, 0.0) draw_circle_arc(center, radius, angle_from, angle_to, color)
结果:public override void _Draw(){ var center = new Vector2(200, 200); float radius = 80; float angleFrom = 75; float angleTo = 195; var color = new Color(1, 0, 0); DrawCircleArc(center, radius, angleFrom, angleTo, color);}
弧多边形函数
我们可以更进一步, 不仅仅绘制一个由弧定义的扇形的边缘, 还可以绘制其形体. 该方法与以前完全相同, 只是我们绘制的是多边形而不是线条: GDScript C#func draw_circle_arc_poly(center, radius, angle_from, angle_to, color): var nb_points = 32 var points_arc = PoolVector2Array() points_arc.push_back(center) var colors = PoolColorArray([color]) for i in range(nb_points + 1): var angle_point = deg2rad(angle_from + i * (angle_to - angle_from) / nb_points - 90) points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) draw_polygon(points_arc, colors)
public void DrawCircleArcPoly(Vector2 center, float radius, float angleFrom, float angleTo, Color color){ int nbPoints = 32; var pointsArc = new Vector2[nbPoints + 1]; pointsArc[0] = center; var colors = new Color[] { color }; for (int i = 0; i < nbPoints; ++i) { float anglePoint = Mathf.Deg2Rad(angleFrom + i * (angleTo - angleFrom) / nbPoints - 90); pointsArc[i + 1] = center + new Vector2(Mathf.Cos(anglePoint), Mathf.Sin(anglePoint)) * radius; } DrawPolygon(pointsArc, colors);}
动态自定义绘图
好吧, 我们现在能够在屏幕上绘制自定义内容. 然而, 它是静态的; 我们让这个形状围绕中心转动吧. 这样做的方法就是随着时间的推移改变angle_from和angle_to值. 对于我们的示例, 我们将简单地将它们递增50. 此增量值必须保持不变, 否则旋转速度将相应地改变.get_node()访问它们. GDScript C#extends Node2Dvar rotation_angle = 50var angle_from = 75var angle_to = 195
我们在_process(delta)函数中更改这些值.public class CustomNode2D : Node2D{ private float _rotationAngle = 50; private float _angleFrom = 75; private float _angleTo = 195;}
wrap()在0到360°之间! 也就是说, 如果角度是361°, 那么它实际上是1°. 如果您不包装这些值, 脚本将正常工作, 但角度值将随着时间的推移变得越来越大, 直到它们达到Godot可以管理的最大整数值(2^31 - 1). 当发生这种情况时,Godot可能会崩溃或产生意外行为.update()函数, 它自动调用_draw(). 这样, 您可以控制何时要刷新这一帧. GDScript C#func _process(delta): angle_from += rotation_angle angle_to += rotation_angle # We only wrap angles when both of them are bigger than 360. if angle_from > 360 and angle_to > 360: angle_from = wrapf(angle_from, 0, 360) angle_to = wrapf(angle_to, 0, 360) update()
public override void _Process(float delta){ _angleFrom += _rotationAngle; _angleTo += _rotationAngle; // We only wrap angles when both of them are bigger than 360. if (_angleFrom > 360 && _angleTo > 360) { _angleFrom = Mathf.Wrap(_angleFrom, 0, 360); _angleTo = Mathf.Wrap(_angleTo, 0, 360); } Update();}
_draw()函数来使用这些变量: GDScript C#func _draw(): var center = Vector2(200, 200) var radius = 80 var color = Color(1.0, 0.0, 0.0) draw_circle_arc( center, radius, angle_from, angle_to, color )
我们运行吧! 它工作正常, 但弧线旋转快得疯掉了! 怎么了?public override void _Draw(){ var center = new Vector2(200, 200); float radius = 80; var color = new Color(1, 0, 0); DrawCircleArc(center, radius, _angleFrom, _angleTo, color);}
_process()函数的delta参数.delta包含最后两个渲染帧之间经过的时间. 它通常很小(约0.0003秒, 但这取决于你的硬件). 因此, 使用delta来控制绘图可确保程序在每个人的硬件上以相同的速度运行._process()函数中将rotation_angle变量乘以delta. 这样, 我们的2个角度会以一个小得多的值递增, 值的大小直接取决于渲染速度. GDScript C#func _process(delta): angle_from += rotation_angle * delta angle_to += rotation_angle * delta # We only wrap angles when both of them are bigger than 360. if angle_from > 360 and angle_to > 360: angle_from = wrapf(angle_from, 0, 360) angle_to = wrapf(angle_to, 0, 360) update()
让我们再运行一次! 这次, 旋转显示正常!public override void _Process(float delta){ _angleFrom += _rotationAngle * delta; _angleTo += _rotationAngle * delta; // We only wrap angles when both of them are bigger than 360. if (_angleFrom > 360 && _angleTo > 360) { _angleFrom = Wrap(_angleFrom, 0, 360); _angleTo = Wrap(_angleTo, 0, 360); } Update();}
工具
绘制自己的节点的功能在编辑器中, 用来可视化/预览某种特性或行为的时候可能会有用. GDScript 基础 参考文档).
