admin管理员组

文章数量:1437030

做了一只哈基米桌宠

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

一、灵感的诞生

小时候的电脑里,总有些奇奇怪怪又让人爱不释手的玩意儿,比如能陪你聊天的微软小助手 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("喂食 

本文标签: 做了一只哈基米桌宠