Claude
Skills
Sign in
Back

godot-3d-world-building

Included with Lifetime
$97 forever

Expert patterns for 3D level design using GridMap with MeshLibrary, CSG constructive solid geometry, WorldEnvironment setup, ProceduralSkyMaterial, and volumetric fog. Use when building 3D levels, modular tilesets, BSP-style geometry, or environmental effects. Trigger keywords: GridMap, MeshLibrary, set_cell_item, get_cell_item, map_to_local, local_to_map, CSGCombiner3D, CSGBox3D, CSGSphere3D, CSGPolygon3D, WorldEnvironment, Environment, Sky, ProceduralSkyMaterial, PanoramaSkyMaterial, fog_enabled, volumetric_fog_enabled.

Designscripts

What this skill does


# 3D World Building

Expert guidance for level design with GridMaps, CSG, and environmental setup.

## NEVER Do

- **NEVER forget to bake GridMap navigation** — GridMaps don't auto-generate navigation meshes. Use EditorPlugin or manual NavigationRegion3D.
- **NEVER use CSG for final game geometry** — CSG is for prototyping. Convert to static meshes for performance (use "Bake CSG Mesh" in editor).
- **NEVER scale GridMap cell size after placing tiles** — Changing `cell_size` doesn't update existing tiles, causing misalignment. Set it once at the start.
- **NEVER use MeshLibrary without collision shapes** — Items without collision spawn visual-only geometry that players fall through.
- **NEVER enable volumetric fog without DirectionalLight3D** — Volumetric fog requires at least one light to scatter. No lights = no visible fog.
- **NEVER animate CSG nodes during gameplay** — Moving a CSG node within another forces the CPU to recalculate the boolean geometry, causing significant performance drops.
- **NEVER place generic logic nodes in a GridMap** — GridMap is highly optimized only for meshes, navigation, and collision. It is not a general-purpose system for placing arbitrary node structures on a grid.
- **NEVER use non-manifold meshes in CSG** — If you import a custom mesh for CSGMesh3D, it must be manifold (closed, no self-intersections, no interior faces, no negative volume). Non-manifold meshes will break the CSG algorithm and are completely unsupported.

---

## Available Scripts

> **MANDATORY**: Read the appropriate script before implementing the corresponding pattern.

### [collision_gen.gd](scripts/collision_gen.gd)
Automatic collision shape generation from meshes. Use when importing models without collision or for procedural geometry.

### [gridmap_runtime_builder.gd](scripts/gridmap_runtime_builder.gd)
Runtime GridMap tile placement with batch operations and auto-navigation baking.

### [csg_bake_tool.gd](scripts/csg_bake_tool.gd)
EditorScript to bake CSG geometry to static meshes with proper materials and collision. Use when finalizing level prototypes.

### [safe_csg_baking.gd](scripts/safe_csg_baking.gd)
Expert technique for safe CSG baking. Awaits the end of the frame before extracting baked meshes to avoid empty data.

### [lod_manager.gd](scripts/lod_manager.gd)
Level-of-detail switching based on camera distance. Manages mesh swapping and visibility for large outdoor scenes.

### [occlusion_setup.gd](scripts/occlusion_setup.gd)
OccluderInstance3D configuration for manual occlusion culling. Use for indoor levels with many rooms.

---

## GridMap Fundamentals

### Setup Workflow

```gdscript
# 1. Create MeshLibrary resource (editor)
# Scene → New Inherits Scene → Create Grid-aligned meshes
# Scene → Convert To → MeshLibrary...

# 2. Assign to GridMap
extends GridMap

func _ready() -> void:
    mesh_library = load("res://tilesets/dungeon_library.tres")
    cell_size = Vector3(2, 2, 2)  # Must match library cell size
```

### Cell Manipulation

```gdscript
# gridmap_builder.gd
extends GridMap

# Place cell
func place_tile(grid_pos: Vector3i, tile_index: int) -> void:
    set_cell_item(grid_pos, tile_index)

# Get cell
func get_tile(grid_pos: Vector3i) -> int:
    return get_cell_item(grid_pos)  # Returns index or INVALID_CELL_ITEM (-1)

# Remove cell
func remove_tile(grid_pos: Vector3i) -> void:
    set_cell_item(grid_pos, INVALID_CELL_ITEM)

# Rotate cell (0-23, see GridMap.ROTATION_* constants)
func place_rotated(grid_pos: Vector3i, tile_index: int, orientation: int) -> void:
    set_cell_item(grid_pos, tile_index, orientation)
```

### Coordinate Conversion

