抽象小鱼🐟.

代码结构与模块拆分

代码延续了点阵动画的核心框架,主要分为常量配置数据存储类工具函数核心逻辑类渲染控制五部分:

1. 常量配置(参数化控制)

python 复制代码
# 画布与位置参数
CANVAS_WIDTH = 600
CANVAS_HEIGHT = 400
CANVAS_CENTER_X = CANVAS_WIDTH / 2  # 画布中心X坐标
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2  # 画布中心Y坐标

# 视觉与颜色参数(蓝色系核心)
IMAGE_ENLARGE = 32  # 缩放比例(控制鱼的大小)
FISH_BODY = "#42a5f5"  # 鱼身亮蓝
FISH_FIN = "#1e88e5"   # 鱼鳍深蓝(增强立体感)
FISH_EYE = "#000000"   # 纯黑眼睛(高对比度,突出鱼头)
FISH_EYE_WHITE = "#ffffff"  # 眼球高光(增强真实感)

# 动画与点数量参数
FRAME_DELAY = 120  # 帧延迟(控制动画速度,值越小越快)
NUM_BODY = 700     # 身体点数量
NUM_HEAD = 400     # 鱼头点数量(单独设置,强化清晰度)
NUM_FIN = 200      # 鱼鳍点数量
NUM_TAIL = 300     # 尾部点数量
  • 颜色参数采用蓝色系渐变,从亮蓝(鱼身)到深蓝(鱼鳍),配合黑色眼睛形成清晰视觉层次
  • 鱼头点数量(NUM_HEAD)单独设置且密度较高,是鱼头清晰的关键参数

