Files
spacel/scripts/pause_menu.gd
T
alpacaman edc40f9008 Initial commit — Godot space roguelite source
- Touch controls: direct InputEventScreenTouch in shop_ui (bypass relay)
- ItemDB: static preload list instead of DirAccess scan (export fix)
- All 18 items with EN localisation (name_en, desc_en, category_en)
- Ship playstyles: NOVA-1 shield, INFERNO ram, AURORA agile/tank
- Quasar: SMBH visual, jet boost, merge, push, BH-eating
- Atlas & UI text updated EN+DE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:38:09 +02:00

381 lines
13 KiB
GDScript

extends Node2D
# Pause-Menü + Optionen-Menü im Cockpit-Stil.
# Node braucht process_mode = PROCESS_MODE_ALWAYS (wird in _ready gesetzt).
signal resume_requested
signal quit_to_menu_requested
var W: float = 960.0
var H: float = 600.0
const COL_BG := Color(0.0, 0.0, 0.04, 0.75)
const COL_PRIMARY := Color(0.0, 1.0, 0.533, 1.0)
const COL_ACCENT := Color(0.27, 1.0, 0.8, 1.0)
const COL_DIM := Color(0.0, 0.27, 0.13, 0.55)
const COL_WHITE := Color(1.0, 1.0, 1.0, 0.90)
const COL_WARN := Color(1.0, 0.67, 0.0, 0.90)
# 0=Pause, 1=Optionen, 2=Steuerung
var _screen: int = 0
var _cursor: int = 0
var _blink: float = 0.0
var _ctrl_cursor: int = 0
var _ctrl_waiting: bool = false
func _pause_items() -> Array:
return [Tr.t("pause_resume"), Tr.t("menu_options"), Tr.t("pause_main_menu"), Tr.t("menu_quit")]
func _opt_items() -> Array:
return [Tr.t("opt_sfx"), Tr.t("opt_music"), Tr.t("opt_mute"), Tr.t("opt_fullscreen"),
Tr.t("opt_nebula"), Tr.t("opt_stars"), Tr.t("opt_language"), Tr.t("opt_touch"),
Tr.t("opt_controls"), Tr.t("opt_back")]
func _ctrl_labels() -> Array:
return [Tr.t("ctrl_thrust"), Tr.t("ctrl_left"), Tr.t("ctrl_right"),
Tr.t("ctrl_shoot"), Tr.t("ctrl_wipe")]
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
func open() -> void:
_screen = 0
_cursor = 0
_blink = 0.0
_ctrl_waiting = false
visible = true
func _process(delta: float) -> void:
if not visible: return
_blink += delta
queue_redraw()
func _unhandled_input(event: InputEvent) -> void:
if not visible: return
match _screen:
0: _input_pause(event)
1: _input_options(event)
2: _input_controls(event)
func _input_pause(event: InputEvent) -> void:
var pitems := _pause_items()
if event.is_action_pressed("ui_up"):
_cursor = (_cursor - 1 + pitems.size()) % pitems.size()
elif event.is_action_pressed("ui_down"):
_cursor = (_cursor + 1) % pitems.size()
elif event.is_action_pressed("ui_accept"):
match _cursor:
0: _do_resume()
1: _screen = 1; _cursor = 0
2: _do_quit_menu()
3: get_tree().quit()
elif event.is_action_pressed("ui_cancel"):
_do_resume()
func _input_options(event: InputEvent) -> void:
var opts := _opt_items()
if event.is_action_pressed("ui_up"):
_cursor = (_cursor - 1 + opts.size()) % opts.size()
elif event.is_action_pressed("ui_down"):
_cursor = (_cursor + 1) % opts.size()
elif event.is_action_pressed("ui_left"):
_change_option(_cursor, -1)
elif event.is_action_pressed("ui_right"):
_change_option(_cursor, 1)
elif event.is_action_pressed("ui_accept"):
if _cursor == opts.size() - 1: # Back
_screen = 0; _cursor = 1
elif _cursor == opts.size() - 2: # Controls
_screen = 2; _ctrl_cursor = 0; _ctrl_waiting = false
else:
_change_option(_cursor, 1)
elif event.is_action_pressed("ui_cancel"):
_close_options()
func _input_controls(event: InputEvent) -> void:
var labels := _ctrl_labels()
var reset_idx := labels.size()
var back_idx := labels.size() + 1
var total := labels.size() + 2
if _ctrl_waiting:
if event is InputEventKey and event.pressed:
var kc: int = event.physical_keycode
if kc in [KEY_SHIFT, KEY_CTRL, KEY_ALT, KEY_META, KEY_CAPSLOCK]:
return
if kc == KEY_ESCAPE:
_ctrl_waiting = false
return
_rebind_key(Settings.REBIND_ACTIONS[_ctrl_cursor], kc)
_ctrl_waiting = false
get_viewport().set_input_as_handled()
elif event is InputEventJoypadButton and event.pressed:
_rebind_joy(Settings.REBIND_ACTIONS[_ctrl_cursor], event.button_index)
_ctrl_waiting = false
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("ui_up"):
_ctrl_cursor = (_ctrl_cursor - 1 + total) % total
elif event.is_action_pressed("ui_down"):
_ctrl_cursor = (_ctrl_cursor + 1) % total
elif event.is_action_pressed("ui_accept"):
if _ctrl_cursor == back_idx:
_screen = 1
elif _ctrl_cursor == reset_idx:
Settings.reset_key_bindings()
else:
_ctrl_waiting = true
elif event.is_action_pressed("ui_cancel"):
_screen = 1
func _rebind_key(action: String, physical_keycode: int) -> void:
if not InputMap.has_action(action): return
for ev in InputMap.action_get_events(action):
if ev is InputEventKey:
InputMap.action_erase_event(action, ev)
var new_ev := InputEventKey.new()
new_ev.physical_keycode = physical_keycode
InputMap.action_add_event(action, new_ev)
Settings.key_bindings[action] = physical_keycode
Settings.save_settings()
func _rebind_joy(action: String, button_index: int) -> void:
if not InputMap.has_action(action): return
for ev in InputMap.action_get_events(action):
if ev is InputEventJoypadButton:
InputMap.action_erase_event(action, ev)
var new_ev := InputEventJoypadButton.new()
new_ev.button_index = button_index
InputMap.action_add_event(action, new_ev)
Settings.key_bindings[action + "_joy"] = button_index
Settings.save_settings()
func _change_option(idx: int, dir: int) -> void:
match idx:
0: # SFX volume
Settings.master_volume = clamp(Settings.master_volume + dir * 0.1, 0.0, 1.0)
Settings.apply_volume()
1: # Music volume
Settings.music_volume = clamp(Settings.music_volume + dir * 0.1, 0.0, 1.0)
Settings.apply_music_volume()
2: # Mute
Settings.sfx_muted = not Settings.sfx_muted
Settings.apply_volume()
3: # Fullscreen
Settings.fullscreen = not Settings.fullscreen
if Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
4: # Nebula
Settings.nebula_enabled = not Settings.nebula_enabled
5: # Stars
Settings.star_density = (Settings.star_density + dir + 3) % 3
6: # Language
Settings.language = "en" if Settings.language == "de" else "de"
7: # Touch mode
Settings.touch_mode = (Settings.touch_mode + 1) % 3
# 8 = Controls → handled in _input_options
Settings.save_settings()
func _do_resume() -> void:
visible = false
resume_requested.emit()
func _close_options() -> void:
_screen = 0
_cursor = 1
func _do_quit_menu() -> void:
visible = false
quit_to_menu_requested.emit()
# ── Drawing ───────────────────────────────────────────────────────────────────
func _draw() -> void:
var vs: Vector2 = get_viewport_rect().size
W = vs.x; H = vs.y
draw_rect(get_viewport_rect(), COL_BG)
match _screen:
0: _draw_pause()
1: _draw_options()
2: _draw_controls()
func _draw_pause() -> void:
var bw := 300.0; var bh := 255.0
var bx := (W - bw) * 0.5; var by := (H - bh) * 0.5
_draw_terminal_box(bx, by, bw, bh)
_draw_text_c(Tr.t("pause_title"), W * 0.5, by + 16.0, 10, COL_DIM)
var pitems := _pause_items()
var item_y := by + 55.0
for i in pitems.size():
var pulse := 0.6 + 0.4 * sin(_blink * 3.0)
var col: Color
if i == _cursor:
col = Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, pulse)
else:
col = Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.65)
var prefix := "" if i == _cursor else " "
_draw_text_c(prefix + pitems[i], W * 0.5, item_y + i * 40.0, 14, col)
_draw_text_c(Tr.hint("pause_footer"), W * 0.5, by + bh - 20.0, 8, COL_DIM)
func _draw_options() -> void:
var bw := 480.0; var bh := 380.0
var bx := (W - bw) * 0.5; var by := (H - bh) * 0.5
_draw_terminal_box(bx, by, bw, bh)
_draw_text_c(Tr.t("opt_title"), W * 0.5, by + 16.0, 10, COL_DIM)
var star_labels := [Tr.t("star_low"), Tr.t("star_mid"), Tr.t("star_high")]
var touch_labels := [Tr.t("touch_auto"), Tr.t("touch_on"), Tr.t("touch_off")]
var opts := _opt_items()
var values: Array = [
_volume_bar(Settings.master_volume),
_volume_bar(Settings.music_volume),
Tr.t("yes") if Settings.sfx_muted else Tr.t("no"),
Tr.t("yes") if Settings.fullscreen else Tr.t("no"),
Tr.t("yes") if Settings.nebula_enabled else Tr.t("no"),
star_labels[Settings.star_density],
Settings.language.to_upper(),
touch_labels[Settings.touch_mode],
"", # Controls sub-screen
"" # Back (rendered specially)
]
var item_y := by + 52.0
for i in opts.size():
var is_sel := i == _cursor
var pulse := 0.6 + 0.4 * sin(_blink * 3.0)
var row_y := item_y + i * 36.0
if i == opts.size() - 1: # Back
var p := pulse if is_sel else 0.4
_draw_text_c("[ %s ]" % opts[i], W * 0.5, row_y, 12,
Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, p))
else:
var label_col: Color
if is_sel:
label_col = Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, pulse)
else:
label_col = Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.70)
var val_col := Color(COL_WHITE.r, COL_WHITE.g, COL_WHITE.b,
0.90 if is_sel else 0.50)
var prefix := "" if is_sel else " "
_draw_text(prefix + opts[i], bx + 28.0, row_y, 11, label_col)
if values[i] != "":
var val_str: String
if values[i] == "":
val_str = ""
else:
val_str = "%s" % values[i]
_draw_text(val_str,
bx + bw - 28.0 - _text_w(val_str, 11), row_y, 11, val_col)
_draw_text_c(Tr.hint("opt_footer"), W * 0.5, by + bh - 20.0, 8, COL_DIM)
func _draw_controls() -> void:
var bw := 400.0; var bh := 310.0
var bx := (W - bw) * 0.5; var by := (H - bh) * 0.5
_draw_terminal_box(bx, by, bw, bh)
_draw_text_c(Tr.t("ctrl_title"), W * 0.5, by + 16.0, 10, COL_DIM)
var labels := _ctrl_labels()
var actions := Settings.REBIND_ACTIONS
var reset_idx := labels.size()
var back_idx := labels.size() + 1
var total := labels.size() + 2
var item_y := by + 52.0
for i in total:
var is_sel := i == _ctrl_cursor
var pulse := 0.6 + 0.4 * sin(_blink * 3.0)
var row_y := item_y + i * 36.0
if i == back_idx:
_draw_text_c("[ %s ]" % Tr.t("opt_back"), W * 0.5, row_y, 12,
Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, pulse if is_sel else 0.4))
elif i == reset_idx:
_draw_text_c("[ %s ]" % Tr.t("ctrl_reset"), W * 0.5, row_y, 12,
Color(COL_WARN.r, COL_WARN.g, COL_WARN.b, pulse if is_sel else 0.35))
else:
var label_col := Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b,
pulse) if is_sel else Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.70)
var val_col := Color(COL_WHITE.r, COL_WHITE.g, COL_WHITE.b,
0.90 if is_sel else 0.50)
var prefix := "" if is_sel else " "
var key_str: String
if _ctrl_waiting and is_sel:
key_str = Tr.t("ctrl_waiting")
val_col = Color(COL_WARN.r, COL_WARN.g, COL_WARN.b,
0.5 + 0.5 * sin(_blink * 6.0))
else:
key_str = "[ %s ]" % _action_key_name(actions[i])
_draw_text(prefix + labels[i], bx + 28.0, row_y, 11, label_col)
_draw_text(key_str, bx + bw - 28.0 - _text_w(key_str, 11), row_y, 11, val_col)
_draw_text_c(Tr.hint("ctrl_footer"), W * 0.5, by + bh - 20.0, 8, COL_DIM)
func _action_key_name(action: String) -> String:
if not InputMap.has_action(action): return "?"
for ev in InputMap.action_get_events(action):
if ev is InputEventKey:
var kev := ev as InputEventKey
var kc: int = kev.physical_keycode if kev.physical_keycode != KEY_NONE else kev.keycode
return OS.get_keycode_string(kc)
elif ev is InputEventJoypadButton:
var jev := ev as InputEventJoypadButton
return _joy_btn_label(jev.button_index)
return "?"
func _joy_btn_label(idx: int) -> String:
match idx:
JOY_BUTTON_A: return "A"
JOY_BUTTON_B: return "B"
JOY_BUTTON_X: return "X"
JOY_BUTTON_Y: return "Y"
JOY_BUTTON_LEFT_SHOULDER: return "LB"
JOY_BUTTON_RIGHT_SHOULDER: return "RB"
JOY_BUTTON_LEFT_STICK: return "L3"
JOY_BUTTON_RIGHT_STICK: return "R3"
_: return "BTN%d" % idx
func _volume_bar(vol: float) -> String:
var filled := int(round(vol * 10.0))
var bar := ""
for i in 10:
bar += "" if i < filled else ""
return bar
# ── Drawing helpers ───────────────────────────────────────────────────────────
func _draw_terminal_box(bx: float, by: float, bw: float, bh: float) -> void:
draw_rect(Rect2(bx, by, bw, bh), Color(0.0, 0.04, 0.02, 0.95))
var bc := Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.35)
draw_line(Vector2(bx, by), Vector2(bx+bw, by), bc, 1.0)
draw_line(Vector2(bx, by+bh), Vector2(bx+bw, by+bh), bc, 1.0)
draw_line(Vector2(bx, by), Vector2(bx, by+bh), bc, 1.0)
draw_line(Vector2(bx+bw, by), Vector2(bx+bw, by+bh), bc, 1.0)
var cl := 12.0
for cx: float in [bx, bx+bw]:
for cy: float in [by, by+bh]:
var sx := 1.0 if cx == bx else -1.0
var sy := 1.0 if cy == by else -1.0
draw_line(Vector2(cx, cy), Vector2(cx + sx*cl, cy), COL_PRIMARY, 1.5)
draw_line(Vector2(cx, cy), Vector2(cx, cy + sy*cl), COL_PRIMARY, 1.5)
func _draw_text(text: String, x: float, y: float, sz: int, col: Color) -> void:
draw_string(ThemeDB.fallback_font, Vector2(x, y + sz),
text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz, col)
func _draw_text_c(text: String, x: float, y: float, sz: int, col: Color) -> void:
var tw := _text_w(text, sz)
draw_string(ThemeDB.fallback_font, Vector2(x - tw * 0.5, y + sz),
text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz, col)
func _text_w(text: String, sz: int) -> float:
return ThemeDB.fallback_font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, sz).x