1.7 的长按提示是一条红色进度条,加一个“正在关闭…”和倒计时,背景是一片渐变的暗红。功能没有问题,但用户看了一眼说:背景太简单了,能不能更有游戏的感觉,要炫酷一点,但不要太土。
这一篇记录 1.8 里这件事是怎么改的:改了好几轮,中间有整套方案被推翻,最后落在“像素画的四季”上。也记录哪些东西是我实际看到了的,哪些只能靠用户在真机上确认。
推翻重来:四种风格都不对
第一轮做的是游戏 HUD 的路子:斜切的面板、发光的边线、慢慢流动的斜纹,倒计时越走面板越“烧热”,进度条前端迸出火星。用户觉得动画效果非常不错,接着提出可以做几套不同主题,在设置里切换,比如樱花、我的世界、日式赛博朋克。
于是做了四套:默认的烈焰,加上樱花、像素方块、东京霓虹。看完之后用户的判断很干脆:这几个风格都不对,默认的也不对。新的方向是他给的:直接拿我的世界的质感当作春天,再做夏、秋、冬,一共四个季节,用像素画的方式简单地表达,四个主题四种颜色;冬天就是下面结冰,长一点也没关系;还有一条逻辑:靠近边缘的地方完整,越靠近中间越淡。
这一步我觉得值得记下来:前面那套方案在技术上都是能用的,动画也流畅,但方向不对。用户不是在挑细节,而是在告诉我“我要的是什么感觉”。把整套推掉,比在错的方向上继续调参数要快得多。
设计规则:像素画的四季
最后的版本是一幅贴着 Touch Bar 边缘的横版像素小场景。春天是草地、小屋和炊烟;夏天是海面、棕榈岛和方块太阳;秋天是落叶铺的地面、稻草人和南瓜;冬天是结冰的地面、落满雪的木屋和远处的雪山。每个季节一个主色,进度条也换成对应的颜色和纹理:春天的草绿、夏天带浪沫的蓝、秋天橙红金的叶子、冬天带反光的冰蓝。
第二轮反馈把地面砍到了最少:最下面直接就是地面,只有几格厚;进度条坐在地面上,是像素游戏里常见的分段条,上亮下暗,每隔几格一道缝;一个小角色沿着它往前跑:春天是小狗,夏天是帆船,秋天是狐狸,冬天是雪橇,走满就是倒计时结束。远景是向后滚动的山丘、树和房子的剪影,方向和角色跑的方向相反,人是在往前走的;烟囱在冒烟,天上有蝴蝶或小鸟飞过,花瓣、落叶、雪在背景里飘。
“边缘完整、往中间渐渐淡”我没有用平滑的渐变去做,而是用有序抖动:越往里,画出来的方块越稀疏,一块一块地消失。像素画里没有平滑的渐变,抖动才是它自己的语言,也和整个画面更统一。
技术上怎么做:全部是代码里的字符画
整个场景没有任何图片资源。每个小东西都是一组字符串,比如小狗是七行、十二列的字符,每个字符对应一种颜色,启动时画进一块每格 1pt 的画布。Retina 屏上一格正好是一个像素点,放大也不会糊。App 体积因此只从 1.1 MB 涨到 1.5 MB,其中一部分是这些字符画。
远景是三个周期宽的一张图,滚动一个周期正好首尾相接,所以可以一直滚。要让它也在中间淡出,不能把淡出画进图里(图会跟着滚),所以用一张固定不动的抖动遮罩,只露出靠边缘的一段。角色的跑步、蝴蝶的扑翅是逐帧切换的图片,烟、花瓣、落叶、雪是粒子发射器,全部由 Core Animation 播放。
文字也顺手改了:放大到 14pt、粗体、加一层淡淡的投影。“看不清”的真正原因不只是字小,还有我之前为了做流光,把文字大部分时间压在了 42% 的亮度上,这才是发灰的根源。现在基础亮度是 80%,流光扫过时才是 100%。
有些东西,只有把窗口真的跑起来才看得见
这个项目原来有一个离屏渲染的工具,把 Touch Bar 画成一张 PNG,用来检查布局。但离屏渲染画不出粒子发射器,也画不出任何正在进行的动画。所以这一轮我加了一种模式:把窗口真的显示在屏幕上跑起来,再用系统的截图命令按窗口截下来。README 里的四张季节图就是这样来的,图里的小角色、粒子都是真的在动的那一帧。
这个办法直接抓出了两个问题。一是从顶上落下来的花瓣和雪根本看不见:我把发射点放在了面板外面,粒子没进画面就已经在淡出了,用户在真机上看到的反馈也是“飘的东西太靠屏幕上方外面”,最后改成在面板里面、地面上方生成,先小后大再淡出。二是一个我读代码时发现的隐患:某个季节的颜色字典里同一个键写了两次,编译器不报错,但运行时会直接崩溃,在安装之前就改掉了。
右侧按钮:咖啡杯和窗口居中
同一轮里,右侧的两个按钮也换了。小眼睛换成了咖啡杯:点一下是“歇一会儿”,暂时把 Touch Bar 还给系统,含义比眼睛更直接。窗口居中按钮是这个版本新增的:把最前面 App 的窗口居中到屏幕正中,大小在菜单里选,用的是公开的辅助功能接口。两个按钮的顺序对调,居中按钮在最右边;图标都是白色的 8 位像素风,和长按提示里的像素画是一个味道,咖啡杯的杯口有三帧蒸汽轮流上飘。
我担心过这个常驻的蒸汽动画会破坏“空闲唤醒 0 次”,所以装上之后重新量了:读内核的进程计数器隔 20 秒做差,量了四次,空闲唤醒每次都是 0,CPU 0.0%,内存约 29 MB。但有一点要老实写:中断唤醒不再总是 0,四次里是 0、1、0、7,我没法确认这些是不是蒸汽造成的,更可能来自系统事件。蒸汽是系统的渲染进程在画,不是 App 自己在动,这一点是成立的,但系统那边的绘制开销我测不了。
验证:这次我能验证什么,不能验证什么
能验证的:四个季节在真实窗口里跑起来的样子(截图里能看到小角色沿进度条移动、炊烟、飘落物、滚动的远景);提示贴在左边时的镜像;很长的 App 名字会被截断而不会挤到倒计时上;构建、签名、公证和 DMG 里的 App 都通过了检查,二进制里没有带出本机路径。
不能验证的:Touch Bar 上真实的触摸手感;这些只有 7 到 8 个点高的小角色,在真机上是否足够清楚,这要靠用户自己看;夏天的海浪流动和太阳光芒的一闪一闪,我没有抓到清晰的静帧,代码和其他季节用的是同一套机制,但不能说“我看到了”。这些我不会写成“测试通过”,只写成已经按上面的思路做好,等用户在真机上确认。
还有一点关于命名:用户最初说的是“我的世界风格”,但这个软件是公开分发的,所以菜单和文档里都没有出现这个游戏的名字,也没有用任何官方素材,画面里只有通用的像素方块和绿色的小光点。