这个博客底下有一座地下城

Lobsters Hottest 产品

摘要

一位开发者描述了如何使用 WebRTC 和 Trystero 将点对点多人地下城游戏嵌入静态博客,采用确定性程序生成,且无需后端服务器。

<p><a href="https://lobste.rs/s/ci7bnd/there_s_dungeon_under_this_blog">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/11 09:09

# 这个博客下面有一座地牢 · Ata Kuyumcu 的博客 Source: https://blog.lvmbdv.dev/posts/theres-a-dungeon-under-this-blog/ 这个博客的每个页面下面都藏着一座地牢。找到入口,你就会得到一个像素小角色(骑士、巫师、公主等,取决于你的 peer id 的哈希值),站在阳光明媚的村庄广场上。穿过水井北面的石拱门,便是程序生成的地牢探险:怪物、药水、有时长着牙齿的宝箱。如果此刻还有别人在读这个博客,他们也可以加入同一个村庄、同一座地牢。你可以看着他们四处游荡,也可以互相喊话。 博客是静态的,所以没有游戏服务器、没有 WebSocket 后端、也没有数据库。多人联机完全由点对点(P2P)方式处理。 ## 两个浏览器,没有中间人 [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) 能让两个浏览器直接建立数据通道。问题一直出在“引荐”环节:在对等端开始通信之前,它们需要通过某个双方都能访问的媒介来交换连接提议,而这个媒介传统上就是你自行运行的信号服务器。 [Trystero](https://github.com/dmotz/trystero) 的花招在于改用了已经存在、且不属于任何人的基础设施来完成引荐。默认情况下它使用公共的 [nostr](https://nostr.com/) 中继;它也可以利用 BitTorrent 跟踪器或 MQTT 代理。每个打开地牢的人都会加入一个以 UTC 日期命名的房间,通过中继宣告自己的存在,然后完成握手。之后中继就退出循环,游戏流量完全在对等端之间传输。 多人联机在服务器端唯一的改动,是对 Content-Security-Policy 头的一处编辑:`connect-src` 现在允许 `wss:`,以便中继的 WebSocket 能够连接。 ## 地图留在本地 显而易见的多人设计是让某个人生成地牢,再发送给所有人。但在这里没有人发送任何东西。每个客户端都会取当天日期,将其哈希为种子,然后喂给 [mulberry32](https://gist.github.com/tommyettinger/46a874533244883189143505d203312c) PRNG。相同的种子,相同的房间、走廊,以及站在相同位置的怪物——所有当天打开地牢的机器都一样。第 3 层是在你抵达的那一刻,由当天种子与“层数乘以黄金比例常数”异或生成的,所以整个下潜过程只由一个从未被传输的常量隐含决定。 真正通过网络传输的是实时状态:位置、生命值,以及偶尔一句“我开了这个宝箱”,这样战利品不会为下一位访客重生。一次完整更新不到一百字节。 世界在 UTC 午夜重置。正在迷宫深处的人可以继续自己的冒险,但他们会变成昨日的幽灵:新来者加入明天的房间,两个时代互相看不见。无论如何,金币都只属于单次冒险。你死了,地牢就把它们留下。 ## 总得有人当“地牢主” 确定性生成让所有人面对相同的初始怪物,但接下来怪物需要移动,而没有服务器就没有裁判来移动它们。两个玩家看着同一只史莱姆,必须就它去了哪里达成一致。 地牢会选出一位“地牢主”。每个 peer id 都是一个随机字符串,因此特定深度上字典序最小的 id 就成了该深度的权威节点:它每 400 毫秒推进一次怪物、结算攻击,并广播结果。其他人则镜像这些结果。当权威节点关闭标签页时,次小的 id 会发现并从上一次广播的状态接管,因为每个客户端本来就在保存一份副本。单人游戏则是退化情况:你永远是自己所在楼层里 id 最小的那个。 这套方案处处依赖于同样的确定性。宝箱怪是最干净的例子:有些宝箱其实是怪物,选择哪些宝箱是怪物完全由当天种子决定,所以每个玩家都被同一个宝箱欺骗。 ## 每个页面要付出的代价 在每个页面上放彩蛋,就必须让每个页面的开销几乎为零。始终加载的部分是一个 1,057 字节的脚本,它只负责监听入口,不做其他事。当有人穿过大门,它会动态导入真正的游戏:94 KB 的包(包含 Trystero)和 10 KB 的瓦片集。 美术资源是 Kenney 的 [Tiny Dungeon](https://kenney.nl/assets/tiny-dungeon) 及其姊妹包 [Tiny Town](https://kenney.nl/assets/tiny-town)(村庄),每套 132 个 CC0 瓦片,合成为一张图集;我大约用到了其中九十来个。声音占用零字节:每个音效都是几个 [Web Audio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) 振荡器加上增益包络,现场合成。药水咕嘟咕嘟往上冒,受伤音效向下滑,宝箱怪则是两个失谐的锯齿波。如果觉得太吵,按 `m` 键。 ## 墙壁知道自己的朝向 其中大部分是由 Claude Fable 在一个晚上的来回沟通中构建的,分工也正如你所期望:我提出类似“把它做成地牢探索游戏”和“消息滚动可以更有力”的要求,而它编写了网络代码,然后通过启动两个无头浏览器来证明网络代码可用——这两个浏览器通过真实的公共中继找到了彼此,并截屏展示它们互相看见。看着两个机器人在地牢里相遇,算是一种奇怪的代码审查。 我在正确性方面的实际贡献,是注意到墙壁看起来不对劲这类事。Kenney 的瓦片是有方向性的:有些砖块带有边缘阴影,那是用于墙壁与虚空交界处的,而第一版把它们随意撒在各处,于是朝南的墙壁贴上了朝东的纹理。修复方法来自瓦片集本身:压缩包里附带了一个 [Tiled](https://www.mapeditor.org/) 示例地图,解码它(包括 gids、翻转标志等)后便揭示了原本的语法:与地板相接的墙壁使用朴素的无缝砖块,装饰只出现在朝南的面上,滴水石像鬼的下方需要有粘液盆。现在渲染器遵循这套语法,石像鬼也滴在了该滴的地方。 ## 对着黑暗呼喊 一座共享地牢,却永远找不到任何人,是件孤独的工程,所以有一个“呼喊”按钮。呼喊会广播你的位置,同一楼层的人会听到一声双音符呼叫,根据你相对他们的方位左右声像,按距离衰减;消息滚动中会出现一条罗盘线,你头顶还有一个无视视线阻挡的短暂标记。声音会穿过墙壁;这正是呼喊的意义。 如果你在不同楼层,听到的呼喊会变调:从下层传来更低沉,从上层传来更尖细。你的耳朵会告诉你楼梯在哪边。这一切就是一个 [StereoPannerNode](https://developer.mozilla.org/en-US/docs/Web/API/StereoPannerNode) 加上一点算数,也是我最喜欢的特性。 有一个分析事件会在某次会话第一次遇到另一位玩家时触发,这样我就知道两个陌生人是否真的会在下面找到彼此。我的期望并不高。 至于入口:经典方案。有一个比 Web 还古老,你只需要按而不是输入。咒语也有效:一句给深思熟虑者,一句给冒险者,一句给矮人,一句给注定牺牲者。没有键盘?像不耐烦一样点一下页脚。如果你遇到了人,就喊一声。

相似文章

为MMORPG添加离线模式和自定义服务器

Lobsters Hottest

一位开发者详细介绍了为其自制MMORPG Trolddom添加离线模式和自定义服务器支持所面临的技术挑战,灵感来源于Stop Killing Games运动,涵盖了架构和实现方面。

DOOMQL

Simon Willison's Blog

Peter Gostev 使用 SQLite 作为游戏引擎构建了一款类似 Doom 的游戏,通过递归 CTE 实现光线追踪,并与 Datasette 集成提供实时网页界面。该项目由 GPT-5.6 Sol 创建,展示了使用 SQL 构建游戏逻辑的创造性潜力。