I saw a WeChat video today, harmonious symbiosis, the approximate effect is as follows
https://live.csdn.net/v/306826
After studying Godot for such a long time, try to see if the above effect can be achieved today
At a glance, this effect is achieved in several steps:
1. Draw a circle and determine the regularity of the positions of multiple circles
2. Moving points, and determine the movement law of each moving point
3. Comprehensive debugging
1. Draw a circle
This should be relatively simple, and the intuitive feeling is to draw it. Directly create a new project draw, the root scene is Node2D, named Circle, and bind the script circle.gd. Come down and deal directly with circle.gd
From the perspective of learning and understanding, it should be to rewrite the _draw() function and try to call the draw_circle function directly
func _draw():
draw_circle(Vector2(300, 300), 150, Color.WHITE);
The result came out, it seems not difficult.
But it seems that you need to draw a hollow circle, but looking at the Godot source code, the draw_circle function has only three parameters, which are the coordinates of the center point, the length of the radius, and the color.
void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color) {
ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color);
}
Track source canvas_item_add_circle
void RendererCanvasCull::canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_COND(!canvas_item);
Item::CommandPolygon *circle = canvas_item->alloc_command<Item::CommandPolygon>();
ERR_FAIL_COND(!circle);
circle->primitive = RS::PRIMITIVE_TRIANGLES;
Vector<int> indices;
Vector<Vector2> points;
static const int circle_points = 64;
points.resize(circle_points);
Vector2 *points_ptr = points.ptrw();
const real_t circle_point_step = Math_TAU / circle_points;
for (int i = 0; i < circle_points; i++) {
float angle = i * circle_point_step;
points_ptr[i].x = Math::cos(angle) * p_radius;
points_ptr[i].y = Math::sin(angle) * p_radius;
points_ptr[i] += p_pos;
}
indices.resize((circle_points - 2) * 3);
int *indices_ptr = indices.ptrw();
for (int i = 0; i < circle_points - 2; i++) {
indices_ptr[i * 3 + 0] = 0;
indices_ptr[i * 3 + 1] = i + 1;
indices_ptr[i * 3 + 2] = i + 2;
}
Vector<Color> color;
color.push_back(p_color);
circle->polygon.create(indices, points, color);
}
I figured it out, drawing a circle is actually using polygons internally. Feel
circle->primitive = RS::PRIMITIVE_TRIANGLES;
It controls the effect, directly look at the definition:
enum PrimitiveType {
PRIMITIVE_POINTS,
PRIMITIVE_LINES,
PRIMITIVE_LINE_STRIP,
PRIMITIVE_TRIANGLES,
PRIMITIVE_TRIANGLE_STRIP,
PRIMITIVE_MAX,
};
Then test one by one, the first 5 respectively correspond to the following effects
It doesn't feel like the desired effect. Looking at these enumerations again, I feel a little familiar, as if there is a similar definition in OpenGL. Doesn't Godot have a way to draw hollow circles.
Of course not, one way is to draw a solid circle, and then draw a solid circle with a smaller radius and use the background color, and finally feel like a hollow circle. for example
draw_circle(Vector2(300, 300), 150, Color.WHITE);
draw_circle(Vector2(300, 300), 149, Color.BLACK);
result
In order to be more similar, first draw the background in black
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
draw_circle(Vector2(300, 300), 150, Color.WHITE);
draw_circle(Vector2(300, 300), 149, Color.BLACK);
When the result is obtained, it feels like a hollow circle.
But this method is a pseudo-hollow circle. If you draw two partially overlapping circles, you will find the problem.
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
draw_circle(Vector2(300, 300), 150, Color.WHITE);
draw_circle(Vector2(300, 300), 149, Color.BLACK);
draw_circle(Vector2(450, 300), 150, Color.WHITE);
draw_circle(Vector2(450, 300), 149, Color.BLACK);
So this approach won't work.
Study the source code, the various API functions of drawing
void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true);
void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
void draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_start_angle, real_t p_end_angle, int p_point_count, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0);
void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0);
void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0);
void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color);
void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1));
void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false);
void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false);
void draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), double p_outline = 0.0, double p_pixel_range = 4.0, double p_scale = 1.0);
void draw_lcd_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1));
void draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect);
void draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_colored_polygon(const Vector<Point2> &p_points, const Color &p_color, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture);
void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void draw_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_size = 1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void draw_multiline_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;
void draw_char_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;
void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0));
void draw_set_transform_matrix(const Transform2D &p_matrix);
void draw_animation_slice(double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset = 0);
void draw_end_animation();
I feel that draw_arc may be useful, try it
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
draw_arc(Vector2(300, 300), 150, 0, PI * 2, 200, Color.WHITE, -1, true);
draw_arc(Vector2(450, 300), 150, 0, PI * 2, 200, Color.WHITE, -1, true);
result
feasible.
Then unify the transformation. Because it is possible to draw a solid circle, it is also possible to draw a hollow circle, so implement the function
func drawCircle(pos, radius, color, line_width = -1, filled = false):
if filled :
draw_circle(pos, radius, color);
else:
draw_arc(pos, radius, 0, PI * 2, 200, color, line_width, true);
Therefore, with a simple modification, you can draw a hollow circle + solid point
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
drawCircle(Vector2(300, 300), radius, lineColor);
drawCircle(Vector2(450, 300), 2, Color.WHITE, -1, true);
2. Moving point
Move it to see the effect
From a theoretical point of view, the moving point rotates counterclockwise along the circumference, and there is an angle theta between it and the center of the circle, which is controlled by time t. Of course, the most precise time control is in the _physics_process function, you can define a global angle variable deltaAngle = 0, update deltaAngle in _physics_process and force refresh
func _physics_process(delta):
deltaAngle -= 0.125 / PI;
queue_redraw();
func _draw():
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
drawCircle(Vector2(300, 300), radius, lineColor);
drawCircle(Vector2(300 + cos(deltaAngle) * 150, 300 + sin(deltaAngle) * 150), 2, Color.WHITE, -1, true);
move now
3. Distribution law
The next step is to look at the law of the position of the circle and the angle of the moving point. This process will not be analyzed. Anyway, I will directly propose the rules after a simple analysis, and write codes to test and verify.
For better presentation, add level control
func _draw():
draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
drawCircleAtAngle(0, PI + deltaAngle);
drawCircleAtAngle(PI, PI + deltaAngle - PI);
var step = 2;
while step <= pow(2, level):
for i in step:
drawCircleAtAngle(PI / step + i * 2 * PI / step, PI + deltaAngle - PI / step - i * 2 * PI / step);
step *= 2;
pass
and then the final effect
http://42.192.128.33:1880/hx.mp4
reach the initial goal.