一探究竟 · · 阅读约12分钟
Kortress Beach 制作记:从并肩坐下,到一起听歌的夜间海滩
本文目录
我想做一个可以一起望着夜海、点燃手中烟花、听同一首歌的空间。 Kortress Beach 的起点是山寺。 不必说很多话,也能和其他人待在同一个空间里,这一点给了我启发。 把这个想法带到自己网站的角色和记忆中的场景里,就成了 Beach。
最初,我想过另外制作一个像山寺那样朴拙又可爱的角色。 最后还是决定使用 Kortress 的吉祥物 Korsh。 我让原本在 Kortress 中使用的 Korsh,也出现在 Beach 里。 为了营造亲密的氛围,也让角色的外观更加多样,我还加入了女性 Korsh。 她的名字还没有决定。

当前版本中并肩坐着的 Korsh。2026 年 11 月 4 日在本地模拟模式下拍摄,旁边出现的是电脑角色。
这篇制作记既介绍功能,也记录画面背后修正过的问题。
代码以 2026 年 11 月 4 日的 0280637 提交为基准进行查看。
文中区分了新拍摄的当前画面和保留下来的开发中录像。
录像里的 Mina 是测试对象的昵称,并不是女性 Korsh 的名字。
如果想了解带来启发的空间,可以先读在山寺观察到的无言互动。
在海边可以做什么
进入 Kortress Beach 后,先选择昵称和角色。 之后可以在海滩上走动,或选择工具点燃烟花。 可以独自看看四周,也可以向其他人发起共同动作的邀请。
目前工具菜单除了空手之外,还有五种工具。 分别是手持仙女棒、手持连发烟花,以及放在地面的喷泉烟花、爆裂连发烟花和大花连发烟花。 手边闪耀的火光,与升上远处天空的烟花,观看距离本来就不同。 不同工具的手持方式或地面放置过程也不一样。
| 想做的事 | 画面中的操作 | 值得一起观察的部分 |
|---|---|---|
| 逛逛海滩 | 移动、转动画面或缩放 | 角色与地平线之间的距离 |
| 点燃小烟花 | 选择仙女棒或手持连发烟花后点火 | 手与工具末端的动作 |
| 在地面放烟花 | 摆放喷泉、连发烟花并点火 | 工具间距、点火姿势与发射方向 |
| 用光写字 | 在仙女棒书写画面中直接画线 | 输入的线条与角色手臂的连接 |
| 一起停留 | 邀请对方并肩坐下或牵手散步 | 接受之后的距离与视线 |
| 听同一首歌 | 通过一起听音乐共享 YouTube 音乐 | 同时保持坐下、行走与音乐状态 |
桌面端可以用 WASD 或方向键移动,拖动来旋转视角。 滚轮调整距离,Shift 用于奔跑,Space 用于跳跃。 F 会根据场景用于放置或点火。 刚开始,与其记住快捷键,跟着屏幕上的工具名称和动作按钮操作更容易。 移动端则有独立的移动摇杆和跳跃按钮。
在一起坐下和行走的场景里,放进过去的记忆
并肩坐下、一起散步和听歌这些功能里,包含着我过去的记忆。 即使烟花暂时停歇,也可以继续坐在一起;坐着的时候,也能听同一段音乐。 想在 Beach 营造亲密氛围的念头,不只体现在角色外观,也延续到了这些行为上。
观看视频:彼此对望——开发中的原型
共同动作的流程是:选择对方,发出邀请,然后等待接受。 看起来好像只要把两个角色放进场景,播放一个预设动画就够了,实际却有许多准备工作。 如果彼此离得远,就需要先靠近;各自模型准备完成的时间也不同。 等待过程中可能取消,也可能断开连接。 必须避免已经取消的准备任务迟迟完成后,又覆盖新的状态。
因此,代码里同时有请求、靠近、准备、激活状态和取消处理。 靠近和准备阶段设有超时,会重复发送准备就绪信号,也会筛掉过期的异步任务。 两人相伴的短暂场景,也必须在两个不同画面上按相同顺序成立。 如果点击按钮后,看起来什么都没发生的时间太长,用户可能还没等到那个场景就失去了兴趣。
尤其是在接受邀请的瞬间,复制实际角色骨架、测量手掌表面和步态数据,这些工作造成了负担。 我把不必每次重复的测量移入预先准备的数据,并复用现有材质和步态表。 同时检查指纹,避免模型或计算方式变化后,还继续使用旧数据。 这里想缩短的,不是画面中动作持续的时间,而是开始动作前反复执行的准备工作。
相关修改和验证范围,保留在仓库的 deploy/couple-stability-2026-11-03.md 中。
牵起手后,手腕的偏差最先显现
观看视频:牵手散步——开发中的原型
牵手散步时,比起让两个角色以相同速度移动,更难的是手接触时的形状。 只把手的位置放近,可能会让手掌方向错开,或把手臂拉扯得不自然。 如果只靠一个手腕的旋转来解决,皮肤就可能像糖果包装纸一样扭起来。
当前实现以手掌中心和方向为基准建立接触关系,并反复修正两侧手臂。 遇到手臂长度不够的姿势时,也会调整接触点。 还有一层处理,会把扭转分摊到肩膀和前臂,避免旋转都集中在手腕。 手指握紧和交握的形状,也作为姿态的一部分来处理。
这里使用的 IK,是先确定手应该到达的位置,再调整手臂关节的方法。 比起名称,结果更重要。 即使身体移动、头部转动,手也应当保持与对方的关系。 需要一起检查:静止画面里看着还不错的手,开始行走之后会不会变得别扭。
彼此对望时的头部旋转,以及从行走到停止时的权重和速度,也都改为逐渐变化。 不过,目前并不是所有坐下、起身的转换,都已经连成一套完整的全身动画。 我想把已经打磨的部分,与以后还需要变得更自然的部分区分开。

