extends Node3D class_name ChunkManager const CHUNK_SIZE: int = 16 @export var render_distance: int = 2 @export var world_seed: int = 12345 var chunks: Dictionary = {} func _ready() -> void: _load_initial_chunks() func _load_initial_chunks() -> void: for cx: int in range(-render_distance, render_distance + 1): for cy: int in range(-render_distance, render_distance + 1): for cz: int in range(-render_distance, render_distance + 1): _load_chunk(Vector3i(cx, cy, cz)) func update_player_position(pos: Vector3) -> void: var player_chunk: Vector3i = world_to_chunk_coord(pos) var to_unload: Array[Vector3i] = [] for coord: Vector3i in chunks.keys(): var dist: Vector3i = coord - player_chunk if abs(dist.x) > render_distance or abs(dist.y) > render_distance or abs(dist.z) > render_distance: to_unload.append(coord) for coord: Vector3i in to_unload: _unload_chunk(coord) for cx: int in range(player_chunk.x - render_distance, player_chunk.x + render_distance + 1): for cy: int in range(player_chunk.y - render_distance, player_chunk.y + render_distance + 1): for cz: int in range(player_chunk.z - render_distance, player_chunk.z + render_distance + 1): var coord: Vector3i = Vector3i(cx, cy, cz) if not chunks.has(coord): _load_chunk(coord) func _regen_border_neighbors(chunk_coord: Vector3i, local: Vector3i) -> void: # Regen adjacent chunk only if block is on the border face var offsets: Array[Vector3i] = [] if local.x == 0: offsets.append(Vector3i(-1, 0, 0)) elif local.x == CHUNK_SIZE - 1: offsets.append(Vector3i(1, 0, 0)) if local.y == 0: offsets.append(Vector3i(0, -1, 0)) elif local.y == CHUNK_SIZE - 1: offsets.append(Vector3i(0, 1, 0)) if local.z == 0: offsets.append(Vector3i(0, 0, -1)) elif local.z == CHUNK_SIZE - 1: offsets.append(Vector3i(0, 0, 1)) for offset: Vector3i in offsets: var neighbor: Vector3i = chunk_coord + offset if chunks.has(neighbor): chunks[neighbor].generate_mesh() func break_block(world_pos: Vector3) -> int: var chunk_coord: Vector3i = world_to_chunk_coord(world_pos) if not chunks.has(chunk_coord): return 0 var chunk: Chunk = chunks[chunk_coord] var local: Vector3i = world_to_local(world_pos) var old_id: int = chunk.get_block(local.x, local.y, local.z) if old_id == BlockDatabase.BlockType.AIR: return 0 chunk.set_block(local.x, local.y, local.z, BlockDatabase.BlockType.AIR) chunk.generate_mesh() _regen_border_neighbors(chunk_coord, local) return old_id func place_block(world_pos: Vector3, block_id: int) -> bool: var chunk_coord: Vector3i = world_to_chunk_coord(world_pos) if not chunks.has(chunk_coord): return false var chunk: Chunk = chunks[chunk_coord] var local: Vector3i = world_to_local(world_pos) var current: int = chunk.get_block(local.x, local.y, local.z) if current != BlockDatabase.BlockType.AIR and current != BlockDatabase.BlockType.WATER: return false chunk.set_block(local.x, local.y, local.z, block_id) chunk.generate_mesh() _regen_border_neighbors(chunk_coord, local) return true func get_block(world_pos: Vector3) -> int: var chunk_coord: Vector3i = world_to_chunk_coord(world_pos) if not chunks.has(chunk_coord): return BlockDatabase.BlockType.AIR var chunk: Chunk = chunks[chunk_coord] var local: Vector3i = world_to_local(world_pos) return chunk.get_block(local.x, local.y, local.z) func world_to_chunk_coord(pos: Vector3) -> Vector3i: return Vector3i( int(floor(pos.x / float(CHUNK_SIZE))), int(floor(pos.y / float(CHUNK_SIZE))), int(floor(pos.z / float(CHUNK_SIZE))) ) func chunk_to_world(coord: Vector3i) -> Vector3: return Vector3( float(coord.x * CHUNK_SIZE), float(coord.y * CHUNK_SIZE), float(coord.z * CHUNK_SIZE) ) func world_to_local(pos: Vector3) -> Vector3i: var ix: int = int(floor(pos.x)) var iy: int = int(floor(pos.y)) var iz: int = int(floor(pos.z)) return Vector3i( ((ix % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE, ((iy % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE, ((iz % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE ) func _load_chunk(coord: Vector3i) -> void: var data: PackedInt32Array = WorldGenerator.generate_chunk(coord.x, coord.y, coord.z, world_seed) var chunk: Chunk = Chunk.new() add_child(chunk) chunk.init_blocks(data) chunk.position = chunk_to_world(coord) chunk.generate_mesh() chunks[coord] = chunk func _unload_chunk(coord: Vector3i) -> void: if chunks.has(coord): var chunk: Chunk = chunks[coord] chunk.queue_free() chunks.erase(coord)