首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >做了一只哈基米桌宠

做了一只哈基米桌宠

原创
作者头像
繁依Fanyi
发布2025-05-02 16:17:52
发布2025-05-02 16:17:52
75210
代码可运行
举报
运行总次数:0
代码可运行

“在某个加班到深夜的晚上,我望着一成不变的桌面,心想:如果这儿有只可爱的小猫在走来走去、还能冲我眨眼,是不是整个编程体验都能变得柔和一些?”

一、灵感的诞生

小时候的电脑里,总有些奇奇怪怪又让人爱不释手的玩意儿,比如能陪你聊天的微软小助手 Clippy,或者是蹦来蹦去的小浣熊宠物。如今虽然操作系统越来越精简高效,但也未免显得太过冷清了。

这篇文章,记录了我如何用 PyQt 打造出一款“现代桌面宠物”的完整旅程。它不是玩具,也不是纯粹的代码练习,而是一次结合 GUI、动画、透明窗口、多线程、文件管理、逻辑控制等诸多知识点的 桌面宠物开发实战

而最重要的是,它真的能在你电脑上“活起来”。


二、项目构思与功能概览

我打算做一只可以:

  • 在桌面自由走动的小猫咪(随机移动)
  • 可以通过右键菜单进行交互(喂食、聊天、换衣服等)
  • 能根据时间段变换状态(比如深夜就犯困)
  • 点击会有反应(如喵一声或弹出提示)
  • 自带优雅的启动动画、灵动的 UI 与换肤支持

为了更直观地理清楚流程,我画了一张最初的功能模块图:

在这里插入图片描述
在这里插入图片描述

这个架构并不复杂,但覆盖的技术点很多,需要我们精细地设计每一个细节。


三、环境准备与基础设置

首先,确定技术栈与开发环境:

  • 语言:Python 3.11+(看自己)
  • GUI 框架:PyQt5(稳定成熟)
  • 开发工具:PyCharm + QSS 样式 + 动图资源 + 图床(演示图像)
  • 依赖库
    • PyQt5
    • pygame(用于音效播放)
    • QMovie(用于动态 gif 播放)

安装基础库

代码语言:bash
复制
pip install PyQt5 pygame

准备好环境后,我们就可以动手构建窗口。


四、打造“透明贴图窗口”

一个桌面宠物的核心,就是它必须“漂浮”在桌面上,并且没有任何系统边框。于是我们需要创建一个 无边框+透明背景 的窗口。

创建主窗口

代码语言:python
代码运行次数:0
运行
复制
from PyQt5.QtWidgets import QApplication, QLabel, QWidget
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import Qt, QTimer, QPoint
import sys

class PetWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.FramelessWindowHint | 
                            Qt.WindowStaysOnTopHint | 
                            Qt.Tool)  # 无边框、置顶、不出现在任务栏
        self.setAttribute(Qt.WA_TranslucentBackground, True)

        self.label = QLabel(self)
        self.movie = QMovie("assets/cat_idle.gif")
        self.label.setMovie(self.movie)
        self.movie.start()

        self.resize(self.movie.currentPixmap().size())
        self.setMouseTracking(True)

        self.offset = None  # 拖动支持

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.offset = event.pos()

    def mouseMoveEvent(self, event):
        if self.offset:
            self.move(self.pos() + event.pos() - self.offset)

    def mouseReleaseEvent(self, event):
        self.offset = None

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

这段代码完成了以下功能:

  • 创建一个透明窗口
  • 播放 gif 动画(作为宠物形象)
  • 支持点击拖动窗口
  • 始终置顶显示

至此,一只“傻乎乎地站着”的猫咪已经出现在桌面上了。


接下来,我要带着这只“逛桌面”的小猫,教你如何让它“活”起来——会自己闲逛、会打盹、还会偶尔伸个懒腰。要实现这些,我们需要一个状态机来管理宠物的各种行为,然后再用定时器驱动动画切换与位置更新。


