跳到主要内容
知仓学习社ZHICANG

godot-genre-sandbox

Expert blueprint for sandbox games (Minecraft, Terraria, Garry's Mod) with physics-based interactions, cellular automata, emergent gameplay, and cre…

不碰外部(只输出文字)无严重或高危命中thedivergentai/GD-Agentic-Skills

它会碰到什么

扫了多少3 个文本文件,25 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

NEVER Do (Expert Anti-Patterns)

Performance & Scalability

  • NEVER use individual RigidBody nodes for every block; strictly use Static Colliders for the world and reserve physics for dynamic props.
  • NEVER simulate the entire world every frame; strictly process "Dirty" chunks with active changes. Sleeping chunks must consume zero CPU.
  • NEVER update MultiMesh buffers every frame; strictly batch changes and only rebuild the buffer when a modification completes (e.g., player stops painting).
  • NEVER use standard Godot Nodes for every grid cell; strictly use PackedInt32Arrays or typed Dictionaries to keep RAM overhead minimal.
  • NEVER raycast against every individual voxel for placement; strictly use Grid Quantization (floor(pos/size)) for direct O(1) cell calculation.
  • NEVER render every block face in a chunk; strictly generate an ArrayMesh that only pushes visible exterior faces to the GPU (Culling/Greedy Meshing).

Data & Persistence

  • NEVER save raw arrays of every block transform; strictly use Run-Length Encoding (RLE) (e.g., "Air x 50,000") to compress uniform spaces.
  • NEVER load massive terrain chunks synchronously; strictly use ResourceLoader.load_threaded_request() to prevent frame stutter.
  • NEVER use standard text .tscn files for voxel datasets; strictly use binary .res files for 10x faster parsing.
  • NEVER ignore Floating-Point Precision limits (32,768 units); strictly implement floating-origin shifting for massive worlds.

Systems & Architecture

  • NEVER hardcode element interactions (if water and fire); strictly use a Property System where interactions emerge from material attributes (flammability, density).
  • NEVER trust client-side placement in multiplayer; strictly require the Server to validate bounds and resources.
  • NEVER manipulate the SceneTree from background generation threads; strictly use call_deferred() or Mutex locks for safety.
  • NEVER leave orphaned chunks in memory; strictly track loaded regions and call queue_free() on discarded branches.

🛠 Expert Components (scripts/)

> MANDATORY / Do NOT Load by path

> - 2D falling-sand / CA only: load cellular_automata_liquid.gd + property/tool patterns below. Do NOT Load voxel_chunk_*.gd, voxel_world.gd, or greedy-mesh paths.

> - 3D voxel / chunk worlds: load voxel_world.gdvoxel_chunk_manager.gdMANDATORY voxel_chunk_mesher.gd for exterior-face meshes. Do NOT Load 2D CA liquid unless you also run a 2D element layer.

> - Placement / multiplayer validation: load dynamic_placement_validator.gd before trusting client dig/place.

> - Persistence: load sandbox_world_serializer.gd for RLE/binary chunk IO; keep sandbox_patterns.gd for async load + floating origin.

Chunk / voxel (3D)

  • [voxel_world.gd](scripts/voxel_world.gd) — Top-level world controller for grid state, tool-based editing, and chunk lifecycle.
  • [voxel_chunk_manager.gd](scripts/voxel_chunk_manager.gd) — Chunk lifecycle + MultiMeshInstance3D batch updates for medium worlds.
  • [voxel_chunk_mesher.gd](scripts/voxel_chunk_mesher.gd) — MANDATORY for large worlds: WorkerThreadPool visible-face ArrayMesh build + deferred set_mesh.

Elements / tools (2D CA)

  • [cellular_automata_liquid.gd](scripts/cellular_automata_liquid.gd) — Liquids/powders via property-based density checks.

Placement / save / utilities

  • [dynamic_placement_validator.gd](scripts/dynamic_placement_validator.gd) — Bounds/resource/server-side placement checks (do not trust client).
  • [sandbox_world_serializer.gd](scripts/sandbox_world_serializer.gd) — RLE/binary chunk persistence patterns.
  • [sandbox_patterns.gd](scripts/sandbox_patterns.gd) — Async chunk loading, multithreading helpers, floating-origin shift.

Architecture Patterns

1. Element System (Property-Based Emergence)

Model material properties, not behaviors. Interactions emerge from overlapping properties.

# 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. Cellular Automata Grid (Falling Sand Simulation)

Update order matters. Top-down prevents "teleporting" 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 System (Strategy Pattern)

Decouple input from world modification.

# 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. Chunk-Based Rendering (3D Voxels) — MultiMesh vs ArrayMesh

MANDATORY: For exterior-face / greedy-style chunk meshes, read and adapt [voxel_chunk_mesher.gd](scripts/voxel_chunk_mesher.gd) (WorkerThreadPool + SurfaceTool + call_deferred("set_mesh")). Do not inline incomplete mesher stubs in project code.

| World scale | Render path | Load |

|-------------|-------------|------|

| Small (<100k blocks) | Single MeshInstance3D + SurfaceTool | Mesher patterns only |

| Medium (100k–1M) | Chunked MultiMeshInstance3D (one mesh, many instances; batch buffer on edit complete) | MANDATORY [voxel_chunk_manager.gd](scripts/voxel_chunk_manager.gd) |

| Large (>1M) / editable terrain | Chunked ArrayMesh with visible-face / greedy quads + LOD; optional RenderingServer instance RIDs | MANDATORY [voxel_chunk_mesher.gd](scripts/voxel_chunk_mesher.gd) + manager |

Rule: Prefer MultiMesh when every instance shares one mesh and you only need per-instance transforms/colors. Prefer ArrayMesh meshing when adjacent voxels must merge into unique surfaces (greedy faces, UV atlases, per-chunk collision).

Save System for Sandbox Worlds

# 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!)

Physics Joints for Player Creations

# 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-Specific Expert Notes

  • MultiMeshInstance3D.multimesh.instance_count: MUST be set before buffer allocation. Cannot dynamically grow — requires recreation.
  • RigidBody2D.sleeping: Bodies auto-sleep after 2 seconds of no movement. Use apply_central_impulse(Vector2.ZERO) to force wake without adding force.
  • GridMap vs MultiMesh: GridMap uses MeshLibrary (great for variety), MultiMesh uses single mesh (great for speed). Combine: GridMap for structures, MultiMesh for terrain.
  • Continuous CD: continuous_cd requires convex collision shapes. Use CapsuleShape2D for projectiles, NOT RectangleShape2D.

🚀 Elite Technical Implementations (Batch 09)

1. Greedy / Visible-Face Meshing

Do not paste placeholder meshers. MANDATORY read [voxel_chunk_mesher.gd](scripts/voxel_chunk_mesher.gd) for threaded visible-face generation. Extend that pattern for full greedy quad merging; for MultiMesh vs ArrayMesh choice see §4 above. Extreme draw paths may push committed arrays via RenderingServer (see Official Documentation → Using servers) after the mesher owns the surface data.

2. VoxelGI (demoted — use docs + lighting skill)

Sandbox chunk lighting is not owned by an incomplete RenderingServer.voxel_gi_allocate_data stub here. For dynamic GI on procedural volumes, follow Using VoxelGI and route implementation detail to godot-3d-lighting. Prefer baked/probe strategies from that skill unless you truly need runtime VoxelGI.

3. Blueprint-Sharing (Base64/JSON Serialization)

Allow players to share creations via simple strings. Use JSON for readable serialization and DisplayServer for clipboard integration.

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 {}

Deep recipes (on demand)

| Topic | Reference / script |

|-------|-------------------|

| Elite meshing & blueprint sharing | [elite-technical-patterns.md](references/elite-technical-patterns.md) + [voxel_chunk_mesher.gd](scripts/voxel_chunk_mesher.gd) |

| Element / CA grids | Architecture Patterns §1–3 in SKILL.md + [cellular_automata_liquid.gd](scripts/cellular_automata_liquid.gd) |

| Chunk RLE persistence | Save System § in SKILL.md + [sandbox_world_serializer.gd](scripts/sandbox_world_serializer.gd) |

Reference

> Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.

Official Documentation

  • Using MultiMesh — batch voxel/prop instances per chunk and avoid per-frame buffer rebuilds.
  • Using GridMaps — MeshLibrary cell placement when structures need variety beyond a single MultiMesh mesh.
  • ArrayMesh — push greedy-meshed exterior faces as one surface instead of per-block meshes.
  • SurfaceTool — build and index chunk meshes with normals before committing to MeshInstance3D.
  • Background loading — ResourceLoader threaded chunk streaming so exploration does not hitch.
  • Saving games — persist player-built worlds (groups, JSON/var_to_str, binary Resources).
  • Using multiple threads — WorkerThreadPool meshing/generation with SceneTree mutations deferred.
  • Using servers — RenderingServer mesh/instance RIDs when bypassing the SceneTree for chunk draw.
  • Using VoxelGI — dynamic GI allocation for large procedural sandbox volumes.
  • Large world coordinates — precision limits and floating-origin strategies past ~32k units.
  • Ray-casting — aim/place/break queries via direct space state instead of per-voxel raycasts.
  • PinJoint2D — hinge-style joints for player-created physics contraptions.

Related Skills

Prerequisites

  • godot-project-foundations — scene tree, Resources, and import basics before chunk scenes and binary .res world data.
  • godot-physics-3d — StaticBody colliders for terrain, RigidBody props, and shape queries used in placement validation.
  • godot-gdscript-mastery — typed Dictionaries/Packed arrays, WorkerThreadPool tasks, and deferred SceneTree edits in meshers.

Complements

Downstream / consumers

Master

  • godot-master — library router and mirrored module entry for cross-skill discovery.

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。