今年一月,我做过一个很简单的“电子鱼塘”。
逻辑不复杂:环境保持安静,鱼会慢慢成长;出现明显声音,鱼会受到影响。当时我几乎不会写前端代码,主要做法就是把需求告诉 Gemini,让它生成代码,我负责运行、看效果,再把遇到的问题继续用自然语言描述给它。
最后做出来的是一个本地 HTML 文件,下载解压以后双击就能运行。
后来公众号一直没怎么打理,这个项目也就放在那里了。
前几天我偶然打开很久没看的公众号后台,看到有人还在以前那篇文章下面问源码怎么获取。我才想起来,自己几个月前还做过这么一个东西。
于是把旧版本重新翻了出来。
第一反应是:确实挺简陋的。
但我不觉得它是一个失败项目。至少在当时,它已经真的跑起来了,也实现了我最初想要的玩法。
当时我是怎么做的
V1 最开始甚至连鱼的正式素材都没有。
我先让 Gemini 按需求写出一个能运行的版本:保持安静一段时间后鱼会成长,屏幕显示专注时间和下一次成长倒计时,有声音时鱼会受到影响。为了减少环境底噪带来的误触发,当时还尝试用音频频段去近似判断人声。
功能跑起来以后,我再去找鱼的素材,然后继续往里面替换。
也正是在这个阶段,出现了一个很典型的问题。
换完素材后,鱼的视觉朝向和实际运动方向反了。鱼头看起来朝右,实际却往左移动;反过来也一样。
当时我在文章里很直观地形容成“所有的鱼都是倒着游的,像极了倒放”。
后来重新核对逐帧运动,也确认它确实不是错觉,而是鱼头朝向和位移方向不一致。
我当时并没有去读代码找是哪一段算法出了问题,而是继续告诉 AI:
“鱼倒着游了,改一下。”
类似的问题还有水草。最初的水草不是摆动,而是整片左右平移。
整个开发过程基本就是:
我提需求 → AI 改代码 → 我运行 → 发现问题 → 再反馈。
很原始,但有效。
后来 V1 也已经有了成长事件和状态反馈。
这张当年的运行画面里,已经能看到“新鱼诞生!全体成长!”提示,以及专注时间、鱼群状态和下一次成长倒计时。它说明 V1 的成长反馈机制已经实际运行起来,但我不把这张图当成“这一刻确实新增了一条鱼”的证据——保存下来的动画里,鱼群数字并没有同步上升。
这次我没有继续修 V1
重新打开旧版本以后,我一开始也想过,是不是换一下背景、重新做几条鱼、把界面修一修就算了。
很快就放弃了这个思路。
因为真正让我觉得旧的,不只是画面。
V1 更像一个“已经能工作的 Demo”:所有东西都挤在一个页面里,素材风格不统一,鱼的运动比较简单,环境声音判断也更接近单纯的阈值控制。
如果只是给它换一套漂亮素材,本质上还是原来的项目。
所以这一次我没有在旧代码上继续装修,而是重新做了一版。
左边是从旧 ZIP 原样恢复出来的 V1。没有改代码,直接运行当年的 cyberfish.html,20 条初始鱼仍然可以正常工作。
右边是现在的 Electronic Fish 2.0,使用当前正式版本、默认初始状态和真实麦克风完成环境校准,没有人工填充居民,也没有使用开发测试数据。
两张图放在一起以后,差别基本不用解释。
先决定鱼塘应该是什么样
V1 当年做素材的方法很直接:需要鱼,就去找几张鱼图。
这一次我先确定了一个视觉目标。
我想要的不是卡通小游戏式的蓝色背景,而是更接近一个安静的水族箱:水草、沉木、石头和沙地形成固定环境,鱼是画面里的居民,而不是贴在背景上移动的图标。
这张只是当时确定视觉方向时使用的参考图,不是最终产品截图。
方向确定以后,再按同一套风格制作实际生产素材。
最初有灯鱼、神仙鱼、孔雀鱼,后面又增加了金橙色观赏鱼、鼠鱼、樱花虾和斑马螺。
不同生物也不再只是“换了一张图片”。
鱼需要在水体里游动,鼠鱼的位置更接近底层,虾和螺又有完全不同的运动节奏。素材真正放进环境以后,还需要重新检查尺寸、遮挡和视觉密度。
这一步让我明显感觉到,这次已经不像几个月前那样是在“给网页加几张鱼”。
更接近在做一个很小的完整项目。
声音也不再只是一个阈值
V1 右下角有一个滑块,用来手动调整环境声音触发阈值。
这种方式简单,也能工作,但不同房间、不同麦克风、不同设备的底噪差别很大。
V2 启动以后,会先听一小段当前环境声音,建立一个基线,再根据声音相对这个基线的变化判断当前状态。项目里也区分了短暂声响和持续噪声,而不是听到一次声音就立刻中断成长。
短暂的动静会让鱼受到惊吓;只有持续达到更高程度的噪声,成长计时才会暂停。恢复安静以后,再慢慢回到正常状态。
我还保留了三档敏感度,以及不同的成长节奏。
这些都不是为了把功能堆得更多。
主要目的还是解决 V1 当时很实际的问题:同一个固定阈值,在不同环境里并不好用。
鱼还是会出问题
有点巧的是,两次做电子鱼,最后都在“鱼怎么游”这件事上出了问题。
V1 是鱼的素材朝向和实际移动方向相反。
V2 则是另一种问题。
最初实现运动时,鱼的整体旋转角度直接跟速度方向绑定。鱼往右游时问题不大,一旦逐渐往左转,角度接近 180°,整张鱼图也会跟着翻过去。
看起来就像鱼突然侧翻甚至上下颠倒。
这次没有留下修复前的截图,所以我也没有为了写文章重新制造一个错误现场。现有的运动测试图都是修复之后的结果。
最后的处理方式是把两个概念拆开:
左右朝向只负责决定鱼面向哪边;上下运动只允许很小幅度的俯仰。
鱼往左时做水平镜像,而不是让整张图片旋转 180°。
听起来只是一个小问题,但真正做的时候,这类小问题非常多。
AI 可以很快给出一套实现,但画面看起来对不对,通常还是要实际跑起来以后才能发现。
几个月后,变化最大的不是代码
如果只看最终画面,V1 和 V2 最大的区别似乎只是:
一个比较简陋,一个比较完整。
但重新做完以后,我觉得变化更明显的是自己的工作方式。
几个月前,我更多是在这样使用 AI:
我想要一个功能 → AI 给我代码 → 我复制下来 → 运行 → 出问题 → 再问 AI。
代码、素材和文件基本都需要我自己在中间搬来搬去。
这一次,我很少再直接问“这段代码怎么写”。
更多时间是在决定:
这个项目到底要不要重做;
哪些旧逻辑应该保留;
鱼塘应该长什么样;
需要哪些鱼;
声音应该怎么判断;
什么算 Bug;
哪些功能值得继续做,哪些做到这里就够了。
这些问题确定以后,我在 ChatGPT 里继续讨论设计和取舍,再把具体实现、运行测试和修改交给 Codex。
最后还专门把 V1 原样恢复了一次,用相同尺寸重新截图,再检查旧的 GIF、测试图和生产素材到底能证明什么,哪些不能证明。
这已经和几个月前“让 AI 帮我写一个网页”不太一样了。
AI 做的事情更多了,我自己反而更少直接碰代码。
但需要我决定的事情并没有变少。
只是从“这行代码怎么改”,慢慢变成了“这个东西到底应该怎么做”。
Electronic Fish 2.0 现在已经放到了我的个人网站:
https://play.zhuzg.com/aquarium/
这次没有再把它做成一个需要下载到电脑、本地双击运行的 HTML 文件。
打开网页,允许麦克风以后,就可以直接开始养。
至于后面会不会继续增加新的鱼或者玩法,目前没有计划。
先让这个鱼塘安静地待在那里。