五、宠物行为状态机与随机漫步

设计思路

我希望猫猫能以“吟游诗人”的姿态漫步桌面:有时候它在窗边打个盹,有时候它闲庭信步,还会不经意地伸个懒腰。于是我给它规划了三个主要状态:

  1. 闲逛(Idle):猫在当前坐标上下左右小范围游走。
  2. 打盹(Sleep):猫在某个固定位置静止,并播放打盹动画。
  3. 伸懒腰(Stretch):偶尔一次,从打盹切换到伸腰动画后再回到闲逛。

描绘一下这个状态机的样子:

在这里插入图片描述
在这里插入图片描述

这个状态机很简单,但能让猫猫显得生动。接下来,我们要用 Python + PyQt 的定时器来驱动它。

实现状态机

首先,在 PetWidget 类中增加一个内部状态管理器和定时器:

代码语言:python
代码运行次数:0
运行
复制
import random
from PyQt5.QtCore import QTimer, QRect

class PetWidget(QWidget):
    def __init__(self):
        # … 前面透明窗口与动画设置不变 …
        
        # --- 行为状态机 ---
        self.state = "Idle"           # 当前状态
        self.energy = 100             # “精力”值,下降到0时进入 Sleep
        self.state_timer = QTimer()   # 驱动状态逻辑
        self.state_timer.timeout.connect(self.update_state)
        self.state_timer.start(500)   # 每 0.5s 调度一次

        # 位置与移动
        self.move_timer = QTimer()    # 用于平滑移动
        self.move_timer.timeout.connect(self.random_walk)
        self.move_timer.start(50)     # 每 0.05s 更新坐标

    def update_state(self):
        """根据当前状态和条件切换行为状态,并更新对应动画。"""
        if self.state == "Idle":
            self.energy -= 1
            # 当精力耗尽,进入打盹
            if self.energy <= 0:
                self.transition_to("Sleep")
            # 5% 概率触发伸懒腰
            elif random.random() < 0.05:
                self.transition_to("Stretch")

        elif self.state == "Sleep":
            # 睡 10 次调度后醒来
            if self.energy >= 50:
                self.transition_to("Idle")
            else:
                self.energy += 5

        elif self.state == "Stretch":
            # 伸懒腰一次后恢复闲逛
            self.transition_to("Idle")

    def transition_to(self, new_state):
        """切换状态并加载相应动画。"""
        self.state = new_state
        if new_state == "Idle":
            self.movie.stop()
            self.movie = QMovie("assets/cat_idle.gif")
            self.movie.start()
        elif new_state == "Sleep":
            self.movie.stop()
            self.movie = QMovie("assets/cat_sleep.gif")
            self.movie.start()
        elif new_state == "Stretch":
            self.movie.stop()
            self.movie = QMovie("assets/cat_stretch.gif")
            self.movie.start()

        # 每次切换都要调整窗口大小,以适应不同动作帧
        self.resize(self.movie.currentPixmap().size())

    def random_walk(self):
        """在 Idle 状态下,猫进行随机漫步;其他状态不移动。"""
        if self.state != "Idle":
            return

        # 当前窗口位置
        x, y = self.x(), self.y()
        # 随机生成一个微小位移
        dx = random.randint(-5, 5)
        dy = random.randint(-5, 5)
        # 限制不超出屏幕范围(假设 1920x1080)
        screen_w, screen_h = 1920, 1080
        new_x = max(0, min(x + dx, screen_w - self.width()))
        new_y = max(0, min(y + dy, screen_h - self.height()))
        self.move(new_x, new_y)
代码解析

我在 __init__ 中新增了两个定时器:

  • state_timer:每 500ms 触发一次,用于消耗“精力”、判断何时进入打盹或伸腰,并完成状态切换。
  • move_timer:每 50ms 触发一次,用于在闲逛状态下不断更新位置,形成平滑的移动效果。

