想让自己家的猫住进屏幕里,在任务栏上走来走去、伸懒腰、打盹。这篇文章记录我从一张猫照片开始,做桌面宠物的完整过程,包括素材、程序、踩过的坑。

从零制作桌面宠物文章封面

一、先把问题拆开:桌面宠物 = 素材 + 程序 + 行为

一开始我以为桌面宠物就是"一张会动的图",动手后才发现它有三层,每层独立可做、可以换:

桌面宠物的素材、程序与行为三层结构

想清楚这三层之后,就可以分头开工,互不干扰。

二、素材篇:从一张猫照片到动作动画

2.1 我的路线:AI 从照片生成动作短片

我家猫是只圆滚滚的白灰色折耳猫,拍照很好看,但照片是静态的。我的思路是:用 AI 图生视频,从照片生成"走路、跳跃、打招呼、玩耍、伸懒腰、坐下、睡觉"等动作短片,再抠成透明素材。

听起来很顺,但实操里全是细节。下面这套规范,是我用三轮试错换来的:

2.2 素材的硬性规范(血泪版)

  1. 每一帧,猫的身体必须完整在画面内——头顶、尾巴尖都不许出画。AI 生成视频时很容易把尾巴甩出画面,废片率最高的一项。
  2. 绿幕背景每一帧都要铺满,不能有穿帮,方便后面抠像。
  3. 所有动作里猫的体型必须一致,不然切动作时会突然变大变小,非常出戏。
  4. 构图按动作选:横向动作(走路、玩耍、伸懒腰)用横版;纵向动作(跳上、跳下)用竖版。横版硬套竖版内容,身体边缘会被挡住。
  5. 统一尺寸和帧率,8 个动作一个标准,程序里才好处理。
从猫照片到透明动画的素材制作流程

2.3 三轮试错实录

三轮下来,我决定先暂停这条路线。不是 AI 不行,是"照片→视频→抠像"这条链路对质检的要求极高,每一帧都要检查,工作量远超预期。

2.4 如果重来,我会这样做

桌面宠物不同动作的连续帧示意

三、程序篇:让猫在桌面上动起来

素材是皮,程序是骨。下面是一个最小可跑的思路,我用 Python + PySide6 实现,几十行代码就能让猫住进桌面。

3.1 窗口三件套

self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
self.setAttribute(Qt.WA_TranslucentBackground)

无边框、背景透明、永远置顶——桌面宠物的窗口就这三行。Qt.SubWindow 是为了让它不抢任务栏图标。

3.2 动画播放:一个 QLabel + QMovie 就够了

每个动作对应一个透明 GIF,切换动作就是换一个文件。GIF 尺寸变化时调一下 adjustSize(),窗口跟着变。

3.3 行为状态机

桌面宠物行为状态机

核心就是一个定时器:每 60ms 走一拍,走动时平移窗口,到屏幕边缘回头;每隔几秒随机切一次状态(待机/走动/睡觉);双击切换到"打招呼";长时间不动就睡觉,被拖拽或点击就醒来。

3.4 完整示例代码(可直接运行)

import random
import sys
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QMovie, QAction
from PySide6.QtWidgets import QApplication, QLabel, QMenu

# 动作名 -> 对应 GIF 文件(第二章做好的透明素材放 assets/ 里)
GIF = {
    "idle":  "assets/idle.gif",
    "walk":  "assets/walk.gif",
    "greet": "assets/greet.gif",
    "sleep": "assets/sleep.gif",
}

class DesktopPet(QLabel):
    def __init__(self):
        super().__init__()
        self.movie = QMovie()
        self.setMovie(self.movie)

        # 窗口三件套:无边框 + 透明 + 置顶
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
        self.setAttribute(Qt.WA_TranslucentBackground)

        self._drag = None
        self._vx = 0          # 走动时的水平速度
        self._cooldown = 0
        self.set_state("idle")
        self.move(900, 600)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self._tick)
        self.timer.start(60)

    def set_state(self, name):
        self.movie.stop()
        self.movie.setFileName(GIF[name])
        self.movie.start()
        self.adjustSize()
        self._vx = random.choice([-4, 4]) if name == "walk" else 0

    def _tick(self):
        if self._vx:  # 走动:平移窗口,到边缘回头
            x = self.x() + self._vx
            if x < 0 or x + self.width() > self.screen().size().width():
                self._vx = -self._vx
            self.move(x, self.y())
        self._cooldown -= 1
        if self._cooldown <= 0:
            self.set_state(random.choice(["idle", "idle", "walk", "sleep"]))
            self._cooldown = random.randint(60, 140)

    # 拖拽:按下记偏移,移动跟鼠标
    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self._drag = e.globalPosition().toPoint() - self.pos()

    def mouseMoveEvent(self, e):
        if self._drag:
            self.move(e.globalPosition().toPoint() - self._drag)

    def mouseReleaseEvent(self, e):
        self._drag = None

    def mouseDoubleClickEvent(self, e):
        self.set_state("greet")
        self._cooldown = 80

    # 右键菜单:手动切动作 / 退出
    def contextMenuEvent(self, e):
        menu = QMenu(self)
        for name in GIF:
            act = QAction(f"切换到:{name}", self)
            act.triggered.connect(lambda _=False, n=name: self.set_state(n))
            menu.addAction(act)
        menu.addSeparator()
        quit_act = QAction("退出", self)
        quit_act.triggered.connect(QApplication.quit)
        menu.addAction(quit_act)
        menu.exec(e.globalPos())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    pet = DesktopPet()
    pet.show()
    sys.exit(app.exec())

运行前:pip install PySide6,把做好的透明 GIF 按名字放进 assets/ 文件夹,python main.py 就能看到猫了。

3.5 可以继续加的功能

四、在 macOS 上跑起来:被系统拦截怎么办

自己写的程序没有开发者签名,macOS 第一次运行大概率拦截。按顺序试:

  1. 右键 → 打开:在访达里对着程序右键选择"打开",会多一个"仍要打开"的选项,这是最正规的单次放行。
  2. 仅处理自己构建且确认可信的程序:如果程序确由自己构建或来源完全可信,但复制后仍帶圉隔离标记,可谨慎执行:
    xattr -cr /Applications/你的程序.app
    不要对来源不明的程序使用这条命令。
  3. 本地临时签名:仅用于自己测试的打包程序,可以添加临时签名;它不能替代正式的开发者签名、公证和安全审核:
    codesign --force --deep --sign - /Applications/你的程序.app

打包建议用 PyInstaller:

pip install pyinstaller
pyinstaller --windowed --name DesktopPet main.py

--windowed 表示不要弹终端窗口。注意把 assets/ 资源一起打进包(--add-data),否则换台机器 GIF 就找不到了。

五、开工 checklist


记录于 2026 年 10 月。从一张猫照片开始,到屏幕上多了一只猫——过程比结果有意思。