文档版本:1.0
适用平台:Windows
开发语言:Python 3.10
GUI 框架:PySide6
当前宠物:橘白猫「小橘」、藏獒「小山」


请添加图片描述

请添加图片描述
请添加图片描述

请添加图片描述

目录

  1. 项目概览
  2. 技术栈
  3. 项目结构
  4. 系统架构
  5. 模块详解
  6. 动画清单格式 (animations.json)
  7. 交互系统
  8. 精灵生成管线
  9. 构建与打包
  10. 测试
  11. 配置与环境变量

1. 项目概览

本项目是一款离线的 Windows 桌面宠物应用。宠物以透明、无边框、始终置顶的窗口呈现在桌面上,支持自主行走、奔跑、休息,观察鼠标并响应用户的点击、拖拽、喂食等操作。

当前版本包含两只宠物:

宠物 ID 昵称 状态数 总帧数 步行动画
橘白猫 orange_cat 小橘 17 87 16 帧
藏獒 tibetan_mastiff 小山 17 137 20 帧

2. 技术栈

层级 技术 版本 用途
语言 Python ≥3.10 主体逻辑
GUI PySide6 ≥6.6, <7 透明窗口、渲染、系统托盘
图像处理 Pillow ≥9.1, <12 精灵图加载与处理
打包 PyInstaller ≥5.1 生成独立 .exe
精灵生成 NumPy / OpenCV ≥1.23 / ≥4.5 光流插值、色键去底
配置格式 JSON 动画清单、用户设置
设置存储 Windows Registry 开机自启注册表项

3. 项目结构

桌面宠物游戏/
├── main.py                            # 应用入口
├── run.bat                            # 快速启动脚本
├── build.bat                          # 完整构建脚本
├── pyproject.toml                     # 项目元数据、Ruff 配置
├── requirements.txt                   # 运行时依赖
├── requirements-dev.txt               # 开发构建依赖
├── orange_cat_pet.spec                # PyInstaller 打包配置
│
├── desktop_pet/                       # 核心程序包
│   ├── __init__.py                    # 版本号 (1.0.0)
│   ├── app.py                         # 应用生命周期协调器
│   ├── window.py                      # 透明宠物窗口
│   ├── controller.py                  # 动画状态机
│   ├── assets.py                      # 动画资源加载器
│   ├── models.py                      # 数据模型 & 枚举
│   ├── settings.py                    # 设置持久化 & 自启管理
│   ├── resources.py                   # 路径解析 (源码/打包)
│   ├── selection.py                   # 宠物选择对话框
│   └── pet_catalog.py                 # 宠物定义目录
│
├── assets/                            # 游戏资源
│   ├── animations.json                # 猫咪动画清单
│   ├── tibetan_mastiff_animations.json # 藏獒动画清单
│   ├── IMAGEGEN_PROMPTS.md            # 精灵图 AI 生成提示词
│   ├── icons/orange_cat.ico           # 应用图标
│   └── sprites/                       # 精灵图表 / 生成帧
│       ├── generated/                 # 猫咪已处理帧 (87 张 PNG)
│       └── tibetan_mastiff/generated/ # 藏獒已处理帧 (137 张 PNG)
│
├── tools/                             # 构建工具
│   ├── build_sprites.py               # 猫精灵抽取 & 步态插值
│   ├── build_mastiff_sprites.py       # 藏獒精灵抽取 & 动画生成
│   └── make_icon.py                   # Windows .ico 生成
│
├── tests/                             # 单元测试
│   ├── test_assets.py                 # 资源完整性测试
│   ├── test_models_settings.py        # 设置序列化测试
│   ├── test_qt_smoke.py               # Qt 烟雾测试
│   └── test_selection.py              # 选择对话框测试
│
├── build/orange_cat_pet/              # PyInstaller 构建中间产物
└── dist/OrangeCatPet/                 # 最终发布目录

4. 系统架构

┌──────────────────────────────────────────────────────────┐
│                       main.py                            │
│                   (入口, HIGHDPI 设置)                     │
└─────────────────┬────────────────────────────────────────┘
                  │
┌─────────────────▼────────────────────────────────────────┐
│                 DesktopPetApplication                    │
│  ┌──────────────────────────────────────────────────┐    │
│  │  应用协调: QApplication  /  SettingsStore        │    │
│  │  宠物切换: _activate_pet() / _choose_pet()       │    │
│  │  托盘管理: _create_or_refresh_tray()             │    │
│  └──────────────────────────────────────────────────┘    │
└─────────────────┬────────────────────────────────────────┘
                  │
    ┌─────────────┼─────────────┐
    ▼             ▼             ▼
