文章目录
iPet 是一个基于开源项目 VPet 的 Apple 平台原生桌宠项目。
项目最开始的目标其实很简单:VPet 原本主要运行在 Windows 上,我想尝试把这套桌宠体验带到 macOS。
但真正开始做之后,我很快发现,这并不是一次简单的“跨平台编译”。
原项目建立在 C#、WPF 和 Windows 生态之上,而 macOS 有完全不同的窗口系统、输入机制、图形框架和应用生命周期。如果希望最终得到的是一个真正符合 macOS 使用习惯的应用,就不能只是在外面套一层兼容壳,而需要重新理解原项目的行为,再用 Apple 平台自己的技术重新实现。
于是,iPet 逐渐变成了一次完整的软件迁移、重构和产品化实验。
01. 从 Fork 开始
项目最初直接 Fork 自 VPet:
LorisYounger/VPet
↓ Fork
xufilps/iPet
一开始我对 Git 的协作模型还不熟悉,直接在 Fork 仓库的
main 分支上进行了开发。
随着修改越来越多,一个问题逐渐显现出来:
我的 iPet 和上游 VPet 已经开始承担两个完全不同的角色。
VPet 仍然在继续更新,而 iPet 已经逐渐变成一个独立的 Apple
平台实现。如果继续把自己的大量修改直接堆在 main
上,以后同步上游、比较差异和维护项目都会越来越困难。
因此后来重新整理了 Git 结构:
upstream/main
↓
原版 VPet
origin/main
↓
跟踪 / 同步上游
origin/ipet-dev
↓
iPet 实际开发
现在仓库默认展示的也是 ipet-dev。
这个过程让我第一次真正理解了 Fork、Remote、Branch 和 Pull Request 之间的关系。
以前 Git 对我来说更多是:
add
commit
push
而在 iPet 里,我开始真正理解:
origin
upstream
branch
merge
PR
worktree
这些工具为什么会存在。
02. 第一个问题:不能“移植”,只能重新实现
VPet 原版依赖的是 Windows 技术栈,而 iPet 最终采用:
Swift 6
AppKit
→ macOS 桌面窗口与系统交互
SpriteKit
→ 桌宠动画与渲染
SwiftUI
→ 状态、活动、商店、背包、设置等功能界面
这意味着原来的 C# 界面代码无法直接复用。
真正能够继承的主要是三类东西:
原项目行为逻辑
原始动画与图片资源
原始数据与配置
然后重新实现为:
VPet 行为
↓ 理解
平台无关的数据 / 规则
↓ 重建
Swift Core
↓
AppKit / SpriteKit / SwiftUI
因此 iPet 从一开始实际上就是一次 native reimplementation。
我需要回答的不是:
这段 C# 怎么翻译成 Swift?
而是:
原程序到底在做什么?
例如“摸头”看起来只是一个简单交互,但继续往下拆就会涉及:
鼠标位置
→ 点击区域判断
→ PNG Alpha 判断
→ 当前动作状态
→ 动画切换
→ 心情 / 好感变化
→ 消息反馈
→ 存档更新
当系统被拆开以后,很多原本看似简单的功能才真正显现出复杂度。
03. 先搭一个能活起来的桌宠
第一阶段的目标不是追求功能完整,而是先建立最基本的运行骨架:
透明窗口
↓
角色显示
↓
Sprite 动画
↓
鼠标穿透
↓
点击与拖动
↓
基础状态
↓
本地存档
最初的 macOS 版本已经可以做到:
- 在桌面显示透明角色
- 播放原始逐帧动画
- 根据图片 Alpha 判断实际角色区域
- 让透明区域继续点击下面的窗口
- 抚摸角色
- 长按、拖动和提起角色
- 隐藏与重新显示
- 保存位置和基础养成状态
其中一个很有代表性的问题就是透明窗口的点击穿透。
一个桌宠窗口虽然视觉上只有角色,但从系统角度看,实际上仍然是一个完整矩形:
┌─────────────────┐
│ │
│ Pet │
│ │
└─────────────────┘
如果什么都不处理,整个矩形都会截获鼠标。
因此 iPet 需要进一步判断 PNG 当前像素的 Alpha:
鼠标位置
↓
转换到 Sprite 坐标
↓
读取对应像素 Alpha
↓
角色区域 → 接收交互
透明区域 → 让事件穿透
这也是我第一次意识到:
“看起来正确”和“系统行为正确”完全是两件事情。
04. 动画资源并不是直接全部塞进去
VPet 自带大量角色动画。
如果只是把所有 PNG 原封不动装进应用,不仅项目体积会很大,资源组织、加载和维护都会变得非常混乱。
因此项目后来建立了一套资源转换流程:
VPet 原始资源
↓
解析配置
↓
筛选当前需要的动作
↓
生成标准资源描述
↓
复制对应 PNG
↓
iPet Runtime
应用运行时不需要理解完整的原始 LinePutScript 配置,而是读取已经转换好的资源。
同时还做了:
- PNG 有效性检查
- 帧顺序整理
- 动画阶段划分
- 原文件 Hash 校验
- 按需纹理加载
- 纹理缓存限制
- 重复转换避免无意义写盘
后来随着功能逐渐恢复,动画也从最初几十组扩展到了超过一百组动作、Graph 和状态组合。
这部分工作让我第一次接触到一个很实际的工程问题:
软件不仅需要“能运行”,还必须考虑资源从哪里来、怎样生成、怎样验证,以及未来怎样重新生成。
05. 从“会动”变成真正的养成系统
只有动画的桌宠其实仍然只是一个动态 Widget。
第二个重要阶段,是把 VPet 的养成系统逐渐重新建立起来。
iPet 中逐步加入:
体力
饱腹
口渴
心情
健康
好感
经验
等级
等级突破
金币
随后又加入:
工作
学习
娱乐
商店
物品
食物
药品
背包
以及活动收益、等级限制、连续食用衰减、库存参数、统计等规则。
这一阶段最大的变化,是项目开始从:
Pet Animation
变成:
Pet Simulation
角色不再只是随机播放动作,而是拥有一个持续运行的内部状态。
06. 桌宠最复杂的部分其实是“状态”
随着功能增加,真正困难的地方逐渐从 UI 和动画转移到了状态管理。
比如:
正在工作
正在睡觉
正在被拖动
正在吃东西
正在说话
正在播放特殊动画
正在升级
这些状态不能随意互相覆盖。
例如角色正在吃东西时突然触发升级:
升级发生
↓
显示升级消息
↓
升级动画进入等待队列
↓
当前进食动画结束
↓
播放升级动画
↓
恢复正常状态
类似的问题还有:
- 拖动时是否允许自主移动
- 隐藏后是否继续养成
- 系统睡眠期间是否结算活动
- 暂停活动后计时如何恢复
- 动画被新动作打断时如何清理
- 屏幕切换后角色如何避免跑出可见区域
这让我逐渐理解:
交互软件真正难维护的往往不是页面,而是页面背后的状态和生命周期。
07. 一点一点恢复原版行为
在核心系统稳定以后,项目进入了很长一段“行为还原”阶段。
很多功能都是一个一个对照原版实现:
自主待机
自主移动
特殊待机
边缘动作
移动范围
多屏幕位置保护
桌宠工具栏
活动菜单
活动计时器
说话气泡
逐字显示
消息停留
长文本
消息关闭 / 复制
选择式对话
升级提示
升级动画
其中有不少功能第一次实现后仍然会在真实使用中暴露问题。
例如工具栏中的活动菜单最初采用多级菜单结构,在实际测试时出现无法正常展开的问题。后来重新设计成单层的:
活动
工作
├─ …
└─ …
学习
├─ …
└─ …
娱乐
├─ …
└─ …
重新验收以后才真正稳定下来。
这也是整个项目逐渐形成的一套工作方式:
查原实现
↓
写规格
↓
实现
↓
自动测试
↓
真实运行
↓
发现问题
↓
修复
↓
重新验证
而不是简单地:
写完代码 → 完成
08. 存档是另一个独立系统
当程序开始拥有真正的养成状态以后,存档很快成为一个必须认真处理的问题。
iPet 使用版本化 JSON 存档。
随着数据结构不断变化,存档也经历了多个版本,当前已经发展到 JSON v9。
升级时不能简单覆盖旧文件,而需要考虑:
旧版本
↓
验证
↓
迁移
↓
保留升级前原件
↓
写入新版本
同时还需要处理:
- 主存档损坏
- Previous Backup
- 未来版本存档
- 写入失败
- 非法数据
- 数据大小限制
- 升级回滚
其中一个很重要的规则是:
如果程序发现这是自己无法理解的未来版本存档,就停止覆盖写入。
否则一个旧版本程序可能仅仅因为“不认识新字段”,就把用户的新存档覆盖掉。
这类问题让我开始接触到真正的软件数据兼容问题。
09. 尝试理解 Windows 原版存档
之后项目又向前走了一步:
能不能读取原 Windows VPet 的存档?
这件事比想象中复杂得多。
原版存档使用 LinePutScript,并且存在:
固定点数值
普通浮点数
Int32 / Int64
重复字段
Inventory
Statistics
Data
Hash
甚至不同字段的数字编码规则都不完全相同。
因此这里没有直接做:
Windows Save
→
iPet Save
而是采用了一条更谨慎的路线:
读取
↓
解析
↓
验证
↓
生成 Preview
↓
显示不兼容项
↓
确认
↓
未来再允许写入
目前已经能够只读分析宠物属性、库存、统计和部分 Hash,但完整 Windows 整档导入仍然没有开放。
相比“尽快让它能导入”,我更希望这里优先保证:
不知道的东西就明确说不知道,而不是猜。
10. 从 macOS 继续走向 iPhone 和 iPad
项目最初只准备做 macOS。
但随着核心逻辑逐渐从界面中抽离:
PetCore
PetRendering
PetMacInput
我发现其中相当一部分规则已经可以跨平台共享。
于是 iPet 又增加了 iOS / iPadOS 应用。
不过手机上的桌宠不可能像 macOS 一样漂浮在其他应用上,所以 iOS 没有直接复制桌面交互方式,而是重新设计成:
应用内养宠。
共享:
养成
经济
活动
物品
存档
重新实现:
iOS UI
触摸交互
页面导航
应用生命周期
目前已经有养宠、活动、商店、背包和设置等页面,并采用 Apple 原生界面体系。
这一步也让我更加理解所谓的“跨平台”:
跨平台不一定意味着每个平台长得一样,而是决定什么应该共享,什么应该尊重平台本身。
11. 项目的开发方式也发生了变化
iPet 对我来说还有一个比较特别的地方:
这是一个大量使用 AI 辅助开发的项目。
我主要负责:
定义目标
决定产品范围
提出行为要求
体验应用
发现问题
进行真实验收
决定取舍
而 Codex 参与了大量:
代码阅读
原版行为分析
Swift 实现
测试编写
文档维护
代码审查
问题修复
这也让我逐渐形成了一种新的开发方式。
不是:
我描述一个软件
↓
AI 一次把它生成出来
而更像:
我定义一个阶段
↓
分析现有系统
↓
形成规格
↓
实现
↓
自动测试
↓
我进行真实体验
↓
发现新的问题
↓
继续下一轮
随着项目变大,单靠聊天记录已经无法维持上下文,因此仓库里逐渐出现了:
ROADMAP
HANDOFF
规格文档
行为对照表
上游差异矩阵
验收记录
这些文档实际上成为了项目的长期记忆。
AI 负责执行大量工作,而 Git、文档、测试和规格负责让这些工作保持连续。
12. Git 也从“上传代码”变成了工程的一部分
iPet 最开始让我真正开始使用 Git,并不仅仅是把代码上传到 GitHub。
后来逐渐形成:
upstream
│
└── VPet 原项目
origin/main
│
└── 上游基线
origin/ipet-dev
│
└── iPet 开发
如果需要同时处理不同分支,还可以使用 Git Worktree:
iPet/
→ ipet-dev
iPet-main/
→ main
iPet-feature/
→ feature/...
不同开发任务拥有各自的工作目录,而不是不断在一个目录里 checkout 来 checkout 去。
这是一个很小的变化,但也是我从“会使用 Git 命令”走向“开始理解版本控制工作流”的过程。
13. 当前的 iPet
截至目前,iPet 已经不是最初那个“让角色在 Mac 上动起来”的实验。
它已经包含:
macOS 原生透明桌宠
SpriteKit 动画系统
完整基础养成
工作 / 学习 / 娱乐活动
商店与背包
状态、统计与设置界面
自主行为与移动
消息与选择式对话
版本化本地存档
备份与恢复
Windows 存档兼容研究
iPhone / iPad 应用
当前开发版本为 v0.2.0。
项目使用 Swift 6,当前主要面向 macOS 26、iOS 26 和 iPadOS 26。
自动验证已经覆盖数百项 Swift 测试以及额外的 Python、构建、资源和签名检查。
但它仍然不是一个“已经完成”的项目。
目前仍然存在:
- 长时间真实运行测试
- 多显示器与系统睡眠完整验收
- 部分原版动画和界面行为
- Windows 整档正式导入
- iOS 真机完整验证
- 正式 Developer ID 签名、公证与发行
等工作。
我更愿意把现在的 iPet 看成一个仍然持续演化的工程。
14. 我从这个项目里学到的东西
最开始做 iPet 时,我关注的问题可能只是:
怎么让一个 Windows 桌宠跑在 Mac 上?
但做到后面,我实际接触到的东西已经远远超过 Swift 本身:
Git / GitHub
分支与上游管理
应用架构
模块划分
GUI 与事件系统
动画资源管线
状态机
持久化与数据迁移
兼容性
自动测试
真实设备验收
许可证与开源合规
项目文档
AI 协作开发
它让我越来越明显地意识到:
真正的软件工程不是“把功能写出来”,而是建立一套让功能能够持续存在、验证、修改和演进的系统。
很多第一次接触时觉得多余的东西——分支、测试、规格、迁移、回滚、验收、文档——往往恰恰是在项目变大以后最重要的部分。
所以对我来说,iPet 的意义并不仅仅是做出了一个桌宠。
它更像是我第一次真正把一个已有的大型开源项目拆开、理解,再重新构造成属于自己的产品。
Project
iPet
Native VPet implementation for Apple platforms
Tech
Swift · AppKit · SpriteKit · SwiftUI · Git · GitHub
Platform
macOS · iOS · iPadOS
Repository
https://github.com/xufilps/iPet
Upstream
VPet / LorisYounger
https://github.com/LorisYounger/VPet