godot-3d-world-building

安装量: 72
排名: #10776

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-3d-world-building
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.
Available Scripts
MANDATORY
Read the appropriate script before implementing the corresponding pattern. collision_gen.gd Automatic collision shape generation from meshes. Use when importing models without collision or for procedural geometry. gridmap_runtime_builder.gd Runtime GridMap tile placement with batch operations and auto-navigation baking. csg_bake_tool.gd EditorScript to bake CSG geometry to static meshes with proper materials and collision. Use when finalizing level prototypes. lod_manager.gd Level-of-detail switching based on camera distance. Manages mesh swapping and visibility for large outdoor scenes. occlusion_setup.gd OccluderInstance3D configuration for manual occlusion culling. Use for indoor levels with many rooms. GridMap Fundamentals Setup Workflow

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

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

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

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

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

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

❌ 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

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

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

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

Requires DirectionalLight3D for scattering

env . volumetric_fog_enabled = true env . volumetric_fog_density = 0.05 env . volumetric_fog_albedo = Color ( 0.9 , 0.9 , 1.0 ) env . volumetric_fog_emission = Color . BLACK env . volumetric_fog_gi_inject = 1.0

How much GI affects fog

Performance settings

env . volumetric_fog_temporal_reprojection_enabled = true env . volumetric_fog_detail_spread = 2.0 Level Streaming / LOD GridMap Chunking

level_streamer.gd - Load/unload GridMap chunks based on player position

extends Node3D @ export var chunk_size := 32

Grid cells per chunk

@ export var load_radius := 2

Chunks to keep loaded

var loaded_chunks := { }

Vector2i → GridMap

func _process ( delta : float ) -> void : var player_pos := get_player_position ( ) var player_chunk := Vector2i ( int ( player_pos . x / ( chunk_size * cell_size . x ) ) , int ( player_pos . z / ( chunk_size * cell_size . z ) ) )

Load nearby chunks

for x in range ( - load_radius , load_radius + 1 ) : for z in range ( - load_radius , load_radius + 1 ) : var chunk_coord := player_chunk + Vector2i ( x , z ) if chunk_coord not in loaded_chunks : load_chunk ( chunk_coord )

Unload distant chunks

for chunk_coord in loaded_chunks . keys ( ) : var dist := chunk_coord . distance_to ( player_chunk ) if dist

load_radius : unload_chunk ( chunk_coord ) func load_chunk ( coord : Vector2i ) -> void : var gridmap := GridMap . new ( ) gridmap . mesh_library = preload ( "res://library.tres" ) add_child ( gridmap ) loaded_chunks [ coord ] = gridmap

TODO: Load chunk data from file/database

gridmap.set_cell_item(...)

func unload_chunk ( coord : Vector2i ) -> void : var gridmap : GridMap = loaded_chunks [ coord ] gridmap . queue_free ( ) loaded_chunks . erase ( coord ) Procedural Generation Random Dungeon with GridMap

dungeon_generator.gd

extends GridMap enum Tile { FLOOR , WALL , DOOR } func generate_room ( pos : Vector3i , size : Vector3i ) -> void :

Fill with floor

for x in range ( size . x ) : for z in range ( size . z ) : set_cell_item ( pos + Vector3i ( x , 0 , z ) , Tile . FLOOR )

Add walls

for x in range ( size . x ) : set_cell_item ( pos + Vector3i ( x , 0 , 0 ) , Tile . WALL )

North

set_cell_item ( pos + Vector3i ( x , 0 , size . z - 1 ) , Tile . WALL )

South

for z in range ( size . z ) : set_cell_item ( pos + Vector3i ( 0 , 0 , z ) , Tile . WALL )

West

set_cell_item ( pos + Vector3i ( size . x - 1 , 0 , z ) , Tile . WALL )

East

func _ready ( ) -> void : generate_room ( Vector3i ( 0 , 0 , 0 ) , Vector3i ( 10 , 1 , 10 ) ) Edge Cases GridMap Cells Not Colliding

Problem: MeshLibrary items lack collision

Solution: Ensure StaticBody3D + CollisionShape3D in source scene

Verify in code:

var item_shapes := mesh_library . get_item_shapes ( tile_index ) if item_shapes . is_empty ( ) : push_error ( "Tile %d has no collision!" % tile_index ) CSG Mesh Flickering

Problem: Z-fighting between overlapping CSG operations

Solution: Add small offset (0.001) to prevent exact overlap

var box := CSGBox3D . new ( ) box . size = Vector3 ( 10 , 5 , 10 ) var cutout := CSGBox3D . new ( ) cutout . operation = CSGShape3D . OPERATION_SUBTRACTION cutout . size = Vector3 ( 2 , 3 , 2.002 )

Slightly larger depth

Reference Master Skill: godot-master

返回排行榜