┌───────┐  ┌──────────┐  ┌─────────────┐
│Assets │  │Controller│  │Selection    │
│Library│  │(状态机)   │  │Dialog       │
└───┬───┘  └────┬─────┘  └─────────────┘
    │           │
    ▼           ▼
┌─────────────────────────────────────┐
│           PetWindow                 │
│  ┌───────────────────────────────┐  │
│  │  QWidget (透明, 置顶, 无边框)  │  │
│  │  ┌─────────────────────────┐  │  │
│  │  │ QPainter 渲染当前帧      │  │  │
│  │  │ 眼球追踪计算 & 绘制      │  │  │
│  │  ├─────────────────────────┤  │  │
│  │  │ motion_timer  (30ms)    │  │  │
│  │  │ behaviour_timer (1s)    │  │  │
│  │  │ single_click_timer      │  │  │
│  │  ├─────────────────────────┤  │  │
│  │  │ 鼠标事件处理            │  │  │
│  │  │ 右键上下文菜单           │  │  │
│  │  └─────────────────────────┘  │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

5. 模块详解

5.1 入口 & 应用生命周期 (main.py / app.py)

main.py — 应用入口,设置 QT_ENABLE_HIGHDPI_SCALING=1 环境变量后创建并启动 DesktopPetApplication

DesktopPetApplication — 应用生命周期协调器 (desktop_pet/app.py:16),职责如下:

  • 初始化 QApplication(应用名 “桌面宠物伙伴”,组织名 “OrangeCatDesktopPet”)
  • setQuitOnLastWindowClosed(False) 确保关闭窗口后隐藏到托盘而非退出
  • run() 方法:启动时先弹出宠物选择对话框,选择后进入 Qt 事件循环
  • _activate_pet() 方法:切换宠物时销毁旧窗口、重新加载动画库、创建新窗口、刷新托盘
  • quit() 方法:保存设置、隐藏托盘、退出应用

应用级信号流:

window.request_quit ──────────> app.quit()
window.request_pet_selection ─> app.choose_pet()

5.2 动画资源管理 (assets.py)

AnimationLibrary (desktop_pet/assets.py:12) — 从 JSON 动画清单文件加载并管理所有动画资源。

核心功能:

方法 说明
_load() 解析 JSON 清单,为 17 个 PetState 构建 AnimationClip
clip(state) 返回指定状态的 AnimationClip
pixmap(frame) 惰性加载并缓存 QPixmap(按路径缓存,避免重复文件 I/O)

加载过程:

  1. 读取 JSON → 解析 canvas 尺寸
  2. 遍历 PetState 枚举 → 从 animations 对象中取出帧数组
  3. 每帧解析 pathduration_mseyes(眼球锚点)、eye_radiushitbox
  4. 校验文件存在性(缺失直接抛 FileNotFoundError
  5. 构建不可变 AnimationClipfrozen dataclass)

5.3 数据模型 (models.py)

PetState (desktop_pet/models.py:9) — 17 种宠物状态的字符串枚举:

枚举值 中文 枚举值 中文
IDLE 待机 BLINK 眨眼
WATCH 观察 WALK 行走
RUN 奔跑 SIT 坐下
LIE 趴下 SLEEP 睡觉
WAKE 醒来 STRETCH 伸懒腰
GROOM 舔毛 YAWN 打哈欠
HAPPY 开心 SURPRISED 惊讶
ANGRY 生气 EAT 进食
DRAGGED 被拖拽

FrameMetadata (desktop_pet/models.py:29) — 不可变帧数据:

字段 类型 说明
path Path 图片文件路径
duration_ms int 帧持续时间 (最小值 16ms)
eyes tuple[tuple[float, float], ...] 眼球锚点坐标序列
eye_radius tuple[float, float] 瞳孔基准半径 (x, y)
hitbox tuple[int, int, int, int] 碰撞检测区域 (x, y, w, h)

AnimationClip (desktop_pet/models.py:38) — 不可变动画片段:

字段 类型 说明
state PetState 所属状态
frames tuple[FrameMetadata, ...] 帧序列
loop bool 是否循环播放
next_state PetState | None 非循环动画结束后的过渡状态