update_state 方法根据当前 self.state 执行不同逻辑。为了让猫猫不至于一直打盹不醒,我设计了一个“精力”数值,越睡越充电;当“精力”充满了一半,就重新开始闲逛。

transition_to 方法负责加载并播放对应状态的 GIF 动画,同时根据新动画的画布大小重新调整窗口尺寸,保证不会出现画面裁剪。

random_walk 则是在闲逛状态下,随机生成 -5~5 像素的偏移,并且做屏幕边界检查,避免猫猫跑出桌面可见区域。

到此为止,我们的猫猫已经能自己在桌面上“走动”—走着走着一累就打个盹,偶尔还会伸个懒腰。为了让整个体验更丰富,下面将加入与用户交互的托盘菜单和右键菜单,以及音效反馈。


六、托盘与右键菜单:让猫猫与你互动

一个只会自己晃来晃去的桌面宠物还是略显孤零零,于是我决定:

  1. 在任务栏托盘(System Tray)里放一个猫爪图标,右键可快速退出或打开设置。
  2. 点击动画窗口本身弹出一个小菜单,支持“喂食”、“换皮肤”、“查看今日心情”等操作。

系统托盘图标

PyQt5 里使用 QSystemTrayIcon 很方便。先在主程序里初始化托盘:

代码语言:python
代码运行次数:0
运行
复制
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction
from PyQt5.QtGui import QIcon

class PetApp(QApplication):
    def __init__(self, argv):
        super().__init__(argv)
        # 创建主窗口
        self.pet = PetWidget()
        self.pet.show()

        # --- 系统托盘 ---
        self.tray = QSystemTrayIcon(QIcon("assets/tray_icon.png"), self)
        self.tray.setToolTip("我的小猫咪")
        tray_menu = QMenu()
        action_show = QAction("显示/隐藏 猫猫")
        action_quit = QAction("退出")
        tray_menu.addAction(action_show)
        tray_menu.addAction(action_quit)
        self.tray.setContextMenu(tray_menu)
        self.tray.show()

        action_show.triggered.connect(self.toggle_pet)
        action_quit.triggered.connect(self.exit_app)

    def toggle_pet(self):
        if self.pet.isVisible():
            self.pet.hide()
        else:
            self.pet.show()

    def exit_app(self):
        self.pet.close()
        self.quit()

这样,右下角就会出现一个猫爪图标。右键它,就能快速隐藏或退出应用,省去了找窗口的麻烦。

右键弹出菜单

为了更直接的互动,我在 PetWidget 里重写了 contextMenuEvent

代码语言:python
代码运行次数:0
运行
复制
    def contextMenuEvent(self, event):
        menu = QMenu(self)
        feed = menu.addAction("喂食 🍣")
        mood = menu.addAction("查看心情 💭")
        skin = menu.addAction("换个皮肤 🎨")
        action = menu.exec_(event.globalPos())

        if action == feed:
            self.on_feed()
        elif action == mood:
            self.on_mood()
        elif action == skin:
            self.on_skin_change()

    def on_feed(self):
        """喂食后精力 +20,上限 100,并播放吃东西动画 & 音效。"""
        self.energy = min(100, self.energy + 20)
        self.movie.stop()
        self.movie = QMovie("assets/cat_eat.gif")
        self.movie.start()
        QTimer.singleShot(2000, lambda: self.transition_to("Idle"))
        # 播放音效
        pygame.mixer.Sound("assets/eat.wav").play()

    def on_mood(self):
        """显示一个气泡,随机输出一句“心情语句”。"""
        phrases = ["今天心情不错!", "有点想睡觉…", "好想要鱼干…"]
        text = random.choice(phrases)
        # 简易气泡提示
        QMessageBox.information(self, "小猫心情", text)

    def on_skin_change(self):
        """循环切换皮肤目录下的不同子文件夹。"""
        # … 读取 skin/ 下所有子目录,切换到下一个 …

经过这一步,猫猫不再是孤独的打工人:你可以喂它🍣,它会“咕噜咕噜”吃掉;你可以问它心情,它会给你一个萌萌的回复;你还能随时给它换新衣服。


