Godot Version
4.3
Question
What am i doing wrong that is causing stuttering? i tried offloading it to another thread and deferring it, but i honestly am new to this and have no idea what the problem is
extends TileMapLayer
@export var chunk_size: int = 16 # Each chunk is 16x16 tiles
@export var render_distance: int = 3 # Chunks to load around the player
@export var terrain_layer: int = 0 # The tile layer index
@export var terrain_set: int = 0 # ID of the Terrain Set in the tileset
var player: Node2D
var world_chunks := {} # Stores ALL generated chunks
var active_chunks := {} # Tracks only currently active chunks
var chunk_queue: Array[Vector2i] = [] # Queue of chunks to generate
var worker_thread := Thread.new() # Single background worker thread
var should_stop := false # Graceful shutdown flag
func _ready():
player = get_parent().get_node("Player")
worker_thread.start(Callable(self, "_worker_process")) # Start worker thread
_update_terrain_around_player(true)
func _process(_delta):
_update_terrain_around_player()
func _update_terrain_around_player(force: bool = false):
if not player:
return
# Convert player's global position to tilemap coordinates
var player_tile_pos = local_to_map(to_local(player.global_position))
var player_chunk = Vector2i(
int(floor(player_tile_pos.x / float(chunk_size))),
int(floor(player_tile_pos.y / float(chunk_size)))
)
# Identify chunks to activate
for x in range(player_chunk.x - render_distance, player_chunk.x + render_distance + 1):
for y in range(player_chunk.y - render_distance, player_chunk.y + render_distance + 1):
var chunk_key = Vector2i(x, y)
if active_chunks.has(chunk_key) and not force:
continue
if world_chunks.has(chunk_key):
active_chunks[chunk_key] = true
else:
if not chunk_queue.has(chunk_key):
chunk_queue.append(chunk_key)
_deactivate_distant_chunks(player_chunk)
func _worker_process():
while not should_stop:
if chunk_queue.size() > 0:
# Process **one chunk per frame** to prevent lag
var chunk_pos = chunk_queue.pop_front()
var chunk_data = _generate_chunk_data(chunk_pos)
call_deferred("_apply_generated_chunk", chunk_pos, chunk_data)
func _generate_chunk_data(chunk_pos: Vector2i) -> Dictionary:
var start_tile = chunk_pos * chunk_size
var tile_positions: Array[Vector2i] = []
var terrain_types: Array[int] = []
for x in range(start_tile.x, start_tile.x + chunk_size):
for y in range(start_tile.y, start_tile.y + chunk_size):
var terrain_id = _generate_tile_at(x, y)
tile_positions.append(Vector2i(x, y))
terrain_types.append(terrain_id)
return {
"chunk_pos": chunk_pos,
"tile_positions": tile_positions,
"terrain_types": terrain_types
}
func _apply_generated_chunk(chunk_pos: Vector2i, chunk_data: Dictionary):
# Skip if chunk is already generated
if world_chunks.has(chunk_pos):
return
var tile_positions = chunk_data["tile_positions"]
var terrain_types = chunk_data["terrain_types"]
# **Apply batch updates to avoid excessive tilemap recalculations**
set_cells_terrain_connect(tile_positions, terrain_layer, terrain_set, true)
world_chunks[chunk_pos] = true
active_chunks[chunk_pos] = true
func _generate_tile_at(x: int, y: int) -> int:
# Example noise-based terrain generation
var noise = FastNoiseLite.new()
noise.seed = 42
noise.frequency = 0.1
var height = noise.get_noise_2d(x, y)
return 0 if height > 0 else 1 # Example: Grass or Water
func _deactivate_distant_chunks(player_chunk: Vector2i):
var to_remove = []
for chunk_key in active_chunks.keys():
if chunk_key.distance_to(player_chunk) > render_distance + 1:
to_remove.append(chunk_key)
for chunk_key in to_remove:
active_chunks.erase(chunk_key)
func _exit_tree():
should_stop = true
worker_thread.wait_to_finish()