🏆 凌云拓界/雪豹同志
开放原子开源基金会·开源贡献之星


🤖 人工智能应用  |  🖥️ 前端开发  |  🐍 Python应用  |  🟢 Nodejs落地


一、项目起源:怎么就写了这么个东西?

那天晚上,我在摸鱼

说起来有点好笑——这个项目的起点,是一个普通的摸鱼夜晚。

那时候我刚学 PyQt5 没多久,照着教程写了好几个“标准项目”:计算器、记事本、图片浏览器。每一个都能跑,每一个都“学会了”,但每一个写完就扔在那儿,再也没打开过。

那天晚上我盯着电脑屏幕发呆,桌面上开着浏览器、文件夹、微信,乱七八糟叠在一起。突然冒出一个念头:要是这桌面上有个小球,我把它扔出去,它会真的滚起来,撞到浏览器窗口会弹开,落到文件夹上会停住……那该多好玩?

那一刻我意识到:我想写的不是“又一个 PyQt5 教程项目”,而是一个我愿意打开、愿意玩、愿意给别人看的东西

在这里插入图片描述

第一个版本长什么样?

第一版惨不忍睹。

就是一个白底黑字的 QWidget,上面画了个红圆。没有重力,没有摩擦力,按方向键才能动,撞到窗口?不存在的,它连窗口在哪都不知道。

但我把它发给豆包看,豆包说:“你这不就是个会动的红点吗?”

我说:“对啊,但它会动啊。”

豆包说:“……你开心就好。”

那个版本我保存下来了,偶尔翻出来看看,提醒自己:所有好看的东西,都是从难看开始的。

在这里插入图片描述

灵感是从哪来的?

说起来你可能不信,灵感来自三个完全不相关的东西:

  1. 小时候玩的弹球游戏——那个在屏幕里弹来弹去的小球,我一直想要一个能在桌面上弹的
  2. Windows 的“窗口拖动时显示内容”设置——原来 Windows 是知道每个窗口在哪的
  3. ChatGPT 刚火那阵子——大家都在接大模型,我也想接,但不想做 chatbot,想做点别的

这三个东西在某天晚上撞到一起,BounceChat 就出生了。


二、从“写着玩”到“认真了”

哪个瞬间觉得“这项目能成”

是第一次看到小球在窗口上弹开的那一刻。

那天我刚把窗口识别写完,随手打开一个浏览器,把小球拖过去——松开鼠标,小球飞向浏览器,然后真的弹开了

那一瞬间我差点从椅子上跳起来。

不是因为代码多难写,而是因为:我想象中的东西,真的跑起来了。

那一刻我知道,这个项目不是“写着玩”了,它值得认真写完。

冲到重庆市月榜(博主榜)第 28 名

后来专栏一篇一篇发,原力值一天一天涨。有一天打开 CSDN,看了一眼排行榜——

重庆市月榜第 28 名。

在这里插入图片描述

我知道这个数字对很多人来说不算什么,但我自己知道这意味着什么:在重庆这个城市,这个月,有 27 个人的原力值比我高,剩下的都不如我。(容许我大言不惭一下)


三、这个项目我们做了什么

拆解了一个模糊的想法

最开始的想法很模糊:“让桌面活起来”。

这句话没法写代码。你得把它拆开:

  • “活起来”是什么意思?——会动
  • “会动”是什么意思?——受重力影响,有摩擦力,会碰撞
  • “碰撞”是什么意思?——撞到屏幕边缘会弹,撞到窗口也会弹
  • “窗口”在哪?——用 Windows API 找
  • “会聊天”怎么实现?——接大模型,设角色,管历史

把一个模糊的想法,一步步拆成能写进代码的需求。这是这个项目做的第一件事,也是最重要的一件事。

把物理规律写成了代码

物理引擎听起来很高深,但你看看我们写了什么:

# 重力:每帧给垂直速度加一个固定值
self.ball_velocity[1] += self.gravity