七、声音,让猫猫“有声有色”

我一直觉得,动画如果没有声音,就像动画片没配音,总缺点什么。于是,给猫猫加上音效和简易的语音反馈,立刻让它从“纯视觉动画”跃升成了“多感官”交互小伙伴。

1. 音效播放:pygame 的“小能手”

PyQt 自带的 QSound 虽然能播放 WAV,但用起来灵活性和兼容性都不够。朋友推荐我用 pygame.mixer,它能同时加载多个音效,还能控制音量、循环次数,非常适合这种场景。

在程序最开始,我加上一段初始化音频的代码:

代码语言:python
代码运行次数:0
运行
复制
import pygame

# 在 QApplication 之前初始化 pygame
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)

这样,后续在任何交互函数里,只要一句

代码语言:python
代码运行次数:0
运行
复制
pygame.mixer.Sound("assets/meow.wav").play()

就能放出一声可爱的“喵~”。我还给猫猫的伸懒腰、打盹、吃东西等动作都配了专属音效,每次状态切换、交互反馈,桌面就像开了“猫猫声音秀”。

2. 语音反馈:文本到语音的趣味尝试

想让猫猫更“聪明一点”,我又尝试接入了一个简易的 TTS(Text-to-Speech)模块,让它在某些互动时“开口说话”。出于轻量考量,我用的是系统自带的命令行 TTS——macOS 下的 say,Windows 下的 SAPI.SpVoice

下面是调用 macOS say 的示例(封装在一个工具函数里):

代码语言:python
代码运行次数:0
运行
复制
import subprocess
import platform

def speak(text):
    system = platform.system()
    if system == "Darwin":  # macOS
        subprocess.Popen(["say", text])
    elif system == "Windows":
        # Windows 平台请自行安装 pywin32 并使用 Dispatch
        from win32com.client import Dispatch
        speaker = Dispatch("SAPI.SpVoice")
        speaker.Speak(text)
    else:
        # 其他平台暂不支持
        pass

on_mood 方法里调用:

代码语言:python
代码运行次数:0
运行
复制
def on_mood(self):
    phrases = ["今天心情不错!", "想晒晒太阳…", "有点想鱼干…"]
    text = random.choice(phrases)
    speak(text)  # 猫猫突然“开口”!
    QMessageBox.information(self, "小猫心情", text)

刚加入这段后,我打开程序,轻轻右键点“查看心情”,居然听到一声“今天心情不错!”,那一刻,我仿佛回到了怦然心动的童年游戏时光——原来,给 GUI 配上声音,就是这么有魔力。


八、用 QSS 打造统一现代的视觉风格

在我看来,良好的 UI 不仅要“能用”,更要“好看且一致”。PyQt 的控件默认样式虽然靠谱,但如果你想让它更时尚,就得动手写 QSS(Qt Style Sheets)。

1. 全局样式加载

先在主程序入口加载一个全局 QSS 文件,让所有控件都套上主题:

代码语言:python
代码运行次数:0
运行
复制
def load_stylesheet(path):
    with open(path, "r", encoding="utf-8") as f:
        style = f.read()
    app.setStyleSheet(style)

if __name__ == "__main__":
    app = PetApp(sys.argv)
    load_stylesheet("styles/theme.qss")
    sys.exit(app.exec_())

styles/theme.qss 的大体结构,我用了深色半透明+圆角+柔和过渡动画,看上去既现代又不刺眼:

代码语言:css
复制
QMenu {
    background: rgba(40, 40, 40, 0.8);
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 8px;
    padding: 5px;
}

QMenu::item {
    padding: 6px 30px 6px 20px;
    color: #eee;
}

QMenu::item:selected {
    background: rgba(255, 255, 255, 0.1);
}

QMessageBox {
    background: rgba(50, 50, 50, 0.9);
    border-radius: 10px;
}

