diff --git a/Game/project.godot b/Game/project.godot index d41adac3..987ab58a 100644 --- a/Game/project.godot +++ b/Game/project.godot @@ -116,7 +116,12 @@ roll_right={ common/physics_interpolation=true 3d/physics_engine="Jolt Physics" +[rendering] + +anti_aliasing/quality/msaa_3d=2 +anti_aliasing/quality/screen_space_aa=1 + [autoload] - GameSettings="*res://scripts/game_settings.gd" +VideoSettings="*res://scripts/video_settings.gd" diff --git a/Game/scenes/arena_01.tscn b/Game/scenes/arena_01.tscn index f68a2d82..1e699c7c 100644 --- a/Game/scenes/arena_01.tscn +++ b/Game/scenes/arena_01.tscn @@ -36,6 +36,22 @@ sky = SubResource("Sky_space") ambient_light_source = 2 ambient_light_color = Color(0.55, 0.6, 0.75, 1) ambient_light_energy = 0.4 +glow_enabled = true +glow_levels/1 = 0.0 +glow_levels/2 = 0.4 +glow_levels/3 = 0.8 +glow_levels/4 = 0.6 +glow_levels/5 = 0.2 +glow_intensity = 0.9 +glow_strength = 1.2 +glow_bloom = 0.05 +glow_blend_mode = 1 +glow_hdr_threshold = 1.0 +glow_hdr_scale = 2.0 +adjustment_enabled = true +adjustment_brightness = 1.0 +adjustment_contrast = 1.05 +adjustment_saturation = 1.08 [node name="Arena" type="Node3D"] script = ExtResource("1_iywne") diff --git a/Game/scenes/arena_02.tscn b/Game/scenes/arena_02.tscn index b28f5554..ebed875d 100644 --- a/Game/scenes/arena_02.tscn +++ b/Game/scenes/arena_02.tscn @@ -21,6 +21,22 @@ sky = SubResource("Sky_nebula") ambient_light_source = 2 ambient_light_color = Color(0.65, 0.35, 0.75, 1) ambient_light_energy = 0.45 +glow_enabled = true +glow_levels/1 = 0.0 +glow_levels/2 = 0.4 +glow_levels/3 = 0.8 +glow_levels/4 = 0.6 +glow_levels/5 = 0.2 +glow_intensity = 0.8 +glow_strength = 1.0 +glow_bloom = 0.03 +glow_blend_mode = 1 +glow_hdr_threshold = 1.1 +glow_hdr_scale = 2.0 +adjustment_enabled = true +adjustment_brightness = 1.0 +adjustment_contrast = 1.05 +adjustment_saturation = 1.08 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dust"] transparency = 1 diff --git a/Game/scenes/arena_03.tscn b/Game/scenes/arena_03.tscn index 8a18b89a..b0e9f112 100644 --- a/Game/scenes/arena_03.tscn +++ b/Game/scenes/arena_03.tscn @@ -17,6 +17,22 @@ sky = SubResource("Sky_asteroid") ambient_light_source = 2 ambient_light_color = Color(0.6, 0.45, 0.3, 1) ambient_light_energy = 0.3 +glow_enabled = true +glow_levels/1 = 0.0 +glow_levels/2 = 0.4 +glow_levels/3 = 0.8 +glow_levels/4 = 0.6 +glow_levels/5 = 0.2 +glow_intensity = 0.9 +glow_strength = 1.2 +glow_bloom = 0.05 +glow_blend_mode = 1 +glow_hdr_threshold = 1.0 +glow_hdr_scale = 2.0 +adjustment_enabled = true +adjustment_brightness = 1.0 +adjustment_contrast = 1.05 +adjustment_saturation = 1.08 [node name="Arena" type="Node3D"] script = ExtResource("1_iywne") diff --git a/Game/scenes/main_menu.tscn b/Game/scenes/main_menu.tscn index 708a5044..ce45505f 100644 --- a/Game/scenes/main_menu.tscn +++ b/Game/scenes/main_menu.tscn @@ -100,6 +100,14 @@ custom_minimum_size = Vector2(0, 56) layout_mode = 2 text = "Play Match" +[node name="SettingsSeparator" type="HSeparator" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 + +[node name="SettingsButton" type="Button" parent="CenterContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 56) +layout_mode = 2 +text = "Settings" + [node name="DevSection" type="VBoxContainer" parent="CenterContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 @@ -174,4 +182,5 @@ text = "Watch Match" [connection signal="pressed" from="CenterContainer/VBoxContainer/FreePlayButton" to="." method="_on_free_play_pressed"] [connection signal="pressed" from="CenterContainer/VBoxContainer/MatchButton" to="." method="_on_match_pressed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/SettingsButton" to="." method="_on_settings_pressed"] [connection signal="pressed" from="CenterContainer/VBoxContainer/DevSection/SpectateButton" to="." method="_on_spectate_pressed"] diff --git a/Game/scenes/settings.tscn b/Game/scenes/settings.tscn new file mode 100644 index 00000000..5fecb6b5 --- /dev/null +++ b/Game/scenes/settings.tscn @@ -0,0 +1,118 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/settings_menu.gd" id="1_settings"] + +[node name="SettingsMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_settings") + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +custom_minimum_size = Vector2(420, 0) +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="TitleLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 36 +text = "Settings" +horizontal_alignment = 1 + +[node name="TitleSpacer" type="Control" parent="CenterContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 14) +layout_mode = 2 + +[node name="AARow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="AALabel" type="Label" parent="CenterContainer/VBoxContainer/AARow"] +custom_minimum_size = Vector2(110, 0) +layout_mode = 2 +text = "Anti-aliasing" + +[node name="AADropdown" type="OptionButton" parent="CenterContainer/VBoxContainer/AARow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GlowRow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="GlowLabel" type="Label" parent="CenterContainer/VBoxContainer/GlowRow"] +custom_minimum_size = Vector2(110, 0) +layout_mode = 2 +text = "Glow intensity" + +[node name="GlowSlider" type="HSlider" parent="CenterContainer/VBoxContainer/GlowRow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 24) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +min_value = 0.0 +max_value = 1.5 +step = 0.05 +value = 1.0 + +[node name="GlowValueLabel" type="Label" parent="CenterContainer/VBoxContainer/GlowRow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(48, 0) +layout_mode = 2 +text = "100%" +horizontal_alignment = 2 + +[node name="BrightnessRow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="BrightnessLabel" type="Label" parent="CenterContainer/VBoxContainer/BrightnessRow"] +custom_minimum_size = Vector2(110, 0) +layout_mode = 2 +text = "Brightness" + +[node name="BrightnessSlider" type="HSlider" parent="CenterContainer/VBoxContainer/BrightnessRow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 24) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +min_value = 0.7 +max_value = 1.3 +step = 0.02 +value = 1.0 + +[node name="BrightnessValueLabel" type="Label" parent="CenterContainer/VBoxContainer/BrightnessRow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(48, 0) +layout_mode = 2 +text = "100%" +horizontal_alignment = 2 + +[node name="ButtonSpacer" type="Control" parent="CenterContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 14) +layout_mode = 2 + +[node name="BackButton" type="Button" parent="CenterContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 56) +layout_mode = 2 +text = "Back" + +[connection signal="item_selected" from="CenterContainer/VBoxContainer/AARow/AADropdown" to="." method="_on_aa_dropdown_item_selected"] +[connection signal="value_changed" from="CenterContainer/VBoxContainer/GlowRow/GlowSlider" to="." method="_on_glow_slider_value_changed"] +[connection signal="value_changed" from="CenterContainer/VBoxContainer/BrightnessRow/BrightnessSlider" to="." method="_on_brightness_slider_value_changed"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/BackButton" to="." method="_on_back_pressed"] diff --git a/Game/scripts/arena.gd b/Game/scripts/arena.gd index 9d8625de..f9d27e42 100644 --- a/Game/scripts/arena.gd +++ b/Game/scripts/arena.gd @@ -8,6 +8,9 @@ extends Node3D func _ready(): add_to_group("arena") + var world_env := get_node_or_null("WorldEnvironment") as WorldEnvironment + if world_env and world_env.environment: + VideoSettings.apply_to_environment(world_env.environment) func get_ball_spawn() -> Transform3D: diff --git a/Game/scripts/main_menu.gd b/Game/scripts/main_menu.gd index 394f495b..6e70e9f2 100644 --- a/Game/scripts/main_menu.gd +++ b/Game/scripts/main_menu.gd @@ -129,6 +129,10 @@ func _on_match_pressed() -> void: get_tree().change_scene_to_file("res://scenes/match.tscn") +func _on_settings_pressed() -> void: + get_tree().change_scene_to_file("res://scenes/settings.tscn") + + func _on_spectate_pressed() -> void: GameSettings.spectate_bot_a_path = _selected_path(bot_a_dropdown) GameSettings.spectate_bot_b_path = _selected_path(bot_b_dropdown) diff --git a/Game/scripts/settings_menu.gd b/Game/scripts/settings_menu.gd new file mode 100644 index 00000000..948c4d4d --- /dev/null +++ b/Game/scripts/settings_menu.gd @@ -0,0 +1,70 @@ +extends Control + +# Settings screen: a small set of player-facing video knobs on top of +# VideoSettings (the autoload holding + persisting them). Anti-aliasing +# applies immediately since it's a Viewport-wide setting; glow/brightness +# apply the next time an arena loads (see arena.gd), since they scale each +# arena's own tuned Environment values rather than something viewport-wide. + +const MAIN_MENU_SCENE_PATH := "res://scenes/main_menu.tscn" + +const AA_OPTIONS := [ + {"name": "Off", "mode": VideoSettings.AAMode.OFF}, + {"name": "FXAA", "mode": VideoSettings.AAMode.FXAA}, + {"name": "MSAA 4x", "mode": VideoSettings.AAMode.MSAA}, + {"name": "MSAA 4x + FXAA", "mode": VideoSettings.AAMode.MSAA_FXAA}, +] + +@onready var aa_dropdown: OptionButton = %AADropdown +@onready var glow_slider: HSlider = %GlowSlider +@onready var glow_value_label: Label = %GlowValueLabel +@onready var brightness_slider: HSlider = %BrightnessSlider +@onready var brightness_value_label: Label = %BrightnessValueLabel + + +func _ready() -> void: + aa_dropdown.clear() + var selected := 0 + for i in AA_OPTIONS.size(): + aa_dropdown.add_item(AA_OPTIONS[i]["name"]) + if AA_OPTIONS[i]["mode"] == VideoSettings.aa_mode: + selected = i + aa_dropdown.select(selected) + + glow_slider.value = VideoSettings.glow_scale + brightness_slider.value = VideoSettings.brightness + _update_glow_label() + _update_brightness_label() + + +func _update_glow_label() -> void: + glow_value_label.text = "%d%%" % roundi(glow_slider.value * 100.0) + + +func _update_brightness_label() -> void: + brightness_value_label.text = "%d%%" % roundi(brightness_slider.value * 100.0) + + +func _on_aa_dropdown_item_selected(index: int) -> void: + VideoSettings.aa_mode = AA_OPTIONS[index]["mode"] + VideoSettings.apply_aa() + + +func _on_glow_slider_value_changed(value: float) -> void: + VideoSettings.glow_scale = value + _update_glow_label() + + +func _on_brightness_slider_value_changed(value: float) -> void: + VideoSettings.brightness = value + _update_brightness_label() + + +func _on_back_pressed() -> void: + VideoSettings.save() + get_tree().change_scene_to_file(MAIN_MENU_SCENE_PATH) + + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("ui_cancel"): + _on_back_pressed() diff --git a/Game/scripts/video_settings.gd b/Game/scripts/video_settings.gd new file mode 100644 index 00000000..cacd86f3 --- /dev/null +++ b/Game/scripts/video_settings.gd @@ -0,0 +1,65 @@ +extends Node + +# Autoload: persisted player-facing video preferences (AA, glow, brightness). +# AA is a Viewport-wide setting applied immediately via apply_aa(). Glow and +# brightness instead scale each arena's own tuned Environment values (see +# arena.gd's _ready(), which calls apply_to_environment() once per arena +# load) rather than overwriting them outright, so the per-arena bloom tuning +# in arena_01/02/03.tscn survives underneath the user's preference. + +enum AAMode { OFF, FXAA, MSAA, MSAA_FXAA } + +const SETTINGS_PATH := "user://settings.cfg" + +var aa_mode: AAMode = AAMode.MSAA_FXAA +var glow_scale: float = 1.0 +var brightness: float = 1.0 + + +func _ready() -> void: + _load() + apply_aa() + + +func _load() -> void: + var cfg := ConfigFile.new() + if cfg.load(SETTINGS_PATH) != OK: + return + aa_mode = cfg.get_value("video", "aa_mode", aa_mode) as AAMode + glow_scale = cfg.get_value("video", "glow_scale", glow_scale) + brightness = cfg.get_value("video", "brightness", brightness) + + +func save() -> void: + var cfg := ConfigFile.new() + cfg.set_value("video", "aa_mode", aa_mode) + cfg.set_value("video", "glow_scale", glow_scale) + cfg.set_value("video", "brightness", brightness) + cfg.save(SETTINGS_PATH) + + +func apply_aa() -> void: + var viewport := get_tree().root + match aa_mode: + AAMode.OFF: + viewport.msaa_3d = Viewport.MSAA_DISABLED + viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED + AAMode.FXAA: + viewport.msaa_3d = Viewport.MSAA_DISABLED + viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA + AAMode.MSAA: + viewport.msaa_3d = Viewport.MSAA_4X + viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED + AAMode.MSAA_FXAA: + viewport.msaa_3d = Viewport.MSAA_4X + viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA + + +# Called once by each arena's _ready() to fold the user's glow/brightness +# preference into that arena's own baked Environment tuning. +func apply_to_environment(env: Environment) -> void: + if env == null: + return + env.glow_enabled = glow_scale > 0.0 + env.glow_intensity *= glow_scale + env.adjustment_brightness *= brightness