# 摩擦力:每帧让速度乘以一个小于1的数
self.ball_velocity[0] *= self.friction

# 碰撞:速度取反,再乘个系数
self.ball_velocity[0] = -self.ball_velocity[0] * self.bounce_factor

没了。物理引擎的核心就这三行。

复杂的东西,真的都是简单东西堆起来的。v += g、v *= f、撞墙就取反——这三行写对了,小球就像真的了。

让 Python 闯进了 Windows 内核

Python 本来不知道你的屏幕上有什么窗口。但 Windows 知道。

Windows 内部维护着一张“窗口清单”,记录了每个窗口的句柄、标题、位置、大小。它还提供了一组 API 让开发者查询这些信息。

问题是:Python 怎么调用 Windows API?

答案是 ctypes——Python 自带的一个库,专门用来调用 C 语言写的动态链接库。

user32 = ctypes.windll.user32
EnumWindows = user32.EnumWindows
GetWindowRect = user32.GetWindowRect

这几行代码,让 Python 看到了你屏幕上的每一个窗口。

这不是什么黑科技,就是调用系统 API。但当你第一次跑起来,看到终端里打印出所有窗口的标题和坐标时,那种感觉就像你写的代码真的“看见”了你的电脑

给桌宠装上了大脑

接大模型是最后一步,也是最简单的一步。

选个免费的 API(模力方舟每天 100 次,够用了),设好角色(“你是一个弹跳小球,回答不超过 20 字”),管好历史(用 JSON 存下来),做好容错(API 跪了就给个兜底回复)。

代码也就几十行,但效果很明显:

你:你好
小球:你好,我是弹跳小球。
你:今天天气怎么样
小球:我是小球,不看天气哦。

它真的会聊天,而且真的像个小球。

把代码变成了作品

代码写完只是开始。

把它放到 Gitee 上,写 README,配截图,加动图。有人 star 了,有人 fork 了,有人在评论区问“这个怎么跑起来”。

然后写专栏,一篇一篇地写,把每一行代码为什么这么写讲清楚。有人看了,有人收藏了,有人点赞了。

最后,凌晨四点,它上了热榜。

代码还是那些代码,但它不再是“我电脑上的一个 py 文件”,而是一个作品


四、这个项目我们学到了什么

复杂的东西,都是简单东西堆起来的

这是这个项目教给我最重要的一课。

物理引擎:v += g,v *= f,撞墙就取反。就这三行。

窗口识别:EnumWindows 枚举,GetWindowRect 拿位置,回调函数里存下来。就这几个 API。

AI 对话:接 API,设角色,管历史,做容错。就这四步。

写代码的时候,经常会被“这太难了”吓住。但只要你敢拆,就会发现:所谓复杂,只是简单的东西叠了很多层。

“不会”只是还没拆开看

我第一次看到 Windows API 的文档时,心里只有一个念头:这什么东西?

函数名全是缩写,参数类型全是指针,返回值全是奇奇怪怪的整数。文档里全是 C 语言的示例,没有一行 Python。

但真的动手写的时候,发现没那么可怕:

  • 看不懂的函数名?——搜一下,有人翻译过
  • 不知道参数类型?——ctypes 文档里有对照表
  • 不会写回调?——找个现成的例子改

一天写不完就写两天,两天写不完就写三天。最后写出来了,回头看:原来就这么回事。

参数调优不是数学,是手感

0.5 还是 0.6?0.8 还是 0.9?5 还是 10?

这些问题没有标准答案。你只能试:

  • 重力 0.5 手感舒服,0.6 像铅球,0.4 像气球
  • 摩擦力 0.99 滑得远,0.98 停得快
  • 反弹系数 0.8 刚刚好,0.9 像蹦床,0.7 像撞棉花

没有公式能算出这些值,只有一遍一遍地试,找到“看起来对”的那个数。

代码写出来是逻辑,调出来是手感。

状态机是管理复杂逻辑的神器