QMessageBox QLabel {
    color: #fff;
    font-size: 14px;
    padding: 10px;
}

QSystemTrayIcon {
    /* 托盘图标无需 QSS,但可以定制提示气泡样式 */
}

心得:QSS 和 CSS 十分相似,但并不完全等价。控件类名、伪状态(如 :selected)都要照 Qt 文档写,否则不会生效。写深色主题时,注意文字和图标的对比度,别让用户看不清菜单项。

2. 右键菜单与提示框的统一

通过全局 QSS,QMenuQMessageBox 都能立即“皮肤化”。之前我写的那几个交互函数,在重启程序后,立刻显得高大上——菜单圆角柔和、文字带点发光感,就如同专业软件的交互提示。

在这里插入图片描述
在这里插入图片描述
键菜单。


九、资源管理:优雅组织你的帧动画与音效

随着项目功能越来越多,我的 assets/ 目录也逐渐“杂乱”起来:有十几套 GIF、各式各样的 WAV、还有皮肤文件夹...早晚要被自己搞丢。于是我给它设计了一个 ResourceManager,来统一管理路径、加载和热切换。

代码语言:python
代码运行次数:0
运行
复制
import os
import json

class ResourceManager:
    def __init__(self, base_path="assets"):
        self.base = base_path
        # 加载配置表,里边写明了每种状态对应的动画、音效
        with open(os.path.join(self.base, "resources.json"), "r", encoding="utf-8") as f:
            self.config = json.load(f)

    def get_animation(self, state, skin="default"):
        # e.g., self.config["default"]["Idle"] -> "default/idle.gif"
        rel = self.config.get(skin, {}).get(state, None)
        if rel:
            return os.path.join(self.base, skin, rel)
        else:
            # 回退到 default 皮肤
            rel = self.config["default"][state]
            return os.path.join(self.base, "default", rel)

    def get_sound(self, action):
        rel = self.config["sounds"].get(action, None)
        return os.path.join(self.base, "sounds", rel) if rel else None

assets/resources.json 示例如下:

代码语言:json
复制
{
  "default": {
    "Idle": "cat_idle.gif",
    "Sleep": "cat_sleep.gif",
    "Stretch": "cat_stretch.gif",
    "Eat": "cat_eat.gif"
  },
  "skin2": {
    "Idle": "cat2_idle.gif",
    "Sleep": "cat2_sleep.gif",
    "Stretch": "cat2_stretch.gif",
    "Eat": "cat2_eat.gif"
  },
  "sounds": {
    "meow": "meow.wav",
    "eat": "eat.wav",
    "stretch": "stretch.wav"
  }
}

在代码里,只要调用:

代码语言:python
代码运行次数:0
运行
复制
anim_path = res_mgr.get_animation(self.state, self.current_skin)
self.movie = QMovie(anim_path)

就能省去手动拼路径的烦恼,新增皮肤也只需在 JSON 中登记即可。


十、用户配置与状态持久化:让猫猫“记住”你

每次重启程序时,猫猫都重新从桌面中央“降落”,还得你手动再把它拖到喜欢的位置,多不自然?为此,我设计了一个 配置管理模块,它负责保存并加载用户偏好:包括猫猫的位置、当前皮肤、音量等级等,让猫猫像真正的小伙伴一样“记得”你的喜好。

1. 设计思路与流程图

我们要做到的其实很简单:

  1. 程序启动:尝试读取配置文件,恢复猫猫上次的位置、皮肤、音量。
  2. 用户交互:当用户拖动猫猫、切换皮肤或调节音量时,实时更新内存中的配置。
  3. 程序退出:将最新的配置写回磁盘,保证下次启动时依旧生效。
在这里插入图片描述
在这里插入图片描述

2. 代码实现

我把配置管理封装在一个 SettingsManager 类里,底层使用 JSON 存储,跨平台都可以无忧。

代码语言:python
代码运行次数:0
运行
复制
import os, json

