commit 7ed5f3155ef741611db4c959c50b39c02718ab6d Author: midnight Date: Thu Oct 26 15:27:25 2023 +0200 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/addons/debug_menu/LICENSE.md b/addons/debug_menu/LICENSE.md new file mode 100644 index 0000000..54fc020 --- /dev/null +++ b/addons/debug_menu/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright © 2023-present Hugo Locurcio and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/debug_menu/debug_menu.gd b/addons/debug_menu/debug_menu.gd new file mode 100644 index 0000000..adce78e --- /dev/null +++ b/addons/debug_menu/debug_menu.gd @@ -0,0 +1,462 @@ +extends Control + +@export var fps: Label +@export var frame_time: Label +@export var frame_number: Label +@export var frame_history_total_avg: Label +@export var frame_history_total_min: Label +@export var frame_history_total_max: Label +@export var frame_history_total_last: Label +@export var frame_history_cpu_avg: Label +@export var frame_history_cpu_min: Label +@export var frame_history_cpu_max: Label +@export var frame_history_cpu_last: Label +@export var frame_history_gpu_avg: Label +@export var frame_history_gpu_min: Label +@export var frame_history_gpu_max: Label +@export var frame_history_gpu_last: Label +@export var fps_graph: Panel +@export var total_graph: Panel +@export var cpu_graph: Panel +@export var gpu_graph: Panel +@export var information: Label +@export var settings: Label + +## The number of frames to keep in history for graph drawing and best/worst calculations. +## Currently, this also affects how FPS is measured. +const HISTORY_NUM_FRAMES = 150 + +const GRAPH_SIZE = Vector2(150, 25) +const GRAPH_MIN_FPS = 10 +const GRAPH_MAX_FPS = 160 +const GRAPH_MIN_FRAMETIME = 1.0 / GRAPH_MIN_FPS +const GRAPH_MAX_FRAMETIME = 1.0 / GRAPH_MAX_FPS + +## Debug menu display style. +enum Style { + HIDDEN, ## Debug menu is hidden. + VISIBLE_COMPACT, ## Debug menu is visible, with only the FPS, FPS cap (if any) and time taken to render the last frame. + VISIBLE_DETAILED, ## Debug menu is visible with full information, including graphs. + MAX, ## Represents the size of the Style enum. +} + +## The style to use when drawing the debug menu. +var style := Style.HIDDEN: + set(value): + style = value + match style: + Style.HIDDEN: + visible = false + Style.VISIBLE_COMPACT, Style.VISIBLE_DETAILED: + visible = true + frame_number.visible = style == Style.VISIBLE_DETAILED + $VBoxContainer/FrameTimeHistory.visible = style == Style.VISIBLE_DETAILED + $VBoxContainer/FPSGraph.visible = style == Style.VISIBLE_DETAILED + $VBoxContainer/TotalGraph.visible = style == Style.VISIBLE_DETAILED + $VBoxContainer/CPUGraph.visible = style == Style.VISIBLE_DETAILED + $VBoxContainer/GPUGraph.visible = style == Style.VISIBLE_DETAILED + information.visible = style == Style.VISIBLE_DETAILED + settings.visible = style == Style.VISIBLE_DETAILED + +# Value of `Time.get_ticks_usec()` on the previous frame. +var last_tick := 0 + +var thread := Thread.new() + +## Returns the sum of all values of an array (use as a parameter to `Array.reduce()`). +var sum_func := func avg(accum: float, number: float) -> float: return accum + number + +# History of the last `HISTORY_NUM_FRAMES` rendered frames. +var frame_history_total: Array[float] = [] +var frame_history_cpu: Array[float] = [] +var frame_history_gpu: Array[float] = [] +var fps_history: Array[float] = [] # Only used for graphs. + +var frametime_avg := GRAPH_MIN_FRAMETIME +var frametime_cpu_avg := GRAPH_MAX_FRAMETIME +var frametime_gpu_avg := GRAPH_MIN_FRAMETIME +var frames_per_second := float(GRAPH_MIN_FPS) +var frame_time_gradient := Gradient.new() + +func _init() -> void: + # This must be done here instead of `_ready()` to avoid having `visibility_changed` be emitted immediately. + visible = false + + if not InputMap.has_action("cycle_debug_menu"): + # Create default input action if no user-defined override exists. + # We can't do it in the editor plugin's activation code as it doesn't seem to work there. + InputMap.add_action("cycle_debug_menu") + var event := InputEventKey.new() + event.keycode = KEY_F3 + InputMap.action_add_event("cycle_debug_menu", event) + + +func _ready() -> void: + fps_graph.draw.connect(_fps_graph_draw) + total_graph.draw.connect(_total_graph_draw) + cpu_graph.draw.connect(_cpu_graph_draw) + gpu_graph.draw.connect(_gpu_graph_draw) + + fps_history.resize(HISTORY_NUM_FRAMES) + frame_history_total.resize(HISTORY_NUM_FRAMES) + frame_history_cpu.resize(HISTORY_NUM_FRAMES) + frame_history_gpu.resize(HISTORY_NUM_FRAMES) + + # NOTE: Both FPS and frametimes are colored following FPS logic + # (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS). + # This makes the color gradient non-linear. + # Colors are taken from . + frame_time_gradient.set_color(0, Color8(239, 68, 68)) # red-500 + frame_time_gradient.set_color(1, Color8(56, 189, 248)) # light-blue-400 + frame_time_gradient.add_point(0.3333, Color8(250, 204, 21)) # yellow-400 + frame_time_gradient.add_point(0.6667, Color8(128, 226, 95)) # 50-50 mix of lime-400 and green-400 + + get_viewport().size_changed.connect(update_settings_label) + + # Display loading text while information is being queried, + # in case the user toggles the full debug menu just after starting the project. + information.text = "Loading hardware information...\n\n " + settings.text = "Loading project information..." + thread.start( + func(): + # Disable thread safety checks as they interfere with this add-on. + # This only affects this particular thread, not other thread instances in the project. + # See for details. + # Use a Callable so that this can be ignored on Godot 4.0 without causing a script error + # (thread safety checks were added in Godot 4.1). + if Engine.get_version_info()["hex"] >= 0x040100: + Callable(Thread, "set_thread_safety_checks_enabled").call(false) + + # Enable required time measurements to display CPU/GPU frame time information. + # These lines are time-consuming operations, so run them in a separate thread. + RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true) + update_information_label() + update_settings_label() + ) + + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("cycle_debug_menu"): + style = wrapi(style + 1, 0, Style.MAX) as Style + + +func _exit_tree() -> void: + thread.wait_to_finish() + + +## Update hardware information label (this can change at runtime based on window +## size and graphics settings). This is only called when the window is resized. +## To update when graphics settings are changed, the function must be called manually +## using `DebugMenu.update_settings_label()`. +func update_settings_label() -> void: + settings.text = "" + if ProjectSettings.has_setting("application/config/version"): + settings.text += "Project Version: %s\n" % ProjectSettings.get_setting("application/config/version") + + var rendering_method_string := "" + match str(ProjectSettings.get_setting("rendering/renderer/rendering_method")): + "forward_plus": + rendering_method_string = "Forward+" + "mobile": + rendering_method_string = "Forward Mobile" + "gl_compatibility": + rendering_method_string = "Compatibility" + settings.text += "Rendering Method: %s\n" % rendering_method_string + + var viewport := get_viewport() + + # The size of the viewport rendering, which determines which resolution 3D is rendered at. + var viewport_render_size := Vector2i() + + if viewport.content_scale_mode == Window.CONTENT_SCALE_MODE_VIEWPORT: + viewport_render_size = viewport.get_visible_rect().size + settings.text += "Viewport: %d×%d, Window: %d×%d\n" % [viewport.get_visible_rect().size.x, viewport.get_visible_rect().size.y, viewport.size.x, viewport.size.y] + else: + # Window size matches viewport size. + viewport_render_size = viewport.size + settings.text += "Viewport: %d×%d\n" % [viewport.size.x, viewport.size.y] + + # Display 3D settings only if relevant. + if viewport.get_camera_3d(): + var antialiasing_3d_string := "" + if viewport.use_taa: + antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "TAA" + if viewport.msaa_3d >= Viewport.MSAA_2X: + antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "%d× MSAA" % pow(2, viewport.msaa_3d) + if viewport.screen_space_aa == Viewport.SCREEN_SPACE_AA_FXAA: + antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FXAA" + + var scaling_mode = "Unknown" + match viewport.scaling_3d_mode: + Viewport.SCALING_3D_MODE_BILINEAR: + scaling_mode = "Bilinear" + Viewport.SCALING_3D_MODE_FSR: + scaling_mode = "FSR 1.0" + Viewport.SCALING_3D_MODE_FSR2: + scaling_mode = "FSR 2.2" + + settings.text += "3D scale (%s): %d%% = %d×%d" % [ + scaling_mode, + viewport.scaling_3d_scale * 100, + viewport_render_size.x * viewport.scaling_3d_scale, + viewport_render_size.y * viewport.scaling_3d_scale, + ] + + if not antialiasing_3d_string.is_empty(): + settings.text += "\n3D Antialiasing: %s" % antialiasing_3d_string + + var environment := viewport.get_camera_3d().get_world_3d().environment + if environment: + if environment.ssr_enabled: + settings.text += "\nSSR: %d Steps" % environment.ssr_max_steps + + if environment.ssao_enabled: + settings.text += "\nSSAO: On" + if environment.ssil_enabled: + settings.text += "\nSSIL: On" + + if environment.sdfgi_enabled: + settings.text += "\nSDFGI: %d Cascades" % environment.sdfgi_cascades + + if environment.glow_enabled: + settings.text += "\nGlow: On" + + if environment.volumetric_fog_enabled: + settings.text += "\nVolumetric Fog: On" + var antialiasing_2d_string := "" + if viewport.msaa_2d >= Viewport.MSAA_2X: + antialiasing_2d_string = "%d× MSAA" % pow(2, viewport.msaa_2d) + + if not antialiasing_2d_string.is_empty(): + settings.text += "\n2D Antialiasing: %s" % antialiasing_2d_string + + +## Update hardware/software information label (this never changes at runtime). +func update_information_label() -> void: + var adapter_string := "" + # Make "NVIDIA Corporation" and "NVIDIA" be considered identical (required when using OpenGL to avoid redundancy). + if RenderingServer.get_video_adapter_vendor().trim_suffix(" Corporation") in RenderingServer.get_video_adapter_name(): + # Avoid repeating vendor name before adapter name. + # Trim redundant suffix sometimes reported by NVIDIA graphics cards when using OpenGL. + adapter_string = RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2") + else: + adapter_string = RenderingServer.get_video_adapter_vendor() + " - " + RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2") + + # Graphics driver version information isn't always availble. + var driver_info := OS.get_video_adapter_driver_info() + var driver_info_string := "" + if driver_info.size() >= 2: + driver_info_string = driver_info[1] + else: + driver_info_string = "(unknown)" + + var release_string := "" + if OS.has_feature("editor"): + # Editor build (implies `debug`). + release_string = "editor" + elif OS.has_feature("debug"): + # Debug export template build. + release_string = "debug" + else: + # Release export template build. + release_string = "release" + + var graphics_api_string := "" + if str(ProjectSettings.get_setting("rendering/renderer/rendering_method")) != "gl_compatibility": + graphics_api_string = "Vulkan" + else: + if OS.has_feature("web"): + graphics_api_string = "WebGL" + elif OS.has_feature("mobile"): + graphics_api_string = "OpenGL ES" + else: + graphics_api_string = "OpenGL" + + information.text = ( + "%s, %d threads\n" % [OS.get_processor_name().replace("(R)", "").replace("(TM)", ""), OS.get_processor_count()] + + "%s %s (%s %s), %s %s\n" % [OS.get_name(), "64-bit" if OS.has_feature("64") else "32-bit", release_string, "double" if OS.has_feature("double") else "single", graphics_api_string, RenderingServer.get_video_adapter_api_version()] + + "%s, %s" % [adapter_string, driver_info_string] + ) + + +func _fps_graph_draw() -> void: + var fps_polyline := PackedVector2Array() + fps_polyline.resize(HISTORY_NUM_FRAMES) + for fps_index in fps_history.size(): + fps_polyline[fps_index] = Vector2( + remap(fps_index, 0, fps_history.size(), 0, GRAPH_SIZE.x), + remap(clampf(fps_history[fps_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) + ) + # Don't use antialiasing to speed up line drawing, but use a width that scales with + # viewport scale to keep the line easily readable on hiDPI displays. + fps_graph.draw_polyline(fps_polyline, frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) + + +func _total_graph_draw() -> void: + var total_polyline := PackedVector2Array() + total_polyline.resize(HISTORY_NUM_FRAMES) + for total_index in frame_history_total.size(): + total_polyline[total_index] = Vector2( + remap(total_index, 0, frame_history_total.size(), 0, GRAPH_SIZE.x), + remap(clampf(frame_history_total[total_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) + ) + # Don't use antialiasing to speed up line drawing, but use a width that scales with + # viewport scale to keep the line easily readable on hiDPI displays. + total_graph.draw_polyline(total_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) + + +func _cpu_graph_draw() -> void: + var cpu_polyline := PackedVector2Array() + cpu_polyline.resize(HISTORY_NUM_FRAMES) + for cpu_index in frame_history_cpu.size(): + cpu_polyline[cpu_index] = Vector2( + remap(cpu_index, 0, frame_history_cpu.size(), 0, GRAPH_SIZE.x), + remap(clampf(frame_history_cpu[cpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) + ) + # Don't use antialiasing to speed up line drawing, but use a width that scales with + # viewport scale to keep the line easily readable on hiDPI displays. + cpu_graph.draw_polyline(cpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) + + +func _gpu_graph_draw() -> void: + var gpu_polyline := PackedVector2Array() + gpu_polyline.resize(HISTORY_NUM_FRAMES) + for gpu_index in frame_history_gpu.size(): + gpu_polyline[gpu_index] = Vector2( + remap(gpu_index, 0, frame_history_gpu.size(), 0, GRAPH_SIZE.x), + remap(clampf(frame_history_gpu[gpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) + ) + # Don't use antialiasing to speed up line drawing, but use a width that scales with + # viewport scale to keep the line easily readable on hiDPI displays. + gpu_graph.draw_polyline(gpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) + + +func _process(_delta: float) -> void: + if visible: + fps_graph.queue_redraw() + total_graph.queue_redraw() + cpu_graph.queue_redraw() + gpu_graph.queue_redraw() + + # Difference between the last two rendered frames in milliseconds. + var frametime := (Time.get_ticks_usec() - last_tick) * 0.001 + + frame_history_total.push_back(frametime) + if frame_history_total.size() > HISTORY_NUM_FRAMES: + frame_history_total.pop_front() + + # Frametimes are colored following FPS logic (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS). + # This makes the color gradient non-linear. + frametime_avg = frame_history_total.reduce(sum_func) / frame_history_total.size() + frame_history_total_avg.text = str(frametime_avg).pad_decimals(2) + frame_history_total_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_min: float = frame_history_total.min() + frame_history_total_min.text = str(frametime_min).pad_decimals(2) + frame_history_total_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_max: float = frame_history_total.max() + frame_history_total_max.text = str(frametime_max).pad_decimals(2) + frame_history_total_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + frame_history_total_last.text = str(frametime).pad_decimals(2) + frame_history_total_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var viewport_rid := get_viewport().get_viewport_rid() + var frametime_cpu := RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu() + frame_history_cpu.push_back(frametime_cpu) + if frame_history_cpu.size() > HISTORY_NUM_FRAMES: + frame_history_cpu.pop_front() + + frametime_cpu_avg = frame_history_cpu.reduce(sum_func) / frame_history_cpu.size() + frame_history_cpu_avg.text = str(frametime_cpu_avg).pad_decimals(2) + frame_history_cpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_cpu_min: float = frame_history_cpu.min() + frame_history_cpu_min.text = str(frametime_cpu_min).pad_decimals(2) + frame_history_cpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_cpu_max: float = frame_history_cpu.max() + frame_history_cpu_max.text = str(frametime_cpu_max).pad_decimals(2) + frame_history_cpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + frame_history_cpu_last.text = str(frametime_cpu).pad_decimals(2) + frame_history_cpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_gpu := RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid) + frame_history_gpu.push_back(frametime_gpu) + if frame_history_gpu.size() > HISTORY_NUM_FRAMES: + frame_history_gpu.pop_front() + + frametime_gpu_avg = frame_history_gpu.reduce(sum_func) / frame_history_gpu.size() + frame_history_gpu_avg.text = str(frametime_gpu_avg).pad_decimals(2) + frame_history_gpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_gpu_min: float = frame_history_gpu.min() + frame_history_gpu_min.text = str(frametime_gpu_min).pad_decimals(2) + frame_history_gpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + var frametime_gpu_max: float = frame_history_gpu.max() + frame_history_gpu_max.text = str(frametime_gpu_max).pad_decimals(2) + frame_history_gpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + frame_history_gpu_last.text = str(frametime_gpu).pad_decimals(2) + frame_history_gpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + + frames_per_second = 1000.0 / frametime_avg + fps_history.push_back(frames_per_second) + if fps_history.size() > HISTORY_NUM_FRAMES: + fps_history.pop_front() + + fps.text = str(floor(frames_per_second)) + " FPS" + var frame_time_color := frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) + fps.modulate = frame_time_color + + frame_time.text = str(frametime).pad_decimals(2) + " mspf" + frame_time.modulate = frame_time_color + + var vsync_string := "" + match DisplayServer.window_get_vsync_mode(): + DisplayServer.VSYNC_ENABLED: + vsync_string = "V-Sync" + DisplayServer.VSYNC_ADAPTIVE: + vsync_string = "Adaptive V-Sync" + DisplayServer.VSYNC_MAILBOX: + vsync_string = "Mailbox V-Sync" + + if Engine.max_fps > 0 or OS.low_processor_usage_mode: + # Display FPS cap determined by `Engine.max_fps` or low-processor usage mode sleep duration + # (the lowest FPS cap is used). + var low_processor_max_fps := roundi(1000000.0 / OS.low_processor_usage_mode_sleep_usec) + var fps_cap := low_processor_max_fps + if Engine.max_fps > 0: + fps_cap = mini(Engine.max_fps, low_processor_max_fps) + frame_time.text += " (cap: " + str(fps_cap) + " FPS" + + if not vsync_string.is_empty(): + frame_time.text += " + " + vsync_string + + frame_time.text += ")" + else: + if not vsync_string.is_empty(): + frame_time.text += " (" + vsync_string + ")" + + frame_number.text = "Frame: " + str(Engine.get_frames_drawn()) + + last_tick = Time.get_ticks_usec() + + +func _on_visibility_changed() -> void: + if visible: + # Reset graphs to prevent them from looking strange before `HISTORY_NUM_FRAMES` frames + # have been drawn. + var frametime_last := (Time.get_ticks_usec() - last_tick) * 0.001 + fps_history.resize(HISTORY_NUM_FRAMES) + fps_history.fill(1000.0 / frametime_last) + frame_history_total.resize(HISTORY_NUM_FRAMES) + frame_history_total.fill(frametime_last) + frame_history_cpu.resize(HISTORY_NUM_FRAMES) + var viewport_rid := get_viewport().get_viewport_rid() + frame_history_cpu.fill(RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu()) + frame_history_gpu.resize(HISTORY_NUM_FRAMES) + frame_history_gpu.fill(RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid)) diff --git a/addons/debug_menu/debug_menu.tscn b/addons/debug_menu/debug_menu.tscn new file mode 100644 index 0000000..2aaf0c1 --- /dev/null +++ b/addons/debug_menu/debug_menu.tscn @@ -0,0 +1,402 @@ +[gd_scene load_steps=3 format=3 uid="uid://cggqb75a8w8r"] + +[ext_resource type="Script" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ki0n8"] +bg_color = Color(0, 0, 0, 0.25098) + +[node name="CanvasLayer" type="CanvasLayer"] +process_mode = 3 +layer = 128 + +[node name="DebugMenu" type="Control" parent="." node_paths=PackedStringArray("fps", "frame_time", "frame_number", "frame_history_total_avg", "frame_history_total_min", "frame_history_total_max", "frame_history_total_last", "frame_history_cpu_avg", "frame_history_cpu_min", "frame_history_cpu_max", "frame_history_cpu_last", "frame_history_gpu_avg", "frame_history_gpu_min", "frame_history_gpu_max", "frame_history_gpu_last", "fps_graph", "total_graph", "cpu_graph", "gpu_graph", "information", "settings")] +custom_minimum_size = Vector2(400, 400) +layout_mode = 3 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -416.0 +offset_top = 8.0 +offset_right = -16.0 +offset_bottom = 408.0 +grow_horizontal = 0 +size_flags_horizontal = 8 +size_flags_vertical = 4 +mouse_filter = 2 +script = ExtResource("1_p440y") +fps = NodePath("VBoxContainer/FPS") +frame_time = NodePath("VBoxContainer/FrameTime") +frame_number = NodePath("VBoxContainer/FrameNumber") +frame_history_total_avg = NodePath("VBoxContainer/FrameTimeHistory/TotalAvg") +frame_history_total_min = NodePath("VBoxContainer/FrameTimeHistory/TotalMin") +frame_history_total_max = NodePath("VBoxContainer/FrameTimeHistory/TotalMax") +frame_history_total_last = NodePath("VBoxContainer/FrameTimeHistory/TotalLast") +frame_history_cpu_avg = NodePath("VBoxContainer/FrameTimeHistory/CPUAvg") +frame_history_cpu_min = NodePath("VBoxContainer/FrameTimeHistory/CPUMin") +frame_history_cpu_max = NodePath("VBoxContainer/FrameTimeHistory/CPUMax") +frame_history_cpu_last = NodePath("VBoxContainer/FrameTimeHistory/CPULast") +frame_history_gpu_avg = NodePath("VBoxContainer/FrameTimeHistory/GPUAvg") +frame_history_gpu_min = NodePath("VBoxContainer/FrameTimeHistory/GPUMin") +frame_history_gpu_max = NodePath("VBoxContainer/FrameTimeHistory/GPUMax") +frame_history_gpu_last = NodePath("VBoxContainer/FrameTimeHistory/GPULast") +fps_graph = NodePath("VBoxContainer/FPSGraph/Graph") +total_graph = NodePath("VBoxContainer/TotalGraph/Graph") +cpu_graph = NodePath("VBoxContainer/CPUGraph/Graph") +gpu_graph = NodePath("VBoxContainer/GPUGraph/Graph") +information = NodePath("VBoxContainer/Information") +settings = NodePath("VBoxContainer/Settings") + +[node name="VBoxContainer" type="VBoxContainer" parent="DebugMenu"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -300.0 +offset_bottom = 374.0 +grow_horizontal = 0 +mouse_filter = 2 +theme_override_constants/separation = 0 + +[node name="FPS" type="Label" parent="DebugMenu/VBoxContainer"] +modulate = Color(0, 1, 0, 1) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 5 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 18 +text = "60 FPS" +horizontal_alignment = 2 + +[node name="FrameTime" type="Label" parent="DebugMenu/VBoxContainer"] +modulate = Color(0, 1, 0, 1) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "16.67 mspf (cap: 123 FPS + Adaptive V-Sync)" +horizontal_alignment = 2 + +[node name="FrameNumber" type="Label" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Frame: 1234" +horizontal_alignment = 2 + +[node name="FrameTimeHistory" type="GridContainer" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +mouse_filter = 2 +theme_override_constants/h_separation = 0 +theme_override_constants/v_separation = 0 +columns = 5 + +[node name="Spacer" type="Control" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(60, 0) +layout_mode = 2 +mouse_filter = 2 + +[node name="AvgHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Average" +horizontal_alignment = 2 + +[node name="MinHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Best" +horizontal_alignment = 2 + +[node name="MaxHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Worst" +horizontal_alignment = 2 + +[node name="LastHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Last" +horizontal_alignment = 2 + +[node name="TotalHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Total:" +horizontal_alignment = 2 + +[node name="TotalAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="TotalMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="TotalMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="TotalLast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="CPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "CPU:" +horizontal_alignment = 2 + +[node name="CPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="CPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "12.34" +horizontal_alignment = 2 + +[node name="CPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="CPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="GPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "GPU:" +horizontal_alignment = 2 + +[node name="GPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="GPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "1.23" +horizontal_alignment = 2 + +[node name="GPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="GPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] +modulate = Color(0, 1, 0, 1) +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "123.45" +horizontal_alignment = 2 + +[node name="FPSGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +mouse_filter = 2 +alignment = 2 + +[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/FPSGraph"] +custom_minimum_size = Vector2(0, 27) +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "FPS: ↑" +vertical_alignment = 1 + +[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/FPSGraph"] +custom_minimum_size = Vector2(150, 25) +layout_mode = 2 +size_flags_vertical = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") + +[node name="TotalGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +mouse_filter = 2 +alignment = 2 + +[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/TotalGraph"] +custom_minimum_size = Vector2(0, 27) +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Total: ↓" +vertical_alignment = 1 + +[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/TotalGraph"] +custom_minimum_size = Vector2(150, 25) +layout_mode = 2 +size_flags_vertical = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") + +[node name="CPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +mouse_filter = 2 +alignment = 2 + +[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/CPUGraph"] +custom_minimum_size = Vector2(0, 27) +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "CPU: ↓" +vertical_alignment = 1 + +[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/CPUGraph"] +custom_minimum_size = Vector2(150, 25) +layout_mode = 2 +size_flags_vertical = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") + +[node name="GPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] +layout_mode = 2 +mouse_filter = 2 +alignment = 2 + +[node name="Title" type="Label" parent="DebugMenu/VBoxContainer/GPUGraph"] +custom_minimum_size = Vector2(0, 27) +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "GPU: ↓" +vertical_alignment = 1 + +[node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/GPUGraph"] +custom_minimum_size = Vector2(150, 25) +layout_mode = 2 +size_flags_vertical = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") + +[node name="Information" type="Label" parent="DebugMenu/VBoxContainer"] +modulate = Color(1, 1, 1, 0.752941) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "12th Gen Intel(R) Core(TM) i0-1234K +Windows 12 64-bit (double precision), Vulkan 1.2.34 +NVIDIA GeForce RTX 1234, 123.45.67" +horizontal_alignment = 2 + +[node name="Settings" type="Label" parent="DebugMenu/VBoxContainer"] +modulate = Color(0.8, 0.84, 1, 0.752941) +layout_mode = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 3 +theme_override_font_sizes/font_size = 12 +text = "Project Version: 1.2.3 +Rendering Method: Forward+ +Window: 1234×567, Viewport: 1234×567 +3D Scale (FSR 1.0): 100% = 1234×567 +3D Antialiasing: TAA + 2× MSAA + FXAA +SSR: 123 Steps +SSAO: On +SSIL: On +SDFGI: 1 Cascades +Glow: On +Volumetric Fog: On +2D Antialiasing: 2× MSAA" +horizontal_alignment = 2 + +[connection signal="visibility_changed" from="DebugMenu" to="DebugMenu" method="_on_visibility_changed"] diff --git a/addons/debug_menu/plugin.cfg b/addons/debug_menu/plugin.cfg new file mode 100644 index 0000000..851c3da --- /dev/null +++ b/addons/debug_menu/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Debug Menu" +description="In-game debug menu displaying performance metrics and hardware information" +author="Calinou" +version="1.1.2" +script="plugin.gd" diff --git a/addons/debug_menu/plugin.gd b/addons/debug_menu/plugin.gd new file mode 100644 index 0000000..5ec132e --- /dev/null +++ b/addons/debug_menu/plugin.gd @@ -0,0 +1,29 @@ +@tool +extends EditorPlugin + +func _enter_tree() -> void: + add_autoload_singleton("DebugMenu", "res://addons/debug_menu/debug_menu.tscn") + + # FIXME: This appears to do nothing. +# if not ProjectSettings.has_setting("application/config/version"): +# ProjectSettings.set_setting("application/config/version", "1.0.0") +# +# ProjectSettings.set_initial_value("application/config/version", "1.0.0") +# ProjectSettings.add_property_info({ +# name = "application/config/version", +# type = TYPE_STRING, +# }) +# +# if not InputMap.has_action("cycle_debug_menu"): +# InputMap.add_action("cycle_debug_menu") +# var event := InputEventKey.new() +# event.keycode = KEY_F3 +# InputMap.action_add_event("cycle_debug_menu", event) +# +# ProjectSettings.save() + + +func _exit_tree() -> void: + remove_autoload_singleton("DebugMenu") + # Don't remove the project setting's value and input map action, + # as the plugin may be re-enabled in the future. diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..b65518b --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,102 @@ +[preset.0] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="C:/Users/Midnight/Desktop/export2/not_bar.exe" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" + +[preset.1] + +name="Linux/X11" +platform="Linux/X11" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="C:/Users/Midnight/Desktop/export2/not_bar.x86_64" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false +binary_format/architecture="x86_64" +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..b370ceb --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..2c51913 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8fqumed5vu5w" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/luts/lut_saturated.png b/luts/lut_saturated.png new file mode 100644 index 0000000..a4ac7da Binary files /dev/null and b/luts/lut_saturated.png differ diff --git a/luts/lut_saturated.png.import b/luts/lut_saturated.png.import new file mode 100644 index 0000000..fa7f9d6 --- /dev/null +++ b/luts/lut_saturated.png.import @@ -0,0 +1,26 @@ +[remap] + +importer="3d_texture" +type="CompressedTexture3D" +uid="uid://bus418i6c6y3w" +path="res://.godot/imported/lut_saturated.png-bf0a35cafccb1516d07adffb4e04f5f3.ctex3d" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://luts/lut_saturated.png" +dest_files=["res://.godot/imported/lut_saturated.png-bf0a35cafccb1516d07adffb4e04f5f3.ctex3d"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +slices/horizontal=8 +slices/vertical=8 diff --git a/prefabs/character.tscn b/prefabs/character.tscn new file mode 100644 index 0000000..0e87f8d --- /dev/null +++ b/prefabs/character.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=6 format=3 uid="uid://c4orppao35wg6"] + +[ext_resource type="Script" path="res://src/character/character_controller.gd" id="1_2yciu"] +[ext_resource type="Script" path="res://src/character/physics_interpolate.gd" id="2_jbufo"] +[ext_resource type="Script" path="res://src/character/camera_controller.gd" id="3_i1tod"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_m1ch2"] +radius = 0.4 +height = 1.8 + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_64ysj"] +radius = 0.4 +height = 1.8 + +[node name="Character" type="Node"] + +[node name="Physics" type="CharacterBody3D" parent="."] +script = ExtResource("1_2yciu") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Physics"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0) +shape = SubResource("CapsuleShape3D_m1ch2") + +[node name="Visual" type="Node3D" parent="." node_paths=PackedStringArray("physicsObject")] +script = ExtResource("2_jbufo") +physicsObject = NodePath("../Physics") + +[node name="CameraTarget" type="Node3D" parent="Visual"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.28439, 0) + +[node name="CSGMesh3D" type="CSGMesh3D" parent="Visual"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0) +gi_mode = 0 +mesh = SubResource("CapsuleMesh_64ysj") + +[node name="CameraArm" type="Node3D" parent="." node_paths=PackedStringArray("target")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0) +script = ExtResource("3_i1tod") +target = NodePath("../Visual/CameraTarget") + +[node name="Camera3D" type="Camera3D" parent="CameraArm"] diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..82c2f4b --- /dev/null +++ b/project.godot @@ -0,0 +1,108 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="3D Character Controller Template" +config/version="0.0.2" +config/tags=PackedStringArray("3d") +run/main_scene="res://scenes/main.tscn" +config/features=PackedStringArray("4.2", "Forward Plus") +boot_splash/bg_color=Color(0, 0, 0, 1) +config/icon="res://icon.svg" + +[autoload] + +DebugMenu="*res://addons/debug_menu/debug_menu.tscn" +GameManager="*res://src/game_manager.gd" + +[display] + +window/size/mode=4 + +[editor_plugins] + +enabled=PackedStringArray("res://addons/debug_menu/plugin.cfg") + +[input] + +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) +] +} +move_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) +] +} +move_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) +] +} +jump={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) +] +} +sprint={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null) +] +} +zoom_in={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} +zoom_out={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} +look_left={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null) +] +} +look_right={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null) +] +} +look_up={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null) +] +} +look_down={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null) +] +} + +[rendering] + +lights_and_shadows/directional_shadow/16_bits=false +scaling_3d/mode=2 +environment/defaults/default_clear_color=Color(0, 0, 0, 1) +environment/defaults/default_environment="res://scenes/main/main_environment.tres" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3eaba61 --- /dev/null +++ b/readme.md @@ -0,0 +1,8 @@ +# 3D Character Controller Template + +A simple template to get started with 3D + +## Features + +- Performance menu addon (`F3`) +- First/Third person character controller with basic input maps \ No newline at end of file diff --git a/scenes/main.tscn b/scenes/main.tscn new file mode 100644 index 0000000..437ee7a --- /dev/null +++ b/scenes/main.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=7 format=3 uid="uid://dgfh1lgxud87u"] + +[ext_resource type="PackedScene" uid="uid://c4orppao35wg6" path="res://prefabs/character.tscn" id="2_v34ds"] +[ext_resource type="Script" path="res://src/spawner.gd" id="3_xxi2i"] + +[sub_resource type="GDScript" id="GDScript_12xd3"] +script/source = "extends Node3D + +func _ready() -> void: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED +" + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xxbnc"] +albedo_color = Color(0.266667, 0.145098, 0.0823529, 1) + +[sub_resource type="PlaneMesh" id="PlaneMesh_uelfv"] +material = SubResource("StandardMaterial3D_xxbnc") +size = Vector2(50, 50) + +[sub_resource type="BoxShape3D" id="BoxShape3D_vbj2x"] +size = Vector3(50, 1, 50) + +[node name="Main" type="Node3D"] +script = SubResource("GDScript_12xd3") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.276457, -0.764665, 0.582116, 0.11662, 0.62794, 0.769474, -0.953924, -0.14484, 0.262774, 0, 0, 0) +light_energy = 2.0 +shadow_enabled = true +directional_shadow_fade_start = 1.0 +directional_shadow_max_distance = 50.0 + +[node name="Spawner" type="Node3D" parent="."] +script = ExtResource("3_xxi2i") +object_to_spawn = ExtResource("2_v34ds") + +[node name="CSGMesh3D" type="CSGMesh3D" parent="."] +mesh = SubResource("PlaneMesh_uelfv") + +[node name="StaticBody3D" type="StaticBody3D" parent="CSGMesh3D"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGMesh3D/StaticBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0) +shape = SubResource("BoxShape3D_vbj2x") + +[node name="CSGCombiner3D" type="CSGCombiner3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) +use_collision = true + +[node name="Box" type="CSGBox3D" parent="CSGCombiner3D"] + +[node name="Box5" type="CSGBox3D" parent="CSGCombiner3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.37539, 1.21157, -5.02753) + +[node name="Box2" type="CSGBox3D" parent="CSGCombiner3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0.5, 0.5) + +[node name="Box4" type="CSGBox3D" parent="CSGCombiner3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.06635, 1.17009, -1.15682) +size = Vector3(1, 1, 4.31364) + +[node name="Box3" type="CSGBox3D" parent="CSGCombiner3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.15636, 1.09924, 0.824171) +size = Vector3(3.28895, 0.716575, 1) diff --git a/scenes/main/main_environment.tres b/scenes/main/main_environment.tres new file mode 100644 index 0000000..51c1ecc --- /dev/null +++ b/scenes/main/main_environment.tres @@ -0,0 +1,34 @@ +[gd_resource type="Environment" load_steps=4 format=3 uid="uid://b8fqed8jo6yqj"] + +[ext_resource type="CompressedTexture3D" uid="uid://bus418i6c6y3w" path="res://luts/lut_saturated.png" id="1_3f5y7"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_rxwic"] +sky_top_color = Color(0.317647, 0.415686, 0.643137, 1) +sky_horizon_color = Color(0.878431, 0.929412, 1, 1) +ground_bottom_color = Color(0.439216, 0.290196, 0.239216, 1) +ground_horizon_color = Color(0.988235, 0.890196, 0.823529, 1) +sun_angle_max = 0.0 +sun_curve = 1e-05 + +[sub_resource type="Sky" id="Sky_wbc5y"] +sky_material = SubResource("ProceduralSkyMaterial_rxwic") + +[resource] +background_mode = 2 +sky = SubResource("Sky_wbc5y") +tonemap_mode = 2 +tonemap_white = 6.0 +ssao_enabled = true +ssao_ao_channel_affect = 1.0 +sdfgi_use_occlusion = true +sdfgi_bounce_feedback = 0.9 +glow_enabled = true +glow_levels/4 = 1.0 +glow_levels/6 = 1.0 +glow_levels/7 = 1.0 +glow_blend_mode = 4 +fog_sun_scatter = 0.5 +fog_aerial_perspective = 0.9 +volumetric_fog_albedo = Color(0.960784, 0.984314, 1, 1) +adjustment_enabled = true +adjustment_color_correction = ExtResource("1_3f5y7") diff --git a/src/character/camera_controller.gd b/src/character/camera_controller.gd new file mode 100644 index 0000000..55d0f2e --- /dev/null +++ b/src/character/camera_controller.gd @@ -0,0 +1,29 @@ +extends Node3D + +@export var target : Node3D +@onready var camera = $Camera3D +var lookInput : Vector2 = Vector2.ZERO + +func _input(event): + if event is InputEventMouseMotion: + lookInput.x += event.relative.y + lookInput.y += event.relative.x + return + + var zoom = .0 + if event.is_action_released("zoom_in"): + zoom -= .2 + if event.is_action_released("zoom_out"): + zoom += .2 + + camera.position.z = clamp(camera.position.z + zoom, 0, 4) + +func _process(delta): + lookInput += Input.get_vector('look_left','look_right', 'look_down', 'look_up') + + global_position = target.global_position + + rotation.x = clamp(rotation.x - lookInput.x * delta * 0.3, PI*-0.48, PI*0.2) + rotation.y -= lookInput.y * delta * 0.3 + + lookInput = Vector2.ZERO diff --git a/src/character/character_controller.gd b/src/character/character_controller.gd new file mode 100644 index 0000000..5b301e1 --- /dev/null +++ b/src/character/character_controller.gd @@ -0,0 +1,52 @@ +extends CharacterBody3D + +var input3D : Vector3 + +@export var SPEED = 1.4 +@export var SPRINT_SPEED = 3.0 +@export var JUMP_VELOCITY = 4.5 + +var speed = .0 + +@onready var camera : Camera3D = get_viewport().get_camera_3d() + +var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") + +func _ready(): + process_priority = 2 + +func getCameraRelativeInput() -> Vector3 : + var cam_basis = camera.global_transform.basis + cam_basis.x.y = 0 + cam_basis.x = cam_basis.x.normalized() + cam_basis.z.y = 0 + cam_basis.z = cam_basis.z.normalized() + + var input = Input.get_vector("move_left", "move_right", "move_up", "move_down") + var relativeInput = Vector3.ZERO + relativeInput += input.x * cam_basis.x + relativeInput += input.y * cam_basis.z + + return relativeInput + +func _physics_process(delta): + if not is_on_floor(): + velocity.y -= gravity * delta + + if Input.is_action_just_pressed("jump") and is_on_floor(): + velocity.y = JUMP_VELOCITY + + input3D = getCameraRelativeInput() + + speed = SPEED + Input.get_action_strength("sprint") * SPRINT_SPEED + + var direction = input3D.normalized() + if direction: + velocity.x = direction.x * speed + velocity.z = direction.z * speed + look_at(direction + global_position) + else: + velocity.x = move_toward(velocity.x, 0, speed) + velocity.z = move_toward(velocity.z, 0, speed) + + move_and_slide() diff --git a/src/character/physics_interpolate.gd b/src/character/physics_interpolate.gd new file mode 100644 index 0000000..3098d1f --- /dev/null +++ b/src/character/physics_interpolate.gd @@ -0,0 +1,28 @@ +extends Node3D + +@export var physicsObject : PhysicsBody3D + +var physics_delta : float = 1 + +var target : Transform3D +var prev : Transform3D + +func _ready(): + target = physicsObject.global_transform + prev = physicsObject.global_transform + +var d = 0 +func _process(delta): + d += delta + + var t = d / physics_delta + + global_transform = prev.interpolate_with(target, t) + +func _physics_process(delta): + physics_delta = delta + d = 0 + + global_transform = target + prev = target + target = physicsObject.global_transform diff --git a/src/game_manager.gd b/src/game_manager.gd new file mode 100644 index 0000000..9a6bc98 --- /dev/null +++ b/src/game_manager.gd @@ -0,0 +1,20 @@ +extends Node + +var pause_menu_res = preload('res://ui/pause_menu.tscn') +var pause_menu: PauseMenu + +func _ready() -> void: + pause_menu = pause_menu_res.instantiate() + get_tree().root.add_child.call_deferred(pause_menu) + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed('ui_cancel'): + pause_game(not get_tree().paused) + +func quit_game(): + get_tree().quit() + +func pause_game(pause = true): + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if pause else Input.MOUSE_MODE_CAPTURED + get_tree().paused = pause + pause_menu.visible = pause diff --git a/src/shaders/ui/blur.gdshader b/src/shaders/ui/blur.gdshader new file mode 100644 index 0000000..68c5c30 --- /dev/null +++ b/src/shaders/ui/blur.gdshader @@ -0,0 +1,10 @@ +shader_type canvas_item; +render_mode blend_disabled; + +uniform float blur_amount : hint_range(0,5) = 5.0; +uniform vec4 tint : source_color = vec4(.5, .5, .5, 1.0); +uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap; + +void fragment() { + COLOR = textureLod(screen_texture, SCREEN_UV, blur_amount) * tint; +} \ No newline at end of file diff --git a/src/spawner.gd b/src/spawner.gd new file mode 100644 index 0000000..a78cd12 --- /dev/null +++ b/src/spawner.gd @@ -0,0 +1,12 @@ +class_name Spawner extends Node3D + +@export var object_to_spawn: PackedScene +@export var auto_spawn: bool = true + +func _ready() -> void: + if auto_spawn: + spawn.call_deferred() + +func spawn() -> void: + var object = object_to_spawn.instantiate() + get_parent_node_3d().add_child(object) diff --git a/ui/pause_menu.tscn b/ui/pause_menu.tscn new file mode 100644 index 0000000..25bdc77 --- /dev/null +++ b/ui/pause_menu.tscn @@ -0,0 +1,61 @@ +[gd_scene load_steps=4 format=3 uid="uid://dilbjuywlv0mc"] + +[ext_resource type="Shader" path="res://src/shaders/ui/blur.gdshader" id="1_q1q1u"] +[ext_resource type="Script" path="res://ui/src/pause_menu.gd" id="1_qw2fh"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_dcwl4"] +shader = ExtResource("1_q1q1u") +shader_parameter/blur_amount = 3.0 +shader_parameter/tint = Color(0.5, 0.5, 0.5, 1) + +[node name="PauseMenu" type="Control"] +process_mode = 2 +visible = false +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_qw2fh") + +[node name="MainPanel" type="PanelContainer" parent="."] +material = SubResource("ShaderMaterial_dcwl4") +layout_mode = 1 +anchors_preset = 13 +anchor_left = 0.5 +anchor_right = 0.5 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MainPanel"] +layout_mode = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 30 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MainPanel/MarginContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +alignment = 1 + +[node name="Label" type="Label" parent="MainPanel/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_type_variation = &"HeaderLarge" +text = "Paused" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ContinueButton" type="Button" parent="MainPanel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Continue" + +[node name="QuitButton" type="Button" parent="MainPanel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Quit +" + +[connection signal="pressed" from="MainPanel/MarginContainer/VBoxContainer/ContinueButton" to="." method="_on_continue"] +[connection signal="pressed" from="MainPanel/MarginContainer/VBoxContainer/QuitButton" to="." method="_on_quit"] diff --git a/ui/src/pause_menu.gd b/ui/src/pause_menu.gd new file mode 100644 index 0000000..2fc81ab --- /dev/null +++ b/ui/src/pause_menu.gd @@ -0,0 +1,7 @@ +class_name PauseMenu extends Node + +func _on_continue() -> void: + GameManager.pause_game(false) + +func _on_quit() -> void: + GameManager.quit_game()