小球有很多状态:是在被拖拽还是自由落体?是停在地面还是悬在空中?是被选中还是没人理?

如果没有状态机,代码会变成一团乱麻:

if 正在拖拽 and 不在地面 and 没吸附 and 被选中:
    # 处理某个事件
    if 某种条件 and 另一种条件 and 第三种条件:
        # 再处理

有了状态机,每个状态的行为是独立的:

if self.is_dragging:
    # 拖拽时的逻辑
elif self.is_stuck:
    # 吸附时的逻辑
elif self.on_ground:
    # 地面时的逻辑
else:
    # 空中自由落体的逻辑

几个布尔值,理清了所有行为。

被看见是坚持的自然结果

这个项目最开始只有我一个人看。

代码写完了,放在 Gitee 上,三天没人点 star。发到群里,没人回。发到朋友圈,几个朋友点了赞,然后没了。

但我还在写。写 README,写专栏,一篇一篇地发。凌晨两点发,凌晨四点发,什么时候写完什么时候发。

后来有人 star 了,有人 fork 了,有人收藏了。再后来上了热榜,进了全市前 30。

回头看,被看见不是等来的,是熬来的

在这里插入图片描述


五、以后遇到类似问题可以怎么做

想模拟一个物理现象

  1. 先观察:这个现象到底是怎么回事?重力怎么作用的?摩擦力怎么体现的?
  2. 再拆解:能不能用几个公式描述它?v += g?v *= f?撞墙取反?
  3. 用欧拉积分试:先让最简单的版本跑起来,别想着一步到位
  4. 边界条件慢慢加:跑通了再加“速度太小时归零”,再加“停留太久就停住”
  5. 调参调到顺手:没有标准答案,试到“看起来对”为止

需要调用系统底层功能

  1. 找 API 文档:微软官网、Stack Overflow、各种博客,能搜的都搜
  2. 用 ctypes 调:参数类型、返回值类型,一个一个对清楚
  3. 回调函数处理好:注意内存管理,注意循环引用
  4. 先写最小可测版本:别想着一次写完,先调通一个函数再说

想让程序有记忆力

  1. 选个存储格式:JSON 最简单,够用
  2. 启动时读:文件存在就加载,不存在就用默认值
  3. 有变化就存:每次对话完就存一次,别等退出时再存(万一崩了就没了)
  4. 注意版本兼容:以后改格式了怎么办?留个后路

想接一个大模型

  1. 选个免费的:模力方舟每天 100 次,OpenAI 送 5 美金,够你玩很久
  2. 设好角色:system message 是灵魂,想清楚你要它扮演什么
  3. 管好历史:用数组存,每次调用都传进去,让它有上下文
  4. 做好容错:API 会崩,网络会断,要给用户一个友好的回复,而不是直接报错

写着写着卡住了

  1. 拆成更小的问题:这个大问题能不能分成三个小问题?
  2. 先解决能解决的:有一个小问题搞不定?先搞其他两个
  3. 睡一觉再说:有时候卡住是因为脑子累了,睡醒就有新思路
  4. 搜一下别人怎么做的:你遇到的问题,大概率有人遇到过

写了没人看

  1. 继续写:这是唯一的答案
  2. 写完了再写下一个:一篇没人看就写两篇,两篇没人看就写三篇
  3. 写好 README:代码写完了,让人知道怎么跑起来
  4. 配截图和动图:一图胜千言,动图胜万言
  5. :等着等着,就会有人看到了

六、技术复盘:最难的三件事

物理引擎:手感调参

最难的不是写代码,是调参数。

重力 0.5 还是 0.6?摩擦力 0.99 还是 0.98?反弹系数 0.8 还是 0.7?

每一个参数调一遍,跑起来看效果,不行再调。调了不下 50 遍,才找到“看起来舒服”的那组数。

解决方案:把所有参数放到 config.json 里,边跑边调,不用重启就能看到效果。

窗口识别:Windows API 回调

