diff --git a/scenes/Chunk.gd b/scenes/Chunk.gd index a979219..c334296 100644 --- a/scenes/Chunk.gd +++ b/scenes/Chunk.gd @@ -3,12 +3,13 @@ extends TileMap var x:int = -1 var y:int = -1 -var should_remove:bool = true +var should_remove:bool = false # Called when the node enters the scene tree for the first time. -func _init(xpos:int, ypos:int): +func _init(ypos:int, xpos:int, sr: bool): self.x = xpos - self.y = ypos + self.y = ypos + self.should_remove = sr self.name = "Chunk [%d,%d]" % [x, y] self.set_tileset(Globals.TILESET_TERRAIN) diff --git a/scripts/CameraZoom2D.gd b/scripts/CameraZoom2D.gd index b68eff4..a79ed3d 100644 --- a/scripts/CameraZoom2D.gd +++ b/scripts/CameraZoom2D.gd @@ -5,17 +5,22 @@ extends Camera2D var is_panning_camera = false var tween + func camera_zoom_in() -> void: _set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL - Globals.CAMERA_ZOOM_FACTOR) + func camera_zoom_out() -> void: _set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL + Globals.CAMERA_ZOOM_DURATION) + func get_camera_position(): return self.position -func _process(delta) -> void: + +func _process(_delta) -> void: Globals.CAMERA_POSITION = self.position + func _set_camera_zoom_level(value: float) -> void: Globals.CAMERA_ZOOM_LEVEL = clamp(value, Globals.CAMERA_MIN_ZOOM_LEVEL, Globals.CAMERA_MAX_ZOOM_LEVEL) @@ -29,9 +34,11 @@ func _set_camera_zoom_level(value: float) -> void: Globals.CAMERA_ZOOM_DURATION ) + func _on_set_camera_position(pos: Vector2) -> void: self.position = pos + func _unhandled_input(event): if event.is_action_pressed("camera_zoom_in"): camera_zoom_in() diff --git a/scripts/ChunkHandler.gd b/scripts/ChunkHandler.gd index 60f562c..4eb7bbe 100644 --- a/scripts/ChunkHandler.gd +++ b/scripts/ChunkHandler.gd @@ -10,9 +10,12 @@ extends Node2D # which is extremely slow in godot 4.0, 4096x4096 takes minutes to fill with set_cell() commands var chunks:Dictionary = {} -var unready_chunks:Dictionary = {} var window_width = DisplayServer.window_get_size(0).x var distance = abs((window_width/(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X)) / 2 +1 ) +var mutex:Mutex +var semaphore:Semaphore +var thread:Thread +var exit_thread = false func _init() -> void: @@ -20,44 +23,73 @@ func _init() -> void: func _process(_delta): +# if !Globals.worlgen_ready || Globals.chunk_queue.size() > 64: +# return update_chunks() clean_up_chunks() - reset_chunks() + reset_chunks() + + #print(self.get_child_count()) + - func _ready(): - #thread = Thread.new() - #print(distance) - pass + mutex = Mutex.new() + semaphore = Semaphore.new() + exit_thread = false + + thread = Thread.new() + thread.start(start_chunkgen, Thread.PRIORITY_NORMAL) + + +func start_chunkgen(): + while true: + semaphore.wait() + + mutex.lock() + var should_exit = exit_thread # Protect with Mutex. + mutex.unlock() + + if should_exit: + break + + if Globals.chunk_queue.size() > 0: + mutex.lock() + var vars = Globals.chunk_queue.pop_front() + mutex.unlock() + + load_chunk(vars[0].y, vars[0].x, vars[1]) + + +# Thread must be disposed (or "joined"), for portability. +func _exit_tree(): + # Set exit condition to true. + mutex.lock() + exit_thread = true # Protect with Mutex. + mutex.unlock() + + # Unblock by posting. + semaphore.post() + + # Wait until it exits. + thread.wait_to_finish() -func add_chunk(x:int, y:int) -> void: - var key = str(y) + "," + str(x) - if chunks.has(key): - return - - load_chunk(x, y, key) - - func clean_up_chunks(): for key in chunks: var chunk = chunks[key] if chunk.should_remove: chunk.queue_free() chunks.erase(key) - - -func clear_chunk(pos:Vector2i) -> void: - self.chunks[pos.y][pos.x].clear() -func correction_factor(distance) -> float: +func correction_factor(d) -> float: if Globals.CAMERA_ZOOM_LEVEL < 0.6: - return distance * 2.0 + return d * 2.0 elif Globals.CAMERA_ZOOM_LEVEL > 1.0: - return distance + return d else: - return distance * ( 1 + 2 * (1-Globals.CAMERA_ZOOM_LEVEL) ) + return d * ( 1 + 2 * (1-Globals.CAMERA_ZOOM_LEVEL) ) + func get_chunk(x:int, y:int): var key = str(y) + "," + str(x) @@ -65,22 +97,26 @@ func get_chunk(x:int, y:int): return chunks.get(key) return null - -func load_chunk(x:int, y:int, key:String): - var chunk = Chunk.new(x,y) - self.add_child(chunk) + +func load_chunk(y:int, x:int, key): + var chunk = Chunk.new(y, x, false) + call_deferred("add_child", chunk) + + mutex.lock() chunks[key] = chunk - - Globals.chunks_loaded += 1 + mutex.unlock() func reset_chunks(): + # avoid trying to edit already removed chunks + mutex.lock() for key in chunks: chunks[key].should_remove = true + mutex.unlock() -func update_chunks(): +func update_chunks(): var p_x = int(Globals.CAMERA_POSITION.x- Globals.CHUNK_SIZE.x) / Globals.TILE_SIZE_X / Globals.CHUNK_SIZE.x var p_y = int(Globals.CAMERA_POSITION.y- Globals.CHUNK_SIZE.y) / Globals.TILE_SIZE_Y / Globals.CHUNK_SIZE.y @@ -88,13 +124,23 @@ func update_chunks(): # based on current zoom level. var zoom_corrected = correction_factor(distance) + # iterate through all the chunks. if a chunk is in camera range, + # and it exists, it should not be removed + # if chunk should be in range and it doesn't exist, it will be + # created by adding the coords to a work queue for y in Globals.map_size/Globals.CHUNK_SIZE.y: for x in Globals.map_size/Globals.CHUNK_SIZE.x: - if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected): - add_chunk(x, y) - - var chunk = get_chunk(x,y) - if chunk != null: - chunk.should_remove = false + if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected): + var key = str(y) + "," + str(x) + if chunks.has(key): + var chunk = get_chunk(x,y) + if chunk != null: + chunk.should_remove = false + else: + mutex.lock() + Globals.chunk_queue.push_back([Vector2i(x, y), key]) + mutex.unlock() + semaphore.post() + diff --git a/scripts/Control.gd b/scripts/Control.gd index 757ebda..be33a0a 100644 --- a/scripts/Control.gd +++ b/scripts/Control.gd @@ -26,7 +26,7 @@ func _process(_delta): str(get_viewport().get_mouse_position()) +"\n" + "FPS " + str(Engine.get_frames_per_second()) + "\n" + "Zoom lvl: " + str(Globals.CAMERA_ZOOM_LEVEL) + "\n" + - "Chunks loaded: " + str(Globals.chunks_loaded) + "\n" + + "Chunk queue: " + str(Globals.chunk_queue.size()) + "\n" + "Camera pos: " + str(Globals.CAMERA_POSITION) ) diff --git a/scripts/Globals.gd b/scripts/Globals.gd index c51b51e..3e8f91c 100644 --- a/scripts/Globals.gd +++ b/scripts/Globals.gd @@ -4,23 +4,25 @@ extends Node var chunks_loaded:int = 0 +var chunk_queue:Array = [] +var worlgen_ready:bool = false ################################### # CHUNK AND TERRAIN SETTINGS # ################################### + # world map chunk size const CHUNK_SIZE:Vector2i = Vector2i(32,32) + # tilemap tile types enum {TILE_WATER, TILE_TERRAIN, TILE_FOREST, TILE_BOG} + # tilemap layers enum {LAYER_TERRAIN, LAYER_BUILDINGS} const TILESET_TERRAIN:TileSet = preload("res://scenes/Chunk.tres") - -func choose_randomly(list_of_entries): - return list_of_entries[randi() % list_of_entries.size()] # map size is based on input image x*y pixel size -var map_size:int +var map_size:int = 0 # store terrain type (water, land, forest etc. for every map cell) var map_terrain_data:Array[Array] = [[]] diff --git a/scripts/Main.gd b/scripts/Main.gd index 530ce78..1559e15 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -31,29 +31,22 @@ func _init(): # Vector2i(3800,2000) # ) Globals.CAMERA_POSITION = Vector2(16*256/2, 16*256/2) - -#func _process(_delta): -# Globals.CAMERA_POSITION = _2d_camera.position + # Called when the node enters the scene tree for the first time. func _ready(): # create a new world and worldgenerator - _world_generator = WorldGenerator.new() - _chunk_handler = ChunkHandler.new() + _world_generator = WorldGenerator.new() #_2d_camera = CameraZoom2D.new() # add chunk handler if worldgen was successful if _world_generator.generate_world(map_filename): + Globals.worlgen_ready = true + _chunk_handler = ChunkHandler.new() add_child(_chunk_handler) - #add_child(_2d_camera) - -# for y in Globals.map_size/Globals.CHUNK_SIZE.y: -# for x in Globals.map_size/Globals.CHUNK_SIZE.x: -# #if (y + x) % 2 == 0: -# _chunk_handler.load_chunk(x, y) else: push_error("World generation failed :-(") - + # center camera to world map emit_signal( "set_camera_position", diff --git a/scripts/WorldGenerator.gd b/scripts/WorldGenerator.gd index 9d079bc..0911996 100644 --- a/scripts/WorldGenerator.gd +++ b/scripts/WorldGenerator.gd @@ -3,6 +3,7 @@ extends RefCounted var image:Image = Image.new() + var directions:Array = [ Vector2i(0,1), # south Vector2i(1,0), # east @@ -10,6 +11,7 @@ var directions:Array = [ Vector2i(-1,0) # west ] + func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool: if bounds.x > value or value > bounds.y: errmsg = errmsg % [value, bounds.x, bounds.y] @@ -17,6 +19,11 @@ func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool: return false return true + + +func choose_randomly(list_of_entries): + return list_of_entries[randi() % list_of_entries.size()] + func choose_tile(tile:Vector2i, selected, surrounding) -> Array: var surrounding_tiles:Array = [] @@ -37,14 +44,15 @@ func choose_tile(tile:Vector2i, selected, surrounding) -> Array: if selected_tile == null: tile_coords = Globals.td[selected].get("default")[0] elif selected_tile.size() > 1: - tile_coords = Globals.choose_randomly(selected_tile) + tile_coords = choose_randomly(selected_tile) else: tile_coords = selected_tile[0] return [ tile_coords, - 0 if selected_tile else Globals.choose_randomly([0,1,2,3]) + 0 if selected_tile else choose_randomly([0,1,2,3]) ] + # Generates biomes, like forest and bog func generate_biomes() -> void: @@ -80,6 +88,7 @@ func generate_biomes() -> void: if noise_sample < 0.1: Globals.map_terrain_data[y][x] = Globals.TILE_FOREST # can add other tresholds here for other biomes + func generate_world(filename) -> bool: var image_size:Vector2i @@ -106,29 +115,30 @@ func generate_world(filename) -> bool: var start = Time.get_ticks_usec() read_image_pixel_data() var end = Time.get_ticks_usec() - print("read image data ", (end-start)/1000.0, "ms") + print("1/5: read image data ", (end-start)/1000.0, "ms") start = Time.get_ticks_usec() smooth_land_features(Globals.TILE_WATER) # smooth water end = Time.get_ticks_usec() - print("smooth water ", (end-start)/1000.0, "ms") + print("2/5: smooth water ", (end-start)/1000.0, "ms") start = Time.get_ticks_usec() generate_biomes() end = Time.get_ticks_usec() - print("generate biomes ", (end-start)/1000.0, "ms") + print("3/5: generate biomes ", (end-start)/1000.0, "ms") start = Time.get_ticks_usec() smooth_land_features(Globals.TILE_FOREST) # smooth out forest end = Time.get_ticks_usec() - print("smooth forest ", (end-start)/1000.0, "ms") - + print("4/5: smooth forest ", (end-start)/1000.0, "ms") + start = Time.get_ticks_usec() select_tilemap_tiles() end = Time.get_ticks_usec() - print("select tiles ", (end-start)/1000.0, "ms") + print("5/5: select tiles ", (end-start)/1000.0, "ms") return true + func read_image_pixel_data(): # initialize the array to have enough rows @@ -145,23 +155,25 @@ func read_image_pixel_data(): if image.get_pixel(x, y) == Globals.WATER_TILE_COLOR_IN_MAP_FILE: Globals.map_terrain_data[y][x] = Globals.TILE_WATER else: - Globals.map_terrain_data[y][x] = Globals.TILE_TERRAIN + Globals.map_terrain_data[y][x] = Globals.TILE_TERRAIN + func select_tilemap_tiles() -> void: for y in Globals.map_terrain_data.size(): - for x in Globals.map_terrain_data[y].size(): + for x in Globals.map_terrain_data[y].size(): # layer | position coords | tilemap id | coords of the tile at tilemap | alternative tile match Globals.map_terrain_data[y][x]: Globals.TILE_WATER: # water or shoreline - Globals.map_tile_data[y][x] = choose_tile(Vector2i(x, y), Globals.TILE_WATER, Globals.TILE_TERRAIN) + Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_WATER, Globals.TILE_TERRAIN) Globals.TILE_TERRAIN: #terrain or forest edge Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_TERRAIN, Globals.TILE_FOREST) - Globals.TILE_FOREST: - Globals.map_tile_data[y][x] = [Vector2i(5,1), Globals.choose_randomly([0,1,2,3])] + Globals.TILE_FOREST: + Globals.map_tile_data[y][x] = [Vector2i(5,1), choose_randomly([0,1,2,3])] _: #default + push_error("should be never here in worldgen!") pass @@ -187,6 +199,7 @@ func smooth_land_features(tile_type:int) -> void: Globals.TILE_FOREST, Globals.TILE_TERRAIN ) + # TEMP SPAGHETTI SOLUTION func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void: @@ -222,7 +235,8 @@ func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void: _: return - #smooth_forest_recursively(pos, selected, comp) + smooth_forest_recursively(pos, selected, comp) + func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void: # now we are supposed to be inspecting a tile with land @@ -256,6 +270,7 @@ func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void: smooth_recursively(pos, selected, comp) + func validate_mapgen_params() -> bool: if !are_coords_valid( Globals.map_size,