class SettingsManager:
    def __init__(self, path="config.json"):
        self.path = path
        self.defaults = {
            "position": [100, 100],
            "skin": "default",
            "volume": 0.8
        }
        self.data = {}
        self.load()

    def load(self):
        """加载配置;若文件不存在,则使用默认并创建文件。"""
        if os.path.exists(self.path):
            with open(self.path, "r", encoding="utf-8") as f:
                try:
                    self.data = json.load(f)
                except json.JSONDecodeError:
                    self.data = self.defaults.copy()
        else:
            self.data = self.defaults.copy()
            self.save()

    def save(self):
        """将内存中的配置写入磁盘。"""
        with open(self.path, "w", encoding="utf-8") as f:
            json.dump(self.data, f, indent=4, ensure_ascii=False)

    def get(self, key):
        return self.data.get(key, self.defaults.get(key))

    def set(self, key, value):
        self.data[key] = value
PetWidget 中接入配置
代码语言:python
代码运行次数:0
运行
复制
class PetWidget(QWidget):
    def __init__(self, settings: SettingsManager):
        super().__init__()
        self.settings = settings

        # 还原位置
        x, y = self.settings.get("position")
        self.move(x, y)

        # 还原皮肤
        self.current_skin = self.settings.get("skin")

        # 还原音量
        volume = self.settings.get("volume")
        pygame.mixer.music.set_volume(volume)

        # …其他初始化…

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        # 在拖动结束后,保存位置
        if self.offset:
            pos = [self.x(), self.y()]
            self.settings.set("position", pos)
            self.settings.save()
            self.offset = None

    def on_skin_change(self):
        # 切换皮肤后,保存到配置
        skins = list(self.res_mgr.config.keys())
        idx = skins.index(self.current_skin)
        new_skin = skins[(idx + 1) % len(skins)]
        self.current_skin = new_skin
        self.settings.set("skin", new_skin)
        self.settings.save()
        # 重新加载当前状态对应的动画
        self.transition_to(self.state)

    def set_volume(self, vol: float):
        """外部可调用,例如在设置窗口里调节滑条后调用。"""
        pygame.mixer.music.set_volume(vol)
        self.settings.set("volume", vol)
        self.settings.save()

这样一来,无论你拖猫猫到哪,它都再也不会“迷路”;无论你换多少次皮,它都记得最新的造型;无论你调过多少次音量,下次启动也能立即生效。


十一、打包发布:让朋友也能轻松拥有

到这里,桌面宠物的功能已趋完整,接下来要做的就是打包,让没有 Python 环境、不会安装依赖的朋友也能一键运行。

1. 选择打包工具

我比较推荐两款工具:

  • PyInstaller:成熟、文档丰富,支持 Windows/macOS/Linux。
  • cx_Freeze:Python 官方推荐,兼容性好。

我以 PyInstaller 为例,讲讲我的打包流程。

2. PyInstaller 打包步骤

  1. 编写 spec 文件(可选,默认也行)
  2. 运行打包命令
  3. 调整资源路径
  4. 测试可执行文件

下面是我项目根目录下的 pet.spec 样例:

代码语言:python
代码运行次数:0
运行
复制
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None

a = Analysis(
    ['main.py'],
    pathex=['.'],
    binaries=[],
    datas=[
        ('assets/*', 'assets'),
        ('styles/*', 'styles'),
        ('config.json', '.')
    ],
    hiddenimports=['pygame', 'win32com']  # 如果用到 SAPI
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='DesktopPet',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    name='DesktopPet'
)

然后运行:

代码语言:bash
复制
pyinstaller pet.spec

打包完成后,会在 dist/DesktopPet 下生成一个可执行文件。你只需把整个文件夹打包成 ZIP,分享给朋友即可。

3. 常见坑与调试

  • 资源加载路径:打包后可执行文件的工作目录通常变了,要用 sys._MEIPASS 来定位临时提取目录。
  • 音频初始化失败:在某些 Linux 发行版上,可能需要安装系统级的音频库(如 libasound2)。
  • TTS 调用:macOS 下 say 命令默认可用,Windows 需打包 pywin32 并注意权限。