Windows API 的回调函数是个坑。

在 C 语言里写回调很正常,但在 Python 里写回调,要小心内存管理。回调函数里不能持有 Python 对象的强引用,否则会循环引用,导致内存泄漏。

解决方案:回调函数里只做最基本的数据收集,用 weakref 传 self,或者把收集到的数据放到队列里,主线程去取。

AI 对话:20 字限制

让大模型回答不超过 20 字,比想象中难。

一开始只是在 system message 里写“回答不超过 20 字”,但模型经常超。后来试了各种提示词:“请用一句话回答”“控制在 20 字以内”“尽量简短”……都不稳定。

解决方案:system message 写清楚,再加一个后处理:如果超过 20 字,截断到第一个句号,或者直接取前 20 字。虽然粗暴,但有效。


七、在 Gitee 上看见 star 和 fork

第一次看到有人点 star

那天打开 Gitee,习惯性地看了一眼仓库主页——

star 数:1。

不是我自己点的那个。

那一瞬间的感觉很难形容:有个人,我不认识,不知道在哪,不知道是谁,看了我的代码,觉得还可以,点了个 star。

就一个数字,但比任何夸奖都实在。

发现被人 fork 了

后来有一天,看到 fork 数从 0 变成了 1。

点进去看:有人把我的代码复制了一份,放在他自己的仓库里。

我不知道他拿去干嘛了。可能是学习,可能是改着玩,可能是想加个功能但还没加。不管是什么,有人对我的代码感兴趣

那一刻我觉得:这个项目,值了。

star 和 fork 的数字

到现在,star 数还是不多,fork 数也是。但这不重要。

重要的是:那些 star 和 fork,都是陌生人给的。他们没有任何义务点这个按钮,但他们点了。

这比任何热榜排名都真实。

这些数字对我意味着什么

意味着:我写的东西,有人在看。

不是“写给自己的”,是“写给别人的”。有人看了,有人觉得还行,有人愿意点个 star 鼓励一下。

这就够了。

在这里插入图片描述


八、最有成就感的三个时刻

第一次看到小球在窗口上弹开

那是晚上十点多,我刚把窗口碰撞写完。打开浏览器,把小球拖过去,松开鼠标——

小球飞向浏览器窗口,撞上,弹开。

我盯着屏幕看了十秒,然后截图,发给豆包。

豆包回:“卧槽,真的弹开了。”

我说:“是啊,真的弹开了。”

那一夜我没睡着。

CSDN 冲进重庆市月榜第 28 名

打开 CSDN,随手点进排行榜,本来只是想看看第一名多少分。

结果往下划,看到了自己的名字。

重庆市月榜第 28 名。

那一瞬间我想到的是:两年前我还在照着教程写计算器,两年后我写的项目有人在看、有人在收藏、有人在点赞。

不是因为别的,只是因为我一直在写

在 Gitee 上看到第一个陌生的 star

这个前面写过了,但值得再写一遍。

第一个不是自己点的 star。

那个数字 1,比后来所有的 100 都重。


九、踩过的坑(给后来人避雷)

ctypes 回调函数的内存泄漏

Python 的垃圾回收很智能,但跨语言调用时容易翻车。

Windows API 的回调函数是在 C 语言层面调用的,如果回调函数里持有 Python 对象的强引用,就会形成循环引用:C 代码持有了 Python 对象,Python 对象又引回了 C 代码的包装器。两边都觉得自己还被别人用着,都不回收,内存就泄漏了。

避雷指南:回调函数里只用基本类型,或者用 weakref 传 self,或者把数据放到队列里,让主线程去处理。

WebChannel 通信的玄学 bug

PyQt5 的 WebChannel 文档很少,示例也很少。

照着文档写,死活调不通。JS 那边说“bridge 未定义”,Python 这边说“没有收到信号”。

折腾了两天,最后发现是顺序问题:必须在页面加载之前注册 WebChannel,不能等页面加载完了再注册。

