手把手教你用 Python 做一个超可爱的桌面宠物!零基础也能学会~
●
▎ 关键词: Python桌面宠物、PyQt5桌面宠物、Python小项目、桌面宠物源码、Python实战项目
---
一、先看效果
一个会在你桌面上跑来跑去的小猫咪,你可以用鼠标拖拽它,它还会随机走动、眨眼、打瞌睡,甚至被拖到屏幕边缘还会害怕!
是不是很心动?跟着我一步步做出来!
---
二、准备工作
2.1 安装 Python
去 Python官网 (https://www.python.org/) 下载 Python 3.8 以上版本,安装时记得勾选 "Add Python to PATH"。
2.2 安装依赖库
打开命令行(Win+R 输入 cmd),执行:
pip install PyQt5
就这一个依赖就够了!PyQt5 是 Python 最流行的 GUI 框架之一。
2.3 准备素材
你需要准备几张小猫的 PNG 图片(背景透明),放在项目文件夹里:
desktop_pet/
├── pet.py ← 主程序
└── images/
├── cat_normal.png ← 正常状态
├── cat_blink.png ← 眨眼状态
├── cat_sleep.png ← 打瞌睡状态
├── cat_drag.png ← 被拖拽时害怕的表情
└── cat_walk1.png ← 走路动画帧1
└── cat_walk2.png ← 走路动画帧2
▎ 素材从哪来? 推荐去 itch.io (https://itch.io/) 搜索 "pixel cat
▎ sprite",有大量免费素材。或者用我提供的简易方案——用代码画一个也行!(下面会教)
---
三、核心知识点(先搞懂原理)
在写代码之前,先理解几个关键概念:
3.1 无边框透明窗口
桌宠的本质就是一个 无边框 + 背景透明 的窗口,窗口里只显示一张 PNG 图片。
┌──────────────────────┐
│ 操作系统桌面 │
│ │
│ ┌────────┐ │
│ │ 🐱 │ │ ← 这就是一个透明窗口
│ │ 桌宠 │ │
│ └────────┘ │
│ │
└──────────────────────┘
3.2 定时器驱动动画
用 QTimer 定时器每隔一段时间触发动作:移动位置、切换帧、改变状态。
3.3 事件处理
- mousePressEvent — 鼠标按下(开始拖拽)
- mouseMoveEvent — 鼠标移动(拖拽中)
- mouseReleaseEvent — 鼠标释放(放下桌宠)
---
四、完整源码(带详细注释)
新建文件 pet.py,把下面代码完整复制进去:
"""
Python 桌面宠物 - 一只会在桌面上跑来跑去的小猫咪
作者:你的CSDN昵称
环境:Python 3.8+ / PyQt5
"""
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMenu, QAction
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect
from PyQt5.QtGui import QPixmap, QPainter, QColor, QFont
class DesktopPet(QWidget):
"""桌面宠物主类"""
def __init__(self):
super().__init__()
# ========== 基础配置 ==========
self.pet_width = 120 # 宠物显示宽度
self.pet_height = 120 # 宠物显示高度
self.screen_width = 1920 # 屏幕宽度(会自动获取)
self.screen_height = 1080 # 屏幕高度(会自动获取)
# ========== 状态管理 ==========
self.is_dragging = False # 是否正在被拖拽
self.drag_position = QPoint()
self.current_state = "normal" # normal / blink / sleep / walk / drag
self.walk_direction = 1 # 1=向右, -1=向左
self.walk_speed = 2
self.idle_counter = 0 # 空闲计数器
self.animation_frame = 0 # 动画帧计数
# ========== 初始化窗口 ==========
self.init_window()
# ========== 创建显示标签 ==========
self.pet_label = QLabel(self)
self.pet_label.setGeometry(0, 0, self.pet_width, self.pet_height)
# ========== 绘制初始宠物 ==========
self.draw_pet()
# ========== 启动定时器 ==========
self.start_timers()
def init_window(self):
"""初始化窗口属性:无边框、透明背景、置顶"""
# 获取屏幕尺寸
screen = QApplication.primaryScreen().geometry()
self.screen_width = screen.width()
self.screen_height = screen.height()
# 窗口属性设置
self.setWindowFlags(
Qt.FramelessWindowHint | # 无边框
Qt.WindowStaysOnTopHint | # 置顶
Qt.Tool # 不在任务栏显示
)
self.setAttribute(Qt.WA_TranslucentBackground) # 透明背景
self.setFixedSize(self.pet_width, self.pet_height)
# 初始位置:屏幕右下角
start_x = self.screen_width - self.pet_width - 100
start_y = self.screen_height - self.pet_height - 100
self.move(start_x, start_y)
def draw_pet(self):
"""
绘制宠物
这里用代码画一个简单的像素猫咪,
如果你有 PNG 素材,可以换成图片加载方式
"""
pixmap = QPixmap(self.pet_width, self.pet_height)
pixmap.fill(Qt.transparent)
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing)
# 根据状态选择颜色
if self.current_state == "drag":
body_color = QColor(255, 150, 150) # 害怕:变红
eye_text = ">﹏<"
elif self.current_state == "sleep":
body_color = QColor(200, 200, 255) # 困了:变蓝
eye_text = "— —"
elif self.current_state == "blink":
body_color = QColor(255, 220, 180) # 眨眼
eye_text = "—ω—"
else:
body_color = QColor(255, 220, 180) # 正常:暖色
eye_text = "◕ω◕"
# 画身体(椭圆)
painter.setBrush(body_color)
painter.setPen(Qt.NoPen)
painter.drawEllipse(15, 25, 90, 80)
# 画耳朵(三角形)
from PyQt5.QtGui import QPolygon
from PyQt5.QtCore import QPointF
left_ear = QPolygon([QPoint(25, 30), QPoint(15, 5), QPoint(45, 25)])
right_ear = QPolygon([QPoint(95, 30), QPoint(105, 5), QPoint(75, 25)])
painter.setBrush(QColor(255, 180, 140))
painter.drawPolygon(left_ear)
painter.drawPolygon(right_ear)
# 画表情
painter.setPen(QColor(60, 60, 60))
painter.setFont(QFont("Microsoft YaHei", 12))
painter.drawText(QRect(0, 45, self.pet_width, 40), Qt.AlignCenter, eye_text)
# 画嘴巴
painter.setFont(QFont("Microsoft YaHei", 8))
painter.drawText(QRect(0, 75, self.pet_width, 20), Qt.AlignCenter, "ε")
# 画小脚
painter.setBrush(QColor(255, 180, 140))
painter.drawEllipse(30, 100, 20, 12)
painter.drawEllipse(70, 100, 20, 12)
# 如果在走路,画动态小脚
if self.current_state == "walk":
offset = 5 if self.animation_frame % 2 == 0 else -5
painter.drawEllipse(30, 100 + offset, 20, 12)
painter.drawEllipse(70, 100 - offset, 20, 12)
painter.end()
self.pet_label.setPixmap(pixmap)
# ========== 鼠标拖拽事件 ==========
def mousePressEvent(self, event):
"""鼠标按下 - 开始拖拽"""
if event.button() == Qt.LeftButton:
self.is_dragging = True
self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
self.current_state = "drag"
self.draw_pet()
event.accept()
def mouseMoveEvent(self, event):
"""鼠标移动 - 拖拽中"""
if self.is_dragging and event.buttons() & Qt.LeftButton:
self.move(event.globalPos() - self.drag_position)
event.accept()
def mouseReleaseEvent(self, event):
"""鼠标释放 - 停止拖拽"""
self.is_dragging = False
self.current_state = "normal"
self.draw_pet()
event.accept()
def contextMenuEvent(self, event):
"""右键菜单"""
menu = QMenu(self)
action_sleep = QAction("💤 让它睡觉", self)
action_sleep.triggered.connect(lambda: self.set_state("sleep"))
menu.addAction(action_sleep)
action_wake = QAction("☀️ 叫醒它", self)
action_wake.triggered.connect(lambda: self.set_state("normal"))
menu.addAction(action_wake)
menu.addSeparator()
action_quit = QAction("❌ 退出", self)
action_quit.triggered.connect(QApplication.quit)
menu.addAction(action_quit)
menu.exec_(event.globalPos())
# ========== 状态和动画 ==========
def set_state(self, state):
"""切换状态"""
self.current_state = state
self.idle_counter = 0
self.draw_pet()
def start_timers(self):
"""启动所有定时器"""
# 主逻辑定时器:每 100ms 触发一次
self.main_timer = QTimer()
self.main_timer.timeout.connect(self.main_loop)
self.main_timer.start(100)
# 眨眼定时器:每 3~6 秒随机眨眼
self.blink_timer = QTimer()
self.blink_timer.timeout.connect(self.do_blink)
self.blink_timer.start(random.randint(3000, 6000))
def main_loop(self):
"""主循环:处理移动和状态变化"""
if self.is_dragging:
return
self.animation_frame += 1
self.idle_counter += 1
# 空闲久了会打瞌睡
if self.idle_counter > 100 and self.current_state == "normal":
self.current_state = "sleep"
self.draw_pet()
# 走路逻辑
if self.current_state == "normal" and random.random() < 0.02:
# 10% 概率开始走路
self.current_state = "walk"
self.walk_direction = random.choice([-1, 1])
if self.current_state == "walk":
self.walk()
def walk(self):
"""走路移动逻辑"""
current_pos = self.pos()
new_x = current_pos.x() + self.walk_speed * self.walk_direction
# 到达屏幕边缘就转身
if new_x <= 0:
new_x = 0
self.walk_direction = 1
elif new_x >= self.screen_width - self.pet_width:
new_x = self.screen_width - self.pet_width
self.walk_direction = -1
self.move(new_x, current_pos.y())
self.draw_pet()
# 走一段路后停下来
if random.random() < 0.01:
self.current_state = "normal"
self.idle_counter = 0
self.draw_pet()
def do_blink(self):
"""眨眼动画"""
if self.current_state in ("normal", "walk"):
old_state = self.current_state
self.current_state = "blink"
self.draw_pet()
# 200ms 后恢复
QTimer.singleShot(200, lambda: self._restore_from_blink(old_state))
# 重置眨眼间隔
self.blink_timer.start(random.randint(3000, 6000))
def _restore_from_blink(self, old_state):
"""眨眼后恢复原状态"""
if self.current_state == "blink":
self.current_state = old_state
self.draw_pet()
# ========== 程序入口 ==========
if __name__ == "__main__":
app = QApplication(sys.argv)
pet = DesktopPet()
pet.show()
sys.exit(app.exec_())
---
五、运行效果说明
运行后你会看到:
┌──────────────┬──────────────────────────────────┐
│ 操作 │ 效果 │
├──────────────┼──────────────────────────────────┤
│ 静置不动 │ 小猫正常站立,偶尔眨眼 │
├──────────────┼──────────────────────────────────┤
│ 长时间不操作 │ 小猫打瞌睡(— — 表情) │
├──────────────┼──────────────────────────────────┤
│ 鼠标左键拖拽 │ 小猫被拖着走,表情害怕(>﹏<) │
├──────────────┼──────────────────────────────────┤
│ 松开鼠标 │ 恢复正常 │
├──────────────┼──────────────────────────────────┤
│ 随机触发 │ 小猫自己走来走去 │
├──────────────┼──────────────────────────────────┤
│ 右键点击 │ 弹出菜单:睡觉 / 叫醒 / 退出 │
└──────────────┴──────────────────────────────────┘
---
六、进阶玩法(加分项)
6.1 用真实 PNG 图片替换
如果你有像素画素材,把 draw_pet 方法改成图片加载:
def draw_pet(self):
"""用 PNG 图片显示宠物"""
state_images = {
"normal": "images/cat_normal.png",
"blink": "images/cat_blink.png",
"sleep": "images/cat_sleep.png",
"drag": "images/cat_drag.png",
"walk": f"images/cat_walk{self.animation_frame % 2 + 1}.png",
}
image_path = state_images.get(self.current_state, "images/cat_normal.png")
pixmap = QPixmap(image_path)
pixmap = pixmap.scaled(self.pet_width, self.pet_height,
Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.pet_label.setPixmap(pixmap)
6.2 添加气泡对话框
小猫偶尔会冒出一句话:
def show_speech_bubble(self, text):
"""显示气泡"""
from PyQt5.QtWidgets import QToolTip
QToolTip.showText(
self.pos() + QPoint(0, -50),
f"🐱 {text}",
self
)
配合定时器随机触发:
# 在 __init__ 中添加
self.speech_timer = QTimer()
self.speech_timer.timeout.connect(self.random_speech)
self.speech_timer.start(8000)
def random_speech(self):
phrases = ["喵~", "摸摸我嘛", "好困啊...", "你在干嘛?", "嘿嘿", "无聊~"]
self.show_speech_bubble(random.choice(phrases))
6.3 支持多种宠物
你可以扩展成选择不同宠物:
PETS = {
"cat": {"emoji": "🐱", "sound": "喵~"},
"dog": {"emoji": "🐶", "sound": "汪!"},
"rabbit": {"emoji": "🐰", "sound": "~"},
}
---
七、常见问题 FAQ
Q1:运行报错 No module named 'PyQt5'
pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple
用清华镜像源下载更快。
Q2:宠物不透明,有一块白色背景?
确保图片是 PNG 格式且背景透明。代码中 Qt.WA_TranslucentBackground 是关键。
Q3:宠物被其他窗口挡住了?
代码中 Qt.WindowStaysOnTopHint 已经设置了置顶,如果还不行,检查是否有全屏应用覆盖。
Q4:怎么打包成 exe 发给朋友?
pip install pyinstaller
pyinstaller -F -w --icon=cat.ico pet.py
---
八、完整项目结构
desktop_pet/
├── pet.py ← 主程序(直接运行)
├── README.md ← 说明文档
└── images/ ← 图片素材(可选)
├── cat_normal.png
├── cat_blink.png
├── cat_sleep.png
└── cat_drag.png
---
九、总结
这个项目虽然代码不多,但涉及了很多实用知识点:
- ✅ PyQt5 无边框透明窗口
- ✅ 鼠标事件处理(拖拽)
- ✅ QTimer 定时器驱动动画
- ✅ QPainter 自定义绘图
- ✅ 状态机设计模式
适合练手,也很适合写在简历上!
---
▎ 觉得有用的话,点个赞 + 收藏吧! 后续我会更新更多 Python 小项目~
更多推荐
所有评论(0)