只要小心处理这些细节,就能顺利产出一个“免安装”的桌面宠物应用,让更多人来领养你的“猫猫”!


十二、扩展与插件化架构:让桌面宠物不只是“猫猫”

当我把猫猫“跑”得差不多后,突然萌生一个念头:若能支持 插件式扩展,让开发者轻松地给它增添新功能(比如天气预报、番茄钟、消息提醒等),那就更酷了。

1. 插件机制设计

我设计了一个简单的插件接口:

代码语言:python
代码运行次数:0
运行
复制
# plugin_interface.py

class BasePlugin:
    def __init__(self, pet_widget):
        self.pet = pet_widget

    def on_load(self):
        """插件加载时调用,可用于注册定时任务、添加菜单项等。"""

    def on_unload(self):
        """插件卸载时调用,用于清理资源。"""

    def on_event(self, event_type, **kwargs):
        """统一事件回调,可接收 state_change、menu_action 等事件。"""

项目目录下创建一个 plugins/ 文件夹,所有插件仅需放入 .py 文件,入口函数约定为 setup(plugin_manager)。核心的 PluginManager 会在启动时扫描该目录,动态加载插件:

代码语言:python
代码运行次数:0
运行
复制
import os, importlib.util

class PluginManager:
    def __init__(self, pet_widget):
        self.pet = pet_widget
        self.plugins = []

    def load_plugins(self):
        for file in os.listdir("plugins"):
            if file.endswith(".py"):
                path = os.path.join("plugins", file)
                spec = importlib.util.spec_from_file_location(file[:-3], path)
                mod = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(mod)
                plugin = mod.setup(self.pet)
                self.plugins.append(plugin)
                plugin.on_load()

    def unload_plugins(self):
        for plugin in self.plugins:
            plugin.on_unload()
        self.plugins.clear()

    def dispatch(self, event_type, **kwargs):
        for plugin in self.plugins:
            plugin.on_event(event_type, **kwargs)

在主程序中初始化并调用:

代码语言:python
代码运行次数:0
运行
复制
self.plugin_mgr = PluginManager(self)
self.plugin_mgr.load_plugins()

在合适的时机(状态切换、菜单点击等),调用 self.plugin_mgr.dispatch(...),插件就能收到事件并做出反应。

2. 示例插件:天气预报

为了展示插件能力,我写了一个“天气插件”,每小时自动获取一次天气并让猫猫在桌面上“打喷嚏”提示天气变化。

代码语言:python
代码运行次数:0
运行
复制
# plugins/weather_plugin.py
import threading, time
import requests  # 需在打包时加入 hiddenimports

from plugin_interface import BasePlugin

class WeatherPlugin(BasePlugin):
    def on_load(self):
        self.thread = threading.Thread(target=self.fetch_loop, daemon=True)
        self.thread.start()

    def fetch_loop(self):
        while True:
            try:
                data = requests.get("https://api.example.com/weather").json()
                temp = data["temp"]
                if temp < 5:
                    self.pet.speak("好冷,注意保暖!")
                    self.pet.transition_to("Stretch")  # 假装打喷嚏伸懒腰
                time.sleep(3600)
            except Exception:
                time.sleep(600)

    def on_unload(self):
        # 线程为 daemon,无需额外清理
        pass

def setup(pet_widget):
    return WeatherPlugin(pet_widget)

插件加载后,猫猫每到整点就会发布天气预报,进一步丰富了桌面宠物的“社交”属性。


十三、性能优化与资源占用监测

