feat: add post-processing pass and video settings menu

Enable glow/bloom, color adjustments, and MSAA+FXAA across all three
arenas so emissive ship/station accents actually bleed light and edges
read cleanly. Expose the player-facing knobs (anti-aliasing mode, glow
intensity, brightness) through a new Settings screen off the main menu,
persisted via a VideoSettings autoload that scales each arena's own
tuned Environment values on load rather than overwriting them.
This commit is contained in:
Josh Creek
2026-08-03 19:53:56 +01:00
parent 171cd4a840
commit 3fdf270aa9
10 changed files with 323 additions and 1 deletions
+6 -1
View File
@@ -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"
+16
View File
@@ -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")
+16
View File
@@ -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
+16
View File
@@ -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")
+9
View File
@@ -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"]
+118
View File
@@ -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"]
+3
View File
@@ -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:
+4
View File
@@ -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)
+70
View File
@@ -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()
+65
View File
@@ -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