```gdscript
# World position ↔ Grid coordinates
func _input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        var camera := get_viewport().get_camera_3d()
        var from := camera.project_ray_origin(event.position)
        var to := from + camera.project_ray_normal(event.position) * 1000
        
        var space := get_world_3d().direct_space_state
        var query := PhysicsRayQueryParameters3D.create(from, to)
        var result := space.intersect_ray(query)
        
        if result:
            var world_pos: Vector3 = result.position
            var grid_pos := local_to_map(to_local(world_pos))
            place_tile(grid_pos, 0)  # Place tile at clicked position

# Grid → World
func get_cell_center(grid_pos: Vector3i) -> Vector3:
    return to_global(map_to_local(grid_pos))
```

---

## MeshLibrary Creation

### Collision Setup

```gdscript
# tile_scene.tscn (before converting to MeshLibrary)
# Root: Node3D
#   ├─ MeshInstance3D (visual)
#   └─ StaticBody3D (collision)
#       └─ CollisionShape3D

# CRITICAL: StaticBody3D must be sibling/child for GridMap to detect collision
```

### Item Metadata

```gdscript
# Access MeshLibrary item data
func get_tile_name(tile_index: int) -> String:
    return mesh_library.get_item_name(tile_index)

# Custom metadata (stored in MeshLibrary resource)
# Use item_set_name() in editor script to organize
```

---

## CSG (Constructive Solid Geometry)

### Boolean Operations

```
CSG Combiner3D
  ├─ CSGBox3D (Operation: Union)        # Base room
  ├─ CSGBox3D (Operation: Subtraction)  # Door cutout
  └─ CSGSphere3D (Operation: Intersection)  # Rounded corner
```

### CSG Brush Types

```gdscript
# CSGBox3D - Room primitives
var room := CSGBox3D.new()
room.size = Vector3(10, 5, 10)

# CSGCylinder3D - Pillars
var pillar := CSGCylinder3D.new()
pillar.radius = 0.5
pillar.height = 5.0

# CSGSphere3D - Domes
var dome := CSGSphere3D.new()
dome.radius = 3.0
dome.radial_segments = 16
dome.rings = 8

# CSGPolygon3D - Extruded 2D shapes
var arch := CSGPolygon3D.new()
arch.polygon = PackedVector2Array([
    Vector2(-1, 0), Vector2(-1, 2), Vector2(1, 2), Vector2(1, 0)
])
arch.depth = 0.5
```

### CSG Performance

```gdscript
# ❌ BAD: Use CSG at runtime (slow)
func _ready() -> void:
    var csg := CSGBox3D.new()
    add_child(csg)  # Recalculates mesh every frame

# ✅ GOOD: Bake to MeshInstance3D (editor only)
# Select CSG node → Mesh → Bake Mesh Instance
# Then delete CSG node

# ✅ ALSO GOOD: Use CSG for level editor, bake on export
```

---

## WorldEnvironment Setup

### Sky Configuration

```gdscript
# world_env.gd
extends WorldEnvironment

func _ready() -> void:
    var env := Environment.new()
    environment = env
    
    # Procedural sky
    env.background_mode = Environment.BG_SKY
    var sky := Sky.new()
    var sky_mat := ProceduralSkyMaterial.new()
    
    sky_mat.sky_top_color = Color(0.4, 0.6, 1.0)  # Blue
    sky_mat.sky_horizon_color = Color(0.8, 0.9, 1.0)  # Lighter
    sky_mat.ground_bottom_color = Color(0.2, 0.2, 0.1)
    sky_mat.sun_angle_max = 30.0
    
    sky.sky_material = sky_mat
    env.sky = sky
```

### HDRI Skybox

```gdscript
# For realistic lighting
var env := environment
env.background_mode = Environment.BG_SKY

var sky := Sky.new()
var panorama := PanoramaSkyMaterial.new()
panorama.panorama = load("res://hdri/sunset.hdr")  # Equirectangular HDR image

sky.sky_material = panorama
env.sky = sky

# Sky contribution to ambient light
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_sky_contribution = 1.0
```

---

## Fog & Atmosphere

### Exponential Fog

```gdscript
extends WorldEnvironment

func _ready() -> void:
    var env := environment
    
    env.fog_enabled = true
    env.fog_mode = Environment.FOG_MODE_EXPONENTIAL
    env.fog_density = 0.01  # 0.0-1.0
    env.fog_light_color = Color(0.9, 0.95, 1.0)  # Blueish
    env.fog_light_energy = 1.0
```

### Depth Fog

```gdscript
# Distance-based fog
env.fog_enabled = true
env.fog_mode = Environment.FOG_MODE_DEPTH
env.fog_depth_begin = 50.0  # Start distance
env.fog_depth_end = 200.0   # End distance (fully opaque)
env.fog_depth_curve = 1.0   # Falloff curve
```

### Volumetric Fog

```gdscript
# Requires DirectionalLight3D for scattering
env.volumetric_fog_enabled = true
env.volumetric_fog_density = 0.

Related in Design