一开始只是海鹦
Lovund 有挪威数一数二大的海鹦栖息地,所以等我把那座山画好之后,附近不放一只海鹦总觉得说不过去。画了一只小小的像素海鹦,给它设了一条慢慢飞过页头的路径,然后刷了大概三十遍页面,就为了看它飞。
然后我就想,一只鸟看着有点孤单。
然后就停不下来了
加了一只海鸥。又加了一只飞法不一样的海鸥。接着我就有点收不住手了:一只蛎鹬沿着海岸线走来走去,一只燕鸥用比海鸥的慢滑翔更快、更急促的动作来回穿梭,一只鸬鹚低低地伏在水面上,还有一只体型更大、更稀有的海雕,出场率比其他鸟都低得多。
每个物种都有自己的大小、速度,以及出现的频率。海雕尤其被我调得很少见,毕竟这么大的一只鸟,稀有一点感觉才对。

上限,因为真的有点失控了
有一阵子我把出鸟的概率设得挺高,刷新一次页面能看到十五只鸟一起挤在小小的页头空域里抢地盘。看着不像海边博客,倒像希区柯克电影。于是在绘制逻辑里直接加了个小小的保险丝把它卡住:
const guaranteed = CANDIDATES.find((c) => c.guaranteed)!;
const rest = CANDIDATES.filter((c) => !c.guaranteed);
const shuffled = [...rest].sort(() => rand() - 0.5);
const extraCount = 2 + Math.floor(rand() * 3); // 2-4
const chosen = [guaranteed, ...shuffled.slice(0, extraCount)];鱼,还有一个纯属我自己的乌龙
也在水线以下加了鱼,一些小小的像素形状在鸟飞的地方下面来回游。早期有个阶段出过一个 bug,其中一只"鸟"其实是画在水线下面那一层的,我盯着一只鸟看起来像在游泳这件事困惑了足足十分钟。后来才发现,我压根没把每一层真正钉死在各自的区间里:
// first pass: one shared list, whatever top value felt right per sprite
const sprites = [...birds, ...fish].map((s) => ({ ...s, top: s.top }));没有任何东西能阻止一只鸟的 top 值不小心落到水线以下,因为它们全都只是同一堆数字里的数字而已。修法是把每一层钉死在自己的区间里,而不是指望自己随手打的数值靠谱:
// row 3's top edge, 60% down the strip's own height, is literally
// "the waterline" in this layer's own geometry. Humans stand in the
// land band (top 0-60%); fish swim in the sea band (top 60-100%).
//
// Caps: fish <= 3 (here: 3), humans <= 2 (here: 2 -- one standing, one
// running).现在修好了,鸟飞它的,鱼游它的,各走各的道。
顺手还塞了一个很小的像素跑者沿着海岸慢跑。半是玩笑,半是我把自己也放进了这个场景里,毕竟跑步是我在这座岛上真会做的事情。
每次刷新都会重新洗一遍出哪些鸟、几条鱼、跑者在哪儿。小细节而已,但它让页头感觉是活的,而不是一张死图,这本来就是我想要的效果。