当前版本中的彼此对望功能。与本地模拟中的电脑角色一起拍摄。
地面应该在海浪触及之后才变湿
海水与会变湿的地面,是我特别花心思的部分。 只给水面加一些闪光、把沙地调暗,并不能让它像真正的水边。 细小的先后顺序一旦错了,海浪就容易变成一种背景特效。
代码中实际保留的反馈就指出:湿地先出现,随后海浪才涌入,看起来顺序反了。 旧方式会在新一轮海浪开始时,预先把它稍后才能到达的最远位置也视为湿润。 进入下一轮海浪时,上一轮痕迹的形状也会突然变化。 湿痕先于水出现,以及痕迹不连续,这两个问题叠在了一起。
改善前:水边的海浪表现——开发中的原型
改善后:水边的海浪表现——开发中的原型
这两段视频是开发时海浪表现的对比资料,并不是单独验证下述当前湿润处理方式的对照实验。
现在只记录本轮海浪实际到达的最远位置。 同时,另外记住上一轮海浪留下的终点、纹理,以及退水时间。 不会因为新一轮海浪开始,就把先前的痕迹也改成新纹理。 上一轮留下的痕迹按自己的时间逐渐干燥,下一轮则只在水实际进入的范围内生成新痕迹。
按顺序写下来,流程是这样的。
- 白色水波向岸边靠近。
- 随着水触及的范围,扩大湿润区域。
- 水退去,但湿痕留下。
- 随时间逐渐降低残留痕迹的暗度和光泽。
- 下一轮海浪到来时,仍把尚未消失的上一轮痕迹一起计算。
湿润程度以 exp(-age/9) 的形式降低。
这不是一个 9 秒后突然完全干燥的计时器,而是随时间逐渐减弱的衰减。
这个值会继续影响沙地颜色、光泽、天空反射,以及对烟花光照的响应。
在考虑哪里有多闪亮之前,先让“为什么这里仍然是湿的”变得有据可循。
海浪涌入和退去的时机,也会与拍岸声对齐。 近海的白线、涌向岸边的线条,以及后面残留的泡沫,各有不同的作用。 我并没有制作完整的流体模拟。 这是一个用程序化演出,让眼睛看到的痕迹和耳朵听到的声音指向同一轮海浪的海面。
这次修改说明,风景的说服力不只来自某一帧,也来自场景之间的连续性。 前一幕的结果,应该在下一幕里留下来。 对湿沙而言,这份短暂的记忆就是海浪的痕迹。
引线被点燃的瞬间,也是场景的一部分
我也在烟花工具的真实感上下了功夫。 按下按钮后,会展示角色操作工具并点火的过程。 手持工具和放在地面的工具,点火姿势首先就应该不同。
观看点火动作——伸手点燃工具的开发中原型
当前代码分别设置手持工具和地面工具到达点火位置所需的时间,以及整个动作的时长。 点燃地面工具时,角色会转身并压低身体。 如果这时只弯下躯干,脚看起来也可能跟着滑动。 因此,会保存脚的位置和方向,并修正腿部关节,让脚留在原处。
右手握着的点火棒,也会通过重新调整手臂,让末端接触引线。 点火结束后,还留出回到持物姿势的时间。 重要的不只是烟花的亮度,而是手、棒尖和引线要呈现同一件事。 如果火已经点着,手却指着别处,使用工具的感觉就会减弱。

当前版本海滩上的工具摆放与点火周边场景。在本地模拟中拍摄。
我也区分了各类工具的效果性格。 仙女棒表现手边持续的光,喷泉烟花表现从地面向上喷出的火花。 连发烟花和大花烟花则有发射与爆开的先后过程。 这种区分,并不意味着精确复现了所有火药的物理过程。 它是在画面内,让放在哪里、怎样点火、会出现什么光,都显得合理的工作。

