class_name ChunkScene extends Node3D ## A visual representation of a single loaded world chunk. ## ## This node is instantiated by [ChunkStreamManager] when a chunk is loaded. ## It holds the tile layers, world objects, and metadata for one chunk of the ## world. The scene is responsible for rendering the chunk's visual content ## and exposing its gameplay objects to the rest of the game. ## ## ## Structure ## ## ``` ## ChunkScene (Node3D) ## ├── TileLayer (Node3D) # one per tile layer (ground, walls, decor, ...) ## ├── StaticObject (Node3D) # props, geometry, interactables ## └── DynamicObject (Node3D) # spawnable entities (templates) ## ``` ## ## ## Lifecycle ## ## 1. [ChunkStreamManager] requests chunk data from the server. ## 2. Server responds with binary payload. ## 3. [ChunkStreamManager] decodes the payload and creates a [ChunkScene]. ## 4. [ChunkScene] populates itself with tile layers and objects. ## 5. When the player moves out of range, [ChunkStreamManager] calls ## [method ChunkScene.queue_free] to unload it. ## The chunk coordinate this scene represents. var coord: Vector3i = Vector3i.ZERO ## The canonical chunk key. var key: String = "" ## World-space bounding box of this chunk. var bounds: AABB = AABB() ## Format version of the chunk data. var format_version: int = 1 ## Revision counter from the server. var revision: int = 0 ## List of tile layers in this chunk. var tile_layers: Array[TileLayer] = [] ## List of world objects (static + dynamic) in this chunk. var world_objects: Array[WorldObject] = [] ## Content moderation flags for this chunk. var content_flags: ChunkDataDecoder.ContentFlags = ChunkDataDecoder.ContentFlags.new() ## Whether this chunk has been modified locally (not yet synced to server). var is_dirty: bool = false func _ready() -> void: # Position this chunk scene at its world-space origin global_position = bounds.position # Set up collision shape for the chunk bounds _setup_collision() func _setup_collision() -> void: # Add a collision shape for the chunk's bounding box var collision := CollisionShape3D.new() var shape := BoxShape3D.new() shape.size = bounds.size collision.shape = shape collision.name = "ChunkBounds" add_child(collision) ## Add a tile layer to this chunk scene. func add_tile_layer(layer: TileLayer) -> void: tile_layers.append(layer) var layer_node := TileLayerNode.new() layer_node.tile_layer = layer layer_node.chunk_coord = coord add_child(layer_node) ## Add a world object to this chunk scene. func add_object(obj: WorldObject) -> void: world_objects.append(obj) if obj.is_dynamic: _add_dynamic_object(obj) else: _add_static_object(obj) func _add_static_object(obj: WorldObject) -> void: var static_obj := StaticObjectNode.new() static_obj.world_object = obj static_obj.position = obj.position static_obj.rotation = obj.rotation static_obj.scale = obj.scale static_obj.name = "static_%s" % obj.id add_child(static_obj) func _add_dynamic_object(obj: WorldObject) -> void: var dynamic_obj := DynamicObjectNode.new() dynamic_obj.world_object = obj dynamic_obj.position = obj.position dynamic_obj.rotation = obj.rotation dynamic_obj.scale = obj.scale dynamic_obj.name = "dynamic_%s" % obj.id add_child(dynamic_obj) ## Get a world object by its ID. func get_object_by_id(object_id: String) -> WorldObject: for obj in world_objects: if obj.id == object_id: return obj return null ## Get all static objects in this chunk. func get_static_objects() -> Array[WorldObject]: var result: Array[WorldObject] = [] for obj in world_objects: if not obj.is_dynamic: result.append(obj) return result ## Get all dynamic object templates in this chunk. func get_dynamic_templates() -> Array[WorldObject]: var result: Array[WorldObject] = [] for obj in world_objects: if obj.is_dynamic: result.append(obj) return result ## Mark this chunk as dirty (locally modified). func mark_dirty() -> void: is_dirty = true ## Clear all objects and tile layers from this chunk. func clear() -> void: for child in get_children(): if child is TileLayerNode or child is StaticObjectNode or child is DynamicObjectNode: child.queue_free() tile_layers.clear() world_objects.clear() ## Get the world-space position of a tile within this chunk. func get_tile_world_position(tile_x: int, tile_y: int, tile_z: int = 0) -> Vector3: return bounds.position + Vector3( tile_x * 1.0, # Tile size is 1 world unit tile_y * 1.0, tile_z * 1.0 ) ## Check if a world-space position is within this chunk's bounds. func contains_position(pos: Vector3) -> bool: return bounds.has_point(pos) ## Check if a chunk coordinate is adjacent to this chunk. func is_adjacent(other_coord: Vector3i) -> bool: var dx := abs(coord.x - other_coord.x) var dy := abs(coord.y - other_coord.y) var dz := abs(coord.z - other_coord.z) return dx <= 1 and dy <= 1 and dz <= 1 and (dx + dy + dz) > 0