PetSettings (desktop_pet/models.py:46) — 用户设置数据类(可变),支持 from_dict / to_dict JSON 序列化,含输入校验。


5.4 动画状态机 (controller.py)

PetController (desktop_pet/controller.py:9) — 管理动画状态切换与帧推进。

优先级系统: 每个状态有优先级数值,高优先级可抢占低优先级(非循环动画播放中会锁住):

优先级 状态
100 DRAGGED
90 EAT
80 HAPPY
75 SURPRISED, ANGRY
65 WAKE
55 STRETCH, GROOM, YAWN
40 SLEEP
25 RUN
20 WALK
15 WATCH
10 SIT, LIE
8 BLINK
5 IDLE

状态切换逻辑 (set_state, controller.py:51):

if 新状态 == 当前状态 and not force → 忽略
if 当前非循环动画未播完 and not force and 新优先级 < 当前优先级 → 忽略
否则 → 切换状态, 重置帧索引, 发射信号, 重新调度定时器

帧推进 (_advance, controller.py:76):

if 还有下一帧 → frame_index++
elif 循环动画 → 回到第 0 帧
else (非循环动画播完) → 过渡到 next_state (默认 IDLE)
发射 frame_changed → 重新调度定时器

5.5 透明渲染窗口 (window.py)

PetWindow (desktop_pet/window.py:18) — 继承 QWidget,所有渲染与交互的核心。

窗口属性
属性
固定尺寸 library.canvas_size (256×256)
WA_TranslucentBackground True
WA_NoSystemBackground True
autoFillBackground False
窗口标志 FramelessWindowHint | Tool | WindowStaysOnTopHint
定时器
定时器 间隔 用途
motion_timer 30ms 行走/奔跑位移 ±2px (走) / ±5px (跑)
behaviour_timer 1s 饥饿/心情更新、随机行为决策
single_click_timer 单次 区分单击与双击
渲染管线 (paintEvent)
1. QPainter(painter) 描画到 Widget
2. 判断朝向: facing_right 决定是否水平翻转
3. 绘制精灵: drawPixmap(target_rect, pixmap)
4. 眼球追踪计算:
   a. 获取全局鼠标位置 QCursor.pos()
   b. 映射到 Widget 局部坐标
   c. 遍历 frame.eyes 中每只眼睛的锚点
   d. 计算方向向量 → 归一化 → 瞳孔偏移量
   e. 绘制白色虹膜 + 黑色瞳孔 + 白色高光点
多显示器支持
  • 使用 QApplication.screenAt(center) 获取当前所在屏幕
  • 使用 availableGeometry() 获取不含任务栏的工作区
  • 移动时通过 _clamped_position() 约束宠物不出工作区边界
右键菜单

动态构建 QMenu,包含以下选项:

选项 功能
喂食 切换到 EAT 状态
召回 将宠物移到当前屏幕中心底部
选择宠物 弹出选择对话框
暂停 冻结/恢复动画和行为
置顶 切换 WindowStaysOnTopHint
开机启动 写入/删除注册表自启项
退出 保存设置 → 完全退出

5.6 设置持久化 (settings.py)

SettingsStore (desktop_pet/settings.py:22) — JSON 设置文件的读写封装。

存储路径 说明
%LOCALAPPDATA%\OrangeCatDesktopPet\settings.json 默认路径
ORANGE_CAT_DATA_DIR 环境变量覆盖 测试用途

原子保存机制 (save, settings.py:35):

写入 .tmp 文件 → 调用 Path.replace() 原子替换原文件

容错机制 (load, settings.py:26):

任何异常 (OSError, ValueError, TypeError, JSONDecodeError) → 返回默认 PetSettings

AutoStartManager (desktop_pet/settings.py:45) — 通过操作 HKCU\Software\Microsoft\Windows\CurrentVersion\Run 注册表键实现开机自启。

  • is_enabled() — 读取注册表判断是否已有启动项
  • set_enabled(bool) — 写入或删除注册表值
  • command() — 生成正确的启动命令行(区分源码运行 vs PyInstaller 打包)

5.7 路径解析 (resources.py)

resource_path(relative_path) — 统一路径解析:

if sys.frozen (PyInstaller 打包):
    返回 Path(sys._MEIPASS) / relative_path
else:
    返回 Path(__file__).resolve().parents[1] / relative_path

支持参数形式:

  • resource_path("assets/animations.json") — 字符串
  • resource_path(Path("assets/sprites/generated/idle/00.png"))Path 对象

