我做了个「占领城市」的步行App:走到哪,哪就是你的领地

4 阅读5分钟

上个月我把一个想法做成了 App 上线:用真实行走来占领地图上的像素格,走过哪里,哪里就变成你的颜色。这个想法来自我自己——我不喜欢看步数,但我很喜欢「把空白区域填满」的感觉。

这个 App 叫像素征途(PixelGo),现在 App Store 上能搜到。

核心玩法:地图就是你的战场

说白了,玩法就一句话:走到哪里,哪里的像素格就亮起来,变成你的领地颜色。

但实现起来要处理不少细节。每个像素格(Tile)不是简单亮一下就完事,它有等级、有路线阶层(TileRoadTier),还有衰减逻辑——你走过一次和走过二十次,视觉效果不一样。地图上同时叠了好几层:区域网格、热力模式、地标图层,用户可以切换。

热力衰减这块我设计了几个时间节点:

enum MapHeatRules {
    static let routeGlowSoftDecayDay = 4   // 路线光晕开始变淡
    static let routeGlowDecayDay = 7       // 路线光晕基本消退
    static let zoneEmphasisDecayDay = 14   // 区域高亮衰退
    static let tileResidualDecayDay = 30   // 残留底色消退
    static let tileResidualOpacity = 0.12  // 残留透明度
}

这个设计的目的是:最近走过的路线颜色最亮,老路线慢慢变淡,视觉上能直观看出「我最近活跃在哪」。有用户说这个效果「像热力图但更有游戏感」,我觉得这个描述挺准的。

连击系统:让你停不下来

普通步行 App 靠目标驱动(今天走一万步),我想换一个驱动方式——连续探索的倍率奖励

连续走的天数越多,奖励倍率越高。设计上我没有搞太复杂:

static func multiplier(for consecutiveDays: Int) -> Double {
    switch max(0, consecutiveDays) {
    case 5...:
        return 2.0   // 连续5天以上,双倍
    case 3...4:
        return 1.5   // 连续3-4天,1.5倍
    default:
        return 1.0
    }
}

每天有上限(dailyContributionCap = 50),防止有人一天狂走刷爆数据,也给了普通用户公平感。还有一个「容错日」(graceDays = 1),断签一天不会直接清零连击。这个细节是后来加的——早期内测版本没有容错日,我观察到连击达到 5 天以上的用户比例很低,加了容错日之后这个比例明显上来了。样本量不大,但方向是对的,我现在的判断是:惩罚机制太硬,用户宁可直接放弃,也不会咬牙坚持。

每日摘要:数据可视化的取舍

DailyExplorationDigest 这个结构是我想了比较久的地方。每天的探索记录要聚合成一个「战报」,给用户看今天点亮了多少格、走了多远、主要在哪个区域活动。

有一个小设计我挺满意的:活跃强度分四级(0-3),根据当天点亮的新格子数量判断——0 格是 0 级,1-4 格是 1 级(轻度),5-14 格是 2 级(中度),15 格以上是 3 级(重度)。这个值直接驱动日历视图里每一天的色块深浅,一眼就能看出哪天「出击」了哪天宅家了。

用户反馈里挖出来的问题

上线后评分是 5 分(目前 7 条评价),但评论里有几条挺有价值的反馈:

有个用户叫「糖葫芦啦啦啦」,指出导入历史照片位置只支持最近 3 年,他更早的旅游照片位置进不来。这个限制是我当时怕数据量太大加的,现在看来有点武断,应该让用户自己选范围。

另一个用户发现探索榜无法参与——这个功能我还没完全做好就上线了,有点可惜,等下个版本补上。

还有用户说图标「看着像游戏图标」——说实话我自己也知道图标设计是弱项,这条反馈已经在 TODO 里了。

技术上踩过的坑

地图初始化有个细节:用户第一次打开 App、还没授权定位、也没有缓存中心点的情况下,地图该显示在哪里?

如果默认用 (0, 0)——也就是「Null Island」,地图会开在大西洋中间,体验很差。我按照系统 locale 做了区域映射,中国用户默认显示上海陆家嘴,日本用户显示东京站,美国用户显示旧金山,以此类推。这个处理方式很简单,但第一次打开 App 的感受差很多。

另一个麻烦事是多会话数据聚合。用户一天可能开了好几段记录,需要按日期 key 合并成一条 DailyExplorationDigest。这里用了 Dictionarydefault 下标来做分组,代码很干净,但要注意 DateFormatterdateFormat 必须精确到 yyyy-MM-dd,不然跨时区用户会出现日期归错的问题——这个 bug 我排查了好一会儿。

现在的状态

老实说,下载量还很低,刚上线,基本靠朋友传播。但做这个 App 的初衷就是想验证一件事:把「走路」这个动作的反馈从「今天走了多少步」换成「今天占领了哪片区域」,会不会让人更有动力出门

从我自己用的体验来说,确实会。上周出门买咖啡,绕了一条平时不走的路,就因为地图上那片区域还是空白。

对了,想问问做过游戏化设计的朋友:惩罚机制和容错之间你们怎么取舍的?是靠数据跑出来的,还是一开始就有设计直觉?我这个容错日的决策基本靠感觉,想知道有没有更系统的方法。