godot-3d-world-building
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese3D World Building
3D世界构建
Expert guidance for level design with GridMaps, CSG, and environmental setup.
使用GridMap、CSG和环境设置进行关卡设计的专家指南。
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 doesn't update existing tiles, causing misalignment. Set it once at the start.
cell_size - 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.
- 绝对不要忘记烘焙GridMap导航网格 — GridMap不会自动生成导航网格。请使用EditorPlugin或手动创建NavigationRegion3D。
- 绝对不要将CSG用于最终游戏几何体 — CSG仅用于原型设计。为了性能,请转换为静态网格(使用编辑器中的“Bake CSG Mesh”功能)。
- 绝对不要在放置tiles后缩放GridMap的单元格大小 — 修改不会更新已放置的tiles,会导致对齐错误。请在开始时就设置好。
cell_size - 绝对不要使用没有碰撞形状的MeshLibrary — 没有碰撞的物品会生成仅可见的几何体,玩家会直接穿过去。
- 绝对不要在没有DirectionalLight3D的情况下启用体积雾 — 体积雾至少需要一个光源来散射。没有光源的话,雾是不可见的。
Available Scripts
可用脚本
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现相应模式之前,请阅读对应的脚本。
collision_gen.gd
collision_gen.gd
Automatic collision shape generation from meshes. Use when importing models without collision or for procedural geometry.
从网格自动生成碰撞形状。适用于导入无碰撞的模型或程序化几何体。
gridmap_runtime_builder.gd
gridmap_runtime_builder.gd
Runtime GridMap tile placement with batch operations and auto-navigation baking.
运行时GridMap tile放置,支持批量操作和自动导航烘焙。
csg_bake_tool.gd
csg_bake_tool.gd
EditorScript to bake CSG geometry to static meshes with proper materials and collision. Use when finalizing level prototypes.
将CSG几何体烘焙为带正确材质和碰撞的静态网格的EditorScript。适用于完成关卡原型时使用。
lod_manager.gd
lod_manager.gd
Level-of-detail switching based on camera distance. Manages mesh swapping and visibility for large outdoor scenes.
基于相机距离的细节级别切换。管理大型户外场景中的网格切换和可见性。
occlusion_setup.gd
occlusion_setup.gd
OccluderInstance3D configuration for manual occlusion culling. Use for indoor levels with many rooms.
用于手动遮挡剔除的OccluderInstance3D配置。适用于有多个房间的室内关卡。
GridMap Fundamentals
GridMap基础
Setup Workflow
设置工作流
gdscript
undefinedgdscript
undefined1. Create MeshLibrary resource (editor)
1. Create MeshLibrary resource (editor)
Scene → New Inherits Scene → Create Grid-aligned meshes
Scene → New Inherits Scene → Create Grid-aligned meshes
Scene → Convert To → MeshLibrary...
Scene → Convert To → MeshLibrary...
2. Assign to GridMap
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
undefinedextends GridMap
func _ready() -> void:
mesh_library = load("res://tilesets/dungeon_library.tres")
cell_size = Vector3(2, 2, 2) # Must match library cell size
undefinedCell Manipulation
单元格操作
gdscript
undefinedgdscript
undefinedgridmap_builder.gd
gridmap_builder.gd
extends GridMap
extends GridMap
Place cell
Place cell
func place_tile(grid_pos: Vector3i, tile_index: int) -> void:
set_cell_item(grid_pos, tile_index)
func place_tile(grid_pos: Vector3i, tile_index: int) -> void:
set_cell_item(grid_pos, tile_index)
Get cell
Get cell
func get_tile(grid_pos: Vector3i) -> int:
return get_cell_item(grid_pos) # Returns index or INVALID_CELL_ITEM (-1)
func get_tile(grid_pos: Vector3i) -> int:
return get_cell_item(grid_pos) # Returns index or INVALID_CELL_ITEM (-1)
Remove cell
Remove cell
func remove_tile(grid_pos: Vector3i) -> void:
set_cell_item(grid_pos, INVALID_CELL_ITEM)
func remove_tile(grid_pos: Vector3i) -> void:
set_cell_item(grid_pos, INVALID_CELL_ITEM)
Rotate cell (0-23, see GridMap.ROTATION_* constants)
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)
undefinedfunc place_rotated(grid_pos: Vector3i, tile_index: int, orientation: int) -> void:
set_cell_item(grid_pos, tile_index, orientation)
undefinedCoordinate Conversion
坐标转换
gdscript
undefinedgdscript
undefinedWorld position ↔ Grid coordinates
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 positionfunc _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 positionGrid → World
Grid → World
func get_cell_center(grid_pos: Vector3i) -> Vector3:
return to_global(map_to_local(grid_pos))
---func get_cell_center(grid_pos: Vector3i) -> Vector3:
return to_global(map_to_local(grid_pos))
---MeshLibrary Creation
MeshLibrary创建
Collision Setup
碰撞设置
gdscript
undefinedgdscript
undefinedtile_scene.tscn (before converting to MeshLibrary)
tile_scene.tscn (before converting to MeshLibrary)
Root: Node3D
Root: Node3D
├─ MeshInstance3D (visual)
├─ MeshInstance3D (visual)
└─ StaticBody3D (collision)
└─ StaticBody3D (collision)
└─ CollisionShape3D
└─ CollisionShape3D
CRITICAL: StaticBody3D must be sibling/child for GridMap to detect collision
CRITICAL: StaticBody3D must be sibling/child for GridMap to detect collision
undefinedundefinedItem Metadata
物品元数据
gdscript
undefinedgdscript
undefinedAccess MeshLibrary item data
Access MeshLibrary item data
func get_tile_name(tile_index: int) -> String:
return mesh_library.get_item_name(tile_index)
func get_tile_name(tile_index: int) -> String:
return mesh_library.get_item_name(tile_index)
Custom metadata (stored in MeshLibrary resource)
Custom metadata (stored in MeshLibrary resource)
Use item_set_name() in editor script to organize
Use item_set_name() in editor script to organize
---
---CSG (Constructive Solid Geometry)
CSG(构造实体几何)
Boolean Operations
布尔运算
CSG Combiner3D
├─ CSGBox3D (Operation: Union) # Base room
├─ CSGBox3D (Operation: Subtraction) # Door cutout
└─ CSGSphere3D (Operation: Intersection) # Rounded cornerCSG Combiner3D
├─ CSGBox3D (Operation: Union) # Base room
├─ CSGBox3D (Operation: Subtraction) # Door cutout
└─ CSGSphere3D (Operation: Intersection) # Rounded cornerCSG Brush Types
CSG画笔类型
gdscript
undefinedgdscript
undefinedCSGBox3D - Room primitives
CSGBox3D - Room primitives
var room := CSGBox3D.new()
room.size = Vector3(10, 5, 10)
var room := CSGBox3D.new()
room.size = Vector3(10, 5, 10)
CSGCylinder3D - Pillars
CSGCylinder3D - Pillars
var pillar := CSGCylinder3D.new()
pillar.radius = 0.5
pillar.height = 5.0
var pillar := CSGCylinder3D.new()
pillar.radius = 0.5
pillar.height = 5.0
CSGSphere3D - Domes
CSGSphere3D - Domes
var dome := CSGSphere3D.new()
dome.radius = 3.0
dome.radial_segments = 16
dome.rings = 8
var dome := CSGSphere3D.new()
dome.radius = 3.0
dome.radial_segments = 16
dome.rings = 8
CSGPolygon3D - Extruded 2D shapes
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
undefinedvar arch := CSGPolygon3D.new()
arch.polygon = PackedVector2Array([
Vector2(-1, 0), Vector2(-1, 2), Vector2(1, 2), Vector2(1, 0)
])
arch.depth = 0.5
undefinedCSG Performance
CSG性能
gdscript
undefinedgdscript
undefined❌ BAD: Use CSG at runtime (slow)
❌ BAD: Use CSG at runtime (slow)
func _ready() -> void:
var csg := CSGBox3D.new()
add_child(csg) # Recalculates mesh every frame
func _ready() -> void:
var csg := CSGBox3D.new()
add_child(csg) # Recalculates mesh every frame
✅ GOOD: Bake to MeshInstance3D (editor only)
✅ GOOD: Bake to MeshInstance3D (editor only)
Select CSG node → Mesh → Bake Mesh Instance
Select CSG node → Mesh → Bake Mesh Instance
Then delete CSG node
Then delete CSG node
✅ ALSO GOOD: Use CSG for level editor, bake on export
✅ ALSO GOOD: Use CSG for level editor, bake on export
---
---WorldEnvironment Setup
WorldEnvironment设置
Sky Configuration
天空配置
gdscript
undefinedgdscript
undefinedworld_env.gd
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 = skyundefinedextends 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 = skyundefinedHDRI Skybox
HDRI天空盒
gdscript
undefinedgdscript
undefinedFor realistic lighting
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
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
Sky contribution to ambient light
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_sky_contribution = 1.0
---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.0gdscript
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.0Depth Fog
深度雾
gdscript
undefinedgdscript
undefinedDistance-based 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
undefinedenv.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
undefinedVolumetric Fog
体积雾
gdscript
undefinedgdscript
undefinedRequires DirectionalLight3D for scattering
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
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
Performance settings
env.volumetric_fog_temporal_reprojection_enabled = true
env.volumetric_fog_detail_spread = 2.0
---env.volumetric_fog_temporal_reprojection_enabled = true
env.volumetric_fog_detail_spread = 2.0
---Level Streaming / LOD
关卡流加载/LOD
GridMap Chunking
GridMap分块
gdscript
undefinedgdscript
undefinedlevel_streamer.gd - Load/unload GridMap chunks based on player position
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)
---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
使用GridMap生成随机地牢
gdscript
undefinedgdscript
undefineddungeon_generator.gd
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) # Eastfunc _ready() -> void:
generate_room(Vector3i(0, 0, 0), Vector3i(10, 1, 10))
---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) # Eastfunc _ready() -> void:
generate_room(Vector3i(0, 0, 0), Vector3i(10, 1, 10))
---Edge Cases
边缘情况
GridMap Cells Not Colliding
GridMap单元格无碰撞
gdscript
undefinedgdscript
undefinedProblem: MeshLibrary items lack collision
Problem: MeshLibrary items lack collision
Solution: Ensure StaticBody3D + CollisionShape3D in source scene
Solution: Ensure StaticBody3D + CollisionShape3D in source scene
Verify in code:
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)
undefinedvar item_shapes := mesh_library.get_item_shapes(tile_index)
if item_shapes.is_empty():
push_error("Tile %d has no collision!" % tile_index)
undefinedCSG Mesh Flickering
CSG网格闪烁
gdscript
undefinedgdscript
undefinedProblem: Z-fighting between overlapping CSG operations
Problem: Z-fighting between overlapping CSG operations
Solution: Add small offset (0.001) to prevent exact overlap
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
undefinedvar 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
undefinedReference
参考
- Master Skill: godot-master
- 主技能:godot-master