Feat/5 adding settings fundamentals and ui improvements #9

Merged
Dotts merged 9 commits from feat/5-adding-settings-fundamentals-and-ui-improvements into main 2026-06-05 23:03:52 -07:00
20 changed files with 632 additions and 47 deletions
+2 -3
View File
@@ -28,9 +28,8 @@ func _ready() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
var sens := sensitivity var is_ads = camera and camera.fov < params.base_fov - 5.0
if params: var sens = SettingsManager.ads_sensitivity if is_ads else SettingsManager.mouse_sensitivity
sens = params.mouse_sensitivity
# Yaw: rotate the player (parent) # Yaw: rotate the player (parent)
get_parent().rotate_y(-event.relative.x * sens) get_parent().rotate_y(-event.relative.x * sens)
# Pitch: rotate this pivot # Pitch: rotate this pivot
+30 -9
View File
@@ -12,7 +12,9 @@ var _grapple_icon: TextureRect
var _dash_icon: TextureRect var _dash_icon: TextureRect
var _grapple_label: Label var _grapple_label: Label
var _dash_label: Label var _dash_label: Label
var _fps_label: Label
var _player: CharacterBody3D var _player: CharacterBody3D
var _debug_ui_panel: PanelContainer
func _ready() -> void: func _ready() -> void:
@@ -400,12 +402,23 @@ func _build_hud() -> void:
crosshair.add_child(line) crosshair.add_child(line)
# ── Info panel background ───────────────────────────────────────────── # ── Info panel background ─────────────────────────────────────────────
var panel := PanelContainer.new() _fps_label = Label.new()
panel.name = "InfoPanel" _fps_label.name = "FPSLabel"
panel.offset_left = 12 _fps_label.text = "FPS: 0"
panel.offset_top = 12 _fps_label.add_theme_font_size_override("font_size", 24)
panel.offset_right = 400 _fps_label.add_theme_color_override("font_color", Color(0.9, 0.9, 0.2))
panel.offset_bottom = 120 _fps_label.add_theme_color_override("font_outline_color", Color(0, 0, 0))
_fps_label.add_theme_constant_override("outline_size", 4)
_fps_label.set_anchors_preset(Control.PRESET_TOP_LEFT)
_fps_label.position = Vector2(12, 12)
canvas.add_child(_fps_label)
_debug_ui_panel = PanelContainer.new()
_debug_ui_panel.name = "InfoPanel"
_debug_ui_panel.offset_left = 12
_debug_ui_panel.offset_top = 50
_debug_ui_panel.offset_right = 400
_debug_ui_panel.offset_bottom = 160
var panel_style := StyleBoxFlat.new() var panel_style := StyleBoxFlat.new()
panel_style.bg_color = Color(0, 0, 0, 0.55) panel_style.bg_color = Color(0, 0, 0, 0.55)
panel_style.corner_radius_top_left = 8 panel_style.corner_radius_top_left = 8
@@ -416,12 +429,12 @@ func _build_hud() -> void:
panel_style.content_margin_top = 8 panel_style.content_margin_top = 8
panel_style.content_margin_right = 12 panel_style.content_margin_right = 12
panel_style.content_margin_bottom = 8 panel_style.content_margin_bottom = 8
panel.add_theme_stylebox_override("panel", panel_style) _debug_ui_panel.add_theme_stylebox_override("panel", panel_style)
canvas.add_child(panel) canvas.add_child(_debug_ui_panel)
var vbox := VBoxContainer.new() var vbox := VBoxContainer.new()
vbox.name = "InfoVBox" vbox.name = "InfoVBox"
panel.add_child(vbox) _debug_ui_panel.add_child(vbox)
_speed_label = Label.new() _speed_label = Label.new()
_speed_label.name = "SpeedLabel" _speed_label.name = "SpeedLabel"
@@ -560,6 +573,14 @@ func _process(_delta: float) -> void:
if not _player or not is_instance_valid(_player): if not _player or not is_instance_valid(_player):
return return
if _debug_ui_panel:
_debug_ui_panel.visible = SettingsManager.show_debug_ui
if _fps_label:
_fps_label.visible = SettingsManager.show_fps
if _fps_label.visible:
_fps_label.text = "FPS: %d" % Engine.get_frames_per_second()
var vel: Vector3 = _player.velocity var vel: Vector3 = _player.velocity
var hspeed := Vector2(vel.x, vel.z).length() var hspeed := Vector2(vel.x, vel.z).length()
var total_speed := vel.length() var total_speed := vel.length()
+119 -6
View File
@@ -23,6 +23,31 @@ var default_bindings = {
} }
var current_bindings = {} var current_bindings = {}
var show_debug_ui: bool = false
var mouse_sensitivity: float = 0.002
var ads_sensitivity: float = 0.001
# Video Settings
var display_mode: int = DisplayServer.WINDOW_MODE_WINDOWED
var resolution_index: int = 2 # Default to 1920x1080
var fps_cap: int = 0
var show_fps: bool = false
# Audio Settings
var vol_master: float = 100.0
var vol_sfx: float = 100.0
var vol_music: float = 100.0
var vol_wind: float = 100.0
var resolutions = [
Vector2i(640, 480), # 480p
Vector2i(1280, 720), # 720p
Vector2i(1920, 1080), # 1080p
Vector2i(2560, 1080), # UW-1080p
Vector2i(2560, 1440), # 1440p
Vector2i(3440, 1440), # UW-1440p
Vector2i(3840, 2160) # 4K
]
func _ready() -> void: func _ready() -> void:
# Initialize defaults # Initialize defaults
@@ -34,6 +59,34 @@ func _ready() -> void:
# Apply to InputMap # Apply to InputMap
_apply_all_bindings() _apply_all_bindings()
# Apply Video Settings
apply_video_settings()
# Setup & Apply Audio Settings
_setup_audio_buses()
apply_audio_settings()
func _setup_audio_buses() -> void:
if AudioServer.bus_count < 4:
# Assuming bus 0 is Master
if AudioServer.get_bus_name(0) != "Master":
AudioServer.set_bus_name(0, "Master")
# SFX
if AudioServer.bus_count <= 1:
AudioServer.add_bus(1)
AudioServer.set_bus_name(1, "SFX")
# Music
if AudioServer.bus_count <= 2:
AudioServer.add_bus(2)
AudioServer.set_bus_name(2, "Music")
# Wind
if AudioServer.bus_count <= 3:
AudioServer.add_bus(3)
AudioServer.set_bus_name(3, "Wind")
func _apply_all_bindings() -> void: func _apply_all_bindings() -> void:
for action in current_bindings: for action in current_bindings:
if not InputMap.has_action(action): if not InputMap.has_action(action):
@@ -74,7 +127,18 @@ func reset_to_defaults() -> void:
func _save_settings() -> void: func _save_settings() -> void:
var save_data = { var save_data = {
"bindings": current_bindings "bindings": current_bindings,
"show_debug_ui": show_debug_ui,
"mouse_sensitivity": mouse_sensitivity,
"ads_sensitivity": ads_sensitivity,
"display_mode": display_mode,
"resolution_index": resolution_index,
"fps_cap": fps_cap,
"show_fps": show_fps,
"vol_master": vol_master,
"vol_sfx": vol_sfx,
"vol_music": vol_music,
"vol_wind": vol_wind
} }
var json_string = JSON.stringify(save_data) var json_string = JSON.stringify(save_data)
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE) var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
@@ -93,8 +157,57 @@ func _load_settings() -> void:
var error = json.parse(json_string) var error = json.parse(json_string)
if error == OK: if error == OK:
var data = json.get_data() var data = json.get_data()
if data is Dictionary and data.has("bindings"): if data is Dictionary:
var loaded_bindings = data["bindings"] if data.has("bindings"):
for action in loaded_bindings: var loaded_bindings = data["bindings"]
if current_bindings.has(action): for action in loaded_bindings:
current_bindings[action] = loaded_bindings[action] if current_bindings.has(action):
current_bindings[action] = loaded_bindings[action]
if data.has("show_debug_ui"):
show_debug_ui = data["show_debug_ui"]
if data.has("mouse_sensitivity"):
mouse_sensitivity = data["mouse_sensitivity"]
if data.has("ads_sensitivity"):
ads_sensitivity = data["ads_sensitivity"]
if data.has("display_mode"): display_mode = data["display_mode"]
if data.has("resolution_index"): resolution_index = data["resolution_index"]
if data.has("fps_cap"): fps_cap = data["fps_cap"]
if data.has("show_fps"): show_fps = data["show_fps"]
if data.has("vol_master"): vol_master = data["vol_master"]
if data.has("vol_sfx"): vol_sfx = data["vol_sfx"]
if data.has("vol_music"): vol_music = data["vol_music"]
if data.has("vol_wind"): vol_wind = data["vol_wind"]
func apply_audio_settings() -> void:
_set_bus_volume("Master", vol_master)
_set_bus_volume("SFX", vol_sfx)
_set_bus_volume("Music", vol_music)
_set_bus_volume("Wind", vol_wind)
func _set_bus_volume(bus_name: String, vol_100: float) -> void:
var bus_idx = AudioServer.get_bus_index(bus_name)
if bus_idx >= 0:
var linear = clampf(vol_100 / 100.0, 0.0001, 1.0)
if vol_100 <= 0.1:
AudioServer.set_bus_mute(bus_idx, true)
else:
AudioServer.set_bus_mute(bus_idx, false)
AudioServer.set_bus_volume_db(bus_idx, linear_to_db(linear))
func apply_video_settings() -> void:
if display_mode == DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
elif display_mode == DisplayServer.WINDOW_MODE_FULLSCREEN:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if resolution_index >= 0 and resolution_index < resolutions.size():
DisplayServer.window_set_size(resolutions[resolution_index])
# Center window on primary screen
var screen = DisplayServer.window_get_current_screen()
var screen_size = DisplayServer.screen_get_size(screen)
var window_size = DisplayServer.window_get_size()
# Simple centering heuristic
DisplayServer.window_set_position(Vector2i((screen_size - window_size) / 2.0))
Engine.max_fps = fps_cap
+14 -2
View File
@@ -74,55 +74,67 @@ func _ready() -> void:
func _setup_audio() -> void: func _setup_audio() -> void:
footstep_player = AudioStreamPlayer.new() footstep_player = AudioStreamPlayer.new()
footstep_player.bus = "SFX"
footstep_player.stream = load("res://assets/sounds/footstep.wav") footstep_player.stream = load("res://assets/sounds/footstep.wav")
add_child(footstep_player) add_child(footstep_player)
jump_player = AudioStreamPlayer.new() jump_player = AudioStreamPlayer.new()
jump_player.bus = "SFX"
jump_player.stream = load("res://assets/sounds/jump.wav") jump_player.stream = load("res://assets/sounds/jump.wav")
add_child(jump_player) add_child(jump_player)
double_jump_player = AudioStreamPlayer.new() double_jump_player = AudioStreamPlayer.new()
double_jump_player.bus = "SFX"
jump_player.bus = "SFX"
double_jump_player.stream = load("res://assets/sounds/double_jump.wav") double_jump_player.stream = load("res://assets/sounds/double_jump.wav")
add_child(double_jump_player) add_child(double_jump_player)
dash_player = AudioStreamPlayer.new() dash_player = AudioStreamPlayer.new()
dash_player.bus = "SFX"
dash_player.stream = load("res://assets/sounds/dash.wav") dash_player.stream = load("res://assets/sounds/dash.wav")
dash_player.volume_db = -5.0 dash_player.volume_db = -5.0
add_child(dash_player) add_child(dash_player)
hit_player = AudioStreamPlayer.new() hit_player = AudioStreamPlayer.new()
hit_player.bus = "SFX"
hit_player.stream = load("res://assets/sounds/hit_confirm.wav") hit_player.stream = load("res://assets/sounds/hit_confirm.wav")
hit_player.max_polyphony = 8 hit_player.max_polyphony = 8
add_child(hit_player) add_child(hit_player)
slide_player = AudioStreamPlayer.new() slide_player = AudioStreamPlayer.new()
slide_player.bus = "SFX"
slide_player.stream = load("res://assets/sounds/slide.wav") slide_player.stream = load("res://assets/sounds/slide.wav")
slide_player.volume_db = -10.0 slide_player.volume_db = -10.0
add_child(slide_player) add_child(slide_player)
wallrun_player = AudioStreamPlayer.new() wallrun_player = AudioStreamPlayer.new()
wallrun_player.bus = "SFX"
wallrun_player.stream = load("res://assets/sounds/wallrun.wav") wallrun_player.stream = load("res://assets/sounds/wallrun.wav")
wallrun_player.volume_db = -10.0 wallrun_player.volume_db = -10.0
add_child(wallrun_player) add_child(wallrun_player)
wind_player = AudioStreamPlayer.new() wind_player = AudioStreamPlayer.new()
wind_player.bus = "Wind"
wind_player.stream = load("res://assets/sounds/wind.wav") wind_player.stream = load("res://assets/sounds/wind.wav")
wind_player.volume_db = -80.0 # Start silent wind_player.volume_db = -80.0 # Start silent
add_child(wind_player) add_child(wind_player)
wind_player.play() wind_player.play()
grapple_shoot_player = AudioStreamPlayer.new() grapple_shoot_player = AudioStreamPlayer.new()
grapple_shoot_player.bus = "SFX"
grapple_shoot_player.stream = load("res://assets/sounds/dash.wav") grapple_shoot_player.stream = load("res://assets/sounds/dash.wav")
grapple_shoot_player.volume_db = -5.0 grapple_shoot_player.volume_db = -5.0
grapple_shoot_player.pitch_scale = 1.5 grapple_shoot_player.pitch_scale = 1.5
add_child(grapple_shoot_player) add_child(grapple_shoot_player)
grapple_latch_player = AudioStreamPlayer.new() grapple_latch_player = AudioStreamPlayer.new()
grapple_latch_player.bus = "SFX"
grapple_latch_player.stream = load("res://assets/sounds/hit_confirm.wav") grapple_latch_player.stream = load("res://assets/sounds/hit_confirm.wav")
grapple_latch_player.pitch_scale = 0.8 grapple_latch_player.pitch_scale = 0.8
add_child(grapple_latch_player) add_child(grapple_latch_player)
grapple_swing_player = AudioStreamPlayer.new() grapple_swing_player = AudioStreamPlayer.new()
grapple_swing_player.bus = "SFX"
grapple_swing_player.stream = load("res://assets/sounds/wind.wav") grapple_swing_player.stream = load("res://assets/sounds/wind.wav")
grapple_swing_player.pitch_scale = 1.5 grapple_swing_player.pitch_scale = 1.5
grapple_swing_player.volume_db = -80.0 grapple_swing_player.volume_db = -80.0
@@ -173,10 +185,10 @@ func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
var label = load("res://ui/floating_damage_text.gd").new() var label = load("res://ui/floating_damage_text.gd").new()
label.text = str(round(amount)) label.text = str(round(amount))
label.add_theme_font_size_override("font_size", 32) label.add_theme_font_size_override("font_size", 24)
label.add_theme_color_override("font_color", Color(1.0, 0.6, 0.1)) label.add_theme_color_override("font_color", Color(1.0, 0.6, 0.1))
label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.8)) label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.8))
label.add_theme_constant_override("outline_size", 6) label.add_theme_constant_override("outline_size", 4)
# Small random offset in 3D space so multiple pellets don't perfectly overlap # Small random offset in 3D space so multiple pellets don't perfectly overlap
var offset = Vector3(randf_range(-0.4, 0.4), randf_range(-0.4, 0.4), randf_range(-0.4, 0.4)) var offset = Vector3(randf_range(-0.4, 0.4), randf_range(-0.4, 0.4), randf_range(-0.4, 0.4))
+403 -11
View File
@@ -23,6 +23,24 @@ var editing_index: int = -1
# Settings UI # Settings UI
var settings_editor: Control var settings_editor: Control
var settings_back_btn: Button var settings_back_btn: Button
var _debug_ui_checkbox: CheckButton
var _mouse_sens_slider: HSlider
var _mouse_sens_input: LineEdit
var _ads_sens_slider: HSlider
var _ads_sens_input: LineEdit
var _display_mode_opt: OptionButton
var _resolution_opt: OptionButton
var _fps_slider: HSlider
var _fps_input: LineEdit
var _show_fps_checkbox: CheckButton
var _vol_master_slider: HSlider
var _vol_master_input: LineEdit
var _vol_sfx_slider: HSlider
var _vol_sfx_input: LineEdit
var _vol_music_slider: HSlider
var _vol_music_input: LineEdit
var _vol_wind_slider: HSlider
var _vol_wind_input: LineEdit
var keybind_vbox: VBoxContainer var keybind_vbox: VBoxContainer
var rebind_overlay: ColorRect var rebind_overlay: ColorRect
var rebind_label: Label var rebind_label: Label
@@ -58,13 +76,11 @@ func _input(event: InputEvent) -> void:
func _pause() -> void: func _pause() -> void:
visible = true visible = true
get_tree().paused = true
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
_show_main_menu() _show_main_menu()
func _resume() -> void: func _resume() -> void:
visible = false visible = false
get_tree().paused = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _respawn() -> void: func _respawn() -> void:
@@ -87,7 +103,7 @@ func _build_ui() -> void:
main_center.add_child(main_vbox) main_center.add_child(main_vbox)
var title = Label.new() var title = Label.new()
title.text = "PAUSED" title.text = "MENU"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 48) title.add_theme_font_size_override("font_size", 48)
main_vbox.add_child(title) main_vbox.add_child(title)
@@ -209,19 +225,263 @@ func _build_ui() -> void:
gen_label.text = "Settings coming soon..." gen_label.text = "Settings coming soon..."
general_tab.add_child(gen_label) general_tab.add_child(gen_label)
var spacer_gen = Control.new()
spacer_gen.size_flags_vertical = Control.SIZE_EXPAND_FILL
general_tab.add_child(spacer_gen)
var reset_gen_btn = Button.new()
reset_gen_btn.text = "Reset to Defaults"
reset_gen_btn.pressed.connect(func():
pass # No settings yet
)
general_tab.add_child(reset_gen_btn)
# Video Tab
var video_tab = VBoxContainer.new()
video_tab.name = "Video"
video_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(video_tab)
var mode_hbox = HBoxContainer.new()
video_tab.add_child(mode_hbox)
var mode_lbl = Label.new()
mode_lbl.text = "Display Mode"
mode_lbl.custom_minimum_size = Vector2(150, 0)
mode_hbox.add_child(mode_lbl)
_display_mode_opt = OptionButton.new()
_display_mode_opt.add_item("Windowed", DisplayServer.WINDOW_MODE_WINDOWED)
_display_mode_opt.add_item("Borderless Fullscreen", DisplayServer.WINDOW_MODE_FULLSCREEN)
_display_mode_opt.add_item("Exclusive Fullscreen", DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
_display_mode_opt.item_selected.connect(func(index: int):
SettingsManager.display_mode = _display_mode_opt.get_item_id(index)
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
mode_hbox.add_child(_display_mode_opt)
var res_hbox = HBoxContainer.new()
video_tab.add_child(res_hbox)
var res_lbl = Label.new()
res_lbl.text = "Resolution"
res_lbl.custom_minimum_size = Vector2(150, 0)
res_hbox.add_child(res_lbl)
_resolution_opt = OptionButton.new()
var res_names = ["640x480 (480p)", "1280x720 (720p)", "1920x1080 (1080p)", "2560x1080 (UW-1080p)", "2560x1440 (1440p)", "3440x1440 (UW-1440p)", "3840x2160 (4K)"]
for i in range(res_names.size()):
_resolution_opt.add_item(res_names[i], i)
_resolution_opt.item_selected.connect(func(index: int):
SettingsManager.resolution_index = index
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
res_hbox.add_child(_resolution_opt)
var fps_hbox = HBoxContainer.new()
video_tab.add_child(fps_hbox)
var fps_lbl = Label.new()
fps_lbl.text = "FPS Cap"
fps_lbl.custom_minimum_size = Vector2(150, 0)
fps_hbox.add_child(fps_lbl)
_fps_slider = HSlider.new()
_fps_slider.min_value = 30.0
_fps_slider.max_value = 361.0
_fps_slider.step = 1.0
_fps_slider.custom_minimum_size = Vector2(200, 0)
_fps_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
fps_hbox.add_child(_fps_slider)
_fps_input = LineEdit.new()
_fps_input.custom_minimum_size = Vector2(80, 0)
fps_hbox.add_child(_fps_input)
_fps_slider.value_changed.connect(func(value: float):
if value > 360.0:
_fps_input.text = "Uncapped"
SettingsManager.fps_cap = 0
else:
_fps_input.text = str(int(value))
SettingsManager.fps_cap = int(value)
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
_fps_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_int()
if val > 360 or val <= 0:
_fps_slider.value = 361.0
_fps_input.text = "Uncapped"
SettingsManager.fps_cap = 0
else:
val = max(30, val)
_fps_slider.value = float(val)
_fps_input.text = str(val)
SettingsManager.fps_cap = val
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
var spacer_vid = Control.new()
spacer_vid.size_flags_vertical = Control.SIZE_EXPAND_FILL
video_tab.add_child(spacer_vid)
var reset_vid_btn = Button.new()
reset_vid_btn.text = "Reset to Defaults"
reset_vid_btn.pressed.connect(func():
SettingsManager.display_mode = DisplayServer.WINDOW_MODE_WINDOWED
SettingsManager.resolution_index = 2
SettingsManager.fps_cap = 0
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
_populate_video_ui()
)
video_tab.add_child(reset_vid_btn)
# Audio Tab
var audio_tab = VBoxContainer.new()
audio_tab.name = "Audio"
audio_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(audio_tab)
# Master
var master_arr = _build_vol_control(audio_tab, "Master Volume")
_vol_master_slider = master_arr[0]
_vol_master_input = master_arr[1]
_vol_master_slider.value_changed.connect(func(v): _on_vol_changed("vol_master", v, _vol_master_input))
_vol_master_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_master", t, _vol_master_slider, _vol_master_input))
# SFX
var sfx_arr = _build_vol_control(audio_tab, "Sound Effects")
_vol_sfx_slider = sfx_arr[0]
_vol_sfx_input = sfx_arr[1]
_vol_sfx_slider.value_changed.connect(func(v): _on_vol_changed("vol_sfx", v, _vol_sfx_input))
_vol_sfx_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_sfx", t, _vol_sfx_slider, _vol_sfx_input))
# Music
var music_arr = _build_vol_control(audio_tab, "Music")
_vol_music_slider = music_arr[0]
_vol_music_input = music_arr[1]
_vol_music_slider.value_changed.connect(func(v): _on_vol_changed("vol_music", v, _vol_music_input))
_vol_music_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_music", t, _vol_music_slider, _vol_music_input))
# Wind
var wind_arr = _build_vol_control(audio_tab, "Wind")
_vol_wind_slider = wind_arr[0]
_vol_wind_input = wind_arr[1]
_vol_wind_slider.value_changed.connect(func(v): _on_vol_changed("vol_wind", v, _vol_wind_input))
_vol_wind_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_wind", t, _vol_wind_slider, _vol_wind_input))
var spacer_aud = Control.new()
spacer_aud.size_flags_vertical = Control.SIZE_EXPAND_FILL
audio_tab.add_child(spacer_aud)
var reset_aud_btn = Button.new()
reset_aud_btn.text = "Reset to Defaults"
reset_aud_btn.pressed.connect(func():
SettingsManager.vol_master = 100.0
SettingsManager.vol_sfx = 100.0
SettingsManager.vol_music = 100.0
SettingsManager.vol_wind = 100.0
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
_populate_audio_ui()
)
audio_tab.add_child(reset_aud_btn)
# Mouse Tab
var mouse_tab = VBoxContainer.new()
mouse_tab.name = "Mouse"
mouse_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(mouse_tab)
var sens_hbox = HBoxContainer.new()
mouse_tab.add_child(sens_hbox)
var sens_title = Label.new()
sens_title.text = "Sensitivity"
sens_title.custom_minimum_size = Vector2(150, 0)
sens_hbox.add_child(sens_title)
_mouse_sens_slider = HSlider.new()
_mouse_sens_slider.min_value = 1.0
_mouse_sens_slider.max_value = 100.0
_mouse_sens_slider.step = 1.0
_mouse_sens_slider.value = SettingsManager.mouse_sensitivity * 10000.0
_mouse_sens_slider.custom_minimum_size = Vector2(200, 0)
_mouse_sens_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
sens_hbox.add_child(_mouse_sens_slider)
_mouse_sens_input = LineEdit.new()
_mouse_sens_input.text = str(round(SettingsManager.mouse_sensitivity * 10000.0))
_mouse_sens_input.custom_minimum_size = Vector2(60, 0)
sens_hbox.add_child(_mouse_sens_input)
_mouse_sens_slider.value_changed.connect(func(value: float):
_mouse_sens_input.text = str(round(value))
SettingsManager.mouse_sensitivity = value / 10000.0
SettingsManager._save_settings()
)
_mouse_sens_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_float()
val = clampf(val, 1.0, 100.0)
_mouse_sens_slider.value = val
_mouse_sens_input.text = str(round(val))
SettingsManager.mouse_sensitivity = val / 10000.0
SettingsManager._save_settings()
)
var ads_sens_hbox = HBoxContainer.new()
mouse_tab.add_child(ads_sens_hbox)
var ads_sens_title = Label.new()
ads_sens_title.text = "ADS Sensitivity"
ads_sens_title.custom_minimum_size = Vector2(150, 0)
ads_sens_hbox.add_child(ads_sens_title)
_ads_sens_slider = HSlider.new()
_ads_sens_slider.min_value = 1.0
_ads_sens_slider.max_value = 100.0
_ads_sens_slider.step = 1.0
_ads_sens_slider.value = SettingsManager.ads_sensitivity * 10000.0
_ads_sens_slider.custom_minimum_size = Vector2(200, 0)
_ads_sens_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
ads_sens_hbox.add_child(_ads_sens_slider)
_ads_sens_input = LineEdit.new()
_ads_sens_input.text = str(round(SettingsManager.ads_sensitivity * 10000.0))
_ads_sens_input.custom_minimum_size = Vector2(60, 0)
ads_sens_hbox.add_child(_ads_sens_input)
_ads_sens_slider.value_changed.connect(func(value: float):
_ads_sens_input.text = str(round(value))
SettingsManager.ads_sensitivity = value / 10000.0
SettingsManager._save_settings()
)
_ads_sens_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_float()
val = clampf(val, 1.0, 100.0)
_ads_sens_slider.value = val
_ads_sens_input.text = str(round(val))
SettingsManager.ads_sensitivity = val / 10000.0
SettingsManager._save_settings()
)
var spacer_mouse = Control.new()
spacer_mouse.size_flags_vertical = Control.SIZE_EXPAND_FILL
mouse_tab.add_child(spacer_mouse)
var reset_mouse_btn = Button.new()
reset_mouse_btn.text = "Reset to Defaults"
reset_mouse_btn.pressed.connect(func():
_mouse_sens_slider.value = 20.0
_ads_sens_slider.value = 10.0
SettingsManager.mouse_sensitivity = 0.002
SettingsManager.ads_sensitivity = 0.001
SettingsManager._save_settings()
)
mouse_tab.add_child(reset_mouse_btn)
# Keybindings Tab # Keybindings Tab
var key_tab = VBoxContainer.new() var key_tab = VBoxContainer.new()
key_tab.name = "Keybindings" key_tab.name = "Keybindings"
tab_container.add_child(key_tab) tab_container.add_child(key_tab)
var reset_btn = Button.new()
reset_btn.text = "Reset to Defaults"
reset_btn.pressed.connect(func():
SettingsManager.reset_to_defaults()
_populate_keybindings()
)
key_tab.add_child(reset_btn)
var scroll = ScrollContainer.new() var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
key_tab.add_child(scroll) key_tab.add_child(scroll)
@@ -230,6 +490,58 @@ func _build_ui() -> void:
keybind_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL keybind_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.add_child(keybind_vbox) scroll.add_child(keybind_vbox)
var reset_key_btn = Button.new()
reset_key_btn.text = "Reset to Defaults"
reset_key_btn.pressed.connect(func():
SettingsManager.reset_to_defaults()
_populate_keybindings()
)
key_tab.add_child(reset_key_btn)
# Advanced Tab
var advanced_tab = VBoxContainer.new()
advanced_tab.name = "Advanced"
advanced_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(advanced_tab)
var debug_label = Label.new()
debug_label.text = "--- Debug ---"
debug_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
advanced_tab.add_child(debug_label)
_debug_ui_checkbox = CheckButton.new()
_debug_ui_checkbox.text = "Show Debug UI (Speed, State, Buffs)"
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
_debug_ui_checkbox.toggled.connect(func(toggled_on: bool):
SettingsManager.show_debug_ui = toggled_on
SettingsManager._save_settings()
)
advanced_tab.add_child(_debug_ui_checkbox)
_show_fps_checkbox = CheckButton.new()
_show_fps_checkbox.text = "Show FPS Counter"
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
_show_fps_checkbox.toggled.connect(func(toggled_on: bool):
SettingsManager.show_fps = toggled_on
SettingsManager._save_settings()
)
advanced_tab.add_child(_show_fps_checkbox)
var spacer_adv = Control.new()
spacer_adv.size_flags_vertical = Control.SIZE_EXPAND_FILL
advanced_tab.add_child(spacer_adv)
var reset_adv_btn = Button.new()
reset_adv_btn.text = "Reset to Defaults"
reset_adv_btn.pressed.connect(func():
_debug_ui_checkbox.button_pressed = false
SettingsManager.show_debug_ui = false
_show_fps_checkbox.button_pressed = false
SettingsManager.show_fps = false
SettingsManager._save_settings()
)
advanced_tab.add_child(reset_adv_btn)
# Rebind Overlay # Rebind Overlay
rebind_overlay = ColorRect.new() rebind_overlay = ColorRect.new()
rebind_overlay.color = Color(0, 0, 0, 0.85) rebind_overlay.color = Color(0, 0, 0, 0.85)
@@ -253,6 +565,43 @@ func _build_ui() -> void:
quit_dialog.dialog_text = "Are you sure you want to close the game?" quit_dialog.dialog_text = "Are you sure you want to close the game?"
add_child(quit_dialog) add_child(quit_dialog)
func _build_vol_control(parent: Node, label_name: String) -> Array:
var hbox = HBoxContainer.new()
parent.add_child(hbox)
var lbl = Label.new()
lbl.text = label_name
lbl.custom_minimum_size = Vector2(150, 0)
hbox.add_child(lbl)
var slider = HSlider.new()
slider.min_value = 0.0
slider.max_value = 100.0
slider.step = 1.0
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
slider.custom_minimum_size = Vector2(150, 0)
hbox.add_child(slider)
var input = LineEdit.new()
input.custom_minimum_size = Vector2(60, 0)
hbox.add_child(input)
return [slider, input]
func _on_vol_changed(setting_name: String, value: float, input_node: LineEdit) -> void:
SettingsManager.set(setting_name, value)
input_node.text = str(value)
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
func _on_vol_text_changed(setting_name: String, text: String, slider_node: HSlider, input_node: LineEdit) -> void:
var val = clampf(text.to_float(), 0.0, 100.0)
SettingsManager.set(setting_name, val)
slider_node.value = val
input_node.text = str(val)
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
func _create_label(text: String) -> Label: func _create_label(text: String) -> Label:
var l = Label.new() var l = Label.new()
l.text = text l.text = text
@@ -281,11 +630,54 @@ func _show_loadouts() -> void:
edit_panel.visible = false edit_panel.visible = false
_populate_loadout_list() _populate_loadout_list()
func _populate_video_ui() -> void:
if _display_mode_opt:
for i in range(_display_mode_opt.item_count):
if _display_mode_opt.get_item_id(i) == SettingsManager.display_mode:
_display_mode_opt.select(i)
break
if _resolution_opt:
_resolution_opt.select(SettingsManager.resolution_index)
if _fps_slider and _fps_input:
if SettingsManager.fps_cap == 0:
_fps_slider.value = 361.0
_fps_input.text = "Uncapped"
else:
_fps_slider.value = float(SettingsManager.fps_cap)
_fps_input.text = str(SettingsManager.fps_cap)
func _populate_audio_ui() -> void:
if _vol_master_slider: _vol_master_slider.value = SettingsManager.vol_master
if _vol_master_input: _vol_master_input.text = str(SettingsManager.vol_master)
if _vol_sfx_slider: _vol_sfx_slider.value = SettingsManager.vol_sfx
if _vol_sfx_input: _vol_sfx_input.text = str(SettingsManager.vol_sfx)
if _vol_music_slider: _vol_music_slider.value = SettingsManager.vol_music
if _vol_music_input: _vol_music_input.text = str(SettingsManager.vol_music)
if _vol_wind_slider: _vol_wind_slider.value = SettingsManager.vol_wind
if _vol_wind_input: _vol_wind_input.text = str(SettingsManager.vol_wind)
func _show_settings() -> void: func _show_settings() -> void:
main_vbox.visible = false main_vbox.visible = false
loadout_editor.visible = false loadout_editor.visible = false
settings_editor.visible = true settings_editor.visible = true
_populate_keybindings() _populate_keybindings()
_populate_video_ui()
_populate_audio_ui()
if _debug_ui_checkbox:
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
if _show_fps_checkbox:
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
if _mouse_sens_slider:
var display_val = round(SettingsManager.mouse_sensitivity * 10000.0)
_mouse_sens_slider.value = display_val
_mouse_sens_input.text = str(display_val)
var ads_display_val = round(SettingsManager.ads_sensitivity * 10000.0)
_ads_sens_slider.value = ads_display_val
_ads_sens_input.text = str(ads_display_val)
_mouse_sens_slider.value = display_val
if _mouse_sens_input:
_mouse_sens_input.text = str(display_val)
func _populate_keybindings() -> void: func _populate_keybindings() -> void:
for c in keybind_vbox.get_children(): for c in keybind_vbox.get_children():
+2
View File
@@ -70,11 +70,13 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/ak47_fire.wav") fire_sound.stream = load("res://assets/sounds/ak47_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -5.0 fire_sound.volume_db = -5.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # generic reload reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # generic reload
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
+12 -4
View File
@@ -62,11 +62,13 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/awp_fire.wav") fire_sound.stream = load("res://assets/sounds/awp_fire.wav")
fire_sound.volume_db = 0.0 fire_sound.volume_db = 0.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing for now reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing for now
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
@@ -74,16 +76,22 @@ func _process(delta: float) -> void:
super._process(delta) super._process(delta)
if camera: if camera:
if Input.is_action_just_pressed("fire_alt"):
is_ads = not is_ads
target_fov = ads_fov if is_ads else default_fov
camera.fov = lerpf(camera.fov, target_fov, 15.0 * delta) camera.fov = lerpf(camera.fov, target_fov, 15.0 * delta)
# Move model slightly based on ADS # Move model slightly based on ADS
var target_pos = Vector3(0.0, -0.15, -0.6) if is_ads else Vector3(0.3, -0.3, -0.8) var target_pos = Vector3(0.0, -0.15, -0.6) if is_ads else Vector3(0.3, -0.3, -0.8)
position = position.lerp(target_pos, 15.0 * delta) position = position.lerp(target_pos, 15.0 * delta)
func _input(event: InputEvent) -> void:
super._input(event)
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
return
if event.is_action_pressed("fire_alt"):
is_ads = not is_ads
target_fov = ads_fov if is_ads else default_fov
func _start_reload() -> void: func _start_reload() -> void:
super._start_reload() super._start_reload()
is_ads = false # Drop out of ADS on reload is_ads = false # Drop out of ADS on reload
+1 -1
View File
@@ -146,7 +146,7 @@ func _shoot_hitscan() -> void:
if result.collider.has_method("take_damage"): if result.collider.has_method("take_damage"):
result.collider.take_damage(damage, result.position, player) result.collider.take_damage(damage, result.position, player)
else: elif result.collider is StaticBody3D or result.collider is CSGShape3D:
# Not a target, spawn a bullet hole # Not a target, spawn a bullet hole
ImpactSpawner.spawn(get_tree(), "bullet", result.position, result.normal, 0.1) ImpactSpawner.spawn(get_tree(), "bullet", result.position, result.normal, 0.1)
+2
View File
@@ -73,12 +73,14 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/dmr_fire.wav") fire_sound.stream = load("res://assets/sounds/dmr_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -3.0 fire_sound.volume_db = -3.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
+3 -1
View File
@@ -66,12 +66,14 @@ func _build_model() -> void:
# Audio Players # Audio Players
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/shotgun_fire.wav") fire_sound.stream = load("res://assets/sounds/shotgun_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -20.0 fire_sound.volume_db = -20.0
add_child(fire_sound) add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
add_child(reload_sound) add_child(reload_sound)
@@ -198,7 +200,7 @@ func _shoot_hitscan() -> void:
if result.collider.has_method("take_damage"): if result.collider.has_method("take_damage"):
result.collider.take_damage(damage, result.position, player) result.collider.take_damage(damage, result.position, player)
else: elif result.collider is StaticBody3D or result.collider is CSGShape3D:
ImpactSpawner.spawn(get_tree(), "bullet", result.position, result.normal, 0.08) ImpactSpawner.spawn(get_tree(), "bullet", result.position, result.normal, 0.08)
# Spawn cosmetic tracer # Spawn cosmetic tracer
+15 -1
View File
@@ -5,6 +5,7 @@ var explosion_radius: float = 2.5
var explosion_knockback: float = 20.0 var explosion_knockback: float = 20.0
var can_self_damage: bool = false var can_self_damage: bool = false
var falloff_curve: float = 1.0 # 1.0 is linear, 2.0 is quadratic (steep drop) var falloff_curve: float = 1.0 # 1.0 is linear, 2.0 is quadratic (steep drop)
var explosion_pitch: float = 1.0 # Audio pitch for explosion
var flight_sound: AudioStreamPlayer3D var flight_sound: AudioStreamPlayer3D
var smoke_sys: GPUParticles3D var smoke_sys: GPUParticles3D
var jet_sys: GPUParticles3D var jet_sys: GPUParticles3D
@@ -59,6 +60,7 @@ func destroy() -> void:
func _setup_effects() -> void: func _setup_effects() -> void:
# Audio # Audio
flight_sound = AudioStreamPlayer3D.new() flight_sound = AudioStreamPlayer3D.new()
flight_sound.bus = "SFX"
flight_sound.stream = load("res://assets/sounds/rocket_flight.wav") flight_sound.stream = load("res://assets/sounds/rocket_flight.wav")
flight_sound.autoplay = true flight_sound.autoplay = true
if flight_sound.stream is AudioStreamWAV: if flight_sound.stream is AudioStreamWAV:
@@ -133,7 +135,8 @@ func _setup_effects() -> void:
func _on_hit(result: Dictionary) -> void: func _on_hit(result: Dictionary) -> void:
if not result.collider.has_method("take_damage"): if not result.collider.has_method("take_damage"):
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5) if result.collider is StaticBody3D or result.collider is CSGShape3D:
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
_explode(result.position) _explode(result.position)
destroy() destroy()
@@ -219,3 +222,14 @@ func _explode(pos: Vector3) -> void:
tween.tween_property(mat, "albedo_color:a", 0.0, 0.3) tween.tween_property(mat, "albedo_color:a", 0.0, 0.3)
tween.parallel().tween_property(visual, "scale", Vector3(1.1, 1.1, 1.1), 0.3) tween.parallel().tween_property(visual, "scale", Vector3(1.1, 1.1, 1.1), 0.3)
tween.tween_callback(visual.queue_free) tween.tween_callback(visual.queue_free)
# Audio Effect
var audio = AudioStreamPlayer3D.new()
audio.stream = load("res://assets/sounds/shotgun_fire.wav") # using shotgun fire for a crash explosion
audio.bus = "SFX"
audio.pitch_scale = explosion_pitch
audio.max_distance = 150.0
get_tree().current_scene.add_child(audio)
audio.global_position = pos
audio.play()
audio.finished.connect(audio.queue_free)
+1
View File
@@ -19,6 +19,7 @@ func _start_homing_effects() -> void:
# Audio # Audio
flight_sound = AudioStreamPlayer3D.new() flight_sound = AudioStreamPlayer3D.new()
flight_sound.bus = "SFX"
flight_sound.stream = load("res://assets/sounds/swarm_flight.wav") flight_sound.stream = load("res://assets/sounds/swarm_flight.wav")
flight_sound.autoplay = true flight_sound.autoplay = true
if flight_sound.stream is AudioStreamWAV: if flight_sound.stream is AudioStreamWAV:
+2
View File
@@ -61,11 +61,13 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/m4_fire.wav") fire_sound.stream = load("res://assets/sounds/m4_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -8.0 fire_sound.volume_db = -8.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
+4 -1
View File
@@ -35,10 +35,12 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/mortar_fire.wav") fire_sound.stream = load("res://assets/sounds/mortar_fire.wav")
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
@@ -61,7 +63,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.logical_source = origin proj.logical_source = origin
proj.velocity = fire_dir * projectile_speed proj.velocity = fire_dir * projectile_speed
if player: if player:
proj.velocity += fire_dir * player.velocity.dot(fire_dir) proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir))
proj.damage = base_damage proj.damage = base_damage
proj.min_damage = min_damage proj.min_damage = min_damage
proj.falloff_start = falloff_start proj.falloff_start = falloff_start
@@ -81,5 +83,6 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.falloff_curve = 2.0 # Steep dropoff: "Should be an instant kill in closest 50%" proj.falloff_curve = 2.0 # Steep dropoff: "Should be an instant kill in closest 50%"
# (0-50% = dist<6. 6/12 = 0.5. 1.0 - 0.5^2 = 0.75 multiplier. 200 * 0.75 = 150 damage -> 1 shot kill) # (0-50% = dist<6. 6/12 = 0.5. 1.0 - 0.5^2 = 0.75 multiplier. 200 * 0.75 = 150 damage -> 1 shot kill)
proj.can_self_damage = true # Can self damage proj.can_self_damage = true # Can self damage
proj.explosion_pitch = 0.4 # Deepest explosion
get_tree().current_scene.add_child(proj) get_tree().current_scene.add_child(proj)
+2
View File
@@ -70,11 +70,13 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/mp7_fire.wav") fire_sound.stream = load("res://assets/sounds/mp7_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -10.0 fire_sound.volume_db = -10.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
+3 -1
View File
@@ -76,12 +76,14 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/nailgun_fire.wav") fire_sound.stream = load("res://assets/sounds/nailgun_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -5.0 fire_sound.volume_db = -5.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
@@ -115,7 +117,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.velocity = fire_dir * projectile_speed proj.velocity = fire_dir * projectile_speed
if player: if player:
proj.velocity += fire_dir * player.velocity.dot(fire_dir) proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir))
proj.damage = base_damage proj.damage = base_damage
proj.min_damage = min_damage proj.min_damage = min_damage
proj.falloff_start = falloff_start proj.falloff_start = falloff_start
+5 -3
View File
@@ -58,12 +58,14 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/plasma_fire.wav") fire_sound.stream = load("res://assets/sounds/plasma_fire.wav")
fire_sound.position = muzzle_flash.position fire_sound.position = muzzle_flash.position
fire_sound.volume_db = -5.0 fire_sound.volume_db = -5.0
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav")
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
@@ -86,8 +88,6 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
var light = OmniLight3D.new() var light = OmniLight3D.new()
light.light_color = Color(0.2, 0.6, 1.0) light.light_color = Color(0.2, 0.6, 1.0)
light.light_energy = 2.0
light.omni_range = 4.0
proj.add_child(light) proj.add_child(light)
proj.position = muzzle_flash.global_position if muzzle_flash else origin proj.position = muzzle_flash.global_position if muzzle_flash else origin
@@ -95,13 +95,15 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.velocity = fire_dir * projectile_speed proj.velocity = fire_dir * projectile_speed
if player: if player:
proj.velocity += fire_dir * player.velocity.dot(fire_dir) proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir))
proj.damage = base_damage proj.damage = base_damage
proj.min_damage = min_damage proj.min_damage = min_damage
proj.falloff_start = falloff_start proj.falloff_start = falloff_start
proj.max_range = max_range proj.max_range = max_range
proj.drop_gravity = projectile_gravity proj.drop_gravity = projectile_gravity
proj.owner_player = player proj.owner_player = player
light.light_energy = 2.0
light.omni_range = 4.0
proj.impact_type = "plasma" proj.impact_type = "plasma"
# Spawn in the world # Spawn in the world
+1 -1
View File
@@ -59,7 +59,7 @@ func _on_hit(result: Dictionary) -> void:
if result.collider.has_method("take_damage"): if result.collider.has_method("take_damage"):
result.collider.take_damage(final_damage, result.position, owner_player) result.collider.take_damage(final_damage, result.position, owner_player)
else: elif result.collider is StaticBody3D or result.collider is CSGShape3D:
var size = 0.4 if impact_type == "plasma" else 0.15 var size = 0.4 if impact_type == "plasma" else 0.15
ImpactSpawner.spawn(get_tree(), impact_type, result.position, result.normal, size) ImpactSpawner.spawn(get_tree(), impact_type, result.position, result.normal, size)
+4 -1
View File
@@ -55,10 +55,12 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/rocket_fire.wav") fire_sound.stream = load("res://assets/sounds/rocket_fire.wav")
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
@@ -89,7 +91,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.logical_source = origin proj.logical_source = origin
proj.velocity = fire_dir * projectile_speed proj.velocity = fire_dir * projectile_speed
if player: if player:
proj.velocity += fire_dir * player.velocity.dot(fire_dir) proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir))
proj.damage = base_damage proj.damage = base_damage
proj.min_damage = min_damage proj.min_damage = min_damage
proj.falloff_start = falloff_start proj.falloff_start = falloff_start
@@ -101,5 +103,6 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.explosion_radius = 4.0 # Moderate AoE proj.explosion_radius = 4.0 # Moderate AoE
proj.explosion_knockback = 45.0 # Strong knockback! proj.explosion_knockback = 45.0 # Strong knockback!
proj.can_self_damage = false # As requested proj.can_self_damage = false # As requested
proj.explosion_pitch = 0.7 # Fairly deep
get_tree().current_scene.add_child(proj) get_tree().current_scene.add_child(proj)
+7 -2
View File
@@ -52,16 +52,21 @@ func _build_model() -> void:
# Audio # Audio
fire_sound = AudioStreamPlayer3D.new() fire_sound = AudioStreamPlayer3D.new()
fire_sound.bus = "SFX"
fire_sound.stream = load("res://assets/sounds/swarm_fire.wav") fire_sound.stream = load("res://assets/sounds/swarm_fire.wav")
model_root.add_child(fire_sound) model_root.add_child(fire_sound)
reload_sound = AudioStreamPlayer3D.new() reload_sound = AudioStreamPlayer3D.new()
reload_sound.bus = "SFX"
reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing reload_sound.stream = load("res://assets/sounds/shotgun_reload.wav") # Reusing
model_root.add_child(reload_sound) model_root.add_child(reload_sound)
func _process(delta: float) -> void: func _process(delta: float) -> void:
super._process(delta) super._process(delta)
if not visible:
return
if camera: if camera:
is_targeting = Input.is_action_pressed("fire_alt") is_targeting = Input.is_action_pressed("fire_alt")
targeting_laser.visible = is_targeting targeting_laser.visible = is_targeting
@@ -163,14 +168,13 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
# Logic # Logic
proj.weapon_source = self proj.weapon_source = self
proj.turn_speed = 6.5 # Slower turn radius so they sweep nicely proj.turn_speed = 6.5 # Slower turn radius so they sweep nicely
proj.position = origin # Start at origin to spread outward around camera proj.position = origin # Start at origin to spread outward around camera
proj.logical_source = origin proj.logical_source = origin
# Initial pop-up speed # Initial pop-up speed
proj.velocity = fire_dir * 5.0 proj.velocity = fire_dir * 5.0
if player: if player:
proj.velocity += fire_dir * player.velocity.dot(fire_dir) proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir))
proj.initial_aim_dir = _current_aim_dir proj.initial_aim_dir = _current_aim_dir
proj.cruise_speed = 65.0 proj.cruise_speed = 65.0
@@ -187,5 +191,6 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
proj.explosion_radius = 1.5 # Tiny AoE effect proj.explosion_radius = 1.5 # Tiny AoE effect
proj.explosion_knockback = 5.0 proj.explosion_knockback = 5.0
proj.can_self_damage = false proj.can_self_damage = false
proj.explosion_pitch = 1.2 # Less deep but still explosion
get_tree().current_scene.add_child(proj) get_tree().current_scene.add_child(proj)