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..a1ab064 --- /dev/null +++ b/addons/debug_menu/debug_menu.gd @@ -0,0 +1,479 @@ +extends CanvasLayer + +@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 + $DebugMenu/VBoxContainer/FrameTimeHistory.visible = style == Style.VISIBLE_DETAILED + $DebugMenu/VBoxContainer/FPSGraph.visible = style == Style.VISIBLE_DETAILED + $DebugMenu/VBoxContainer/TotalGraph.visible = style == Style.VISIBLE_DETAILED + $DebugMenu/VBoxContainer/CPUGraph.visible = style == Style.VISIBLE_DETAILED + $DebugMenu/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 := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method")) + var rendering_method_string := rendering_method + match 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 scaling_3d_mode_string := "(unknown)" + match viewport.scaling_3d_mode: + Viewport.SCALING_3D_MODE_BILINEAR: + scaling_3d_mode_string = "Bilinear" + Viewport.SCALING_3D_MODE_FSR: + scaling_3d_mode_string = "FSR 1.0" + Viewport.SCALING_3D_MODE_FSR2: + scaling_3d_mode_string = "FSR 2.2" + + var antialiasing_3d_string := "" + if viewport.scaling_3d_mode == Viewport.SCALING_3D_MODE_FSR2: + # The FSR2 scaling mode includes its own temporal antialiasing implementation. + antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FSR 2.2" + if viewport.scaling_3d_mode != Viewport.SCALING_3D_MODE_FSR2 and viewport.use_taa: + # Godot's own TAA is ignored when using FSR2 scaling mode, as FSR2 provides its own TAA implementation. + 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" + + settings.text += "3D scale (%s): %d%% = %d×%d" % [ + scaling_3d_mode_string, + 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 rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method")) + var rendering_driver := str(ProjectSettings.get_setting_with_override("rendering/rendering_device/driver")) + var graphics_api_string := rendering_driver + if rendering_method != "gl_compatibility": + if rendering_driver == "d3d12": + graphics_api_string = "Direct3D 12" + elif rendering_driver == "metal": + graphics_api_string = "Metal" + elif rendering_driver == "vulkan": + if OS.has_feature("macos") or OS.has_feature("ios"): + graphics_api_string = "Vulkan via MoltenVK" + else: + graphics_api_string = "Vulkan" + else: + if rendering_driver == "opengl3_angle": + graphics_api_string = "OpenGL via ANGLE" + elif OS.has_feature("mobile") or rendering_driver == "opengl3_es": + graphics_api_string = "OpenGL ES" + elif OS.has_feature("web"): + graphics_api_string = "WebGL" + elif rendering_driver == "opengl3": + 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.gd.uid b/addons/debug_menu/debug_menu.gd.uid new file mode 100644 index 0000000..170cac6 --- /dev/null +++ b/addons/debug_menu/debug_menu.gd.uid @@ -0,0 +1 @@ +uid://bpsslluwe827a diff --git a/addons/debug_menu/debug_menu.tscn b/addons/debug_menu/debug_menu.tscn new file mode 100644 index 0000000..9bfc9d6 --- /dev/null +++ b/addons/debug_menu/debug_menu.tscn @@ -0,0 +1,401 @@ +[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" 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")] +layer = 128 +script = ExtResource("1_p440y") +fps = NodePath("DebugMenu/VBoxContainer/FPS") +frame_time = NodePath("DebugMenu/VBoxContainer/FrameTime") +frame_number = NodePath("DebugMenu/VBoxContainer/FrameNumber") +frame_history_total_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalAvg") +frame_history_total_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMin") +frame_history_total_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMax") +frame_history_total_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalLast") +frame_history_cpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUAvg") +frame_history_cpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMin") +frame_history_cpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMax") +frame_history_cpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPULast") +frame_history_gpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUAvg") +frame_history_gpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMin") +frame_history_gpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMax") +frame_history_gpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPULast") +fps_graph = NodePath("DebugMenu/VBoxContainer/FPSGraph/Graph") +total_graph = NodePath("DebugMenu/VBoxContainer/TotalGraph/Graph") +cpu_graph = NodePath("DebugMenu/VBoxContainer/CPUGraph/Graph") +gpu_graph = NodePath("DebugMenu/VBoxContainer/GPUGraph/Graph") +information = NodePath("DebugMenu/VBoxContainer/Information") +settings = NodePath("DebugMenu/VBoxContainer/Settings") + +[node name="DebugMenu" type="Control" parent="."] +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 + +[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="." to="." method="_on_visibility_changed"] diff --git a/addons/debug_menu/plugin.cfg b/addons/debug_menu/plugin.cfg new file mode 100644 index 0000000..54100f7 --- /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.2.0" +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/addons/debug_menu/plugin.gd.uid b/addons/debug_menu/plugin.gd.uid new file mode 100644 index 0000000..24ac21b --- /dev/null +++ b/addons/debug_menu/plugin.gd.uid @@ -0,0 +1 @@ +uid://bloj6h52fp002 diff --git a/assets/projectiles/basic_projectile.tscn b/assets/projectiles/basic_projectile.tscn new file mode 100644 index 0000000..f37b077 --- /dev/null +++ b/assets/projectiles/basic_projectile.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=4 format=3 uid="uid://d2q0gh76v2wjm"] + +[ext_resource type="Script" uid="uid://d5tiwy16ivu6" path="res://player/weapons/projectile.gd" id="1_4urkw"] +[ext_resource type="Texture2D" uid="uid://b82eovf1htp4i" path="res://assets/sprites/characters/pink/Rock1.png" id="2_5842l"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_o4f18"] +radius = 1.0 + +[node name="BasicProjectile" type="Area2D"] +collision_layer = 16 +collision_mask = 8 +script = ExtResource("1_4urkw") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_o4f18") + +[node name="Sprite2D" type="Sprite2D" parent="."] +position = Vector2(5.96046e-08, 5.96046e-08) +scale = Vector2(0.3125, 0.3125) +texture = ExtResource("2_5842l") diff --git a/assets/sprites/enemies/slimes_blue.png b/assets/sprites/enemies/slimes_blue.png new file mode 100644 index 0000000..4dd1aa1 Binary files /dev/null and b/assets/sprites/enemies/slimes_blue.png differ diff --git a/assets/sprites/enemies/slimes_blue.png.import b/assets/sprites/enemies/slimes_blue.png.import new file mode 100644 index 0000000..95ac8f9 --- /dev/null +++ b/assets/sprites/enemies/slimes_blue.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gan2vkax8nwy" +path="res://.godot/imported/slimes_blue.png-714717b05c2104cf50fc9fd6f4afe1c1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_blue.png" +dest_files=["res://.godot/imported/slimes_blue.png-714717b05c2104cf50fc9fd6f4afe1c1.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 diff --git a/assets/sprites/enemies/slimes_dark.png b/assets/sprites/enemies/slimes_dark.png new file mode 100644 index 0000000..e7ae015 Binary files /dev/null and b/assets/sprites/enemies/slimes_dark.png differ diff --git a/assets/sprites/enemies/slimes_dark.png.import b/assets/sprites/enemies/slimes_dark.png.import new file mode 100644 index 0000000..7be9f93 --- /dev/null +++ b/assets/sprites/enemies/slimes_dark.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ievi6pon3ptw" +path="res://.godot/imported/slimes_dark.png-b89c4cb22ec4d8d8ca0b6255a8a99a81.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_dark.png" +dest_files=["res://.godot/imported/slimes_dark.png-b89c4cb22ec4d8d8ca0b6255a8a99a81.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 diff --git a/assets/sprites/enemies/slimes_green.png b/assets/sprites/enemies/slimes_green.png new file mode 100644 index 0000000..43ae589 Binary files /dev/null and b/assets/sprites/enemies/slimes_green.png differ diff --git a/assets/sprites/enemies/slimes_green.png.import b/assets/sprites/enemies/slimes_green.png.import new file mode 100644 index 0000000..11e43ff --- /dev/null +++ b/assets/sprites/enemies/slimes_green.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqwmi3v81uhmf" +path="res://.godot/imported/slimes_green.png-08e345c322ad9d9ec7922af9737a7e3b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_green.png" +dest_files=["res://.godot/imported/slimes_green.png-08e345c322ad9d9ec7922af9737a7e3b.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 diff --git a/assets/sprites/enemies/slimes_pink.png b/assets/sprites/enemies/slimes_pink.png new file mode 100644 index 0000000..6c9e6b9 Binary files /dev/null and b/assets/sprites/enemies/slimes_pink.png differ diff --git a/assets/sprites/enemies/slimes_pink.png.import b/assets/sprites/enemies/slimes_pink.png.import new file mode 100644 index 0000000..8fa3c6a --- /dev/null +++ b/assets/sprites/enemies/slimes_pink.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1eqf5k6fywlu" +path="res://.godot/imported/slimes_pink.png-306435e1f9ca80f46b8658abf39a07a5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_pink.png" +dest_files=["res://.godot/imported/slimes_pink.png-306435e1f9ca80f46b8658abf39a07a5.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 diff --git a/assets/sprites/enemies/slimes_white.png b/assets/sprites/enemies/slimes_white.png new file mode 100644 index 0000000..c1b4949 Binary files /dev/null and b/assets/sprites/enemies/slimes_white.png differ diff --git a/assets/sprites/enemies/slimes_white.png.import b/assets/sprites/enemies/slimes_white.png.import new file mode 100644 index 0000000..7ba66fa --- /dev/null +++ b/assets/sprites/enemies/slimes_white.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://badj3651o6jc8" +path="res://.godot/imported/slimes_white.png-fb1b6c0ee38065fb578a69501028a0b7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_white.png" +dest_files=["res://.godot/imported/slimes_white.png-fb1b6c0ee38065fb578a69501028a0b7.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 diff --git a/assets/sprites/enemies/slimes_yellow.png b/assets/sprites/enemies/slimes_yellow.png new file mode 100644 index 0000000..d7814af Binary files /dev/null and b/assets/sprites/enemies/slimes_yellow.png differ diff --git a/assets/sprites/enemies/slimes_yellow.png.import b/assets/sprites/enemies/slimes_yellow.png.import new file mode 100644 index 0000000..b9ccc2e --- /dev/null +++ b/assets/sprites/enemies/slimes_yellow.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cv7txwf7xhngf" +path="res://.godot/imported/slimes_yellow.png-7184a698ae163f66b11534f79ba701c9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/sprites/enemies/slimes_yellow.png" +dest_files=["res://.godot/imported/slimes_yellow.png-7184a698ae163f66b11534f79ba701c9.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 diff --git a/assets/sprites/tilesets/forest.tres b/assets/sprites/tilesets/forest.tres index 09ff695..e161dc1 100644 --- a/assets/sprites/tilesets/forest.tres +++ b/assets/sprites/tilesets/forest.tres @@ -1131,7 +1131,8 @@ texture_region_size = Vector2i(32, 32) [resource] tile_size = Vector2i(32, 32) -physics_layer_0/collision_layer = 4 +physics_layer_0/collision_layer = 2 +physics_layer_0/collision_mask = 9 terrain_set_0/mode = 0 terrain_set_0/terrain_0/name = "Floor" terrain_set_0/terrain_0/color = Color(0.5, 0.4375, 0.25, 1) diff --git a/assets/weapons/ranged_weapon.tscn b/assets/weapons/ranged_weapon.tscn new file mode 100644 index 0000000..7309407 --- /dev/null +++ b/assets/weapons/ranged_weapon.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://cgxn1f4p4vik6"] + +[ext_resource type="Script" uid="uid://dcenqdci4hjes" path="res://player/weapons/ranged_weapon.gd" id="1_x1kyd"] + +[node name="RangedWeapon" type="Node2D"] +script = ExtResource("1_x1kyd") diff --git a/enemies/scripts/enemy.gd b/enemies/scripts/enemy.gd new file mode 100644 index 0000000..61510e1 --- /dev/null +++ b/enemies/scripts/enemy.gd @@ -0,0 +1 @@ +extends Node diff --git a/enemies/scripts/enemy.gd.uid b/enemies/scripts/enemy.gd.uid new file mode 100644 index 0000000..3252d34 --- /dev/null +++ b/enemies/scripts/enemy.gd.uid @@ -0,0 +1 @@ +uid://btwllkb7meyrw diff --git a/enemies/test_enemy.tscn b/enemies/test_enemy.tscn new file mode 100644 index 0000000..553831d --- /dev/null +++ b/enemies/test_enemy.tscn @@ -0,0 +1,70 @@ +[gd_scene load_steps=10 format=3 uid="uid://cait7d0k1kmsq"] + +[ext_resource type="Texture2D" uid="uid://gan2vkax8nwy" path="res://assets/sprites/enemies/slimes_blue.png" id="1_lcl3f"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_2bghh"] +radius = 14.0357 + +[sub_resource type="AtlasTexture" id="AtlasTexture_8ifd5"] +atlas = ExtResource("1_lcl3f") +region = Rect2(0, 0, 46, 33) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bkvxi"] +atlas = ExtResource("1_lcl3f") +region = Rect2(46, 0, 46, 33) + +[sub_resource type="AtlasTexture" id="AtlasTexture_if8y3"] +atlas = ExtResource("1_lcl3f") +region = Rect2(92, 0, 46, 33) + +[sub_resource type="AtlasTexture" id="AtlasTexture_evp1i"] +atlas = ExtResource("1_lcl3f") +region = Rect2(138, 0, 46, 33) + +[sub_resource type="AtlasTexture" id="AtlasTexture_38q4i"] +atlas = ExtResource("1_lcl3f") +region = Rect2(184, 0, 46, 33) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8o875"] +atlas = ExtResource("1_lcl3f") +region = Rect2(230, 0, 46, 33) + +[sub_resource type="SpriteFrames" id="SpriteFrames_ciurg"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_8ifd5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bkvxi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_if8y3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_evp1i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_38q4i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8o875") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}] + +[node name="TestEnemy" type="CharacterBody2D" groups=["enemies"]] +collision_layer = 8 +collision_mask = 7 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0, 1) +shape = SubResource("CircleShape2D_2bghh") + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] +sprite_frames = SubResource("SpriteFrames_ciurg") +animation = &"idle" +autoplay = "idle" +frame_progress = 0.531099 diff --git a/map/Map.tscn b/map/Map.tscn index b4071fe..e98271a 100644 --- a/map/Map.tscn +++ b/map/Map.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=4 format=3 uid="uid://cfkusqucsap26"] +[gd_scene load_steps=5 format=3 uid="uid://cfkusqucsap26"] [ext_resource type="Script" uid="uid://begwu0icmrxyw" path="res://map/map.gd" id="1_l804v"] [ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="2_3nv2f"] +[ext_resource type="PackedScene" uid="uid://cait7d0k1kmsq" path="res://enemies/test_enemy.tscn" id="4_raxr4"] [ext_resource type="PackedScene" uid="uid://bo5aw2cad3akl" path="res://player/player.tscn" id="5_3nv2f"] [node name="Map" type="Node2D"] @@ -17,3 +18,7 @@ tile_set = ExtResource("2_3nv2f") tile_set = ExtResource("2_3nv2f") [node name="Player" parent="." instance=ExtResource("5_3nv2f")] +position = Vector2(20, 20) + +[node name="TestEnemy" parent="." instance=ExtResource("4_raxr4")] +position = Vector2(87, 72) diff --git a/player/modifiers/fire_rate_additive.gd b/player/modifiers/fire_rate_additive.gd index 9ffdbd8..aa62830 100644 --- a/player/modifiers/fire_rate_additive.gd +++ b/player/modifiers/fire_rate_additive.gd @@ -8,6 +8,6 @@ func _init(): description = "Increases fire rate by %0.1f shots per second" % fire_rate_bonus modifier_type = ModifierType.ADDITIVE -func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void: +func apply_stats_modification(final_stats: Dictionary, _base_stats: Dictionary) -> void: if final_stats.has("fire_rate"): final_stats.fire_rate += fire_rate_bonus \ No newline at end of file diff --git a/player/modifiers/fire_rate_multiplicative.gd b/player/modifiers/fire_rate_multiplicative.gd index 9a7867f..6b9fcf8 100644 --- a/player/modifiers/fire_rate_multiplicative.gd +++ b/player/modifiers/fire_rate_multiplicative.gd @@ -8,6 +8,6 @@ func _init(): description = "Increases fire rate by %d%%" % ((fire_rate_multiplier - 1.0) * 100) modifier_type = ModifierType.MULTIPLICATIVE -func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void: +func apply_stats_modification(final_stats: Dictionary, _base_stats: Dictionary) -> void: if final_stats.has("fire_rate"): final_stats.fire_rate *= fire_rate_multiplier \ No newline at end of file diff --git a/player/player.tscn b/player/player.tscn index 65945b8..4d7e767 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=82 format=3 uid="uid://bo5aw2cad3akl"] +[gd_scene load_steps=83 format=3 uid="uid://bo5aw2cad3akl"] [ext_resource type="Script" uid="uid://bq038uo4cm6nv" path="res://player/scripts/player.gd" id="1_oul6g"] [ext_resource type="Texture2D" uid="uid://dqgq2c1h6yk3k" path="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png" id="2_yllr7"] @@ -13,6 +13,7 @@ [ext_resource type="Texture2D" uid="uid://538sc3bsdell" path="res://assets/sprites/characters/pink/Pink_Monster_Throw_4.png" id="11_bjvpn"] [ext_resource type="Texture2D" uid="uid://efnfh4mf0ia2" path="res://assets/sprites/characters/pink/Pink_Monster_Walk_6.png" id="12_s7qer"] [ext_resource type="Texture2D" uid="uid://cyfq0x0h2qeof" path="res://assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png" id="13_g4c7l"] +[ext_resource type="PackedScene" uid="uid://cgxn1f4p4vik6" path="res://assets/weapons/ranged_weapon.tscn" id="14_kb6p2"] [sub_resource type="CircleShape2D" id="CircleShape2D_rkbax"] @@ -541,7 +542,7 @@ animations = [{ "speed": 5.0 }] -[node name="Player" type="CharacterBody2D"] +[node name="Player" type="CharacterBody2D" groups=["friendly"]] collision_mask = 14 script = ExtResource("1_oul6g") @@ -551,8 +552,11 @@ shape = SubResource("CircleShape2D_rkbax") [node name="PlayerSprite" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_qjt2w") -animation = &"attack_2" -frame_progress = 0.752485 +animation = &"idle" +autoplay = "idle" +frame_progress = 0.749332 [node name="Camera2D" type="Camera2D" parent="."] zoom = Vector2(2, 2) + +[node name="RangedWeapon" parent="." instance=ExtResource("14_kb6p2")] diff --git a/player/scripts/player.gd b/player/scripts/player.gd index ab1dff0..e893280 100644 --- a/player/scripts/player.gd +++ b/player/scripts/player.gd @@ -1,9 +1,12 @@ extends CharacterBody2D @export var speed = 200 -@export var weapon: RangedWeapon @export var special_ability: Ability -@export var movement: PlayerMovement + +var weapon: RangedWeapon + +var movement: PlayerMovement +var combat: PlayerCombat # Last direction for idle state var last_direction = Vector2.DOWN @@ -11,19 +14,16 @@ var last_direction = Vector2.DOWN @onready var animated_sprite = $PlayerSprite func _ready(): - weapon = RangedWeapon.new() - Log.pr("Weapon", weapon) + weapon = $RangedWeapon + combat = PlayerCombat.new() - # Initialize the movement resource with references - if movement: - movement.player = self - movement.animated_sprite = animated_sprite - movement.last_direction = Vector2.DOWN # Default direction - else: - # Create a new resource instance if none was assigned in the editor - movement = PlayerMovement.new() - movement.player = self - movement.animated_sprite = animated_sprite + movement = PlayerMovement.new() + movement.player = self + movement.animated_sprite = animated_sprite + movement.speed = speed + + combat.player = self + combat.animated_sprite = animated_sprite Log.pr("Adding projectile size additive modifier") weapon.add_modifier(ProjectileSizeAdditive.new()) @@ -37,11 +37,15 @@ func _ready(): # Size is now 1.5 * 1.5 = 2.25 # Add another additive size modifier (+0.7 or 70% increase) - Log.pr("Adding another projectile size additive modifier", 0.7) + Log.pr("Adding another projectile size additive modifier", 2) var another_size_mod = ProjectileSizeAdditive.new() another_size_mod.size_increase = 0.7 weapon.add_modifier(another_size_mod) Log.pr(weapon.stats.get_stat("projectile_size")) + weapon.add_modifier(FireRateAdditive.new()) + + func _physics_process(delta): movement.process(delta) + combat.process(delta) diff --git a/player/scripts/player_combat.gd b/player/scripts/player_combat.gd new file mode 100644 index 0000000..21a25ae --- /dev/null +++ b/player/scripts/player_combat.gd @@ -0,0 +1,30 @@ +extends Resource +class_name PlayerCombat + +var player: CharacterBody2D +var animated_sprite: AnimatedSprite2D + +func process(_delta): + # Get mouse position in global coordinates + var mouse_position = player.get_global_mouse_position() + + # Get player position (assuming this script is attached to the player) + var player_position = player.global_position + + # Calculate direction vector from player to mouse + var direction = mouse_position - player_position + + # You can normalize this vector if you want a unit vector (length of 1) + # This is useful if you only care about direction, not distance + var normalized_direction = direction.normalized() + + if Input.is_action_pressed("fire"): + player.weapon.fire(normalized_direction) + # Update animation + #update_animation() + +func update_animation(): + Log.pr(animated_sprite.animation) + if animated_sprite.animation != "throw": + Log.pr('Throwing animation!') + animated_sprite.play("throw") diff --git a/player/scripts/player_combat.gd.uid b/player/scripts/player_combat.gd.uid new file mode 100644 index 0000000..1d95b1c --- /dev/null +++ b/player/scripts/player_combat.gd.uid @@ -0,0 +1 @@ +uid://cx4ugb3hbh8t1 diff --git a/player/scripts/player_movement.gd b/player/scripts/player_movement.gd index dea2c9e..b1d4f24 100644 --- a/player/scripts/player_movement.gd +++ b/player/scripts/player_movement.gd @@ -4,7 +4,7 @@ class_name PlayerMovement var player: CharacterBody2D var animated_sprite: AnimatedSprite2D -var speed: float = 300.0 +var speed: float var last_direction: Vector2 = Vector2.ZERO func process(_delta): @@ -36,6 +36,9 @@ func process(_delta): func update_animation(direction): var anim_name = "idle" # Default animation + + if animated_sprite.animation == "throw": + return # Don't change animation if throwing if direction == Vector2.ZERO: # Character is idle diff --git a/player/weapons/projectile.gd b/player/weapons/projectile.gd new file mode 100644 index 0000000..62e9956 --- /dev/null +++ b/player/weapons/projectile.gd @@ -0,0 +1,110 @@ +class_name Projectile extends Area2D + +signal on_hit(projectile, target) +signal on_spawned(projectile) +signal on_destroyed(projectile) + +@export var speed: float = 500.0 +@export var damage: float = 10.0 +@export var lifetime: float = 5.0 +@export var direction: Vector2 = Vector2.RIGHT +@export var is_friendly: bool = true + +# Modifier-related properties +var pierce_count: int = 0 +var has_explosive_impact: bool = true +var explosion_projectile_count: int = 2 +var explosion_projectile_damage_mult: float = 0.5 +var explosion_projectile_speed: float = 300.0 +var explosion_spread_angle: float = 360.0 # Full circle by default +# References +var source_weapon: RangedWeapon # Reference to the weapon that fired this +var lifetime_timer: Timer +# Add a variable to track the entity that triggered the explosion +var ignore_target = null + +func _ready(): + lifetime_timer = Timer.new() + add_child(lifetime_timer) + lifetime_timer.one_shot = true + lifetime_timer.wait_time = lifetime + lifetime_timer.connect("timeout", _on_lifetime_timeout) + lifetime_timer.start() + + emit_signal("on_spawned", self) + connect("body_entered", _on_body_entered) + +func _physics_process(delta): + position += direction * speed * delta + +func _on_body_entered(body): + # Check if this is a body we should ignore + if body == ignore_target: + return + + if body.is_in_group("enemies") and is_friendly: + Log.pr("Hit enemy: ", body.name) + # Deal damage to enemy + if body.has_method("take_damage"): + body.take_damage(damage) + + # Emit signal for modifiers to react to + emit_signal("on_hit", self, body) + + # Handle piercing + if pierce_count > 0: + pierce_count -= 1 + else: + # Handle explosive impact + if has_explosive_impact: + # Store the target that triggered the explosion + ignore_target = body + _trigger_explosion() + + # Destroy the projectile + destroy() + +func _trigger_explosion(): + # Create the explosion VFX + # var explosion = preload("res://scenes/explosion_effect.tscn").instantiate() + # explosion.global_position = global_position + # get_tree().root.add_child(explosion) + # Spawn the additional projectiles + if explosion_projectile_count > 0: + _spawn_explosion_projectiles() + +func _spawn_explosion_projectiles(): + # Calculate even angle distribution + var angle_step = explosion_spread_angle / explosion_projectile_count + var start_angle = - explosion_spread_angle / 2 + + for i in range(explosion_projectile_count): + # Create a new projectile + var new_proj = duplicate() + new_proj.global_position = global_position + + # Calculate new direction based on spread + var random_angle = randf_range(0, 2 * PI) + var new_dir = Vector2.RIGHT.rotated(random_angle) + + # Set properties for the new projectile + new_proj.direction = new_dir + new_proj.damage = damage * explosion_projectile_damage_mult + new_proj.speed = explosion_projectile_speed + + # Clear explosive properties so we don't get infinite loops + new_proj.has_explosive_impact = true + new_proj.explosion_projectile_count = 1 + + # Pass the ignore_target to the new projectiles + new_proj.ignore_target = ignore_target + + # Add to scene tree + get_tree().root.add_child(new_proj) + +func destroy(): + emit_signal("on_destroyed", self) + queue_free() + +func _on_lifetime_timeout(): + destroy() diff --git a/player/weapons/projectile.gd.uid b/player/weapons/projectile.gd.uid new file mode 100644 index 0000000..a9e306d --- /dev/null +++ b/player/weapons/projectile.gd.uid @@ -0,0 +1 @@ +uid://d5tiwy16ivu6 diff --git a/player/weapons/ranged_weapon.gd b/player/weapons/ranged_weapon.gd index 167bf68..99ab71a 100644 --- a/player/weapons/ranged_weapon.gd +++ b/player/weapons/ranged_weapon.gd @@ -7,10 +7,12 @@ signal projectile_spawned(projectile) # Base stats - will be modified by modifiers var base_stats = { "damage": 10.0, - "fire_rate": 2.0, # Shots per second + "fire_rate": 2.0, "projectile_speed": 500.0, "projectile_size": 1.0, "projectile_lifetime": 5.0, + "projectile_quantity": 1, + "projectile_spread": 33, "max_pierce": 0 } @@ -24,14 +26,15 @@ func _init() -> void: Log.pr(stats) add_child(stats) -func _ready(): - # Connect to stats updated signal - stats.connect("stats_updated", _on_stats_updated) - # Setup fire timer fire_timer = Timer.new() add_child(fire_timer) fire_timer.one_shot = true + + projectile_scene = preload("res://assets/projectiles/basic_projectile.tscn") + +func _ready(): + stats.connect("stats_updated", _on_stats_updated) fire_timer.connect("timeout", _on_fire_timer_timeout) # Initial update @@ -47,30 +50,55 @@ func fire(direction: Vector2): fire_timer.start(1.0 / stats.get_stat("fire_rate")) func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2): - var projectile = projectile_scene.instantiate() - projectile.global_position = spawn_position - projectile.direction = spawn_direction + # Get projectile quantity and spread from stats + var quantity = stats.get_stat("projectile_quantity") + var spread_angle = stats.get_stat("projectile_spread") - # Apply stats to projectile - projectile.speed = stats.get_stat("projectile_speed") - projectile.damage = stats.get_stat("damage") - projectile.lifetime = stats.get_stat("projectile_lifetime") - projectile.pierce_count = stats.get_stat("max_pierce") - projectile.source_weapon = self + # Calculate the angle between each projectile + var angle_step = 0.0 + if quantity > 1 and spread_angle > 0: + angle_step = spread_angle / (quantity - 1) - # Apply size (scale) - var size = stats.get_stat("projectile_size") - projectile.scale = Vector2(size, size) + # Calculate starting angle (to center the spread) + var start_angle = - spread_angle / 2 - # Allow modifiers to directly modify the projectile - for modifier in stats.modifiers: - modifier.modify_projectile(projectile) - - get_tree().root.add_child(projectile) - projectile.emit_signal("on_spawned", projectile) - emit_signal("projectile_spawned", projectile) + # Spawn each projectile + for i in range(quantity): + var projectile = projectile_scene.instantiate() + projectile.global_position = spawn_position + + # Calculate the direction with spread + var direction = spawn_direction + if quantity > 1: + var current_angle = start_angle + (i * angle_step) + direction = spawn_direction.rotated(deg_to_rad(current_angle)) + + projectile.direction = direction + + # Apply stats to projectile + projectile.speed = stats.get_stat("projectile_speed") + projectile.damage = stats.get_stat("damage") + projectile.lifetime = stats.get_stat("projectile_lifetime") + projectile.source_weapon = self + + # Set base size + var size = stats.get_stat("projectile_size") + projectile.scale = Vector2(size, size) + + # Allow modifiers to directly modify the projectile + for modifier in stats.modifiers: + modifier.modify_projectile(projectile) + + # Add to scene tree + if get_tree() and get_tree().get_root(): + get_tree().get_root().add_child(projectile) + + # Emit the spawn signal + if projectile.has_signal("on_spawned"): + projectile.emit_signal("on_spawned", projectile) func add_modifier(modifier: Modifier): + Log.pr("Adding modifier: ", modifier) stats.add_modifier(modifier) func remove_modifier(modifier_id: String): diff --git a/project.godot b/project.godot index 7a567a2..fe612eb 100644 --- a/project.godot +++ b/project.godot @@ -21,6 +21,7 @@ RNG="*res://utility/RngUtility.gd" Global="*res://utility/Globals.gd" SceneSelector="*res://utility/SceneSelector.gd" MapBuilder="*res://utility/MapBuilder.gd" +DebugMenu="*res://addons/debug_menu/debug_menu.tscn" [display] @@ -30,7 +31,7 @@ window/stretch/scale_mode="integer" [editor_plugins] -enabled=PackedStringArray("res://addons/log/plugin.cfg") +enabled=PackedStringArray("res://addons/debug_menu/plugin.cfg", "res://addons/log/plugin.cfg") [input] @@ -54,6 +55,11 @@ move_right={ "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,"location":0,"echo":false,"script":null) ] } +fire={ +"deadzone": 0.2, +"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":1,"position":Vector2(115, 24),"global_position":Vector2(129, 97),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} [layer_names] @@ -61,6 +67,7 @@ move_right={ 2d_physics/layer_2="Water" 2d_physics/layer_3="Objects" 2d_physics/layer_4="Enemies" +2d_physics/layer_5="Projectiles" [rendering]