一个常驻内存的小程序,最担心的就是内存泄漏CPU 占用过高。为此,我花了点时间监测性能,并做了几项优化:

  1. 动画帧预加载
    • 初始时用 QMovie.setCacheMode(QMovie.CacheAll) 缓存所有帧,避免每帧都从磁盘加载。
  2. 定时器优化
    • 随机漫步时,将位置更新频率从 20ms 调整到 50ms,平滑度依然足够但 CPU 占用更低。
  3. 插件隔离
    • 将耗时或阻塞操作放到子线程,避免主线程卡顿。
  4. 资源释放
    • 切换动画或皮肤时,先 self.movie.stop(),再创建新 QMovie,防止旧对象挂留。

借助任务管理器,我把猫猫的内存及CPU占用很小,几乎感觉不到它的存在。


十四、总结与展望

到此为止,我们用 PyQt 构建了一只:

  • 造型可爱动画流畅 的桌面宠物
  • 状态丰富:闲逛、打盹、伸懒腰、吃东西
  • 交互多样:托盘菜单、右键菜单、TTS 语音、音效
  • 配置完善:位置、皮肤、音量持久化
  • 打包简单:PyInstaller 一键生成可执行文件
  • 插件化:可扩展天气、番茄钟等新功能
  • 性能出色:内存小、CPU 低,常驻桌面毫无压力

整个项目融合了 GUI 设计多线程文件 I/O资源管理打包部署 等多项技术点,既是趣味作品,也可作为 PyQt 进阶指南。

未来可做的改进

  • 跨平台快捷键:支持全局快捷键快速唤出设置面板。
  • 在线皮肤商店:允许用户在线下载、分享皮肤包。 (Live 2D)
  • 深度学习驱动:用人脸识别自动调整猫猫的“视线”,让它“看”着你。
  • 移动端同步:用 WebSocket 让手机端也能操控桌面宠物。

这些实现当然都要靠你们辣!

如果你也想动手打造属于自己的“桌面萌宠”,不妨从这篇实战开始。把这份代码克隆到本地,跟着思路一步步改造、扩展,或许下一只更萌的“小恐龙”或“小机器人”就凭你了!


“夜深人静时,那只小猫会在屏幕角落打盹,偶尔发出‘喵~’的一声,提醒我:再忙,也要适当休息。感谢它,陪我度过无数加班夜晚。”

(完)


附录:懒人制作 GIF 指南

不啦不啦不啦,喵喵喵喵喵喵喵喵,懂得都懂。

在这里插入图片描述
在这里插入图片描述

生不出来找个抠图工具抠一下

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果还有背景的画,可以找一个在线视频抠背景工具,找一个抠一下就行了。

在这里插入图片描述
在这里插入图片描述

然后找个视频文件转换工具转换一下就OK了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、灵感的诞生
  • 二、项目构思与功能概览
  • 三、环境准备与基础设置
    • 安装基础库
  • 四、打造“透明贴图窗口”
    • 创建主窗口
  • 五、宠物行为状态机与随机漫步
    • 设计思路
    • 实现状态机
      • 代码解析
  • 六、托盘与右键菜单:让猫猫与你互动
    • 系统托盘图标
    • 右键弹出菜单
  • 七、声音,让猫猫“有声有色”
    • 1. 音效播放:pygame 的“小能手”
    • 2. 语音反馈:文本到语音的趣味尝试
  • 八、用 QSS 打造统一现代的视觉风格
    • 1. 全局样式加载
    • 2. 右键菜单与提示框的统一
  • 九、资源管理:优雅组织你的帧动画与音效
  • 十、用户配置与状态持久化:让猫猫“记住”你
    • 1. 设计思路与流程图
    • 2. 代码实现
      • 在 PetWidget 中接入配置
  • 十一、打包发布:让朋友也能轻松拥有
    • 1. 选择打包工具
    • 2. PyInstaller 打包步骤
    • 3. 常见坑与调试
  • 十二、扩展与插件化架构:让桌面宠物不只是“猫猫”
    • 1. 插件机制设计
    • 2. 示例插件:天气预报
  • 十三、性能优化与资源占用监测
  • 十四、总结与展望
    • 未来可做的改进
  • 附录:懒人制作 GIF 指南
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档