From 523f7888da49b7443d283762aa88105345c9713d Mon Sep 17 00:00:00 2001 From: Antti Hakkarainen Date: Thu, 16 Feb 2023 20:30:43 +0200 Subject: [PATCH] implement screenshot feature --- project.godot | 5 +++ scenes/Main.tscn | 15 ++++---- scenes/MainMenu.tscn | 62 ++++++++++++++++++++++++++++++++ scripts/CameraZoom2D.gd | 27 ++++++++++++++ scripts/Chunk.gd | 5 +-- scripts/ChunkHandler.gd | 44 +++++++++++++++-------- scripts/Control.gd | 1 + scripts/MapBackground.gd | 2 +- scripts/WorldGenerator.gd | 74 +++++++++++++++++---------------------- 9 files changed, 167 insertions(+), 68 deletions(-) create mode 100644 scenes/MainMenu.tscn diff --git a/project.godot b/project.godot index f271f1b..2205688 100644 --- a/project.godot +++ b/project.godot @@ -81,6 +81,11 @@ camera_reset_rotation={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"echo":false,"script":null) ] } +take_screenshot={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194343,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} [rendering] diff --git a/scenes/Main.tscn b/scenes/Main.tscn index 9a3130b..e4d0cef 100644 --- a/scenes/Main.tscn +++ b/scenes/Main.tscn @@ -15,13 +15,6 @@ script = ExtResource("1_ysxum") [node name="ChunkHandler" type="Node2D" parent="."] script = ExtResource("2_6cequ") -[node name="EntityPlacer" type="Control" parent="."] -layout_mode = 3 -anchors_preset = 0 -offset_right = 40.0 -offset_bottom = 40.0 -script = ExtResource("5_8jju5") - [node name="CameraZoom2D" parent="." instance=ExtResource("4_rx82t")] position = Vector2(1272, 720) ignore_rotation = false @@ -39,6 +32,14 @@ centered = false script = ExtResource("8_ron2j") [node name="UILayer" type="CanvasLayer" parent="."] +visible = false + +[node name="EntityPlacer" type="Control" parent="UILayer"] +layout_mode = 3 +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("5_8jju5") [node name="Control" type="Control" parent="UILayer"] layout_mode = 3 diff --git a/scenes/MainMenu.tscn b/scenes/MainMenu.tscn new file mode 100644 index 0000000..7d93718 --- /dev/null +++ b/scenes/MainMenu.tscn @@ -0,0 +1,62 @@ +[gd_scene load_steps=3 format=3 uid="uid://bflqpejouge8r"] + +[sub_resource type="ImageTexture" id="ImageTexture_qii6u"] + +[sub_resource type="LabelSettings" id="LabelSettings_tb1vr"] +font_size = 64 +outline_size = 20 +outline_color = Color(0, 0, 0, 1) + +[node name="MainMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MenuBackground" type="Sprite2D" parent="."] +position = Vector2(-2, -3.99994) +scale = Vector2(2566, 1448) +texture = SubResource("ImageTexture_qii6u") +flip_h = true + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 0 +offset_right = 2560.0 +offset_bottom = 1440.0 + +[node name="MenuBar" type="VSplitContainer" parent="MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="Menu_NewGame" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "New Game" +label_settings = SubResource("LabelSettings_tb1vr") + +[node name="Menu_LoadGame" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "Load Game" +label_settings = SubResource("LabelSettings_tb1vr") + +[node name="Menu_ResumeGame" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "Resume Game" +label_settings = SubResource("LabelSettings_tb1vr") + +[node name="Menu_Options" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "Options" +label_settings = SubResource("LabelSettings_tb1vr") + +[node name="Menu_Credits" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "Credits" +label_settings = SubResource("LabelSettings_tb1vr") + +[node name="Menu_ExitGame" type="Label" parent="MarginContainer/MenuBar"] +layout_mode = 2 +text = "Exit Game" +label_settings = SubResource("LabelSettings_tb1vr") diff --git a/scripts/CameraZoom2D.gd b/scripts/CameraZoom2D.gd index 16eb094..498471d 100644 --- a/scripts/CameraZoom2D.gd +++ b/scripts/CameraZoom2D.gd @@ -12,6 +12,8 @@ var chunk_in_px:Vector2i = Vector2i(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X, Gl var game_res:Vector2i = DisplayServer.window_get_size(0) var x_min_limit:int = self.game_res.x/2 - chunk_in_px.x +#@onready var captured_image = $CapturedImage + func _on_main_worldgen_ready() -> void: # set camera bounds to map size, with chunk_in_px room to go over @@ -73,6 +75,9 @@ func _unhandled_input(event): if event.is_action_pressed("camera_reset_rotation"): reset_camera_rotation() + if event.is_action_pressed("take_screenshot"): + take_screenshot() + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if !is_panning_camera and event.pressed: is_panning_camera = true @@ -115,5 +120,27 @@ func reset_camera_rotation() -> void: func rotate_camera(step:float) -> void: self.rotation_degrees += step emit_signal("camera_rotation_changed", self.rotation) + + +func take_screenshot() -> void: + # Saves screenshot to user:// + # Windows: %APPDATA%\Godot\app_userdata\[project_name] + # macOS: ~/Library/Application Support/Godot/app_userdata/[project_name] + # Linux: ~/.local/share/godot/app_userdata/[project_name] + + var user_path:String = "user://screenshots/" + var moment:Dictionary = Time.get_datetime_dict_from_system() + var time:String = "%s-%s-%s_%s_%s-%s" % [ + moment.get("year"), + moment.get("month"), + moment.get("day"), + moment.get("hour"), + moment.get("minute"), + moment.get("second") + ] + var path:String = user_path + "acsim_" + time + ".png" + var captured_image:Image = get_viewport().get_texture().get_image() + + captured_image.save_png(path) diff --git a/scripts/Chunk.gd b/scripts/Chunk.gd index 3c2e05c..3fd1766 100644 --- a/scripts/Chunk.gd +++ b/scripts/Chunk.gd @@ -3,14 +3,11 @@ extends TileMap var x:int = -1 var y:int = -1 -var should_remove:bool = false - # Called when the node enters the scene tree for the first time. -func _init(ypos:int, xpos:int, sr: bool): +func _init(ypos:int, xpos:int): self.x = xpos self.y = ypos - self.should_remove = sr #self.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC self.cell_quadrant_size = 32 diff --git a/scripts/ChunkHandler.gd b/scripts/ChunkHandler.gd index e2c52f9..d87ec0d 100644 --- a/scripts/ChunkHandler.gd +++ b/scripts/ChunkHandler.gd @@ -14,15 +14,15 @@ signal chunk_stats(chunks, removal_queue) var chunks:Dictionary = {} var chunks_to_remove:Array[Chunk] = [] -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 window_width:int = DisplayServer.window_get_size(0).x +var distance:int = abs((window_width/(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X)) / 2 +1 ) # for threading var chunk_queue:Array = [] var mutex:Mutex var semaphore:Semaphore var thread:Thread -var exit_thread = false +var exit_thread:bool = false func _exit_tree(): @@ -43,9 +43,8 @@ func _on_main_worldgen_ready(): clean_up_chunks() -func _process(_delta): - update_chunks() - emit_signal("chunk_stats", self.chunks.size(), self.chunks_to_remove.size()) +#func _process(_delta): +# update_chunks() func _ready(): @@ -55,6 +54,22 @@ func _ready(): thread = Thread.new() + process_delay_chunks() + process_delay_stats() + + +func process_delay_chunks() -> void: + while true: + update_chunks() + await get_tree().create_timer(0.05).timeout + + +func process_delay_stats() -> void: + # emit stats about chunk amounts every 0,5s + while true: + emit_signal("chunk_stats", self.chunks.size(), self.chunks_to_remove.size()) + await get_tree().create_timer(0.5).timeout + func start_chunkgen(): while true: @@ -68,7 +83,7 @@ func start_chunkgen(): break # work on emptying the generation queue - if chunk_queue.size() > 0: + if not chunk_queue.is_empty(): mutex.lock() var vars = chunk_queue.pop_front() mutex.unlock() @@ -77,7 +92,7 @@ func start_chunkgen(): func clean_up_chunks(): while true: - if chunks_to_remove.size() > 0: + if not chunks_to_remove.is_empty(): mutex.lock() var chunk = chunks_to_remove.pop_front() mutex.unlock() @@ -102,7 +117,7 @@ func get_chunk(key:Vector2i): func load_chunk(y:int, x:int, key): - var chunk = Chunk.new(y, x, false) + var chunk = Chunk.new(y, x) call_deferred("add_child", chunk) mutex.lock() @@ -111,12 +126,12 @@ func load_chunk(y:int, x:int, key): 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 + var p_x:float = int(Globals.CAMERA_POSITION.x- Globals.CHUNK_SIZE.x) / Globals.TILE_SIZE_X / Globals.CHUNK_SIZE.x + var p_y:float = int(Globals.CAMERA_POSITION.y- Globals.CHUNK_SIZE.y) / Globals.TILE_SIZE_Y / Globals.CHUNK_SIZE.y # When updating chunks, adjust chunk rendering distance # based on current zoom level. - var zoom_corrected = correction_factor(distance) + var zoom_corrected:float = correction_factor(distance) # iterate through all the chunks. if a chunk is in camera range, # and it exists, it should not be removed @@ -132,13 +147,12 @@ func update_chunks(): chunk = get_chunk(key) if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected): - if chunk != null: - chunk.should_remove = false - else: + if chunk == null: mutex.lock() chunk_queue.push_back([Vector2i(x, y), key]) mutex.unlock() semaphore.post() + elif chunks.has(key): chunks_to_remove.append(chunks.get(key)) chunks.erase(key) diff --git a/scripts/Control.gd b/scripts/Control.gd index dc3d5e0..e6b43a3 100644 --- a/scripts/Control.gd +++ b/scripts/Control.gd @@ -54,6 +54,7 @@ func create_buttons(): node_path.set_text(values[1]) node_path.show() + # sends signals which View catches and places selected type of buildings func _on_button_residental_pressed(): emit_signal("button_pressed", Globals.TYPE_RESIDENTIAL) diff --git a/scripts/MapBackground.gd b/scripts/MapBackground.gd index d8da366..9e6c20c 100644 --- a/scripts/MapBackground.gd +++ b/scripts/MapBackground.gd @@ -1,7 +1,7 @@ extends Sprite2D # sets the minimap texture as map background to avoid jarring transitions -func _on_minimap_set_map_background_texture(sprite): +func _on_minimap_set_map_background_texture(sprite) -> void: self.texture = sprite self.scale = Vector2(16, 16) diff --git a/scripts/WorldGenerator.gd b/scripts/WorldGenerator.gd index 30d569e..89e61d4 100644 --- a/scripts/WorldGenerator.gd +++ b/scripts/WorldGenerator.gd @@ -6,8 +6,9 @@ extends RefCounted # Trees with Poisson Disc: http://devmag.org.za/2009/05/03/poisson-disk-sampling/ -var image:Image = Image.new() +signal worldgenerator_function_called(message:String, runtime:float) +var image:Image = Image.new() var directions:Array = [ Vector2i(0,1), # south @@ -46,6 +47,7 @@ func choose_tile(tile:Vector2i, selected, surrounding) -> Array: # this is because a tile can have more than 1 option var selected_tile = Globals.td[surrounding].get(surrounding_tiles) var tile_coords:Vector2i + if selected_tile == null: tile_coords = Globals.td[selected].get("default")[0] elif selected_tile.size() > 1: @@ -53,16 +55,13 @@ func choose_tile(tile:Vector2i, selected, surrounding) -> Array: else: tile_coords = selected_tile[0] - return [ - tile_coords, - 0 if selected_tile else choose_randomly([0,1,2,3]) - ] + return [tile_coords, 0 if selected_tile else choose_randomly([0,1,2,3])] # Generates biomes, like forest and bog func generate_biomes() -> void: # generate a new noisemap which should emulate forest-looking areas - var fnl = FastNoiseLite.new() + var fnl:FastNoiseLite = FastNoiseLite.new() fnl.noise_type = FastNoiseLite.TYPE_SIMPLEX fnl.seed = 69 #randi() fnl.frequency = 0.01 @@ -97,16 +96,14 @@ func generate_biomes() -> void: func generate_parcels() -> void: # divide the land area Cadastres / Parcels - pass + print("generating parcels") func generate_world(filename) -> bool: - var image_size:Vector2i - # Try to load the image which we used to place water & ground to world map image = load(filename) if image == null: - var errmsg = Globals.ERROR_FAILED_TO_LOAD_FILE + var errmsg:String = Globals.ERROR_FAILED_TO_LOAD_FILE push_error(errmsg % filename) return false @@ -114,45 +111,41 @@ func generate_world(filename) -> bool: push_error("Error: image size was invalid in world generator") return false - image_size = image.get_size() + var image_size:Vector2i = image.get_size() Globals.map_size = image_size.x if !validate_mapgen_params(): push_error("Error: invalid mapgen size parameters in world generator") return false - - print("Worldgen speed stats:") - var start = Time.get_ticks_usec() - read_image_pixel_data() - var end = Time.get_ticks_usec() - print("1/5: read image data ", (end-start)/1000.0, "ms") + + # idx 0: message sent to GUI, 1: function call, 2: optional args + var worldgen_calls:Array[Array] = [ + ["Reading image data", "read_image_pixel_data"], + ["Smoothing water", "smooth_land_features", Globals.TILE_WATER], + ["Generating parcels", "generate_parcels"], + ["Generating biomes", "generate_biomes"], + ["Smoothing forests", "smooth_land_features", Globals.TILE_FOREST], + ["Precalculating tilemap tiles", "select_tilemap_tiles"], + ] - start = Time.get_ticks_usec() - smooth_land_features(Globals.TILE_WATER) # smooth water - end = Time.get_ticks_usec() - print("2/5: smooth water ", (end-start)/1000.0, "ms") - - generate_parcels() - - start = Time.get_ticks_usec() - generate_biomes() - end = Time.get_ticks_usec() - 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("4/5: smooth forest ", (end-start)/1000.0, "ms") - - start = Time.get_ticks_usec() - select_tilemap_tiles() - end = Time.get_ticks_usec() - print("5/5: select tiles ", (end-start)/1000.0, "ms") + # do this to send the generation stage and processing time to GUI + var start:int; + var end:int; + + for function in worldgen_calls: + start = Time.get_ticks_usec() + if function.size() == 3: + self.call(function[1], function[2]) + else: + self.call(function[1]) + + end = Time.get_ticks_usec() + emit_signal("worldgenerator_function_called", (end-start)/1000.0, function[0]) return true -func read_image_pixel_data(): +func read_image_pixel_data() -> void: # initialize the array to have enough rows Globals.map_terrain_data.resize(Globals.map_size) Globals.map_tile_data.resize(Globals.map_size) @@ -160,8 +153,7 @@ func read_image_pixel_data(): for y in Globals.map_size: #initialize the row to have enough columns Globals.map_terrain_data[y].resize(Globals.map_size) - Globals.map_tile_data[y].resize(Globals.map_size) - + Globals.map_tile_data[y].resize(Globals.map_size) for x in Globals.map_size: if image.get_pixel(x, y) == Globals.WATER_TILE_COLOR_IN_MAP_FILE: