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 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 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 world-space position (center of tile). func _tile_to_world(x: int, y: int, z: int) -> Vector3: var origin := Vector3( chunk_coord.x * tile_layer.width, chunk_coord.y * tile_layer.height, chunk_coord.z * tile_layer.depth ) return origin + Vector3( x * TILE_SIZE + TILE_SIZE * 0.5, y * TILE_SIZE + TILE_SIZE * 0.5, z * 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()