Feat/2 weapons foundation #6

Merged
Dotts merged 30 commits from feat/2-weapons-foundation into main 2026-06-04 22:22:16 -07:00
5 changed files with 259 additions and 47 deletions
Showing only changes of commit 02c6caa0d6 - Show all commits
+11 -40
View File
@@ -11,11 +11,19 @@ var _player: CharacterBody3D
func _ready() -> void:
build()
# Hide mouse
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
_build_materials()
_build_geometry()
_build_hud()
func build() -> void:
_build_input_map()
func _build_materials() -> void:
pass
func _build_geometry() -> void:
_build_environment()
_build_floor()
_build_walls_arena()
@@ -25,7 +33,6 @@ func build() -> void:
_build_speed_corridor()
_build_lighting()
_build_player()
_build_hud()
# ── Utility ───────────────────────────────────────────────────────────────────
@@ -56,42 +63,6 @@ func _ramp_static(pos: Vector3, size: Vector3, rot_deg: Vector3, color: Color, n
return body
# ── Input Map ─────────────────────────────────────────────────────────────────
func _build_input_map() -> void:
var bindings := {
"jump": [KEY_SPACE],
"crouch": [KEY_CTRL],
"dash": [KEY_SHIFT],
"fire": [MOUSE_BUTTON_LEFT],
"fire_alt": [MOUSE_BUTTON_RIGHT],
"reload": [KEY_R],
"interact": [KEY_E],
"move_forward": [KEY_W],
"move_back": [KEY_S],
"move_left": [KEY_A],
"move_right": [KEY_D],
"weapon_next": [MOUSE_BUTTON_WHEEL_UP],
"weapon_prev": [MOUSE_BUTTON_WHEEL_DOWN],
"weapon_1": [KEY_1],
"weapon_2": [KEY_2],
"weapon_3": [KEY_3],
}
for action in bindings:
if not InputMap.has_action(action):
InputMap.add_action(action)
InputMap.action_erase_events(action)
for code in bindings[action]:
if code in [KEY_SPACE, KEY_SHIFT, KEY_CTRL, KEY_R, KEY_F, KEY_E, KEY_W, KEY_S, KEY_A, KEY_D, KEY_1, KEY_2, KEY_3]:
var ev := InputEventKey.new()
ev.keycode = code
InputMap.action_add_event(action, ev)
else:
var ev := InputEventMouseButton.new()
ev.button_index = code
InputMap.action_add_event(action, ev)
# ── Environment ───────────────────────────────────────────────────────────────
func _build_environment() -> void:
+99
View File
@@ -0,0 +1,99 @@
extends Node
const SAVE_PATH = "user://settings.json"
var default_bindings = {
"jump": {"type": "key", "code": KEY_SPACE},
"crouch": {"type": "key", "code": KEY_CTRL},
"dash": {"type": "key", "code": KEY_SHIFT},
"fire": {"type": "mouse", "code": MOUSE_BUTTON_LEFT},
"fire_alt": {"type": "mouse", "code": MOUSE_BUTTON_RIGHT},
"reload": {"type": "key", "code": KEY_R},
"interact": {"type": "key", "code": KEY_E},
"move_forward": {"type": "key", "code": KEY_W},
"move_back": {"type": "key", "code": KEY_S},
"move_left": {"type": "key", "code": KEY_A},
"move_right": {"type": "key", "code": KEY_D},
"weapon_next": {"type": "mouse", "code": MOUSE_BUTTON_WHEEL_UP},
"weapon_prev": {"type": "mouse", "code": MOUSE_BUTTON_WHEEL_DOWN},
"weapon_1": {"type": "key", "code": KEY_1},
"weapon_2": {"type": "key", "code": KEY_2},
"weapon_3": {"type": "key", "code": KEY_3},
}
var current_bindings = {}
func _ready() -> void:
# Initialize defaults
current_bindings = default_bindings.duplicate(true)
# Load overrides
_load_settings()
# Apply to InputMap
_apply_all_bindings()
func _apply_all_bindings() -> void:
for action in current_bindings:
if not InputMap.has_action(action):
InputMap.add_action(action)
InputMap.action_erase_events(action)
var binding = current_bindings[action]
var ev: InputEvent
if binding["type"] == "key":
ev = InputEventKey.new()
ev.keycode = binding["code"]
elif binding["type"] == "mouse":
ev = InputEventMouseButton.new()
ev.button_index = binding["code"]
if ev:
InputMap.action_add_event(action, ev)
func rebind_action(action: String, event: InputEvent) -> void:
if not current_bindings.has(action):
return
if event is InputEventKey:
var code = event.physical_keycode if event.physical_keycode != 0 else event.keycode
current_bindings[action] = {"type": "key", "code": code}
elif event is InputEventMouseButton:
current_bindings[action] = {"type": "mouse", "code": event.button_index}
else:
return
_apply_all_bindings()
_save_settings()
func reset_to_defaults() -> void:
current_bindings = default_bindings.duplicate(true)
_apply_all_bindings()
_save_settings()
func _save_settings() -> void:
var save_data = {
"bindings": current_bindings
}
var json_string = JSON.stringify(save_data)
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
if file:
file.store_string(json_string)
file.close()
func _load_settings() -> void:
if FileAccess.file_exists(SAVE_PATH):
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
if file:
var json_string = file.get_as_text()
file.close()
var json = JSON.new()
var error = json.parse(json_string)
if error == OK:
var data = json.get_data()
if data is Dictionary and data.has("bindings"):
var loaded_bindings = data["bindings"]
for action in loaded_bindings:
if current_bindings.has(action):
current_bindings[action] = loaded_bindings[action]
+1
View File
@@ -0,0 +1 @@
uid://b6fe1eyas7wxv
+1
View File
@@ -17,6 +17,7 @@ config/icon="res://icon.svg"
[autoload]
SettingsManager="*res://globals/settings_manager.gd"
LoadoutManager="*res://globals/loadout_manager.gd"
PauseMenu="*res://ui/pause_menu.gd"
+147 -7
View File
@@ -4,8 +4,10 @@ var bg: ColorRect
var main_vbox: VBoxContainer
var resume_btn: Button
var loadouts_btn: Button
var settings_btn: Button
var exit_btn: Button
# Loadout UI
var loadout_editor: Control
var loadout_list_vbox: VBoxContainer
var edit_panel: PanelContainer
@@ -15,9 +17,16 @@ var primary2_opt: OptionButton
var special_opt: OptionButton
var save_loadout_btn: Button
var back_to_main_btn: Button
var editing_index: int = -1
# Settings UI
var settings_editor: Control
var settings_back_btn: Button
var keybind_vbox: VBoxContainer
var rebind_overlay: ColorRect
var rebind_label: Label
var waiting_for_input_action: String = ""
var quit_dialog: ConfirmationDialog
func _ready() -> void:
@@ -29,6 +38,17 @@ func _ready() -> void:
_connect_signals()
func _input(event: InputEvent) -> void:
if waiting_for_input_action != "":
if event is InputEventKey or event is InputEventMouseButton:
if event.is_pressed():
SettingsManager.rebind_action(waiting_for_input_action, event)
waiting_for_input_action = ""
rebind_overlay.visible = false
_populate_keybindings()
# Consume event so it doesn't trigger anything else
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("ui_cancel"):
if visible:
_resume()
@@ -52,7 +72,7 @@ func _build_ui() -> void:
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(bg)
# Main Menu
# ── Main Menu ──
var main_center = CenterContainer.new()
main_center.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(main_center)
@@ -77,12 +97,17 @@ func _build_ui() -> void:
loadouts_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(loadouts_btn)
settings_btn = Button.new()
settings_btn.text = "Settings"
settings_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(settings_btn)
exit_btn = Button.new()
exit_btn.text = "Exit Game"
exit_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(exit_btn)
# Loadout Editor Root
# ── Loadout Editor Root ──
loadout_editor = Control.new()
loadout_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(loadout_editor)
@@ -95,7 +120,6 @@ func _build_ui() -> void:
editor_hbox.add_theme_constant_override("separation", 40)
editor_center.add_child(editor_hbox)
# Loadout List
var left_vbox = VBoxContainer.new()
editor_hbox.add_child(left_vbox)
@@ -112,7 +136,6 @@ func _build_ui() -> void:
back_to_main_btn.custom_minimum_size = Vector2(0, 40)
left_vbox.add_child(back_to_main_btn)
# Edit Panel
edit_panel = PanelContainer.new()
editor_hbox.add_child(edit_panel)
@@ -147,7 +170,74 @@ func _build_ui() -> void:
save_loadout_btn.custom_minimum_size = Vector2(0, 40)
edit_vbox.add_child(save_loadout_btn)
# Quit Dialog
# ── Settings Editor Root ──
settings_editor = Control.new()
settings_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(settings_editor)
var settings_center = CenterContainer.new()
settings_center.set_anchors_preset(Control.PRESET_FULL_RECT)
settings_editor.add_child(settings_center)
var settings_vbox = VBoxContainer.new()
settings_vbox.custom_minimum_size = Vector2(600, 400)
settings_center.add_child(settings_vbox)
settings_back_btn = Button.new()
settings_back_btn.text = "Back to Menu"
settings_vbox.add_child(settings_back_btn)
var tab_container = TabContainer.new()
tab_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
settings_vbox.add_child(tab_container)
# General Tab
var general_tab = VBoxContainer.new()
general_tab.name = "General"
tab_container.add_child(general_tab)
var gen_label = Label.new()
gen_label.text = "Settings coming soon..."
general_tab.add_child(gen_label)
# Keybindings Tab
var key_tab = VBoxContainer.new()
key_tab.name = "Keybindings"
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()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
key_tab.add_child(scroll)
keybind_vbox = VBoxContainer.new()
keybind_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.add_child(keybind_vbox)
# Rebind Overlay
rebind_overlay = ColorRect.new()
rebind_overlay.color = Color(0, 0, 0, 0.85)
rebind_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
rebind_overlay.visible = false
add_child(rebind_overlay)
var rebind_center = CenterContainer.new()
rebind_center.set_anchors_preset(Control.PRESET_FULL_RECT)
rebind_overlay.add_child(rebind_center)
rebind_label = Label.new()
rebind_label.text = "Press any key..."
rebind_label.add_theme_font_size_override("font_size", 32)
rebind_center.add_child(rebind_label)
# ── Quit Dialog ──
quit_dialog = ConfirmationDialog.new()
quit_dialog.title = "Quit Game"
quit_dialog.dialog_text = "Are you sure you want to close the game?"
@@ -161,21 +251,72 @@ func _create_label(text: String) -> Label:
func _connect_signals() -> void:
resume_btn.pressed.connect(_resume)
loadouts_btn.pressed.connect(_show_loadouts)
settings_btn.pressed.connect(_show_settings)
exit_btn.pressed.connect(_show_exit_dialog)
back_to_main_btn.pressed.connect(_show_main_menu)
settings_back_btn.pressed.connect(_show_main_menu)
save_loadout_btn.pressed.connect(_save_current_loadout)
quit_dialog.confirmed.connect(_quit_game)
func _show_main_menu() -> void:
main_vbox.visible = true
loadout_editor.visible = false
settings_editor.visible = false
func _show_loadouts() -> void:
main_vbox.visible = false
settings_editor.visible = false
loadout_editor.visible = true
edit_panel.visible = false
_populate_loadout_list()
func _show_settings() -> void:
main_vbox.visible = false
loadout_editor.visible = false
settings_editor.visible = true
_populate_keybindings()
func _populate_keybindings() -> void:
for c in keybind_vbox.get_children():
c.queue_free()
var actions = SettingsManager.default_bindings.keys()
for action in actions:
var hbox = HBoxContainer.new()
var lbl = Label.new()
lbl.text = action.capitalize()
lbl.custom_minimum_size = Vector2(200, 0)
hbox.add_child(lbl)
var btn = Button.new()
btn.text = _get_key_name(action)
btn.custom_minimum_size = Vector2(150, 0)
btn.pressed.connect(func(): _start_rebind(action))
hbox.add_child(btn)
keybind_vbox.add_child(hbox)
func _start_rebind(action: String) -> void:
waiting_for_input_action = action
rebind_label.text = "Press any key for: " + action.capitalize()
rebind_overlay.visible = true
func _get_key_name(action: String) -> String:
if not InputMap.has_action(action):
return "?"
var events = InputMap.action_get_events(action)
for e in events:
if e is InputEventKey:
var code = e.physical_keycode if e.physical_keycode != 0 else e.keycode
return OS.get_keycode_string(code)
elif e is InputEventMouseButton:
if e.button_index == MOUSE_BUTTON_LEFT: return "LClick"
elif e.button_index == MOUSE_BUTTON_RIGHT: return "RClick"
elif e.button_index == MOUSE_BUTTON_MIDDLE: return "MClick"
elif e.button_index == MOUSE_BUTTON_WHEEL_UP: return "MWheel Up"
elif e.button_index == MOUSE_BUTTON_WHEEL_DOWN: return "MWheel Down"
return "?"
func _populate_loadout_list() -> void:
for c in loadout_list_vbox.get_children():
c.queue_free()
@@ -220,7 +361,6 @@ func _populate_options(opt: OptionButton, category: String, current_id: String)
var idx = 0
var select_idx = 0
# Always add "none" as first option
opt.add_item("None", 0)
opt.set_item_metadata(0, "none")
if current_id == "none": select_idx = 0