送你一朵小红花🌼

35 阅读6分钟

image.png

1. 常量定义(参数配置)

CANVAS_WIDTH = 640  # 画布宽度
CANVAS_HEIGHT = 640  # 画布高度
CANVAS_CENTER_X = CANVAS_WIDTH / 2  # 画布中心X坐标
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2  # 画布中心Y坐标
IMAGE_ENLARGE = 50  # 星星大小缩放比例
STAR_COLOR = "#ff3333"  # 星星主色(深红色)
BRIGHT_STAR_COLOR = "#ff6666"  # 亮星颜色(浅红色)
# 其他参数:控制点的散射、收缩、动画节奏等

 数据类(StarParameters

class StarParameters:
    def __init__(self):
        self.points = set()  # 星星主体点集合
        self.edge_diffusion_points = set()  # 边缘扩散点集合
        self.center_diffusion_points = set()  # 中心扩散点集合
        self.all_points = {}  # 存储每帧的所有点数据(帧号: 点列表)

3. 工具函数(生成与处理点)

这些函数负责计算点的位置,是形成星星形状和动态效果的核心:

  • star_function(t, shrink_ratio):生成星星轮廓点基于极坐标方程生成星星形状的基础点:
r = 2 * (1 + sin(5 * t))  # 五角星半径方程(5次正弦函数模拟星形)
x = r * cos(t)  # 极坐标转直角坐标X
y = r * sin(t)  # 极坐标转直角坐标Y
  • scatter_points(x, y):生成扩散点对基础点进行随机散射,模拟星星边缘的模糊效果:通过对数函数和随机数生成偏移量,使点向四周扩散。
  • shrink_points(x, y, ratio):生成收缩点使点向中心收缩,增强星星的聚集感:基于点到中心的距离计算收缩力,距离越远收缩力越强。
  • curve(p):动画节奏控制函数基于正弦函数生成周期性变化的曲线,控制点的运动节奏,使动画呈现 “收缩 - 扩散” 的循环。

4. 核心逻辑类(Star

封装了星星的构建、动画计算和渲染逻辑,是程序的核心:

  • __init__ 初始化创建参数实例,调用build生成基础点,调用calc预计算每帧的点数据。

  • build 构建基础点

    • 生成星星主体点(points):通过循环调用star_function生成大量点,形成星星轮廓。
    • 生成边缘扩散点(edge_diffusion_points):对主体点进行散射,模拟边缘模糊。
    • 生成中心扩散点(center_diffusion_points):在主体点附近生成更多散射点,增强中心密度。
  • calc 计算每帧数据为动画的每一帧计算所有点的位置:

    • 根据curve函数计算当前帧的缩放比例(ratio),控制点的运动幅度。
    • 生成光晕点(star_halo_point):在星星外围生成随机点,增强层次感。
    • 计算主体点、边缘点、中心点的当前位置(加入随机扰动),并存储到all_points中。
  • render 渲染当前帧绘制当前帧的所有点:

    • all_points中获取当前帧的点数据。
    • 对每个点绘制矩形(模拟星星),随机使用主色或亮色,实现闪烁效果。

5. 渲染控制(draw函数与主程序)

  • draw 函数:递归调用自身,实现动画循环

    • 清空画布 → 渲染当前帧 → 延迟后调用下一帧,形成连续动画。
  • 主程序:初始化窗口和画布

    • 创建 Tkinter 窗口和画布,实例化Star类,启动draw函数开始动画。

二、关键技术点

  1. 数学函数生成形状:通过极坐标方程(sin(5t))生成五角星轮廓,比直接绘制图形更灵活。
  2. 点集动画:通过控制大量点的位置变化(散射、收缩)模拟流体效果,避免直接操作复杂图形。
  3. 预计算帧数据:初始化时计算所有帧的点位置,减少实时计算压力,使动画更流畅。
  4. 随机化效果:加入随机偏移和颜色变化,模拟自然的星星闪烁和流动感。

三、可优化方向

  • 减少点的数量(NUM_POINTS等参数)可降低 CPU 占用,适合性能较弱的设备。
  • 替换star_function的方程(如改为sin(6t))可生成不同角数的星形。
  • 增加颜色渐变参数,可实现星星从中心到边缘的颜色过渡效果。

代码

import random
from math import sin, cos, pi, log
from tkinter import *

# 常量设置(主要修改颜色为红色系)
CANVAS_WIDTH = 640
CANVAS_HEIGHT = 640
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 50  # 缩放比例
STAR_COLOR = "#ff3333"  # 红色主色(较深)
BRIGHT_STAR_COLOR = "#ff6666"  # 亮红色(较浅)
SCATTER_BETA = 0.15
SHRINK_RATIO = 10
CURVE_RATIO = 8
FRAME_DELAY = 160
NUM_POINTS = 1200
NUM_HALO_POINTS = 1800


class StarParameters:
    def __init__(self):
        self.points = set()
        self.edge_diffusion_points = set()
        self.center_diffusion_points = set()
        self.all_points = {}


# 星星形状函数
def star_function(t, shrink_ratio=IMAGE_ENLARGE):
    r = 2 * (1 + sin(5 * t))
    x = r * cos(t)
    y = r * sin(t)
    x *= shrink_ratio
    y *= shrink_ratio
    x = max(0, min(CANVAS_WIDTH, x + CANVAS_CENTER_X))
    y = max(0, min(CANVAS_HEIGHT, y + CANVAS_CENTER_Y))
    return int(x), int(y)


def scatter_points(x, y, beta=SCATTER_BETA):
    ratio_x = -beta * log(random.random())
    ratio_y = -beta * log(random.random())
    dx = ratio_x * (x - CANVAS_CENTER_X)
    dy = ratio_y * (y - CANVAS_CENTER_Y)
    x_new = max(0, min(CANVAS_WIDTH, x - dx))
    y_new = max(0, min(CANVAS_HEIGHT, y - dy))
    return x_new, y_new


def shrink_points(x, y, ratio):
    force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6 + 1e-6)
    dx = ratio * force * (x - CANVAS_CENTER_X)
    dy = ratio * force * (y - CANVAS_CENTER_Y)
    return x - dx, y - dy


def curve(p):
    return 2 * (3 * sin(4 * p)) / (2 * pi)


class Star:
    def __init__(self, generate_frame=20):
        self.parameters = StarParameters()
        self.generate_frame = generate_frame
        self.build(NUM_POINTS)
        for frame in range(generate_frame):
            self.calc(frame)

    def build(self, number):
        for _ in range(number):
            t = random.uniform(0, 2 * pi)
            x, y = star_function(t)
            self.parameters.points.add((x, y))

        for _x, _y in list(self.parameters.points):
            for _ in range(2):
                x, y = scatter_points(_x, _y)
                self.parameters.edge_diffusion_points.add((x, y))

        point_list = list(self.parameters.points)
        for _ in range(NUM_HALO_POINTS):
            x, y = random.choice(point_list)
            x, y = scatter_points(x, y, 0.17)
            self.parameters.center_diffusion_points.add((x, y))

    @staticmethod
    def calc_position(x, y, ratio):
        force_denominator = ((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.520 + 1e-6
        force = 1 / force_denominator
        dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
        dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)
        x_new = max(0, min(CANVAS_WIDTH, x - dx))
        y_new = max(0, min(CANVAS_HEIGHT, y - dy))
        return x_new, y_new

    def calc(self, generate_frame):
        ratio = CURVE_RATIO * curve(generate_frame / 10 * pi)
        halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * pi)))
        halo_number = int(2000 + 3000 * abs(curve(generate_frame / 10 * pi) ** 2))

        all_points = []
        star_halo_point = set()

        for _ in range(halo_number):
            t = random.uniform(0, 2 * pi)
            x, y = star_function(t, shrink_ratio=SHRINK_RATIO)
            x, y = shrink_points(x, y, halo_radius)
            if (x, y) not in star_halo_point:
                star_halo_point.add((x, y))
                x += random.randint(-10, 10)
                y += random.randint(-10, 10)
                size = random.choice((1, 2))
                all_points.append((x, y, size))

        for x, y in self.parameters.points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))

        for x, y in self.parameters.edge_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))

        for x, y in self.parameters.center_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = 1
            all_points.append((x, y, size))

        self.parameters.all_points[generate_frame] = all_points

    def render(self, render_canvas, render_frame):
        try:
            points = self.parameters.all_points[render_frame % self.generate_frame]
        except KeyError:
            points = []
        for x, y, size in points:
            if 0 <= x < CANVAS_WIDTH and 0 <= y < CANVAS_HEIGHT:
                # 红色系闪烁效果
                color = BRIGHT_STAR_COLOR if random.random() > 0.7 else STAR_COLOR
                render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=color)


def draw(main: Tk, render_canvas: Canvas, render_star: Star, render_frame=0):
    render_canvas.delete('all')
    render_star.render(render_canvas, render_frame)
    main.after(FRAME_DELAY, draw, main, render_canvas, render_star, render_frame + 1)


if __name__ == '__main__':
    root = Tk()
    root.title('红色动态星星')
    canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
    canvas.pack()
    try:
        star = Star()
        draw(root, canvas, star)
        root.mainloop()
    except Exception as e:
        print(f"运行错误: {e}")
        root.mainloop()