class_name TileLayerNode extends Node3D ## A visual representation of a single tile layer within a chunk. ## ## This node is instantiated by [ChunkScene] for each tile layer in a chunk. ## It renders the layer's tiles as instanced meshes (one per tile definition ## in the palette) using [MultiMeshInstance3D] for efficiency. ## ## ## Structure ## ## ``` ## TileLayerNode (Node3D) ## └── MultiMeshInstance3D # one per palette entry (tile definition) ## ``` ## The tile layer data this node renders. var tile_layer: TileLayer = null ## The chunk coordinate this layer belongs to (for world positioning). var chunk_coord: Vector3i = Vector3i.ZERO ## Tile size in world units (each tile is 1x1x1). const TILE_SIZE: float = 1.0 ## Cache of MultiMeshInstance3D nodes, keyed by tile definition id. var _mesh_instances: Dictionary = {} func _ready() -> void: if tile_layer == null: return _build_meshes() ## Build the MultiMesh instances for each tile definition in the palette. func _build_meshes() -> void: for tile_def in tile_layer.palette: if tile_def.id == 0: continue # Tile 0 is "empty/air", no mesh needed _create_mesh_for_tile(tile_def) func _create_mesh_for_tile(tile_def: TileDef) -> void: # Count how many cells use this tile definition var count := 0 for tile_id in tile_layer.tiles: if tile_id == tile_def.id: count += 1 if count == 0: return var multi_mesh := MultiMesh.new() multi_mesh.transform_format = MultiMesh.TRANSFORM_3D multi_mesh.mesh = _make_unit_cube_mesh() multi_mesh.instance_count = count var col_body: StaticBody3D = null if tile_def.solid: col_body = StaticBody3D.new() col_body.name = "collision_%d" % tile_def.id add_child(col_body) var instance_idx := 0 for z in range(tile_layer.depth): for y in range(tile_layer.height): for x in range(tile_layer.width): var tile_id := tile_layer.get_tile(x, y, z) if tile_id != tile_def.id: continue var world_pos := _tile_to_world(x, y, z) var xform := Transform3D(Basis(), world_pos) multi_mesh.set_instance_transform(instance_idx, xform) instance_idx += 1 if col_body != null: var col_shape := CollisionShape3D.new() var box := BoxShape3D.new() box.size = Vector3(TILE_SIZE, TILE_SIZE, TILE_SIZE) col_shape.shape = box col_shape.position = world_pos col_body.add_child(col_shape) var mesh_instance := MultiMeshInstance3D.new() mesh_instance.multimesh = multi_mesh mesh_instance.name = "tiles_%d" % tile_def.id add_child(mesh_instance) _mesh_instances[tile_def.id] = mesh_instance ## Convert a tile grid coordinate to a chunk-local 3D position (center of tile). func _tile_to_world(x: int, y: int, z: int) -> Vector3: return Vector3( x * TILE_SIZE + TILE_SIZE * 0.5, z * TILE_SIZE + TILE_SIZE * 0.5, y * TILE_SIZE + TILE_SIZE * 0.5 ) ## Create a simple unit cube mesh for tile rendering. func _make_unit_cube_mesh() -> Mesh: var mesh := BoxMesh.new() mesh.size = Vector3(TILE_SIZE, TILE_SIZE, TILE_SIZE) return mesh ## Get the tile definition at a grid position, or null if empty. func get_tile_def_at(x: int, y: int, z: int = 0) -> TileDef: if tile_layer == null: return null var tile_id := tile_layer.get_tile(x, y, z) if tile_id == 0: return null return tile_layer.get_tile_def(tile_id) ## Clear all rendered meshes. func clear() -> void: for child in get_children(): child.queue_free() _mesh_instances.clear()