关于 godot-genre-sandbox
面向 Godot 开发者的沙盒技能,涵盖可编辑体素世界与元胞自动机模拟。内容包括区块渲染、基于材质属性的交互、放置验证和压缩存档。
主要功能
- 01区块式体素世界架构
- 02可见面网格生成
- 03基于材质属性的交互
- 04元胞自动机液体模拟
- 05RLE 与二进制世界存档
- 06异步加载与浮动原点模式
适用场景
- 01构建可编辑的 3D 体素地形
- 02实现落沙式元素交互
- 03保存大型区块世界
- 04验证多人游戏中的玩家放置操作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ thedivergentai/ game-dev-agentic-skills --skill godot-genre-sandbox安装后在 Claude Code 中输入/godot-genre-sandbox确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/elite-technical-patterns.mdmigration-notes.mdscripts/blueprint_manager.gdcellular_automata_liquid.gddynamic_placement_validator.gdsandbox_patterns.gdsandbox_world_serializer.gdvoxel_chunk_manager.gdvoxel_chunk_mesher.gdvoxel_world.gd详细介绍
绝对不要做(专家级反模式)
性能与可扩展性
- 绝对不要为每个方块都使用单独的
RigidBody节点;世界中严格使用静态碰撞体,仅为动态道具保留物理模拟。 - 绝对不要每帧模拟整个世界;严格处理有活动变化的 “脏”区块。休眠区块必须完全不占用 CPU。
- 绝对不要每帧更新
MultiMesh缓冲区;严格批量处理更改,仅在修改完成时(例如玩家停止绘制后)重建缓冲区。 - 绝对不要为每个网格单元都使用标准 Godot
Nodes;严格使用 PackedInt32Arrays 或类型化 Dictionary,将 RAM 开销降至最低。 - 绝对不要针对每个单独的体素进行放置射线检测;严格使用网格量化(
floor(pos/size))直接以 O(1) 计算单元格。 - 绝对不要渲染区块中的每个方块面;严格生成
ArrayMesh,只将可见的外表面提交给 GPU(剔除/贪心网格划分)。
数据与持久化
- 绝对不要保存每个方块变换的原始数组;严格使用**游程编码(RLE)**压缩均匀区域(例如,“空气 x 50,000”)。
- 绝对不要同步加载大型地形区块;严格使用
ResourceLoader.load_threaded_request(),避免帧卡顿。 - 绝对不要将标准文本
.tscn文件用于体素数据集;严格使用二进制.res文件,解析速度快 10 倍。 - 绝对不要忽略浮点精度限制(32,768 个单位);大型世界必须严格实现浮动原点偏移。
系统与架构
- 绝对不要硬编码元素交互(
if water and fire);严格使用属性系统,让交互由材质属性(易燃性、密度)决定。 - 绝对不要在多人游戏中信任客户端的放置操作;严格要求服务器验证边界和资源。
- 绝对不要从后台生成线程操作 SceneTree;必须使用
call_deferred()或 Mutex 锁来确保安全。 - 绝对不要让孤立区块留在内存中;严格跟踪已加载区域,并对丢弃的分支调用
queue_free()。
🛠 专家组件(scripts/)
MANDATORY / Do NOT Load by path
- 仅限 2D 落沙/CA:加载
cellular_automata_liquid.gd+ 下方的属性/工具模式。不要加载voxel_chunk_*.gd、voxel_world.gd或贪心网格路径。- 3D 体素/区块世界:加载
voxel_world.gd→voxel_chunk_manager.gd→ MANDATORYvoxel_chunk_mesher.gd,用于生成外表面网格。除非同时运行 2D 元素层,否则不要加载 2D CA 液体。- 放置/多人游戏验证:在信任客户端挖掘/放置操作前,加载
dynamic_placement_validator.gd。- 持久化:加载
sandbox_world_serializer.gd以处理 RLE/二进制区块 IO;保留sandbox_patterns.gd以支持异步加载 + 浮动原点。
区块/体素(3D)
- voxel_world.gd — 顶层世界控制器,负责网格状态、基于工具的编辑和区块生命周期。
- voxel_chunk_manager.gd — 区块生命周期 + 面向中型世界的
MultiMeshInstance3D批量更新。 - voxel_chunk_mesher.gd — 大型世界的 MANDATORY 组件:使用 WorkerThreadPool 构建可见表面
ArrayMesh,并通过延迟调用set_mesh。
元素/工具(2D CA)
- cellular_automata_liquid.gd — 通过基于属性的密度检查处理液体/粉末。
放置/保存/实用工具
- dynamic_placement_validator.gd — 检查边界、资源和服务器端放置操作(不要信任客户端)。
- sandbox_world_serializer.gd — RLE/二进制区块持久化模式。
- sandbox_patterns.gd — 异步区块加载、多线程辅助工具、浮动原点偏移。
架构模式
1. 元素系统(基于属性的涌现)
对材料属性建模,而不是对行为建模。交互由重叠的属性自然涌现。
# element_data.gd
class_name ElementData extends Resource
enum Type { SOLID, LIQUID, GAS, POWDER }
@export var id: String = "air"
@export var type: Type = Type.GAS
@export var density: float = 0.0 # For liquid flow direction
@export var flammable: float = 0.0 # 0-1: Chance to ignite
@export var ignition_temp: float = 400.0
@export var conductivity: float = 0.0 # For electricity/heat
@export var hardness: float = 1.0 # Mining time multiplier
# EDGE CASE: What if two elements have same density but different types?
# SOLUTION: Use secondary sort (type enum priority: SOLID > LIQUID > POWDER > GAS)
func should_swap_with(other: ElementData) -> bool:
if density == other.density:
return type > other.type # Enum comparison: SOLID(0) > GAS(3)
return density > other.density
2. 元胞自动机网格(落沙模拟)
更新顺序很重要。自上而下更新可防止 godot-particles“瞬移”。
# world_grid.gd
var grid: Dictionary = {} # Vector2i -> ElementData
var dirty_cells: Array[Vector2i] = []
func _physics_process(_delta: float) -> void:
# CRITICAL: Sort top-to-bottom to prevent double-moves
dirty_cells.sort_custom(func(a, b): return a.y < b.y)
for pos in dirty_cells:
simulate_cell(pos)
dirty_cells.clear()
func simulate_cell(pos: Vector2i) -> void:
var cell = grid.get(pos)
if not cell: return
match cell.type:
ElementData.Type.LIQUID, ElementData.Type.POWDER:
# Try down, then down-left, then down-right
var targets = [pos + Vector2i.DOWN,
pos + Vector2i(- 1, 1),
pos + Vector2i(1, 1)]
for target in targets:
var neighbor = grid.get(target)
if neighbor and cell.should_swap_with(neighbor):
swap_cells(pos, target)
mark_dirty(target)
return
ElementData.Type.GAS:
# Gases rise (inverse of liquids)
var targets = [pos + Vector2i.UP,
pos + Vector2i(-1, -1),
pos + Vector2i(1, -1)]
# Same swap logic...
# EDGE CASE: What if multiple godot-particles want to move into same cell?
# SOLUTION: Only mark target dirty, don't double-swap. Next frame resolves conflicts.
3. 工具系统(策略模式)
将输入与世界修改解耦。
# tool_base.gd
class_name Tool extends Resource
func use(world_pos: Vector2, world: WorldGrid) -> void: pass
# tool_brush.gd
extends Tool
@export var element: ElementData
@export var radius: int = 1
func use(world_pos: Vector2, world: WorldGrid) -> void:
var grid_pos = Vector2i(floor(world_pos.x), floor(world_pos.y))
# Circle brush pattern
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
if x*x + y*y <= radius*radius: # Circle boundary
var target = grid_pos + Vector2i(x, y)
world.set_cell(target, element)
# FALLBACK: If element placement fails (e.g., occupied by indestructible block)?
# Check world.can_place(target) before set_cell(), show visual feedback.
4. 基于区块的渲染(3D 体素)——MultiMesh 与 ArrayMesh
必须:对于外表面/贪心式区块网格,请阅读并改造 voxel_chunk_mesher.gd(WorkerThreadPool + SurfaceTool + call_deferred("set_mesh"))。不要在项目代码中内联不完整的网格生成器桩代码。
| 世界规模 | 渲染路径 | 负载 |
|---|---|---|
| 小型(<100k 个方块) | 单个 MeshInstance3D + SurfaceTool | 仅网格生成器模式 |
| 中型(100k–1M) | 分块的 MultiMeshInstance3D(一个网格,多个实例;编辑完成时批量更新缓冲区) | 必须 voxel_chunk_manager.gd |
| 大型(>1M)/可编辑地形 | 分块的 ArrayMesh,使用可见面/贪心四边形 + LOD;可选 RenderingServer 实例 RID | 必须 voxel_chunk_mesher.gd + 管理器 |
规则:当所有实例共享一个网格,且只需要实例级变换/颜色时,优先使用 MultiMesh。当相邻体素必须合并为独特表面(贪心面、UV 图集、每区块碰撞体)时,优先使用 ArrayMesh 网格生成。
沙盒世界的保存系统
# chunk_save_data.gd
class_name ChunkSaveData extends Resource
@export var chunk_coord: Vector2i
@export var rle_data: PackedInt32Array # [type_id, count, type_id, count...]
# EXPERT TECHNIQUE: Run-Length Encoding
static func encode_chunk(grid: Dictionary, chunk_pos: Vector2i, chunk_size: int) -> ChunkSaveData:
var data = ChunkSaveData.new()
data.chunk_coord = chunk_pos
var run_type: int = -1
var run_count: int = 0
for y in range(chunk_size):
for x in range(chunk_size):
var world_pos = chunk_pos * chunk_size + Vector2i(x, y)
var cell = grid.get(world_pos)
var type_id = cell.id if cell else 0 # 0 = air
if type_id == run_type:
run_count += 1
else:
if run_count > 0:
data.rle_data.append(run_type)
data.rle_data.append(run_count)
run_type = type_id
run_count = 1
# Flush final run
if run_count > 0:
data.rle_data.append(run_type)
data.rle_data.append(run_count)
return data
# COMPRESSION RESULT: Empty chunk (16×16 = 256 blocks of air)
# Without RLE: 256 integers = 1024 bytes
# With RLE: [0, 256] = 8 bytes (128x compression!)
玩家创作的物理关节
# joint_tool.gd
func create_hinge(body_a: RigidBody2D, body_b: RigidBody2D, anchor: Vector2) -> void:
var joint = PinJoint2D.new()
joint.global_position = anchor
joint.node_a = body_a.get_path()
joint.node_b = body_b.get_path()
joint.softness = 0.5 # Allows slight flex
add_child(joint)
# EDGE CASE: What if bodies are deleted while joint exists?
# Joint will auto-break in Godot 4.x, but orphaned Node leaks memory.
# SOLUTION:
body_a.tree_exiting.connect(func(): joint.queue_free())
body_b.tree_exiting.connect(func(): joint.queue_free())
# FALLBACK: Player attaches joint to static geometry?
# Check `body.freeze == false` before creating joint.
Godot 专家专属说明
MultiMeshInstance3D.multimesh.instance_count:必须在分配缓冲区之前设置。无法动态扩容——需要重新创建。RigidBody2D.sleeping:物体静止 2 秒后会自动休眠。使用apply_central_impulse(Vector2.ZERO)强制唤醒,且不会施加额外作用力。GridMap与MultiMesh:GridMap 使用 MeshLibrary(适合多样化内容),MultiMesh 使用单个网格(适合高性能)。两者可以结合使用:用 GridMap 构建结构,用 MultiMesh 渲染地形。- 连续 CD:
continuous_cd要求使用凸碰撞形状。投射物请使用CapsuleShape2D,而非RectangleShape2D。
🚀 精英级技术实现(批次 09)
1. 贪心/可见面网格生成
不要粘贴占位用的网格生成器。必须阅读 voxel_chunk_mesher.gd,了解多线程可见面生成。将该模式扩展为完整的贪心四边形合并;关于 MultiMesh 与 ArrayMesh 的选择,请参阅上文第 4 节。极限绘制路径可以通过 RenderingServer 推送已提交的数组(参见官方文档 → 使用服务器),前提是网格生成器已取得表面数据的所有权。
2. VoxelGI(降级——使用文档和光照技能)
此处不通过不完整的 RenderingServer.voxel_gi_allocate_data 桩代码处理沙盒区块光照。对于程序化体积的动态 GI,请遵循 使用 VoxelGI,并将实现细节交由 godot-3d-lighting 处理。除非确实需要运行时 VoxelGI,否则优先采用该技能中的烘焙/探针策略。
3. 蓝图分享(Base64/JSON 序列化)
允许玩家通过简单字符串分享创作。使用 JSON 进行可读序列化,并使用 DisplayServer 集成剪贴板。
class_name BlueprintManager extends Node
## Exports chunk data to the OS clipboard.
static func export_blueprint_to_clipboard(blueprint_data: Dictionary) -> void:
var json_string: String = JSON.stringify(blueprint_data)
DisplayServer.clipboard_set(json_string)
## Imports blueprint from clipboard.
static func import_blueprint_from_clipboard() -> Dictionary:
var json_string: String = DisplayServer.clipboard_get()
var parsed_data = JSON.parse_string(json_string)
return parsed_data if parsed_data is Dictionary else {}
深度配方(按需)
| 主题 | 参考资料 / 脚本 |
|---|---|
| 精英网格生成与蓝图共享 | elite-technical-patterns.md + voxel_chunk_mesher.gd |
| 元素 / CA 网格 | SKILL.md 中的架构模式 §1–3 + cellular_automata_liquid.gd |
| 区块 RLE 持久化 | SKILL.md 中的保存系统 § + sandbox_world_serializer.gd |
参考资料
渐进式披露:仅在研究特定 API 时打开官方文档链接;在转向相关领域时加载相关技能——不要预加载整个技能网络。
官方文档
- 使用 MultiMesh — 为每个区块批量处理体素 / 道具实例,并避免每帧重建缓冲区。
- 使用 GridMaps — 当结构需要单个 MultiMesh 网格之外的多样性时,使用 MeshLibrary 放置单元格。
- ArrayMesh — 将贪心网格生成的外表面作为一个表面提交,而不是逐块创建网格。
- SurfaceTool — 在提交给 MeshInstance3D 前构建区块网格并生成法线。
- 后台加载 — 使用 ResourceLoader 在线程中流式加载区块,避免探索时出现卡顿。
- 保存游戏 — 持久化玩家建造的世界(组、JSON/
var_to_str、二进制 Resources)。 - 使用多个线程 — 使用 WorkerThreadPool 进行网格生成 / 世界生成,并延后执行 SceneTree 修改。
- 使用服务器 — 绕过 SceneTree 绘制区块时使用 RenderingServer 网格 / 实例 RID。
- 使用 VoxelGI — 为大型程序化沙盒空间分配动态 GI。
- 大型世界坐标 — 了解超过约 32k 单位后的精度限制和浮动原点策略。
- 射线投射 — 通过直接空间状态执行瞄准 / 放置 / 破坏查询,而不是逐体素进行射线检测。
- PinJoint2D — 用于玩家创建的物理装置的铰链式关节。
相关技能
前置条件
- godot-project-foundations — 在处理区块场景和二进制
.res世界数据前,先了解场景树、Resources 和导入基础。 - godot-physics-3d — 用于地形的 StaticBody 碰撞体、RigidBody 道具,以及放置验证中使用的形状查询。
- godot-gdscript-mastery — 网格生成器中使用的类型化 Dictionaries / Packed 数组、WorkerThreadPool 任务和延后执行的 SceneTree 编辑。
互补技能
- godot-3d-world-building — GridMap/MeshLibrary 和烘焙流程,沙盒建造工具通常会复用这些内容。
- godot-3d-lighting — 用于程序化体积的 VoxelGI/探针/烘焙策略(不要在此技能中虚构 RenderingServer GI 存根)。
- godot-procedural-generation — 在玩家编辑前生成体素区块的噪声/地牢生成器。
- godot-performance-optimization — 沙盒规模下的 MultiMesh 预算、脏区块模拟和绘制调用上限。
- godot-save-load-systems — RLE/二进制区块持久化和可迁移的存档架构,超越临时 JSON 方案。
- godot-raycasting-queries — 面向方块工具的 PhysicsDirectSpaceState 拾取和形状相交查询。
- godot-2d-physics — 用于 2D 沙盒装置和下落沙粒道具的 PinJoint2D/RigidBody2D 模式。
- godot-scene-management — 线程化加载队列和区块节点生命周期管理,避免产生孤立分支。
- godot-monte-carlo-balancer — 调整沙盒系统中形成的合成成本、元素稀有度和经济循环。
下游/消费者
- godot-genre-open-world — 在可编辑沙盒区块上实现流式加载、浮动原点和 HLOD。
- godot-genre-survival — 消耗可破坏体素/元素世界资源的采集、需求和合成循环。
- godot-multiplayer-networking — 为共享创意世界实现由权威服务器验证的放置操作。
主技能
- godot-master — 用于跨技能发现的技能库路由器和镜像模块入口。