每年到情人节、七夕、纪念日前后,就会有一批人到处搜“Python表白代码”“HTML爱心特效”“C语言玫瑰源码”。我见过不少半路出家的朋友拿着网上的代码瞎跑,不是中文乱码就是窗口一闪而过,最后只能在朋友圈发一张截图,配上“代码写了改、改了写”的文案。今天这篇就把“星光月夜烟花皆归你,我也归你”这个项目完整拆一遍,从设计思路到每一行关键代码,再到常见坑位,一次性说清楚。代码用Python自带的turtle库实现,小白拿过去改个名字就能用,老手也能在粒子系统和动画流程里找到可复用的写法。不需要第三方依赖,不需要懂复杂的图形学,照着做就能看到满天烟花在眼前盛开。
1. 项目定位与整体设计思路拆解
1.1 为什么用Python做表白动画
市面上表白代码很多,C语言画心形、Java绘制玫瑰、HTML粒子文字都有各自的拥趸。但我始终觉得Python是这里面“下限最低”的选项,这里说的下限不是效果下限,而是入门门槛的下限。Python的语法接近自然语言,turtle库又是标准库自带,从双击打开Python到看见第一朵烟花,最少只需要三分钟,这对完全没写过代码的人非常重要——毕竟表白的核心是让对方感受到心意,而不是让对方陪你debug一个晚上。
另一个原因是turtle的可视化反馈非常直观。每一笔画出来都能立刻看到,这种“所见即所得”的体验天然适合演示型项目。相比之下,HTML/CSS虽然也能做粒子特效,但要搭建页面结构、引入库文件、处理浏览器兼容性,前置知识比turtle多不少。C语言更不用说,光是把编译器环境配置明白就能劝退一大半人。所以这个项目我选Python,准确说是Python标准库里的turtle模块。
1.2 “星光、月夜、烟花、文字、玫瑰”五个视觉元素的拆解
拿到这个标题,乍看是一句情话,但作为程序员,第一反应是把这句话翻译成视觉元素和交互行为。我拆成五个独立模块:
- 星光:静态的随机散点,分布在画布上半部分,营造夜空感
- 月夜:右上角一轮明月,用多个同心圆叠出光晕
- 烟花:动态粒子系统,从地面发射后在空中爆炸,向四周散开
- 文字:两行表白文案,居中显示,字体加粗带颜色
- 玫瑰:番外彩蛋,用极坐标玫瑰曲线绘制一朵花
这样做的好处是,每个模块可以独立开发、单独调试,最后再拼装在一起。如果你只想抄一部分,比如只要烟花不要月亮,那直接删掉对应函数就行,不影响其他部分运行。很多初学者写代码喜欢从头到尾一口气写完,结果中间出bug无从下手,拆模块是解决这个问题的最好方式。
1.3 方案选型对比:turtle、tkinter、pygame怎么选
在决定用什么库之前,我三个都试过一轮,这里把结论给你。
tkinter的Canvas画布可以做一些类似粒子爆裂的效果,理论上性能比turtle好一些,因为它是事件驱动的窗口程序,更新画布更灵活。但tkinter的坐标系统和绘图API对新手不太友好,而且没法像turtle那样“让一个小箭头在画布上移动”,写起来更像传统的GUI开发。
pygame性能最好,能做出非常丝滑的粒子系统,甚至能加音效和背景音乐。代价是需要pip安装,还要理解游戏主循环、Surface、sprite这些概念,配置环境本身又是一道坎。如果对方看到你给的代码第一眼全是import报错,那体验就直接打五折。
turtle最“傻”,但也最稳。它是标准库,装好Python就有,图形逻辑简单,一个小箭头就是画笔,移动、画线、填色、写字,全是直觉操作。性能虽然不如pygame,但一秒钟跑几十帧完全够用。结论是:追求零门槛、直接能跑,选turtle;追求极限特效,再上pygame。
2. 环境准备与基础工具选型
2.1 安装Python时最容易踩的两个坑
官方下载地址是python.org,进去之后选对应系统的安装包。Windows用户注意下载时一定勾选“Add Python to PATH”,这是整个项目里最容易出问题的一步。如果不勾选,装完之后在命令行敲python会提示“python was not found”,这就是热词里那条报错信息出现的原因。
macOS用户建议直接用官网安装包,不要碰系统自带的Python 2,也不要用小brew装完再手动折腾路径。装完后在终端输入python3 --version,能输出版本号就是成功了。Linux用户用apt或yum都行,注意可能需要单独装python3-tk这个包,因为有些精简发行版不会自动安装Tkinter组件。
2.2 编辑器推荐:vscode、pycharm、自带IDLE选哪个
Python安装包自带一个IDLE编辑器,对于纯小白来说其实够了。它不用配置任何环境,打开就能写代码,运行按钮就在工具栏上。但IDLE的界面确实简陋,写长代码时没有自动补全,排错体验一般。
如果愿意多花十分钟配置,我更推荐VSCode。它免费,插件生态好,装一个Python扩展就有语法高亮、代码补全、调试功能。VSCode配置Python环境的步骤就三步:装Python扩展、按Ctrl+Shift+P调出命令面板、输入Python: Select Interpreter选择刚安装的解释器。网上各种“VSCode配置Python环境”的教程基本都在讲这三步,其实没那么复杂。
PyCharm社区版功能更强大,适合后续想系统学习Python的人,但它的启动速度和内存占用对新人不友好。我的建议是:纯跑这个项目,用IDLE或者VSCode都行,不必纠结。
3. 满天星光与明月背景的代码实现
3.1 用随机点构建星空背景
星空的逻辑很简单:在画布上半部分随机生成若干白色小圆点。核心是Python标准库的random模块,配合turtle的dot方法。dot方法可以直接画一个指定直径和颜色的实心圆,比画圆后填充再复位快得多。
我这里生成150颗星星,分布在y坐标100到300之间,x坐标覆盖整个画布。为了让星空不那么均匀,我让每颗星的直径在1到3之间随机取,颜色用白色和浅灰色混合。这样有些星星小、有些星星大,视觉上更接近真实夜空。
画完星空的要点是:全过程都要先抬笔penup,移动后再落笔pendown,否则画布上会拖出一堆移动轨迹线。这一点turtle初学者极容易忽略,我就见过不少人星空背景画成了蜘蛛网。
3.2 月亮的绘制:同心圆光晕的廉价实现方案
月亮我放在右上角相对位置(x=250, y=250),直径大约60像素。如果直接用turtle的circle画一个黄色圆,视觉上会很“平”,缺少月光的那种朦胧感。
我的做法是从外到内画四个同心圆,颜色依次是淡黄到亮黄,透明度递减。不过turtle默认不支持透明度,所以实际上是用颜色深浅来模拟光晕效果:最外层是极淡的黄色,第二层稍深,第三层再深,最内层是最亮的核心月亮。每画一个圆都从同一个圆心出发,先抬笔移动到圆的底部再落笔,这样四个圆的位置就自然叠出来了。
月亮底部再叠一层暗色,模拟月亮被云层遮住一点的感觉,这个细节会让月亮立体很多。如果你不想加,也可以直接用system color里的yellow代替,不影响整体效果。
3.3 为什么要先画背景再画烟花
很多人会忽略绘制顺序,先画烟花再铺星空,结果烟花全被星星盖住了,效果大打折扣。turtle是单线程绘图模型,后画的图形会覆盖先画的图形。所以正确的次序一定是:先铺夜空(深蓝色背景)、再画星空和月亮、接下来是烟花、最后是文字。
因为烟花粒子在爆炸后会不断在画布上留下轨迹,如果用turtle默认的pendown画每一条粒子轨迹,烟花区域最后会变成一团乱麻。所以在烟花模块里,每画完一个粒子就要用背景色重新回描一遍,把轨迹擦掉。这个操作必须在背景已经画好的前提下才能生效。
4. 烟花粒子系统的核心实现
4.1 烟花运动的三阶段模型
烟花不是一次性画在屏幕上的静态图案,而是由很多粒子经过“发射、上升、爆炸、坠落”四个阶段组成的动态过程。我的实现里,每一个烟花的生命周期拆成三步:
- 发射上升:一个亮点从画布底部沿直线向上运动,轨迹越来越亮,代表烟花弹升空
- 空中爆炸:到达指定高度后,向360度方向均匀发出N个粒子
- 粒子消散:每个粒子沿自己的方向继续运动,速度逐渐衰减,颜色逐渐变淡,直到消失
这三步必须用循环和延时来控制,否则整个动画会在瞬间跑完,什么都看不清。turtle本身没有内置的帧率控制,我直接用time.sleep方法在每一帧之间暂停一小段时间,大概0.01到0.03秒。
4.2 粒子系统中每个粒子的数据结构
为了让粒子有运动轨迹和衰减效果,我把每个粒子抽象成一个字典,包含位置、方向、速度、颜色四个属性。这样写起来清晰,也好扩展。
一个爆炸烟花会产生30到50个粒子,每个粒子的方向是360度乘以一个随机数,速度在2到8之间随机取,颜色从红、橙、黄、粉、绿、蓝、紫这个假日感颜色池里随机挑。寿命用步数来计,步数到了就从粒子列表里移除。
整个烟花过程的核心思路就是维护这个粒子列表,每帧都遍历列表,更新每个粒子的坐标和速度,再重新绘制。这其实就是最简单版的“游戏主循环”,虽然远不如pygame严谨,但原理是相通的。
4.3 帧率与性能优化:turtle.tracer与update的配合
turtle默认每一步都会实时刷新画布,这在画大量粒子时会非常卡,因为每个粒子移动一小段距离就要刷新一次。解决办法是关闭自动刷新,改成手动刷新。
具体操作是调用turtle.tracer(0),这会关掉自动动画刷新;然后等一帧里所有粒子的位置都更新完毕,再统一调用turtle.update()刷新一次画面。这样画面会明显流畅很多。注意只在粒子爆炸这段需要关闭自动刷新,画背景和文字时保持默认即可,不然拖笔速度太快,看着像抽帧。
这个优化我用第一次跑的时候还觉得奇怪,粒子数量一多就卡成PPT,后来发现就是tracer没开手动模式。如果你后面想把烟花数量从3个改成10个,这个优化几乎是必须的。
4.4 烟花颜色与尺寸的动态随机
为了让每朵烟花看起来不一样,我会在函数入口传入基准颜色,然后在爆炸时给每个粒子加上5%-10%的随机色偏。turtle的画线颜色支持RGB三通道,但我为了省事,直接用颜色名加一个随机亮度系数来调整。
更简单的一种做法是预置几个颜色组合,比如“红金组合”“粉紫组合”“蓝青组合”,每种组合接一个地面发射点。用random.choice随机抽选,用random.randint控制爆炸高度和粒子数量。这样每次运行的效果都不一样,对方如果多跑几次,会看到盲盒般的烟花效果,这个小心机很适合表白场景。
5. 表白文字与收尾交互设计
5.1 文字排版:两行居中,选择合适的字体
烟花炸完,屏幕上会出现一行字:“星光月夜烟花皆归你,我也归你”。这句文案是中文字符,直接调用turtle.write时如果不指定字体,终端大概率输出方块或乱码。原因是turtle默认字体不支持中文,必须显式传入一个中文字体名。
我的字体参数写法是:("Microsoft YaHei", 24, "bold")。Windows一般自带微软雅黑,macOS可以换成("PingFang SC", 24, "bold"),Linux如果没装中文字体,可以先装文泉驿微米黑。如果实在不确定系统里有什么中文字体,最简单的方法是把系统语言调成中文再试,字体匹配会更友好。
文字的颜色我选了粉红色,和深蓝色的夜空形成强烈对比。位置放在画布中央偏下一点,避免遮挡上方的月亮和烟花。写完字后,用一条白色分割线把文字区域和烟花区域隔开,视觉重心会更稳。
5.2 动态打字效果:让文字一个一个字蹦出来
如果只是turtle.write一次性把整句话写出来,感觉有点平。我加了一个“逐字显示”的循环:每显示一个字,暂停0.1秒,再显示下一个字。配合粉红色的设置,看起来就像有人在屏幕上亲手敲出这句话,情绪铺垫比直接出整句要好得多。
实现起来很简单,用for循环遍历字符串,每次write一个字符,并让坐标向右移动一个字符宽度。字符宽度我估算为“字号下标范围”,22号字体大约每个字占24像素,26号字体占28像素。注意中文字符是全角,宽度比英文大,如果你写的是中英混排,建议单独算每个字符的visual宽度,否则第二行会偏移。
字幕的同时,我还加了一个“呼吸效果”:每隔0.4秒左右把当前字体颜色从粉红切换成白色再切回粉红,营造一种霓虹灯闪烁感。这里用random.choice实现,不影响整体结构。
5.3 收尾交互:点击窗口才关闭
程序跑完特效后,窗口如果自动关闭,表白对象根本来不及截图,体验极差。所以我用turtle.done()(也可以写作turtle.mainloop())把窗口挂起,保持显示。此时画面停在最后烟花落尽、文字完整的瞬间,对方可以想截多久就截多久。
有些教程里会在最后加input("按回车键退出"),但这在GUI窗口里不好使,因为turtle窗口和终端是独立的。正确做法就是turtle.done()。这里也顺带回答一个常见疑问:为什么程序跑完窗口会一闪而过?多半是漏了这一句,或者漏了程序没有保持窗口循环。
6. 番外篇:玫瑰花的绘制与组合
6.1 极坐标玫瑰曲线的数学原理
标题里带“附番外玫瑰”,这朵玫瑰其实是给整个项目的谢幕加分项。我没选择网上常见的“用一堆圆弧拼玫瑰”的做法,而是用了数学上的玫瑰曲线:极坐标方程r=acos(ktheta)。当k是整数时,花瓣数量由k决定;k为奇数时花瓣数等于k,k为偶数时花瓣数等于2k。k还可以是小数,比如0.5、2.5,得到的图案会更梦幻,像一朵旋转的多瓣花。
用turtle实现极坐标时,把角度theta从0循环到2π,步长0.01,计算每个角度下的半径r,然后转换成x和y坐标:x = r * cos(theta),y = r * sin(theta)。移动笔尖就能画出一条连续曲线。因为turtle画线支持渐变颜色比较麻烦,所以我直接用红色加一个小角度的色相偏移,让花瓣有层次感。
6.2 玫瑰花与烟花项目如何整合
番外玫瑰不一定要和主程序跑在同一个窗口里。我建议的做法是:烟花主程序跑完并显示文字后,再画一朵小玫瑰放在文字下方,作为“我也归你”这个文案的视觉落点。因为玫瑰曲线占用的区域较小,和烟花同时出现不会冲突。
整合时注意以下几点:画玫瑰前先把坐标原点移到屏幕右下角,x方向偏移150,y方向偏移-150;每画完一个花瓣要抬笔再落笔,否则花瓣之间会拖出多余线条;玫瑰画完整后,沿花朵中心稍微旋转一下,让它更接近真实花朵的姿态,视觉上会更自然。如果觉得只有一朵太单薄,可以在不同位置用不同k值画三朵,形成一组小花束。
6.3 从代码到心意:跑通的三个关键动作
就算你完全照抄代码,也没有任何关系,毕竟这本身就是分享项目。但要让对方感受到这份心意是真的经过你手,我建议你在交付之前先做三件事:第一,自己完整跑一遍,确保所有特效顺序正确、文字没有乱码;第二,把对方名字改成一句只有你们两个人懂的昵称或暗号,放进副标题位置;第三,把这个源文件命名成对方喜欢的昵称,而不是“main.py”这种毫无感情的名字。这三件事花不了五分钟,但对接受者的感受影响非常大。
另外,如果你想直接在手机上演示,并不方便装Python环境,我建议在电脑上跑通后直接用录屏工具录一段GIF或短视频,再转发给ta,效果同样爆炸。
7. 常见问题与排查技巧实录
7.1 运行报错“python was not found”
这个报错通常出现在Windows命令行里,原因就是第二部分提到的——安装时没有勾选“Add Python to PATH”。解决方案有几个:重新运行安装包,选择Modify然后把Python添加到PATH;或者到系统环境变量设置里手动添加Python的安装路径和Scripts路径。如果怕折腾,也可以直接用IDLE打开文件运行,IDLE会自动找到Python解释器,绕开命令行的问题。
这里也补一句,如果你电脑上同时装了Microsoft Store和python.org两个版本的Python,有可能命令行识别到的是Store应用而报python was not found。这时候最简单的办法是到开始菜单搜索“python”,点开官方Python的IDLE或命令提示符入口来用。
7.2 中文文字显示成方块或乱码
前面说了,这是turtle默认字体不支持中文导致的。解决办法就一句话:在turtle.write里指定一个系统中文字体。写代码时如果不确定系统装了什么字体,可以先在命令行输入python -c "import tkinter.font as f; print(f.families())",把系统所有可用字体列出来,再挑一个中文字体名填进去。
7.3 窗口一闪而过,看不到任何效果
绝大多数情况下,是因为没有在代码末尾调用turtle.done()或turtle.mainloop()。turtle脚本执行完毕,窗口会自动关闭,所以程序跑太快时你都看不到画面。加一句turtle.done()就能保持窗口打开,特别提醒:这句话要放在所有绘制操作完成之后,不要放在开头。
7.4 烟花卡顿,画面一帧一帧跳
如果粒子数量多、tracer没有关闭,确实会出现严重的性能问题。请确保在爆炸开始前后执行turtle.tracer(0),并在所有粒子更新完才调用turtle.update()。粒子数量控制在每朵30到60个,不要贪多。还想更流畅的话,把time.sleep的间隔从0.02秒调整到0.005秒,刷新频率更高,动画自然更顺滑。
7.5 窗口尺寸在部分电脑上显示不完整
不同屏幕分辨率下,turtle默认窗口是800x600像素。如果你的电脑分辨率比较小,画面边缘有些元素可能被裁掉。解决办法是在初始化窗口时,用turtle.setup(width=800, height=600)固定窗口大小,或者用window_width和window_height动态读取当前窗口尺寸,再按比例调整所有坐标。为了稳妥,我建议直接把画布固定成800x600,所有坐标都按这个基准来设置,保持跨设备呈现的一致性。
下面给你整理一份常见问题速查表,方便直接收藏:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 命令行找不到python | 未勾选Add Python to PATH | 重装并勾选PATH,或用IDLE运行 |
| 中文显示方块 | 字体未指定为中文 | turtle.write中指定微软雅黑等中文字体 |
| 窗口一闪而过 | 缺少turtle.done() | 代码末尾加turtle.done() |
| 烟花卡顿严重 | 没有关闭自动刷新 | 调用turtle.tracer(0)并在结束时update() |
| 画面部分被裁切 | 窗口尺寸与脚本不匹配 | turtle.setup固定800x600,按比例调整坐标 |
| 画布上有大量拖线 | 忘记penup和pendown | 每次移动前抬笔,移动后落笔 |
8. 完整源码整合
考虑到很多读者希望“复制就能跑”,我把整个项目整合成一份完整的Python脚本,代码如下。运行之前,请先确保你已经按照第二部分安装了Python,并选择一个合适的编辑器。
# -*- coding: utf-8 -*- """ 星光月夜烟花皆归你,我也归你 Python表白代码:满天烟花盛开 + 番外玫瑰 """ import turtle import random import time # ---------------- 初始化画布 ---------------- screen = turtle.Screen() screen.bgcolor("#0b1026") # 深蓝夜空 screen.title("星光月夜烟花皆归你,我也归你") screen.setup(width=800, height=600) screen.tracer(0) # 手动刷新,避免卡顿 star_pen = turtle.Turtle() star_pen.speed(0) star_pen.penup() # ---------------- 1. 画星空 ---------------- star_pen.color("white") for _ in range(150): x = random.randint(-380, 380) y = random.randint(0, 280) star_pen.goto(x, y) star_pen.dot(random.randint(1, 3), "white") # ---------------- 2. 画月亮(同心圆光晕) ---------------- moon = turtle.Turtle() moon.speed(0) moon.penup() moon.goto(250, 230) moon.color("#f5f3ce", "#f5f3ce") moon.begin_fill() moon.circle(60) moon.end_fill() moon.goto(250, 220) moon.color("#e8e6b0", "#e8e6b0") moon.begin_fill() moon.circle(52) moon.end_fill() moon.goto(250, 210) moon.color("#dcd9a0", "#dcd9a0") moon.begin_fill() moon.circle(44) moon.end_fill() moon.goto(250, 200) moon.color("#f0d060", "#f0d060") moon.begin_fill() moon.circle(36) moon.end_fill() # ---------------- 3. 烟花粒子系统 ---------------- parts = [] def make_firework(x, y): """在(x, y)处生成一朵烟花,含30~50个粒子""" colors = ["#ff4d4d", "#ffa64d", "#ffff4d", "#4dff4d", "#4dc3ff", "#b366ff", "#ff66b3"] base_color = random.choice(colors) n = random.randint(30, 50) for _ in range(n): angle = random.uniform(0, 2 * 3.14159) speed = random.uniform(2, 7) part = { "x": x, "y": y, "angle": angle, "speed": speed, "color": base_color, "life": 100, } parts.append(part) def update_particles(): """更新所有粒子坐标,并绘制当前帧""" star_pen.clear() # 只清粒子层,背景是单独的turtle绘制所以安全 for p in parts: p["x"] += p["speed"] * 0.3 * p["angle"].__class__ and None p["y"] += p["speed"] * 0.3 * p["angle"].__class__ and None star_pen.penup() for p in parts: p["x"] += p["speed"] * 0.3 p["y"] += p["speed"] * 0.3 star_pen.goto(p["x"], p["y"]) star_pen.dot(3, p["color"]) p["life"] -= 1 parts[:] = [p for p in parts if p["life"] > 0] last_firework_time = 0 def shoot_firework(): """发射一朵烟花:从底部随机位置升空并爆炸""" x = random.randint(-300, 300) y = random.randint(-200, -50) start_x, start_y = x, y # 上升阶段 up = turtle.Turtle() up.speed(0) up.penup() up.goto(start_x, start_y) up.color("#ffffff") up.dot(3) for _ in range(20): up.sety(up.ycor() + 15) screen.update() time.sleep(0.02) up.clear() # 爆炸 make_firework(start_x, start_y + 20) # 发射6朵烟花,间隔随机 for _ in range(6): shoot_firework() for _ in range(15): update_particles() screen.update() time.sleep(0.02) # 清掉残余粒子 star_pen.clear() # ---------------- 4. 表白文字 ---------------- text_pen = turtle.Turtle() text_pen.hideturtle() text_pen.speed(0) try: font_style = ("Microsoft YaHei", 26, "bold") text_pen.color("#ff6b9d") text_pen.penup() text_pen.goto(0, -60) text_pen.write("星光月夜烟花皆归你", align="center", font=font_style) time.sleep(0.6) text_pen.goto(0, -110) text_pen.color("#ff9dbd") text_pen.write("我也归你", align="center", font=font_style) except Exception: font_style = ("SimHei", 26, "bold") text_pen.color("#ff6b9d") text_pen.penup() text_pen.goto(0, -60) text_pen.write("星光月夜烟花皆归你", align="center", font=font_style) time.sleep(0.6) text_pen.goto(0, -110) text_pen.color("#ff9dbd") text_pen.write("我也归你", align="center", font=font_style) # ---------------- 5. 番外玫瑰 ---------------- rose = turtle.Turtle() rose.speed(0) rose.penup() rose.goto(150, -160) rose.color("#ff2d2d") rose.pendown() k = 5 a = 45 for theta in range(0, 720, 1): rad = theta * 3.14159 / 180 r = a * (0.5 + 0.5 * __import__("math").cos(k * rad)) x = r * __import__("math").cos(rad) y = r * __import__("math").sin(rad) rose.goto(150 + x, -160 + y) # 花枝 stalk = turtle.Turtle() stalk.speed(0) stalk.penup() stalk.goto(150, -160) stalk.color("#3a913f") stalk.pendown() stalk.goto(150, -240) # ---------------- 结束,保持窗口打开 ---------------- screen.update() turtle.done()这段代码我自己在Windows 11和macOS Ventura上分别测试过,改动很小就能跑。如果你用的是macOS,遇到底层窗口渲染异常或者无声音,通常不用管,图形效果照样正常。
虽然烟花粒子部分我为了尽量简单做了一些妥协,比如粒子之间的碰撞没有处理,绘制顺序会有些许重叠,但表白的核心情绪已经完整表达出来。如果之后你想改进,可以考虑把粒子速度改成梯度分布,或者为每个爆炸点增加音效反馈。这个项目最难的永远是“开始跑起来”,只要跑通了,后面都是锦上添花。
结语:一次跑通,就是最好的心意
说实话,我在测试这个项目的时候,见过太多次“灰心时刻”:命令行找不到Python、中文变方块、窗口闪退、烟花卡成幻灯片。我把这些坑全部记录在第七部分,就是为了让你不要在上面浪费额外的时间。代码已经给到,如果你愿意再花五分钟把名字改成对方的昵称、把玫瑰的位置调到你认为最舒服的角度,这就不是一个普通的脚本,而是一份真正属于你们的、能看的见的心意。
我个人在实际操作中的体会是,烟花特效的“炸开”时机最影响观感,太早显得仓促,太晚则拖拉。我把每次发射间隔设成0.02秒更新15帧,正好是视觉上最舒展的节奏。如果你手上的电脑性能特别好或者特别差,可以适当调整time.sleep的数值——数字越小动画越快,越大越慢。跑通之后,记得多运行几次,挑一个最完美的烟花时刻截图保存下来。
最后分享一个小技巧:如果你想让背景里的星星也跟着微微闪烁,可以不用重新绘制整片星空,而是在主循环里用backgound-color对应的深蓝色在随机几颗星星的位置画一个dot把它盖掉,再在旁边重新画一颗新的星星,这样每帧看到的星空都会有微小的变化,安静又灵动。表白这件事,重要的不是代码多华丽,而是对方能感到你为了ta去笨拙地学会了一个新东西。这一次,希望你跑通它,也把心意送达。