2. 数据存储类(FishParams

python 复制代码
class FishParams:
    def __init__(self):
        self.body = set()       # 身体点集合
        self.head = set()       # 鱼头点集合(独立存储,便于单独处理)
        self.fin = set()        # 鱼鳍点集合
        self.tail = set()       # 尾部点集合
        self.eye = set()        # 黑眼球点集合
        self.eye_white = set()  # 眼球高光点集合
        self.all_points = {}    # 按帧存储所有点数据(帧号: 点列表)
  • 通过分集合存储不同部位的点,实现对鱼头、眼睛等关键部位的精细化控制
  • 使用set避免重复点,保证动画流畅性;all_points预存每帧数据,减少实时计算压力

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

这些函数是构建鱼形和动态效果的核心:

  • fish_shape(t, shrink_ratio):生成鱼的基础轮廓点 基于分段极坐标方程生成鱼的轮廓:
python 复制代码
if 0 <= t < pi/2:  # 头部前端(圆润饱满)
    x = (t - pi/4) / (pi/4) * 1.5  # 横向范围扩大
    y = 0.8 * cos(t)  # 上下宽度增加
elif pi/2 <= t < pi:  # 头部到身体过渡
    x = (t - pi/2) / (pi/2) * 2 + 1.5
    y = 0.8 * cos(t)
else:  # 身体到尾部(收窄)
    x = (3*pi/2 - t) / (pi/2) * 3 + 1.5
    y = -0.6 * cos(t)
  • head_details(x, y):生成鱼头细节点专门强化鱼头特征:

    • 黑眼球:2x2 像素块(eye集合),位置固定在头部前端
    • 眼球高光:1x1 白色点(eye_white集合),模拟反光
    • 头部轮廓点:围绕眼睛的弧形点(head_edge),强化头部边界
  • scatter_points(x, y, is_head):点的散射处理对基础点进行随机偏移,模拟轮廓的自然过渡:

    • 鱼头点(is_head=True)散射率降低 50%,避免模糊(ratio_x * 0.3
    • 身体和尾部点散射率较高,形成柔和边缘
  • shrink_points(x, y, ratio, is_head):点的收缩处理基于点到中心的距离计算收缩力,控制点的聚集效果:

    • 鱼头点收缩幅度更小(dx * 0.12),保持清晰形状
    • 尾部点收缩幅度更大,增强摆动感
  • swim_curve(p):游动节奏控制函数基于正弦函数生成周期性曲线,控制点的运动幅度:

python 复制代码
return 1.5 * (sin(3.5 * p) + 0.3 * sin(6.5 * p)) / (2 * pi)

4. 核心逻辑类(Fish

封装了鱼的构建、动画计算和渲染逻辑:

  • build():初始化生成所有基础点

    • 身体点:通过循环调用fish_shape生成NUM_BODY个点
    • 鱼头点:专门在头部角度范围(0 <= t < pi/1.5)生成NUM_HEAD个点,密度更高
    • 细节点:调用head_details生成眼睛、高光和头部轮廓点
    • 鱼鳍和尾部点:在对应区域生成辅助点,强化结构
  • calc(frame):计算每帧的点位置

    • 根据swim_curve计算当前帧的运动比例(ratio
    • 分区域计算点的位置:头部点摆动幅度小(ratio * 0.7),尾部点摆动幅度大(ratio * 2
    • 所有点位置存储到all_points[frame],供渲染使用
  • render(canvas, frame):绘制当前帧

    • all_points中获取当前帧的点数据
    • 按点类型(头部 / 身体 / 鳍 / 眼睛等)分配对应颜色,调用create_rectangle绘制点阵
    • 眼睛和高光单独处理,确保清晰可见

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

  • draw函数:递归实现动画循环
python 复制代码
def draw(main, canvas, fish, frame=0):
    canvas.delete('all')  # 清空画布
    fish.render(canvas, frame)  # 绘制当前帧
    main.after(FRAME_DELAY, draw, main, canvas, fish, frame+1)  # 延迟后绘制下一帧

主程序:初始化窗口和画布,启动动画

python 复制代码
root = Tk()
canvas = Canvas(root, bg='#e0f7fa', ...)  # 浅蓝色背景模拟水面
fish = Fish()  # 创建鱼实例
draw(root, canvas, fish)  # 启动动画
root.mainloop()

关键技术亮点

  1. 分区域精细化控制 :通过独立的点集合(head/eye等)和处理逻辑,实现鱼头清晰化 ------ 头部点密度高、散射少、摆动小,与身体 / 尾部形成差异。
  2. 颜色系统设计 :蓝色系从亮到暗的渐变(#42a5f5#1e88e5)配合高对比度的黑白眼睛,既符合水中生物特征,又强化了关键部位的辨识度。
  3. 物理模拟简化:通过 "散射 + 收缩" 函数模拟点的自然聚集 / 扩散,用正弦曲线控制摆动节奏,在保证视觉效果的同时降低计算复杂度。
  4. 预计算帧数据 :初始化时计算所有帧的点位置(all_points),避免实时计算压力,使动画更流畅。

可调整方向

  • 增大NUM_HEAD可进一步提升鱼头清晰度;减小FRAME_DELAY可加快游动速度。
  • 调整FISH_BODYFISH_FIN的颜色值,可实现不同深浅的蓝色效果(如深海蓝、浅湖蓝)。
  • 修改fish_shape中的三角函数参数,可调整鱼的体型(如更胖 / 更瘦的身体、更长 / 更短的尾部)。

整体而言,代码通过模块化设计和参数化控制,在保持点阵动画特色的同时,成功实现了 "清晰鱼头 + 蓝色小鱼" 的视觉效果,且具有良好的可扩展性。

代码

python 复制代码
import random
from math import sin, cos, pi, log
from tkinter import *

# 常量设置(蓝色系调整)
CANVAS_WIDTH = 600
CANVAS_HEIGHT = 400
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 32
FISH_BODY = "#42a5f5"  # 鱼身亮蓝
FISH_FIN = "#1e88e5"  # 鱼鳍深蓝
FISH_EYE = "#000000"  # 鱼眼纯黑
FISH_EYE_WHITE = "#ffffff"  # 眼球高光
SCATTER_BETA = 0.1
SHRINK_RATIO = 11
CURVE_RATIO = 5
FRAME_DELAY = 120
NUM_BODY = 700
NUM_HEAD = 400  # 鱼头点密度
NUM_FIN = 200
NUM_TAIL = 300


class FishParams:
    def __init__(self):
        self.body = set()
        self.head = set()
        self.fin = set()
        self.tail = set()
        self.eye = set()
        self.eye_white = set()
        self.all_points = {}


# 鱼形函数(保持清晰头部轮廓)
def fish_shape(t, shrink_ratio=IMAGE_ENLARGE):
    if 0 <= t < pi / 2:  # 头部前端(圆润饱满)
        x = (t - pi / 4) / (pi / 4) * 1.5
        y = 0.8 * cos(t)
    elif pi / 2 <= t < pi:  # 头部到身体过渡
        x = (t - pi / 2) / (pi / 2) * 2 + 1.5
        y = 0.8 * cos(t)
    else:  # 身体到尾部
        x = (3 * pi / 2 - t) / (pi / 2) * 3 + 1.5
        y = -0.6 * cos(t)

    if t > 1.8 * pi:
        x = x * 1.4

    x *= shrink_ratio
    y *= shrink_ratio
    x += CANVAS_CENTER_X - 50  # 左移定位
    y += CANVAS_CENTER_Y
    return int(x), int(y)


# 头部细节生成(眼睛+轮廓)
def head_details(x, y):
    details = {
        'eye': [],
        'eye_white': [],
        'head_edge': []
    }
    # 眼睛位置(头部前端)
    eye_x = x - 35
    eye_y = y - 5
    details['eye'].extend([
        (eye_x, eye_y), (eye_x + 1, eye_y),
        (eye_x, eye_y + 1), (eye_x + 1, eye_y + 1)
    ])
    # 眼球高光
    details['eye_white'].append((eye_x + 0.5, eye_y + 0.5))
    # 头部轮廓强化点
    details['head_edge'].extend([
        (x - 40, y - 10), (x - 38, y - 12), (x - 35, y - 13),
        (x - 32, y - 12), (x - 30, y - 10)
    ])
    return details


# 点散射(头部低散射保持清晰)
def scatter_points(x, y, beta=SCATTER_BETA, is_head=False):
    ratio_x = -beta * log(random.random()) * 0.3 if is_head else 0.5
    ratio_y = -beta * log(random.random()) * 0.3 if is_head else 0.6
    dx = ratio_x * (x - CANVAS_CENTER_X) * 0.15
    dy = ratio_y * (y - CANVAS_CENTER_Y) * 0.15
    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, is_head=False):
    force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.7 + 1e-6)
    dx = ratio * force * (x - CANVAS_CENTER_X) * 0.12 if is_head else 0.18
    dy = ratio * force * (y - CANVAS_CENTER_Y) * 0.12 if is_head else 0.22
    return x - dx, y - dy


# 游动曲线
def swim_curve(p):
    return 1.5 * (sin(3.5 * p) + 0.3 * sin(6.5 * p)) / (2 * pi)


class Fish:
    def __init__(self, generate_frame=28):
        self.params = FishParams()
        self.generate_frame = generate_frame
        self.build()
        for frame in range(generate_frame):
            self.calc(frame)

    def build(self):
        # 身体点
        for _ in range(NUM_BODY):
            t = random.uniform(0, 2 * pi)
            x, y = fish_shape(t)
            self.params.body.add((x, y))

        # 鱼头点(高密度)
        for _ in range(NUM_HEAD):
            t = random.uniform(0, pi / 1.5)
            x, y = fish_shape(t)
            x, y = scatter_points(x, y, is_head=True)
            self.params.head.add((x, y))

        # 头部细节
        head_list = list(self.params.head)
        if head_list:
            x, y = random.choice(head_list)
            details = head_details(x, y)
            for ex, ey in details['eye']:
                self.params.eye.add((int(ex), int(ey)))
            for wx, wy in details['eye_white']:
                self.params.eye_white.add((int(wx), int(wy)))
            for hx, hy in details['head_edge']:
                self.params.head.add((hx, hy))

        # 鱼鳍
        body_list = list(self.params.body)
        head_body = [p for p in body_list if p[0] < CANVAS_CENTER_X]
        if head_body:
            x, y = random.choice(head_body)
            self.params.fin.add((x - 8, y - 15))
            self.params.fin.add((x, y - 20))
            self.params.fin.add((x + 8, y - 15))

        # 尾部
        for _ in range(NUM_TAIL):
            t = random.uniform(1.6 * pi, 2 * pi)
            x, y = fish_shape(t)
            for _ in range(2):
                tx, ty = scatter_points(x, y)
                self.params.tail.add((tx, ty))

    @staticmethod
    def calc_position(x, y, ratio, frame, part='body'):
        swim_offset = sin(frame / 7) * 2
        if part == 'head' or part == 'eye':
            ratio = ratio * 0.7
            swim_offset = swim_offset * 0.5
        elif part == 'tail':
            ratio = ratio * 2

        force_denominator = ((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6 + 1e-6
        force = 1 / force_denominator
        dx = ratio * force * (x - CANVAS_CENTER_X) * 0.2 + swim_offset
        dy = ratio * force * (y - CANVAS_CENTER_Y) * 0.25 + random.randint(-1, 1)
        return max(0, min(CANVAS_WIDTH, x - dx)), max(0, min(CANVAS_HEIGHT, y - dy))

    def calc(self, frame):
        ratio = CURVE_RATIO * swim_curve(frame / 14 * pi)
        halo_radius = int(2 + 4 * (1 + swim_curve(frame / 14 * pi)))
        halo_number = int(800 + 1200 * abs(swim_curve(frame / 14 * pi) ** 2))

        all_points = []
        # 光晕
        halo = set()
        for _ in range(halo_number):
            t = random.uniform(0, 2 * pi)
            x, y = fish_shape(t, SHRINK_RATIO)
            is_head = t < pi / 1.5
            x, y = shrink_points(x, y, halo_radius, is_head)
            if (x, y) not in halo:
                halo.add((x, y))
                x += random.randint(-6, 6)
                y += random.randint(-5, 5)
                all_points.append(('halo', x, y, 1))

        # 身体点
        for x, y in self.params.body:
            x, y = self.calc_position(x, y, ratio, frame)
            all_points.append(('body', x, y, random.randint(1, 2)))

        # 鱼头点
        for x, y in self.params.head:
            x, y = self.calc_position(x, y, ratio, frame, 'head')
            all_points.append(('head', x, y, random.randint(1, 2)))

        # 鱼鳍
        for x, y in self.params.fin:
            x, y = self.calc_position(x, y, ratio, frame)
            all_points.append(('fin', x, y, 1))

        # 尾部
        for x, y in self.params.tail:
            x, y = self.calc_position(x, y, ratio, frame, 'tail')
            all_points.append(('tail', x, y, random.randint(1, 2)))

        # 眼睛
        for x, y in self.params.eye:
            x, y = self.calc_position(x, y, ratio, frame, 'eye')
            all_points.append(('eye', int(x), int(y), 1))
        for x, y in self.params.eye_white:
            x, y = self.calc_position(x, y, ratio, frame, 'eye')
            all_points.append(('eye_white', int(x), int(y), 1))

        self.params.all_points[frame] = all_points

    def render(self, canvas, frame):
        try:
            points = self.params.all_points[frame % self.generate_frame]
        except KeyError:
            points = []
        for p_type, x, y, size in points:
            if 0 <= x < CANVAS_WIDTH and 0 <= y < CANVAS_HEIGHT:
                # 蓝色系配色
                if p_type in ('body', 'head', 'halo'):
                    color = FISH_BODY
                elif p_type in ('fin', 'tail'):
                    color = FISH_FIN
                elif p_type == 'eye':
                    color = FISH_EYE
                elif p_type == 'eye_white':
                    color = FISH_EYE_WHITE
                canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=color)


def draw(main, canvas, fish, frame=0):
    canvas.delete('all')
    fish.render(canvas, frame)
    main.after(FRAME_DELAY, draw, main, canvas, fish, frame + 1)


if __name__ == '__main__':
    root = Tk()
    root.title('蓝色小鱼(清晰鱼头)')
    canvas = Canvas(root, bg='#e0f7fa', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)  # 浅蓝背景
    canvas.pack()
    try:
        fish = Fish()
        draw(root, canvas, fish)
        root.mainloop()
    except Exception as e:
        print(f"错误: {e}")
        root.mainloop()
相关推荐
汤姆yu3 小时前
基于python大数据的特产推荐系统
大数据·开发语言·python
金牛大王3 小时前
利用python生成Voronoi图
python
ColderYY3 小时前
Python中的正则表达式
开发语言·python·正则表达式
小叮当⇔4 小时前
知识就是力量——EMQX Dashboard核心规则编写方法及示例
python·iot
星期天要睡觉4 小时前
提示词(Prompt)——链式思维提示词(Chain-of-Thought Prompting)在大模型中的调用(以 Qwen 模型为例)
开发语言·人工智能·python·语言模型·prompt
CLubiy4 小时前
【研究生随笔】Pytorch中的卷积神经网络(2)
人工智能·pytorch·python·深度学习·cnn·卷积神经网络·池化
程序员爱钓鱼5 小时前
Python编程实战 - 函数与模块化编程 - 导入与使用模块
后端·python·ipython
程序员爱钓鱼5 小时前
Python编程实战 - 函数与模块化编程 - 匿名函数(lambda)
后端·python·ipython
清空mega5 小时前
Flask入门学习指南
后端·python·flask