▎ 关键词: 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 小项目~

更多推荐