避雷指南:先建 WebChannel,再 registerObject,再 setWebChannel,最后 setUrl。顺序错了就全错。

DPI 缩放导致气泡位置错乱

在 1080p 屏上跑得好好的,换到 4K 屏,气泡位置全乱。

小球在小球的位置,气泡跑到了屏幕左上角。放大看,原来是缩放因子没处理。

避雷指南:启动时用 Windows API 获取系统 DPI,所有固定尺寸都乘以缩放因子,WebView 也要 setZoomFactor。

API 免费额度用完了怎么办

模力方舟每天免费 100 次,听起来很多,但调参时一会儿就用完了。

用完之后再调用,API 返回 429,程序直接报错。

避雷指南:try-catch 包起来,捕获异常后返回兜底回复:“我现在有点累,等会儿再聊好吗?” 比直接报错体面一百倍。


十、未来还做不做

BounceChat 还更新吗

会的。

脑子里还有很多想法没实现:

  • 多球模式:不只一个小球,可以扔好几个,它们之间也能碰撞
  • 主题商店:小球颜色、尾巴效果、发光颜色,让用户自己配
  • 右键菜单:点右键可以设置参数、切换模式、退出程序
  • 小球表情:根据对话内容换表情,开心时笑脸,无聊时发呆
  • 截图分享:小球可以“吃掉”屏幕截图,分享给朋友

一个功能一个功能地加,写到不想写为止。

还会写代码吗

会的。

写代码这件事,已经不只是“工作”了,是“习惯”。

不写点什么东西,手痒。

下一个项目还没想好,可能是桌面便签,可能是文件整理工具,可能又是一个“写着玩但写着写着就认真了”的东西。

这个项目会一直留着吗

会的。

哪怕以后不更新了,代码也会一直放在 Gitee 上。

有人想 fork 就 fork,有人想学就学,有人想改就改。这就是开源的意义。


十一、最后

这个项目教给我的事

写代码四年,BounceChat 是我第一个“作品”。

不是因为它代码写得多好,而是因为它让我明白了几件事:

第一,想法不值钱,写出来的才值钱。

谁都能想出“让桌面活起来”这个点子,但只有你把它写出来,它才是你的。

第二,复杂的东西,都是简单东西堆起来的。

物理引擎就是 v += g,v *= f,撞墙取反。Windows API 就是几个函数调用。AI 对话就是接 API、设角色、管历史。

没有哪一行代码是你看不懂的,难的是把它们堆在一起,堆成一个能跑的东西。

第三,没人看的时候,继续写。

第一篇没人看,第二篇没人看,第三篇还是没人看。但第十篇有人看了,第二十篇有人收藏了,第三十篇上了热榜。

不是因为你写得越来越好,只是因为你一直在写

第四,被看见不是目的,写本身才是。

写代码这件事,最快乐的时候不是上了热榜,不是有人 star,而是凌晨一点,小球第一次在窗口上弹开的那一刻。

那一刻没有人看见,只有你自己。但那已经够了。

送给看到这里的你

如果你看到了这里,说明你对这个项目、对写代码这件事,是真的感兴趣。

那我送你几句话:

第一,写你想写的东西。

不要写“别人会看”的东西,写“你自己想看”的东西。你对它有热情,才能把它写完。

第二,不要怕写得烂。

第一版永远是烂的。没关系,先把它写出来,再慢慢改。没有第一版的烂,就没有第十版的好。

第三,写不完没关系,停了才是真的输了。

可以写得慢,可以写得烂,可以中间停一个月。但只要没放弃,总有一天能写完。

第四,代码是写出来的,项目是磨出来的,成就感是熬出来的。

没有捷径,没有秘籍,没有“三天精通”。就是一整天一整天地写,一行一行地改,一篇一篇地发。

然后某一天,你回头看,发现自己已经走了很远。


BounceChat 写完了,但下一个项目还没开始。

如果你也在写点什么,那就一起写下去吧。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