5.8 宠物选择对话框 (selection.py / pet_catalog.py)

PetSelectionDialog (desktop_pet/selection.py) — 模态对话框 (960×630),首次启动时弹出,其后可通过右键菜单打开。

  • 展示所有宠物的预览卡片(图片、名称、描述、选中按钮)
  • 当前已选宠物高亮显示
  • 点击确定后触发 app._activate_pet()

PetDefinition (desktop_pet/pet_catalog.py) — 宠物定义的不可变 dataclass:

字段 类型 说明
pet_id str 宠物唯一标识
display_name str 中文显示名
description str 简短描述
manifest Path 动画清单路径
preview_image Path 选择页预览图路径
frame_count int 总帧数

当前宠物目录:

  • 小橘: orange_catassets/animations.json (87 帧)
  • 小山: tibetan_mastiffassets/tibetan_mastiff_animations.json (137 帧)

6. 动画清单格式 (animations.json)

{
  "version": 2,
  "canvas": [256, 256],
  "animations": {
    "idle": {
      "frames": [
        {
          "path": "assets/sprites/generated/idle/00.png",
          "duration_ms": 100,
          "eyes": [[120.3, 80.5], [140.2, 80.5]],
          "eye_radius": [5.0, 4.0],
          "hitbox": [10, 20, 236, 236]
        }
      ],
      "loop": true
    },
    "eat": {
      "frames": [ /* ... */ ],
      "loop": false,
      "next_state": "idle"
    }
  }
}

字段说明:

字段 类型 必填 说明
version int 清单格式版本
canvas [int, int] 画布尺寸
animations.<state>.frames array 帧数组 (至少 1 帧)
frames[].path string 相对路径
frames[].duration_ms int 帧显示时长 (ms)
frames[].eyes [[float, float]] 眼球锚点坐标
frames[].eye_radius [float, float] 瞳孔半径,默认 [5, 4]
frames[].hitbox [int, int, int, int] 点击碰撞区
animations.<state>.loop bool 是否循环,默认 true
animations.<state>.next_state string 播完后过渡到的状态

7. 交互系统

7.1 鼠标眼动追踪

每帧渲染时实时计算瞳孔位置,产生「宠物注视鼠标」的效果。

算法步骤:

1. 获取全局鼠标坐标: QCursor.pos()
2. 映射到 Widget 坐标系: widget->mapFromGlobal(global_pos)
3. 计算宠物中心: (width/2, height/2)
4. 对每只眼睛的锚点 (eye_x, eye_y):
5.   dx = mouse_x - eye_x
6.   dy = mouse_y - eye_y
7.   dist = sqrt(dx² + dy²)
8.   scale = 1 - clamp(dist / max_distance, 0, 1)
9.   pupil_x = eye_x + normalize(dx) * max_offset * scale
10.   pupil_y = eye_y + normalize(dy) * max_offset * scale
11. 绘制:
    - QColor(255, 255, 255, 220) 画白色虹膜
    - QColor(20, 20, 20, 235) 画黑色瞳孔在偏移位置
    - QColor(255, 255, 255, 180) 画小白色高光点

7.2 拖拽与点击

事件 处理方式
mousePressEvent 记录拖拽起点;启动单击计时器 (300ms)
mouseMoveEvent 超过拖拽阈值 (4px) 后进入 DRAGGED 状态;实时更新窗口位置
mouseReleaseEvent 结束拖拽,回到 IDLE;保存位置
mouseDoubleClickEvent 取消单击计时器;切换到 HAPPY 状态
单击超时 (300ms) 切换到 SURPRISED 状态
contextMenuEvent 弹出右键菜单

7.3 自主行为

behaviour_timer (1 秒间隔) 执行以下逻辑:

  1. 饥饿值更新:每秒 -0.02,高活跃度状态额外 -0.03
  2. 心情值更新:非暂停状态下微调
  3. 随机行为决策:根据饥饿值、心情值、当前状态,概率性切换到行走、奔跑、坐下、趴下、睡觉、舔毛、伸懒腰、打哈欠、眨眼等状态
  4. 边缘弹跳:碰到屏幕边缘时调转方向 (facing_right = not facing_right)

7.4 系统托盘

操作 效果
单击 / 双击托盘图标 召回宠物 (call_home())
托盘图标 使用宠物 HAPPY 状态第一帧作为图标
托盘 Tooltip 显示 {宠物名}桌宠
托盘右键菜单 与窗口右键菜单相同

