拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python桌面宠物开发实战:从零打造可交互的蓝色大肥鱼桌宠

最近在GitHub上发现一个特别有意思的开源项目——一个用Python开发的桌面宠物Desktop Pet主角是一只蓝色的“大肥鱼”。它不仅能卖萌还能在你工作时“监督”你提醒你专注。这种将趣味性与生产力工具结合的想法让我眼前一亮于是决定深入研究一下并整理成这篇从零开始的实战教程。无论你是想给枯燥的桌面增添一点乐趣还是希望有一个“伙伴”提醒自己不要摸鱼这个项目都值得一试。本文将带你从环境搭建、源码解析到自定义修改和打包发布完整走一遍开发流程。即使你是Python新手也能跟着一步步做出属于自己的专属桌宠。1. 项目背景与核心概念1.1 什么是“桌宠”“桌宠”Desktop Pet是一种运行在电脑桌面上的小型动画程序。它通常以卡通形象如宠物、角色出现可以在桌面上自由走动、做出各种动作并与用户进行简单的交互。这个概念最早源于一些日本软件后来随着开发门槛降低出现了许多开源和个人开发的桌宠。与传统的桌面小工具Widget不同桌宠更强调“陪伴感”和“趣味性”。它不提供复杂的天气、日历信息而是通过拟人化的行为如睡觉、吃东西、提醒来与用户互动。1.2 “蓝色大肥鱼”项目简介我们本次要剖析和实战的项目在GitHub上暂未找到完全同名的仓库但根据“ds开源桌宠”、“蓝色大肥鱼”等关键词可以推断其属于“桌面宠物”这一大类开源项目。这类项目的核心技术栈通常是开发语言Python因其在图形界面、跨平台方面的便捷性。图形库常见的有PyQt5/PySide6、Tkinter或更轻量的pygame。核心功能窗口置顶、透明背景、GIF/序列帧动画播放、鼠标交互、定时任务等。项目的核心创意点在于“监督工作”。这意味着桌宠除了常规的卖萌行为还可能具备以下功能定时提醒比如每工作45分钟弹出提醒让你休息。专注模式在你启动特定应用如IDE、文档时桌宠进入安静状态。偷懒检测通过监控用户活跃状态如键盘鼠标事件判断你是否在“摸鱼”并做出提醒动作。1.3 为什么用Python开发桌宠快速原型Python语法简洁能快速实现想法适合制作这种轻量级、重创意的应用。丰富的图形库Tkinter内置、PyQt5功能强大、Pygame适合游戏和动画等库为开发提供了坚实基础。跨平台通过Python打包工具如PyInstaller可以轻松生成Windows、macOS、Linux的可执行文件。社区活跃有大量关于桌面应用、透明窗口、系统托盘等开源代码可供参考和学习。接下来我们将从零开始复刻一个具备基础功能和“工作监督”特性的桌宠。2. 开发环境准备在开始编码前我们需要搭建好Python开发环境。这是后续所有步骤的基础。2.1 Python环境安装与配置推荐使用Python 3.8 或更高版本。你可以从 Python官网 下载安装包。安装时请务必勾选“Add Python to PATH”选项这样可以在命令行中直接使用python命令。安装完成后打开终端Windows下是CMD或PowerShellmacOS/Linux下是Terminal输入以下命令验证是否安装成功python --version # 或 python3 --version正常情况会显示类似Python 3.10.11的版本信息。2.2 创建虚拟环境为了避免项目依赖污染全局环境强烈建议为每个项目创建独立的虚拟环境。在项目根目录下执行以下命令# 创建虚拟环境环境文件夹名为 venv python -m venv venv激活虚拟环境Windows (CMD/PowerShell):venv\Scripts\activatemacOS/Linux:source venv/bin/activate激活后命令行提示符前通常会显示(venv)表示你已进入该虚拟环境。2.3 安装核心依赖库我们将使用PyQt5作为图形界面库因为它功能强大能轻松实现透明窗口、不规则窗体等桌宠所需特性。同时安装Pillow用于处理图像。在激活的虚拟环境中运行pip install PyQt5 Pillowpip是Python的包管理工具会自动从PyPIPython包索引下载并安装这些库及其依赖。2.4 项目结构规划在开始编码前先规划好项目目录结构这有助于代码管理。desktop_pet_fish/ ├── assets/ # 资源文件夹 │ ├── sprites/ # 存放鱼的精灵图多张PNG或GIF │ └── sounds/ # 可选存放音效 ├── src/ # 源代码文件夹 │ ├── __init__.py │ ├── main.py # 程序入口 │ ├── pet.py # 桌宠核心类 │ └── utils.py # 工具函数如资源加载 ├── config.ini # 配置文件 ├── requirements.txt # 依赖列表 └── README.md # 项目说明你可以先在本地创建这些空文件和文件夹。3. 核心技术原理拆解要理解桌宠如何工作需要掌握几个关键技术点。3.1 无边框透明窗口桌宠需要漂浮在桌面最上层且没有传统的标题栏和边框。在PyQt5中这通过设置窗口标志Window Flags实现。from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import Qt class PetWindow(QWidget): def __init__(self): super().__init__() # 关键设置无边框、置顶、透明 self.setWindowFlags( Qt.FramelessWindowHint | # 无边框 Qt.WindowStaysOnTopHint | # 始终置顶 Qt.Tool # 不在任务栏显示 ) # 设置窗口背景透明 self.setAttribute(Qt.WA_TranslucentBackground)Qt.FramelessWindowHint移除了窗口的边框和标题栏。Qt.WA_TranslucentBackground属性使窗口背景透明这样只有我们绘制的宠物图像会显示出来。3.2 精灵动画与状态机桌宠的“动”是通过连续播放一系列图像精灵帧实现的。我们需要一个简单的状态机来管理宠物的行为状态如“空闲”、“移动”、“睡觉”、“提醒”。class PetState: IDLE idle WALK walk SLEEP sleep ALERT alert class DesktopPet: def __init__(self): self.state PetState.IDLE self.frame_index 0 self.sprites {} # 字典key为状态value为该状态下的图片列表 self.load_sprites() def update(self): 更新逻辑每帧调用 if self.state PetState.IDLE: # 空闲状态逻辑可能随机切换到其他状态 pass elif self.state PetState.WALK: # 移动逻辑更新位置 pass # ... 其他状态处理 # 更新动画帧 self.frame_index (self.frame_index 1) % len(self.sprites[self.state])通过定时器QTimer定期调用update方法就能驱动动画和逻辑更新。3.3 鼠标交互与拖拽为了让桌宠可以被鼠标拖动我们需要处理鼠标事件。class PetWindow(QWidget): def __init__(self): # ... 初始化代码 self.dragging False self.drag_position None def mousePressEvent(self, event): 鼠标按下事件 if event.button() Qt.LeftButton: self.dragging True # 记录鼠标相对于窗口左上角的位置 self.drag_position event.globalPos() - self.frameGeometry().topLeft() event.accept() def mouseMoveEvent(self, event): 鼠标移动事件 if self.dragging and event.buttons() Qt.LeftButton: # 根据鼠标移动更新窗口位置 self.move(event.globalPos() - self.drag_position) event.accept() def mouseReleaseEvent(self, event): 鼠标释放事件 if event.button() Qt.LeftButton: self.dragging False event.accept()这样用户就可以用鼠标左键按住桌宠并拖到桌面任何位置。3.4 系统托盘与后台运行一个好的桌宠应该可以最小化到系统托盘而不是直接关闭。from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction from PyQt5.QtGui import QIcon class PetWindow(QWidget): def create_tray_icon(self): 创建系统托盘图标和菜单 self.tray_icon QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(assets/icon.png)) # 设置托盘图标 tray_menu QMenu() show_action QAction(显示, self) quit_action QAction(退出, self) show_action.triggered.connect(self.show) quit_action.triggered.connect(self.close_application) tray_menu.addAction(show_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show()当用户点击窗口关闭按钮时我们可以重写closeEvent将其隐藏并显示托盘图标实现后台运行。4. 完整实战打造蓝色大肥鱼桌宠现在我们将上述原理整合一步步实现我们的蓝色大肥鱼桌宠。4.1 准备素材首先你需要准备宠物的图像素材。由于我们暂时没有原项目的“蓝色大肥鱼”素材你可以自己用绘图软件如Aseprite, Photoshop绘制一套简单的2D精灵动画例如静止、摆动尾巴、游动。从开源资源网站如 OpenGameArt 寻找合适的鱼类或宠物素材注意版权。使用简单的几何图形PyQt5绘制代替先实现功能。假设我们有三组PNG序列帧分别对应“空闲(idle)”、“游动(walk)”、“提醒(alert)”状态放在assets/sprites/目录下。4.2 编写核心类pet.py这是桌宠的大脑负责状态、动画和逻辑。# src/pet.py import os import random from PyQt5.QtCore import QTimer, QPoint, Qt from PyQt5.QtGui import QPixmap, QPainter from PyQt5.QtWidgets import QWidget class FishPet(QWidget): def __init__(self): super().__init__() self.state idle self.frame_index 0 self.sprites {} self.direction 1 # 1向右-1向左 self.position QPoint(100, 100) # 初始位置 self.speed 2 self.idle_timer 0 self.work_focus False # 是否处于工作专注模式 self.load_sprites() self.init_ui() self.setup_timers() def load_sprites(self): 加载所有状态的精灵图 sprite_dir assets/sprites states [idle, walk, alert] for state in states: state_path os.path.join(sprite_dir, state) if os.path.exists(state_path): # 假设每个状态文件夹里是 frame0.png, frame1.png... frames [] for i in range(10): # 假设最多10帧 frame_path os.path.join(state_path, fframe{i}.png) if os.path.exists(frame_path): frames.append(QPixmap(frame_path)) if frames: self.sprites[state] frames else: # 如果没有图片创建一个占位矩形 pix QPixmap(64, 64) pix.fill(Qt.blue) self.sprites[state] [pix] def init_ui(self): self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口初始大小根据第一帧图片大小 if self.sprites.get(idle): first_pixmap self.sprites[idle][0] self.resize(first_pixmap.size()) def setup_timers(self): # 动画更新定时器 (每100毫秒一帧) self.anim_timer QTimer(self) self.anim_timer.timeout.connect(self.update_animation) self.anim_timer.start(100) # 逻辑更新定时器 (每500毫秒一次) self.logic_timer QTimer(self) self.logic_timer.timeout.connect(self.update_logic) self.logic_timer.start(500) def update_animation(self): 更新动画帧 if self.state in self.sprites: frames self.sprites[self.state] self.frame_index (self.frame_index 1) % len(frames) self.update() # 触发重绘 def update_logic(self): 更新宠物行为逻辑 # 状态机逻辑 if self.state idle: self.idle_timer 1 # 空闲5秒后有概率开始游动 if self.idle_timer 10 and random.random() 0.3: self.state walk self.idle_timer 0 elif self.state walk: # 游动逻辑更新位置 new_x self.position.x() self.speed * self.direction screen_width 1920 # 应获取实际屏幕宽度 if new_x 0 or new_x screen_width - self.width(): self.direction * -1 # 碰到边界调头 self.position.setX(self.position.x() self.speed * self.direction) self.move(self.position) # 游动一段时间后有概率变回空闲 if random.random() 0.1: self.state idle # 工作监督逻辑简化版检查是否长时间无操作 # 此处可扩展为监听系统空闲时间 def paintEvent(self, event): 绘制宠物当前帧 painter QPainter(self) painter.setRenderHint(QPainter.Antialiasing) if self.state in self.sprites and self.sprites[self.state]: current_pixmap self.sprites[self.state][self.frame_index] # 根据方向决定是否水平翻转图像 if self.direction -1: current_pixmap current_pixmap.transformed(Qt.Horizontal) painter.drawPixmap(self.rect(), current_pixmap) # 鼠标事件处理拖拽 def mousePressEvent(self, event): if event.button() Qt.LeftButton: self.drag_start_position event.globalPos() - self.frameGeometry().topLeft() event.accept() def mouseMoveEvent(self, event): if event.buttons() Qt.LeftButton: self.move(event.globalPos() - self.drag_start_position) self.position self.pos() # 更新内部位置记录 event.accept() def mouseDoubleClickEvent(self, event): 双击切换状态例如切换到提醒状态 if event.button() Qt.LeftButton: self.state alert # 3秒后恢复空闲 QTimer.singleShot(3000, lambda: setattr(self, state, idle))这个类定义了桌宠的基本行为加载图片、播放动画、在屏幕上随机游动、响应鼠标拖拽。4.3 编写主程序入口main.py主程序负责启动应用、创建宠物实例并处理系统托盘。# src/main.py import sys from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction from PyQt5.QtGui import QIcon from src.pet import FishPet def main(): app QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # 防止关闭最后一个窗口时退出程序 # 创建桌宠窗口 pet FishPet() pet.show() # 创建系统托盘 tray_icon QSystemTrayIcon() # 需要一个图标文件这里用占位方式 # tray_icon.setIcon(QIcon(assets/icon.png)) tray_icon.setVisible(True) # 创建托盘菜单 tray_menu QMenu() show_action QAction(显示桌宠) quit_action QAction(退出) def show_pet(): pet.show() def quit_app(): tray_icon.hide() app.quit() show_action.triggered.connect(show_pet) quit_action.triggered.connect(quit_app) tray_menu.addAction(show_action) tray_menu.addAction(quit_action) tray_icon.setContextMenu(tray_menu) # 点击托盘图标显示/隐藏宠物 def on_tray_activated(reason): if reason QSystemTrayIcon.DoubleClick: if pet.isVisible(): pet.hide() else: pet.show() tray_icon.activated.connect(on_tray_activated) sys.exit(app.exec_()) if __name__ __main__: main()4.4 运行与测试确保你的项目结构如2.4节所示。将准备好的精灵图片放入assets/sprites/idle/,assets/sprites/walk/等文件夹。在项目根目录下激活虚拟环境运行python src/main.py你应该能看到一个蓝色矩形或你的图片出现在桌面并且可以拖动。它会在桌面上左右移动双击会短暂切换到“提醒”状态。5. 进阶功能实现“工作监督”基础桌宠已经完成现在我们来加入核心的“监督”功能。思路是监控用户的系统活跃状态。5.1 监听用户活跃状态Windows示例我们可以通过检查系统最后输入事件键盘、鼠标的时间来判断用户是否在操作电脑。这里以Windows平台为例使用pywin32库。# 首先安装 pywin32 pip install pywin32# src/monitor.py import time import win32api import win32gui import win32con from threading import Thread class ActivityMonitor(Thread): def __init__(self, pet_instance, idle_threshold300): :param pet_instance: FishPet实例用于触发提醒 :param idle_threshold: 空闲阈值秒超过此时间认为在偷懒 super().__init__(daemonTrue) self.pet pet_instance self.idle_threshold idle_threshold self.last_active_time time.time() self.running True self.focus_apps [pycharm64.exe, code.exe, devenv.exe] # 定义专注应用列表 def get_last_input_time(self): 获取系统最后输入时间Windows API try: last_input_info win32gui.GetLastInputInfo() tick_count win32api.GetTickCount() idle_time_ms tick_count - last_input_info return idle_time_ms / 1000.0 # 转换为秒 except: return 0 def get_foreground_process(self): 获取当前前台进程名 try: hwnd win32gui.GetForegroundWindow() _, pid win32process.GetWindowThreadProcessId(hwnd) handle win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, pid) exe_name win32process.GetModuleFileNameEx(handle, 0) return os.path.basename(exe_name).lower() except: return def run(self): while self.running: time.sleep(10) # 每10秒检查一次 idle_seconds self.get_last_input_time() current_app self.get_foreground_process() # 判断逻辑 if current_app in self.focus_apps: # 在使用专注应用但长时间无操作 - 可能走神了 if idle_seconds self.idle_threshold: self.pet.state alert # 可以在这里添加更复杂的提醒比如播放声音、弹跳等 print(f[监督者] 检测到你在 {current_app} 中已发呆 {idle_seconds:.0f} 秒快回来工作) else: # 在专注工作宠物保持安静例如切换到一个“工作”状态 if self.pet.state ! work: self.pet.state work else: # 没在用专注应用且长时间无操作 - 可能离开电脑了 if idle_seconds self.idle_threshold * 2: self.pet.state idle # 进入长时间空闲状态 # 没在用专注应用但有操作 - 可能在摸鱼这里逻辑可以更复杂 # 简单起见我们只做应用判断 def stop(self): self.running False然后在main.py中启动这个监控线程# 在 main.py 的 main() 函数中创建pet之后 from src.monitor import ActivityMonitor monitor ActivityMonitor(pet, idle_threshold180) # 3分钟无操作视为发呆 monitor.start()5.2 添加定时休息提醒除了检测发呆还可以主动提醒休息遵循“番茄工作法”。# 在 FishPet 类中添加 class FishPet(QWidget): def __init__(self): # ... 原有初始化 ... self.work_duration 25 * 60 # 25分钟工作 self.break_duration 5 * 60 # 5分钟休息 self.work_timer 0 self.is_working True def update_logic(self): # ... 原有逻辑 ... # 番茄钟逻辑 self.work_timer 0.5 # 因为逻辑定时器是500ms一次 if self.is_working: if self.work_timer self.work_duration: self.state alert # 提醒休息 self.show_notification(工作时间到该休息一下了) self.is_working False self.work_timer 0 else: if self.work_timer self.break_duration: self.state alert # 提醒继续工作 self.show_notification(休息结束继续专注工作吧) self.is_working True self.work_timer 0 def show_notification(self, message): 显示一个简单的提示可以用系统通知或自己绘制 # 这里简单打印可以用QMessageBox或系统托盘通知 print(f[鱼鱼提醒] {message}) # 进阶使用QSystemTrayIcon.showMessage # self.tray_icon.showMessage(休息提醒, message, QSystemTrayIcon.Information, 3000)6. 常见问题与排查思路在开发和使用过程中你可能会遇到以下问题问题现象可能原因解决思路运行后窗口不透明有白色背景setAttribute(Qt.WA_TranslucentBackground)未生效或绘制时填充了背景色。1. 确保在__init__中正确设置了透明属性。2. 检查paintEvent中是否误用了painter.fillRect填充了背景。桌宠无法拖动鼠标事件未被正确捕获或处理。1. 确认窗口标志包含了Qt.FramelessWindowHint。2. 检查mousePressEvent,mouseMoveEvent,mouseReleaseEvent是否被重写且调用了event.accept()。动画播放卡顿或不流畅定时器间隔太短或每帧图像太大处理耗时。1. 调整QTimer的间隔动画定时器100-200ms逻辑定时器500ms-1s。2. 优化图片资源尺寸不宜过大使用PNG8或适当压缩。3. 在paintEvent中只绘制当前帧避免复杂计算。打包成exe后无法运行或找不到图片PyInstaller打包时未包含资源文件。1. 使用--add-data参数指定资源文件夹。例如pyinstaller --add-data assets;assets src/main.py。2. 在代码中使用sys._MEIPASS来定位打包后的资源路径。监控功能不工作特别是跨平台pywin32仅限Windows其他平台API不同。1.Windows: 使用pywin32或ctypes调用GetLastInputInfo。2.macOS: 使用Quartz框架通过pyobjc。3.Linux: 查询/proc文件系统或使用Xlib。考虑使用跨平台库如pynput监听事件或psutil判断进程。宠物被其他全屏窗口如游戏遮挡窗口置顶标志可能在某些全屏模式下失效。这是系统级限制难以完美解决。可以尝试在宠物失去焦点时重新调用raise_()和activateWindow()但效果有限。7. 工程化与最佳实践一个完整的项目不能只停留在“能跑”还需要考虑可维护性、可配置性和用户体验。7.1 配置文件管理将可调整的参数如窗口大小、速度、提醒时间抽离到配置文件中。我们使用Python内置的configparser来读取.ini文件。; config.ini [pet] width 64 height 64 default_state idle move_speed 2 [supervisor] work_interval 1500 ; 25分钟单位秒 break_interval 300 ; 5分钟单位秒 idle_threshold 180 ; 3分钟无操作视为发呆 focus_apps pycharm64.exe, code.exe, devenv.exe, notepad.exe [window] opacity 0.9 ; 窗口透明度 (0.0-1.0) always_on_top true在代码中读取配置# src/config.py import configparser import os def load_config(): config configparser.ConfigParser() config.read(config.ini, encodingutf-8) return config # 在pet.py中使用 config load_config() pet_width config.getint(pet, width, fallback64) work_interval config.getint(supervisor, work_interval, fallback1500)7.2 日志记录添加日志功能便于调试和了解程序运行状态。# src/logger.py import logging import os def setup_logger(name): logger logging.getLogger(name) logger.setLevel(logging.DEBUG) # 控制台处理器 ch logging.StreamHandler() ch.setLevel(logging.INFO) # 文件处理器 log_dir logs os.makedirs(log_dir, exist_okTrue) fh logging.FileHandler(os.path.join(log_dir, desktop_pet.log), encodingutf-8) fh.setLevel(logging.DEBUG) formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s) ch.setFormatter(formatter) fh.setFormatter(formatter) logger.addHandler(ch) logger.addHandler(fh) return logger # 在项目中使用 logger setup_logger(__name__) logger.info(桌宠程序启动...) logger.debug(f加载配置: {config})7.3 使用PyInstaller打包分发开发完成后你可以将项目打包成独立的可执行文件方便分享给没有Python环境的朋友。首先安装PyInstallerpip install pyinstaller在项目根目录下创建一个简单的打包脚本build.spec或直接使用命令。为了包含资源文件推荐使用spec文件。# build.spec a Analysis([src/main.py], pathex[], binaries[], datas[(assets, assets), (config.ini, .)], # 关键包含资源和配置 hiddenimports[], hookspath[], hooksconfig{}, runtime_hooks[], excludes[], win_no_prefer_redirectsFalse, win_private_assembliesFalse, cipherNone, noarchiveFalse) pyz PYZ(a.pure) exe EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], nameBlueFatFishDesktopPet, debugFalse, bootloader_ignore_signalsFalse, stripFalse, upxTrue, upx_exclude[], runtime_tmpdirNone, consoleFalse, # 如果不需要控制台窗口设为False iconassets/icon.ico) # 设置图标运行打包命令pyinstaller build.spec打包后的可执行文件会在dist文件夹中。记得将config.ini和assets文件夹与exe放在同一目录下或者确保spec文件中已正确包含。7.4 代码结构与扩展建议状态模式如果宠物行为变得复杂可以考虑使用状态模式来管理不同的行为状态如 idle, walk, sleep, eat, alert使代码更清晰。插件化将“监督”功能如番茄钟、应用监控设计成插件通过配置文件启用或禁用。皮肤系统将精灵图片路径抽象出来允许用户通过替换assets文件夹来更换宠物皮肤。跨平台兼容将平台相关的代码如活动监控抽象成接口为不同操作系统提供实现。从一只简单的“蓝色大肥鱼”桌宠出发我们不仅实现了一个有趣的桌面小程序还串联了Python GUI开发、多线程、系统交互、打包分发等多个知识点。你可以在此基础上继续深化比如为它添加更多可爱的动画帧、更智能的提醒语句、甚至联网获取天气预报或名言警句来提醒你。开源项目的魅力就在于你可以完全按照自己的想法去定制和创造。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门