文章目录

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