Feat/2 weapons foundation #6
+11
-40
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6fe1eyas7wxv
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user