8. 精灵生成管线

精灵制作与处理全流程由 tools/build_sprites.pytools/build_mastiff_sprites.py 实现。

整体流程

原始 4×4 姿态图集 (cat_pose_atlas.png / mastiff_pose_atlas_v2.png)
        │
        ▼
┌─────────────────────────────────────────────┐
│  1. 提取: 4×4 网格分割,重叠区域扩展        │
│     最大连通分量提取 → 透明背景角色          │
│  2. 归一化: 各姿态统一到 256×256 画布        │
│     保持底部基线对齐                         │
│  3. 去底: 洋红色 (#ff00ff) 色键 → 透明通道   │
│     溢出色彩去除 (spill removal)            │
└─────────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────────┐
│  步态关键帧 (walk_cycle_v5.png 的 4 张       │
│  极值姿势: 左前/左后/右前/右后)               │
│        │                                     │
│        ▼                                     │
│  Farneback 光流插值 (OpenCV)                  │
│  - 猫: 4 关键帧 → 3 中间帧/段 → 16 帧       │
│  - 藏獒: 4 关键帧 → 4 中间帧/段 → 20 帧     │
│  - RGBA 预乘处理避免透明边缘伪影              │
│        │                                     │
│        ▼                                     │
│  状态动画: 对各姿态施加微妙运动变化           │
│  (位移 / 缩放 / 旋转) 实现呼吸、弹跳等效果    │
└─────────────────────────────────────────────┘
        │
        ▼
  输出: generated/ + animations.json
        │
        ▼
  make_icon.py → orange_cat.ico

光流插值关键细节

  • 使用 OpenCV calcOpticalFlowFarneback 对预乘 alpha 的 RGBA 数据做稠密光流估计
  • 每个像素通道独立插值,alpha 通道参与计算但插值后 clamp 到 [0,255]
  • 透明背景区域(alpha == 0)的 RGB 在插值前清零,避免「透明像素 RGB 污染」

9. 构建与打包

环境准备

python -m pip install -r requirements.txt        # 运行时依赖
python -m pip install -r requirements-dev.txt    # 构建依赖

完整构建流程

build.bat 按顺序执行:

tools/build_sprites.py     → 生成猫咪精灵
tools/make_icon.py         → 生成应用图标
pyinstaller --noconfirm --clean orange_cat_pet.spec  → 打包

PyInstaller 配置 (orange_cat_pet.spec)

关键配置项:

  • 入口脚本:main.py
  • 窗口模式:console=False(不显示控制台窗口)
  • 包含资源目录:assets/ 整体打包进 _MEIPASS
  • 额外二进制 / 数据文件:通过 TOC 清单指定

输出

dist/OrangeCatPet/OrangeCatPet.exe   ← 最终可执行文件

10. 测试

运行测试

$env:QT_QPA_PLATFORM="offscreen"
$env:ORANGE_CAT_DATA_DIR="$PWD\.runtime\test-data"
python -m unittest discover -s tests -v

测试覆盖

测试文件 覆盖范围
test_assets.py 动画清单完整性、RGBA 图片验证、尺寸一致性、步态帧数、色键去底效果
test_models_settings.py PetSettings 序列化/反序列化、边界值校验、默认值恢复
test_qt_smoke.py Qt 环境可用性、窗口创建、动画剪辑加载
test_selection.py 选择对话框 UI 元素、宠物卡片渲染

11. 配置与环境变量

运行环境

环境变量 说明 默认值
QT_ENABLE_HIGHDPI_SCALING 启用高 DPI 缩放 1
ORANGE_CAT_DATA_DIR 设置文件存储目录 (用于测试)
QT_QPA_PLATFORM Qt 平台插件 (测试用)

设置文件

  • 路径: %LOCALAPPDATA%\OrangeCatDesktopPet\settings.json
  • 格式: JSON
  • 字段: selected_pet, x, y, volume, always_on_top, autostart, hunger, mood, paused

开机自启

Windows 注册表项:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
  └── OrangeCatDesktopPet = "<pythonw.exe路径>" "<main.py路径>"

源码运行时使用 pythonw.exe(无控制台启动),打包后直接指向 OrangeCatPet.exe


本文档描述的项目版本为 1.0.0,对应 pyproject.toml 中定义的版本。

若想要获取代码和游戏 ,绿泡泡搜索 “码来的小朋友” 然后发送回复“14桌面宠物” 即可获取。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