How maxbittker/sandspiel Works
Sandspiel 属于创意沙盒模拟游戏品类,与《我的世界》2D版或《Powder Toy》等项目有相似之处。其核心差异和竞争优势在于: 1. **技术领先性**:通过 Rust 编译为 WebAssembly (WASM) 并在 WebGL 上渲染,实现了在浏览器中流畅运行大规模(如300x300)像素物理模拟的卓越性能,这是纯 JavaScript 难以企及的。 2. **简洁的社交闭环**:产品设计上,它不仅是一个单机玩具,更是一个轻量级的社交平台。从创作、一键快照、上传分享到浏览、投票、加载(Fork)他人的作品,形成了一个完整的用户内容生成与消费循环,极大地增强了产品的生命力和趣味性。 3. **高度的互动真实感**:集成了独立的 WebGL 流体模拟,实现了与沙盒粒子互动的动态风场效果,为创作增添了更多不可预测的涌现行为和视觉冲击力。
Overview
Sandspiel 属于创意沙盒模拟游戏品类,与《我的世界》2D版或《Powder Toy》等项目有相似之处。其核心差异和竞争优势在于: 1. **技术领先性**:通过 Rust 编译为 WebAssembly (WASM) 并在 WebGL 上渲染,实现了在浏览器中流畅运行大规模(如300x300)像素物理模拟的卓越性能,这是纯 JavaScript 难以企及的。 2. **简洁的社交闭环**:产品设计上,它不仅是一个单机玩具,更是一个轻量级的社交平台。从创作、一键快照、上传分享到浏览、投票、加载(Fork)他人的作品,形成了一个完整的用户内容生成与消费循环,极大地增强了产品的生命力和趣味性。 3. **高度的互动真实感**:集成了独立的 WebGL 流体模拟,实现了与沙盒粒子互动的动态风场效果,为创作增添了更多不可预测的涌现行为和视觉冲击力。
一款在浏览器中运行的高性能“落沙”物理沙盒游戏,用户可以创造性地混合各种元素(沙、水、火等)形成动态艺术场景,并将其作为可交互的作品与社区分享、交流和再创作。
How It Works: End-to-End Flows
用户创作并分享新作品
此流程覆盖了从空白画布到作品在社区发布的完整核心体验。用户通过绘画工具在沙盒中进行创作,利用沙、水、火等元素的物理特性构建动态场景。在创作过程中,系统提供撤销功能以方便修改。当用户对作品满意后,启动上传流程。此时,客户端会将当前沙盒状态进行快照(一张视觉预览图和一张包含完整数据的“数据图”)。用户输入标题后,系统会对其进行身份验证,然后将打包好的作品数据提交至后端。后端服务负责进行反作弊检查、数据去重、持久化存储,并最终使作品在社区的“最新”列表中可见,完成了从个人创作到社区分享的闭环。
- 用户使用绘画工具在画布上绘制各种元素
- 沙盒内的元素根据物理规则进行实时模拟与交互
- 用户点击上传,客户端生成视觉快照和数据编码PNG
- 客户端打包作品数据,并通过认证API提交至后端
- 后端执行反作弊、去重、存储等一系列操作
- 作品成功发布,出现在社区的“最新”列表中
用户发现并“复刻”社区作品
该流程体现了产品的“再创作”核心价值。用户在社区发现页(如热门榜、最新列表)浏览他人作品。当点击进入一个感兴趣的作品后,系统会从服务器获取该作品的“数据图”。客户端随即在本地将这张图解码,并精确地将原始作品的每一个粒子状态还原到用户的沙盒环境中,加载完成后模拟处于暂停状态。此时,用户便拥有了一个与原作一模一样的副本,可以自由地继续进行模拟、添加新元素或进行修改,相当于对原作进行了一次“复刻”(Fork)。这个流程让社区内容得以不断演化和迭代。
- 用户在社区浏览页面(如热门、最新)发现作品
- 用户点击一个作品,客户端根据ID从后端请求加载数据
- 客户端下载数据PNG,并将其解码写入WASM模拟器内存
- 原始作品被完整还原到用户的沙盒中,模拟暂停,等待用户操作
社区成员参与内容评价与治理
此流程描述了普通社区成员如何参与到平台的内容生态治理中。用户在浏览作品时,可以对喜欢的作品进行“投票”,此行为会增加作品的“分数”,影响其在热门榜单中的排名。如果用户发现不适宜或违规的内容,可以发起“举报”。投票和举报操作都需要用户登录,以确保操作的可追溯性。被举报的作品会自动进入后台的审核队列,等待管理员介入处理。这个流程为社区提供了自我调节和净化环境的基本能力。
- 用户在浏览作品时,对喜欢的作品点击投票
- 客户端乐观更新UI,并向后端发送认证后的投票请求
- 用户发现不当内容,点击举报按钮
- 客户端向后端发送认证后的举报请求,作品进入后台审核队列
Key Features
细胞自动机模拟引擎
这是驱动整个沙盒世界的物理核心。它基于一个二维网格的细胞自动机模型,完全用 Rust 语言编写并编译为 WebAssembly(WASM) 在浏览器中运行。这种设计将计算密集型的物理模拟与前端渲染和UI逻辑分离,实现了卓越的性能,从而支持大规模粒子间的复杂互动和涌现行为。引擎不仅负责每个元素的独立行为,还处理风力等全局影响,并内置了健壮的撤销和模拟稳定性机制。
- 多元素行为模拟 — 【设计策略】为每一种元素(如沙、水、火、植物)定义一套独特的、基于其周围环境的更新规则,以此在简单的规则基础上催生出复杂多样的宏观现象。 【业务逻辑】 1. 在每一帧模拟中,系统会遍历网格中的每一个粒子。 2. 根据粒子的类型(如“沙子”),调用其专属的更新逻辑。 3. **以“沙子”为例**: - Step 1: 检查正下方的格子。如果为空,则向下移动一格。 - Step 2: 如果正下方不为空,则随机检查左下或右下的格子。如果其中一个为空,则向该对角线方向移动一格。 - Step 3: 如果下方和对角线下方都被占据,检查正下方的粒子是否为“水”或“油”等液体。如果是,则与该液体粒子交换位置(实现沙子沉入液体的效果)。 4. 其他元素拥有各自的行为逻辑,如“火”会点燃相邻的可燃物并有一定几率熄灭,“植物”会向上生长等,这些独立的简单规则共同构成了丰富多彩的动态世界。
- 风力物理效应 — 【设计策略】引入一个全局的“风场”系统,该系统可以对粒子施加推力,从而增加沙盒的动态性和不可预测性。不同元素的“抗风”能力不同,使得风的效果更具层次感。 【业务逻辑】 1. 在每帧的主更新逻辑开始前,系统会执行一个独立的“风力计算”阶段。 2. 系统读取每个粒子所在位置的风力向量(包含方向和强度)。 3. 将风力强度与该元素预设的“抗风阈值”进行比较。例如,“尘埃”的阈值很低(10),而“石头”的阈值很高(70)。 4. 如果风力强度超过阈值,系统会尝试将该粒子向风的方向推动一格,但前提是目标格子必须为空。 5. **特殊规则**:为了创造更生动的“阵风”效果,当“沙子”、“尘埃”等特定轻质粒子被向上吹时,如果其上方两格都为空,它会直接向上移动两格而不是一格。
- 模拟稳定性保障机制 — 【设计策略】为了保证模拟过程的稳定和视觉效果的自然,必须解决两个核心问题:一是防止同一个粒子在一帧内被多次更新(导致瞬移),二是减少因固定扫描顺序带来的方向性偏差(例如,所有沙子都倾向于向左下角移动)。 【业务逻辑】 1. **防双重更新机制**: 系统维护一个全局的“代数”(generation)计数器,每帧递增。当一个粒子被更新或移动时,其自身的“时钟”(clock)属性会被设置为`当前代数 + 1`。在同一帧的后续处理中,任何逻辑在尝试更新一个粒子前,都会检查其“时钟”是否已经领先于“代数”,如果是,则跳过该粒子。 2. **扫描偏置消除**: 系统在水平方向上遍历整个网格的顺序是每帧交替的。例如,第1帧从左到右扫描,第2帧则从右到左扫描,以此类推。这有效避免了粒子在对角线移动时产生肉眼可见的方向性偏好。
- 多步撤销系统 — 【用户价值】让用户可以无顾忌地进行实验性创作,即使犯了错也能轻松恢复到之前的状态。 【设计策略】采用基于完整状态快照的撤销机制。在用户执行关键操作前,将整个沙盒的当前状态完整保存下来。 【业务逻辑】 1. 在用户开始一次绘画操作(如鼠标按下)时,系统会自动触发一次“压入撤销栈”的动作。 2. 该动作会完整克隆当前网格中所有粒子的状态,并将这个“快照”存入一个队列的头部。 3. 这个队列最多只保留最近的 50 个快照。当新快照存入导致总数超过50时,最老的一个快照将被丢弃。 4. 当用户按下撤销键(Ctrl+Z),系统会从队列头部取回最新的快照,并用它完全覆盖当前的沙盒状态,实现瞬时复原。
创作、分享与社区平台
该模块覆盖了从创作到消费的整个用户内容生命周期。它为用户提供了直观的创作工具,并构建了一套完整的分享和发现机制。用户不仅可以创造,还可以将作品上传到云端,形成一个永久链接。其他用户可以通过这个链接或在社区的发现页面中找到并“加载/复刻”(Fork)这个作品,在其基础上进行二次创作。这套系统由前端的用户界面、与后端API的交互逻辑以及服务端的存储和数据管理共同构成,是产品社区生态的核心。
- 交互式绘画与创作工具 — 【用户价值】提供流畅、精确且富有创造性的方式,让用户将想法“画”入沙盒世界。 【设计策略】提供多种笔刷尺寸,并对用户的连续涂画手势进行平滑处理,以避免在快速移动时产生断点。 【业务逻辑】 1. 用户可以从一个包含5种预设尺寸(半径从1像素到39像素)的列表中选择笔刷大小。 2. 当用户按下鼠标或触摸屏幕时,系统会立即记录一次撤销快照,并开始绘画。 3. 为了实现连续作画,系统会每隔100毫秒重复执行一次绘画操作。 4. 为了让快速划过的笔迹平滑连续,系统实现了“平滑绘制”逻辑:在两次鼠标移动事件之间,根据笔刷大小(步长=笔刷尺寸/5)进行线性插值,自动填充中间的空白点。 5. 支持多点触控,每个触控点都会被视为一个独立的笔刷进行绘画。
- 创作快照与上传 — 【设计策略】将用户的创作打包成两个PNG图片:一个用于视觉预览,另一个作为包含完整模拟数据的“数据图”,并通过API上传到云端。同时,在客户端实施初步的速率限制以防止恶意刷屏。 【业务逻辑】 1. **生成数据包**:当用户点击上传时,系统会执行以下操作: - 生成一张用于社区展示的视觉快照PNG。 - 将当前沙盒中每个粒子的状态(类型、属性等)编码到一个300x300像素图像的RGBA通道中,并生成一张“数据PNG”。 2. **客户端速率限制**:在发送前,系统会检查本地存储中记录的最近5分钟内的发帖时间戳。如果发现已有3次或更多,则暂时阻止本次提交。 3. **上传流程**: - 要求用户登录,并获取其Firebase认证令牌(ID Token)。 - 将作品标题、视觉快照PNG、数据PNG以及可能的父作品ID打包成一个JSON对象。 - 通过HTTP POST请求,将该JSON对象连同认证令牌一起发送到后端API。
- 加载与复刻(Fork)创作 — 【用户价值】允许用户不仅能观看他人的作品,还能将其完整加载到自己的沙盒中进行修改和再创作,极大地促进了社区内容的演化和传播。 【设计策略】通过作品ID从后端获取“数据PNG”,在客户端将其解码并精确还原到本地的WASM模拟环境中。 【业务逻辑】 1. 当用户通过URL哈希(如 `sandspiel.club/#xxxx`)访问一个作品时,客户端会解析出作品ID。 2. 客户端向后端API请求该ID的元数据,其中包括“数据PNG”在云存储中的地址。 3. 客户端下载该“数据PNG”图片。 4. 将图片绘制到一个离屏画布上,然后逐一读取每个像素的RGBA值。 5. 根据预设的编码规则,将RGBA值解码为粒子状态,并直接写入WASM的内存中,从而完成对原始作品的100%精确复制。 6. 加载完成后,模拟器默认处于暂停状态,等待用户开始自己的探索或修改。
- 社区发现与浏览 — 【设计策略】提供多种排序和筛选方式,帮助用户在海量社区作品中发现感兴趣的内容,同时自动过滤掉已被判定为不良的内容。 【业务逻辑】 1. 提供多个浏览列表: - **按用户**: 查看指定用户发布的所有作品。 - **按热度**: 按分数高低排序,可叠加时间窗口(如日、周、月榜)。 - **按时间**: 显示最新发布的作品。 - **按标题搜索**: 进行全文关键字搜索。 2. 所有列表在从后端查询时,都会自动排除那些在后台被管理员标记为“坏 (`bad='yes'`)”的作品。 3. 默认列表展示最近85个作品,热门列表也限制在85个,而按用户和按父作品的列表最多可展示150个。
- 投票与举报系统 — 【用户价值】提供社区成员参与内容评价和环境治理的基本工具。 【设计策略】对于投票采用乐观更新(Optimistic UI)以提升响应速度,所有操作均需用户登录认证。 【业务逻辑】 1. **投票**: 用户点击投票按钮后,界面会立即(乐观地)将作品分数加一。同时,客户端会向后端API发送一个包含用户认证令牌的投票请求。服务器完成处理后返回最终的真实分数,界面再用此真实值进行更新。 2. **举报**: 用户点击举报按钮,客户端同样会发送一个认证后的举报请求到后端。客户端不处理举报逻辑,仅负责触发。后端负责记录举报并将其纳入审核队列。
内容审核与管理系统
这是维护社区健康和内容质量的后台保障系统。它为管理员提供了一套工具,用于审查被用户举报的作品、作出裁决,并对违规用户或IP地址采取封禁措施。此模块完全在服务器端实现,并通过受保护的API端点供管理员前端调用。
- 举报内容审核队列 — 【设计策略】自动聚合被举报且尚未处理的作品,并按举报数量的多少进行排序,以便管理员优先处理问题最严重的内容。 【业务逻辑】 1. 系统提供一个专门的API接口,用于获取待审核的举报列表。 2. 该接口会查询所有在最近20天内创建、收到过举报且尚未被管理员裁决过的作品。 3. 结果会按“被举报次数”降序排列,其次按“创建时间”降序排列。 4. 接口返回的数据中会包含作品信息、被举报的总次数以及发布者的用户ID,供管理员审查。
- 管理员裁决与执行 — 【设计策略】通过多层防御机制(IP封禁、用户封禁、黑名单、发布速率限制)保护社区免受垃圾信息和滥用行为的侵害。所有上传行为都必须通过这些检查。 【业务逻辑】 1. **身份认证**: 所有上传操作都必须提供有效的用户登录令牌,且用户的邮箱必须是已验证状态。 2. **黑名单过滤**: 作品标题会经过一个词语黑名单的过滤,若命中则直接拒绝。 3. **封禁检查**: 系统会检查提交者的用户ID或IP地址是否在过去60天的封禁名单中,若在则拒绝。 4. **速率限制**: 系统会执行双重速率限制: - 同一IP在24小时内最多发布40个作品。 - 同一用户在5分钟内最多发布5个作品。 5. **内容去重**: 系统会根据作品的“数据PNG”计算一个MD5哈希值。如果该哈希值(的前20位)已存在于数据库中,则判定为重复内容并拒绝上传。
实时可视化与流体动力学引擎
该模块负责将WASM核心引擎中抽象的粒子数据实时、高效地渲染成用户可见的动态画面,并叠加一层可交互的流体(风)效果。它包含两个并行的WebGL渲染管线:一个用于绘制粒子本身,另一个用于模拟和渲染流体。这种设计不仅保证了视觉表现力,也通过精巧的内存共享机制实现了极致的性能。
- WASM状态到GPU的实时渲染 — 【设计策略】采用“零拷贝”技术,让GPU直接读取WASM的内存来渲染画面,避免了在CPU和GPU之间来回复制大量数据的性能开销。 【业务逻辑】 1. WASM模拟引擎的所有粒子状态都存储在一块连续的内存中。 2. 在每一帧,前端的WebGL渲染器会获取指向这块内存起始位置的指针。 3. 渲染器直接将这块WASM内存区域的数据上传到GPU的纹理中,而不是先在JavaScript中创建一个副本。 4. GPU上的着色器(Fragment Shader)程序读取这个纹理,根据每个像素(代表一个粒子)的R通道值(代表元素类型)来计算并绘制出对应的颜色和视觉效果(如噪点)。 5. 这个过程每秒重复60次,确保了用户看到的画面与模拟状态的完美同步和极高流畅度。
- 可交互的流体模拟 — 【设计策略】在GPU上独立运行一套完整的欧拉流体解算器,用于模拟和渲染风和压力的动态效果。用户的鼠标操作可以直接向流体中注入“力”,流体模拟的结果又可以反过来影响WASM中的粒子运动,形成闭环互动。 【业务逻辑】 1. 当用户在画布上移动鼠标(且未选择任何元素时),这被视为在向流体场中施加力。 2. 流体引擎在GPU上通过一系列着色器通道(如平流、散度、压力计算、梯度相减等)来解算下一帧的流体速度场。 3. 这个过程使用了“乒乓”技术(双缓冲FBO),在前一帧的结果上进行迭代计算。 4. 计算出的新速度场(即风场)会被回读到CPU,并更新到WASM模拟引擎中,用于影响下一帧的粒子运动。 5. 同时,流体密度场也会被渲染出来,形成用户看到的烟雾状视觉效果。
- 动态颜色拾取 — 【设计策略】通过实际渲染一次所有元素来精确提取它们在当前着色器下的颜色,以确保UI中的元素图标颜色与游戏内完全一致。 【业务逻辑】 1. 当需要生成调色板时,系统会创建一个临时的、仅包含所有元素类型各一个的微型沙盒。 2. 系统渲染这个微型沙盒到一块离屏画布上。 3. 然后使用WebGL的`readPixels`功能,从画布上逐一读取每个元素渲染后的精确颜色值。 4. 最后,将元素ID与其对应的颜色值(如`rgba(186, 169, 137, 1)`)映射起来,存储为一个颜色查找表,供UI组件(如元素选择菜单)使用。
Core Technical Capabilities
基于WebAssembly的高性能浏览器内物理模拟
Problem: 如何在网页中流畅地运行涉及数十万粒子实时交互的复杂物理模拟?若使用传统的JavaScript,其计算性能将成为巨大瓶颈,导致帧率低下、操作卡顿,无法实现大规模、高互动性的沙盒体验。
Solution: 1. **核心逻辑迁移**: 将所有计算密集型任务,包括粒子运动、物理规则判断和状态更新,全部使用高性能系统语言Rust进行编写。 2. **编译为WASM**: 将Rust代码编译成高度优化的WebAssembly(WASM)二进制文件。浏览器能够以接近本机的速度执行WASM,其性能远超解释执行的JavaScript。 3. **J S作为胶水层**: JavaScript仅负责处理用户界面交互、调用WASM暴露的简单命令(如 `paint`, `tick`),以及驱动渲染循环,将复杂的计算任务完全委托给WASM核心。 **核心价值**:通过这种架构,成功地将桌面级应用的性能带到了浏览器中,使得大规模实时物理模拟成为可能。
Technologies: Rust, WebAssembly (WASM), wasm-bindgen
Boundaries & Risks: 虽然性能远超JS,但最终还是受限于用户的CPU性能,极端复杂的场景仍可能导致降帧。JS与WASM之间的通信存在开销,频繁、细碎的调用(而非批处理调用)可能会影响性能。
“零拷贝”的GPU实时渲染
Problem: 如何每秒60次地将数十万粒子的状态从模拟引擎传递给渲染引擎并绘制出来,而不会产生性能瓶颈?传统的做法是每帧都将整个粒子数据从CPU内存(WASM侧)复制到GPU内存(渲染侧),这将产生巨大的数据拷贝开销,导致严重的卡顿。
Solution: 1. **共享内存访问**: WASM模拟引擎的粒子数据存储在一块连续的内存区域中。 2. **直接指针传递**: JavaScript端的WebGL渲染代码不复制数据,而是直接获取指向这块WASM内存区域的指针。 3. **GPU直接读取**: 每一帧,WebGL被指示直接从该内存地址读取数据并上传到GPU纹理中。 **核心价值**:这种“零拷贝”方案消除了CPU到GPU的数据传输瓶颈,使得渲染更新几乎没有延迟,实现了模拟状态与视觉呈现的完美同步和极致流畅。
Technologies: WebGL, WebAssembly.memory, GLSL
Boundaries & Risks: 该方案强依赖于WASM内存地址的稳定性。如果WASM因故需要重新分配其内存(例如扩大沙盒尺寸),该指针将失效,渲染器若未能同步更新则可能崩溃或渲染错误数据。这种紧耦合需要开发者谨慎管理。
利用PNG图像进行无损状态持久化
Problem: 如何以一种紧凑、通用且能在浏览器中轻松处理的格式,来完整地保存和分享一个复杂沙盒场景的精确状态?使用自定义二进制格式难以在Web生态中流通,而大型JSON对象则既臃肿又低效。
Solution: 1. **数据图像化**: 将整个300x300的模拟网格视为一张300x300的图像。 2. **状态编码**: 将每个粒子的状态信息(如元素类型ID、特定属性等)编码到对应像素的R, G, B, A四个颜色通道中。 3. **PNG生成**: 在客户端,通过读取WASM内存,用JavaScript构建一个标准的`ImageData`对象,然后将其绘制到一个隐藏的Canvas上,并最终导出为通用的PNG图片格式。 **核心价值**:这种方法巧妙地将一个复杂的模拟状态打包成了一张标准的PNG图片。这张图片既是无损的数据备份,又是易于在网络上传输和预览的视觉文件。加载时只需逆向操作即可完美还原场景。
Technologies: Canvas API, ImageData, PNG
Boundaries & Risks: 每个粒子能存储的数据量被限制在4个字节(RGBA通道),这限制了未来扩展更复杂粒子属性的可能性。虽然PNG的无损压缩对此类数据效果不错,但极端复杂的场景仍可能导致文件体积较大。
基于云函数和多数据库的弹性后端架构
Problem: 如何构建一个低成本、高弹性且能处理用户认证、内容存储、数据查询和审核等多种任务的后端服务?传统的单体服务器架构难以应对社交应用的潮汐流量,且维护成本高。
Solution: 1. **无服务器计算**: 核心业务逻辑(如上传、投票、查询)部署为独立的Firebase云函数。这些函数按需启动和伸缩,完美应对流量波动,且只为实际计算付费。 2. **分层数据存储**: - 使用Firebase Storage存储用户上传的PNG等静态文件,享受CDN加速和高可用性。 - 使用PostgreSQL数据库存储作品的元数据、用户信息和举报裁决等结构化数据,利用其强大的查询和关系处理能力支持复杂的浏览和审核逻辑。 3. **集成身份认证**: 直接使用Firebase Authentication处理用户注册、登录和会话管理,无需自建用户系统,安全可靠。 **核心价值**:该架构结合了多种云服务的优点,实现了低运维成本、高可扩展性和功能完备性,是现代Web应用的敏捷开发典范。
Technologies: Firebase Cloud Functions, Firebase Authentication, Firebase Storage, PostgreSQL
Boundaries & Risks: 云函数存在冷启动延迟。数据库查询的复杂性会直接影响到函数的执行时间和成本。将文件上传和数据库写入放在`Promise.all`中并行执行而没有事务保证,可能在部分失败时产生孤立数据(例如文件已上传但数据库记录写入失败)。
