今天看到一个微信视频,和谐共生,大概效果如下
https://live.csdn.net/v/306826
研究这么长时间的Godot,今天试试能否实现上述效果
粗看一下,这个效果实现分几步:
1. 画圆,并确定多个圆的位置规律
2. 动点,并确定各动点的运动规律
3. 综合调试
1. 画圆
这个应该是比较简单的,直观感觉是要画出来。直接新建一个工程draw,根场景为Node2D,名为Circle,绑定脚本circle.gd。下来就直接处理circle.gd
从学习理解来看,应该是重写_draw()函数,直接调用draw_circle函数试试
func _draw():
draw_circle(Vector2(300, 300), 150, Color.WHITE);
结果出来了,好象不难。
但好象是需要画空心圆,但看Godot源码中,draw_circle函数居然只有三个参数,分别为中心点坐标、半径长度、颜色。
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);
}
跟踪源码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);
}
搞明白了,画圆其实就是在内部用多边形实现。感觉
circle->primitive = RS::PRIMITIVE_TRIANGLES;
是控制效果的,直接看定义:
enum PrimitiveType {
PRIMITIVE_POINTS,
PRIMITIVE_LINES,
PRIMITIVE_LINE_STRIP,
PRIMITIVE_TRIANGLES,
PRIMITIVE_TRIANGLE_STRIP,
PRIMITIVE_MAX,
};
那就挨个测试一下,前5个分别对应以下效果
感觉都不是想要的效果。再看这些枚举量,感觉有点熟悉,好象在OpenGL中有类似的定义。难道Godot没有画空心圆的方法。
当然不是,一种方法是画一个实心圆,然后再以小一点的半径再用背景色画一个实心圆,最终感觉是一个空心圆。比如
draw_circle(Vector2(300, 300), 150, Color.WHITE);
draw_circle(Vector2(300, 300), 149, Color.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);
得到结果,感觉是个空心圆了。
但这种方式是伪空心圆,画两个部分重叠的圆,就发现问题了
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);
所以这种方法行不通。
研究一下源码,画图的各API函数
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();
感觉draw_arc可能有用,试下
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);
结果
可行。
那就统一改造。因为可能画实心圆,也可能画空心圆,所以实现函数
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);
所以,简单修改一下,就可以画出空心圆+实心点
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. 动点
点得动起来,才能看得出效果
从原理角度来说,动点是沿圆周逆时针转动,其与圆心连线有一个角度theta,该角度受时间t控制。当然,最精确的控制时间的地方在_physics_process函数中,可以定义一个全局角度变量deltaAngle = 0,在_physics_process中更新deltaAngle并强制刷新
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);
现在就动起来了
3. 分布规律
下来就是要看圆位置与动点角度的规律。这个过程就不分析了,反正我是简单分析后就直接提出规律,写代码测试验证即可。
为更好演示,加上级数控制
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
然后就是最终效果
http://42.192.128.33:1880/hx.mp4
达到初步目标。