当前版本中,手持仙女棒照亮了面部与周围。在本地模拟中以近距离角度拍摄。
把仙女棒画出的线,接到角色的手上
用光写字——开发中的坐标映射对比视频
仙女棒书写,不同于输入文字后把它显示出来的功能。 它把用户亲手画出的线,连接到角色所持工具的轨迹上。 上方视频是在开发中调整输入与手臂动作的对比画面;当前 UI 使用全屏画布。
鼠标或手指移动的是平面屏幕,而角色的手位于三维空间。 如果直接把屏幕坐标映射到宽阔空间,就可能产生手够不到的点。 需要同时确定角色的臂长、工具方向,以及用于书写的平面大小。
现在会先按画布对输入坐标进行归一化,再映射到手臂能够触及的书写空间。 扩大输入面积,与放大角色的动作,并不是同一件事。 需要让小屏幕上也容易画,同时又不能让场景中的手过度伸展。 改用全屏书写 UI,也是整理这段输入体验的过程之一。
对读者来说,这只是一个短暂的画线动作;制作时,却需要同时处理屏幕输入与角色身体。 烟花的轨迹,与手持工具的移动相连接。
同一段音乐,让坐在一起的时间延续
一起听音乐的 UI——开发中的原型·无声录像
一起听同一首歌的功能,也承载了过去的记忆。 因此,开始音乐时,不会要求先结束坐下或行走。 可以向已经一起坐着或走着的对象,叠加音乐。 代码里也会保留现有的同行行为,并把音乐状态连接进去。
音乐使用 YouTube 播放。 发起请求的一方会定期发送作为基准的播放时间,接收方则考虑传输后经过的时间。 如果播放位置偏差超过 2 秒,就跳到对应位置。 两个画面并不是由完全相同的音频时钟驱动,也不会共享彼此的缓冲状态。
外部视频的嵌入限制,以及浏览器自动播放政策,也会产生影响。 代码中有在播放受阻时提示错误,以及无法自动播放时引导用户手动播放的处理路径。
上方视频使用测试对象来演示共享 UI,应与实际两人通过网络播放的验证录像区分开。
其他人也应该看到此刻的海滩
独自观看的烟花,与和其他人一起观看的烟花,需要解决不同的实现问题。 后来进入的人,也应该看到已经摆在地上的工具。 如果有人点火,其他画面中的那件工具也应该处于点燃状态。 还需要处理这样的情况:坐下者的手势,在旁观者看来却成了错误姿势。
Beach 并没有采用持续发送所有烟花粒子坐标的方式。 它分开发送工具类型、位置、进行状态和时间等信息,再由各个画面构建效果。 变化较快的手持工具、角色移动,以及停留较久的地面工具,也使用不同的发送周期。 地面工具会通过重新发送完整列表补齐缺失状态,也会发送空列表来清除过期工具。
共同动作中角色的姿态,也不只共享一个动作名称。 代码里有发送、还原动画与关节修正后实际姿态的处理,连手指握紧的形状也包括在内。 因为某个人“正在坐着”,与他现在以什么姿势坐着,是不同的信息。
虽然加入了连接与后进入者恢复状态的机制,但这并不意味着所有卡顿都已消除。 目前房间人数上限这类配置值,也不同于在多台实际设备上确认流畅运行的性能测试。
抵达漂亮场景之前的操作,也在继续打磨
最近的改动包括奔跑、跳跃、入场引导和移动端操作改进。 奔跑从单纯提高行走播放速度,改成了独立的 Run 动画。 在保留现有骨架和动画的同时加入奔跑数据,并测量实际鞋子与手部的形状来确认动作。 之后也继续留下了调整手臂、手腕和步幅节奏的记录。

这是用于检查两位 Korsh 奔跑动画的开发预览,并非海滩中的游玩画面。
移动端上,不可见的移动判定区域曾经比看得见的摇杆更大,导致想转动视角时角色却移动了。 我整理了移动区域与拇指接触位置,同时考虑屏幕边缘和工具栏。 确认小屏幕上按钮是否重叠,与检查真实手机的发热和帧率,需要分别进行。
入场流程也调整为:角色准备完成之前,不开始接受操作。 如果等待过久或发生错误,会告知状态并允许重试。 进入后,则会介绍移动方法。 来到海边却不知道能做什么,或等待时感觉画面卡住,也都是制作中需要解决的部分。
接下来要检查的,同样是这些细小的中断。 判断标准包括:第一次来的访客能否理解移动方式,是否知道发出邀请后为什么需要等待,以及两人结束动作时是否自然。 即使有牵手听歌的功能,如果到达那个场景的过程太难,意图也无法充分传达。
山寺带来的启发,在 Beach 里延伸成了 Korsh、大海、烟花和音乐。 我花心思做的,是把细小动作和痕迹接起来,让相伴的时间不显得生硬。
亲自逛逛,继续阅读
实现与拍摄基准
实现说明对照了 Beach 仓库的 web/index.html、网络代码、2026 年 11 月 3~4 日的变更记录,以及同行稳定性报告。
当前画面是在 2026 年 11 月 4 日公开部署版本的本地模拟模式下拍摄的。
开发录像是另行保存的原型资料,没有作为真实访客或运营性能的证据使用。
详细依据和媒体原始文件清单,另外保存在写作用的调查报告中。
