我用 SpriteKit 给存钱罐装了个物理引擎

调参数调到一半,我顺手往测试账号里又存了 200 块,纯粹是因为看硬币掉落太过瘾。那一刻我觉得这个 App 的核心方向对了。

存钱 App 不少,但我自己用了一圈,基本上三天就卸载了。问题不是功能不够,而是「存进去」这个动作没有任何正向反馈。往银行账户转 500 块,余额多了个数字,然后呢?没声音没动静,脑子里毫无反应,下次就很难再重复这个行为。做「聚沙攒钱」大概花了三个月,现在刚上线,核心想解决的就是这一件事:让存款这个动作本身变得有意思。

用 SpriteKit 做硬币物理动画

这是整个 App 里我花时间最多的部分。存款的时候,硬币从屏幕上方掉落,碰到罐子边缘会弹一下,堆在罐底。存的金额越大,掉的硬币越多。用 SpriteKit 的物理引擎实现,核心逻辑大概是这样:

swift 复制代码
func spawnCoins(count: Int, into scene: SKScene) {
    for _ in 0..<count {
        let coin = SKSpriteNode(imageNamed: "coin")
        coin.physicsBody = SKPhysicsBody(circleOfRadius: coin.size.width / 2)
        coin.physicsBody?.restitution = 0.4
        coin.physicsBody?.friction = 0.6
        let startX = CGFloat.random(in: scene.size.width * 0.3...scene.size.width * 0.7)
        coin.position = CGPoint(x: startX, y: scene.size.height + 20)
        scene.addChild(coin)
    }
}

restitution 控制弹性,friction 控制摩擦。这两个值调了挺久------最开始设 restitution = 0.8,硬币在罐里弹来弹去像乒乓球,完全不对;换成 0.1,又像石头直接沉底,没有金属感。来回试了大概二十组,0.4 + 0.6 是我自己觉得最接近「真实硬币掉进陶瓷罐」的感觉。就是在调这个参数的过程中,我忍不住顺手存了那笔 200 块。

双模式:短期愿望 vs 长期定投

产品结构上做了两种模式。

愿望模式:适合「我要攒钱买 AirPods Max」这种有明确目标的场景,设目标金额,每次存款推进度条,距离目标还差多少天一目了然。

聚沙模式:基于 DCA(定期定额)逻辑,设定每周或每月固定存入金额,内置复利计算器,输入年化收益率之后可以看到 N 年后的预估结果,适合想养成长期储蓄习惯的场景。

两种模式放在一起,设计阶段我自己也担心会让人觉得混乱。但在早期十几个测试用户里,有三四个两个模式都开着------一个用来存旅行基金,一个用来强迫自己每月定存。这个比例让我觉得放在一起是对的,两种心理状态确实可以并存。

成就徽章系统

参考了健身 App 的逻辑,把可见的里程碑作为习惯强化手段。徽章判断条件全部基于 StatsSummary 这个结构体,包括总存款金额、连续天数、存款时间段等等:

swift 复制代码
BadgeDefinition(id: "streak_7", name: "Week Streak",
    description: "Deposit 7 days in a row",
    category: "streak") {
    $0.currentStreak >= 7
},
BadgeDefinition(id: "night_owl", name: "Night Owl",
    description: "Deposit 10 times at night",
    category: "special") {
    $0.nightDeposits >= 10
}

「Night Owl」和「Early Bird」是我比较喜欢的两个,晚上存了 10 次和早上存了 10 次分别解锁。有测试用户看到「Night Owl」的时候说「这个 App 懂我」,这个反馈挺好的------徽章在记录的不只是金额,还有一个人存钱的时间节奏。

每日语录:18×18 组合生成

这个模块有点意思。我不想手写几百句鸡汤,所以用了组合逻辑------18 个「主语」乘以 18 个「谓语」,生成 324 种组合,足够一年内不重复。

比如「固定的存钱节奏」+「会让焦虑一点点淡下去」,「一杯奶茶的钱」+「能抵消很多小小的冲动消费」。有些组合挺通顺,有些拼出来确实略生硬,读起来像机器写的。生硬的那些我做了一个黑名单手动过滤,大概淘汰了 40 句,剩下的整体可读性还不错。说白了,这是个半自动流程,机器打草稿,人工做最后一道筛。

做错了的几个决定

订阅定价改了两次。最开始想做纯免费带广告,后来发现存钱 App 里放广告体验很糟,用户存钱存到一半弹出来一个游戏广告,心情直接崩了。改成一次性内购之后反而顺一些。

数据备份功能上线比预想晚了一个版本。有个测试用户换手机之后数据全没了,找我反馈,最后一条消息就是「我的数据没了」,然后就没再说话。我盯着那条消息看了挺久,没法回复什么。那之后备份功能直接插队到下个版本,别的需求全往后推。用户数据这件事,v1 就该做好,没有借口。

「聚沙模式」的 UI 一开始做得太复杂,复利计算器有七八个输入项,我自己用的时候都觉得烦,后来砍掉大半只留核心参数。试了三个方案,最后全删了重来。功能多不等于有用。

一个没解决的 SpriteKit 问题

目前有个穿模问题没有根治:硬币数量一多,相互重叠之后会出现轻微穿透。我现在的做法是限制单次最大生成数量,同时用 categoryBitMask 给硬币单独分一个碰撞分组,让它们只和罐壁、罐底以及彼此发生碰撞,不影响 UI 层的其他节点:

swift 复制代码
coin.physicsBody?.categoryBitMask = PhysicsCategory.coin
coin.physicsBody?.collisionBitMask = PhysicsCategory.coin | PhysicsCategory.jar
coin.physicsBody?.contactTestBitMask = PhysicsCategory.jar

这样能减少无关碰撞计算,但硬币堆多了之后还是会穿模,治标不治本。有做过 SpriteKit 堆叠物理的朋友吗?是怎么处理这个问题的?

相关推荐
开心就好20252 小时前
Charles配置HTTP和HTTPS抓包完整指南
后端·ios
JarvanMo2 小时前
7 个开源 iOS 应用,让你成为更好的开发者
前端·ios
白玉cfc2 小时前
OC底层原理:alloc&init&new
c++·macos·ios·objective-c·xcode
ZZH_AI项目交付2 小时前
一个 iOS 埋点 SDK 从 0 到 1,再到真实项目接入打磨
ios·app·ai编程
2501_915918413 小时前
使用快蝎IDE进行iOS开发:从项目创建到真机调试全流程
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
iFlyCai3 小时前
iOS开发进阶:深入理解 Getter 与 Setter 的用法(超详细)
ios·objective-c·xcode
2501_9160088916 小时前
深入解析iOS应用启动性能优化策略与实践
android·ios·性能优化·小程序·uni-app·cocoa·iphone
美狐美颜SDK开放平台17 小时前
短视频/直播双场景美颜SDK开发方案:接入、功能、架构详解
android·ios·美颜sdk·第三方美颜sdk·视频美颜sdk
库奇噜啦呼18 小时前
【iOS】内存对齐原理
macos·ios·cocoa