Settings page, pause menu, menu navigation; fix packed-app portraits
- select screen portraits load via pck-safe loader (the blank-cards bug in the shipped build) - Settings (title menu + persisted user://settings.json): fullscreen, hitbox viewer default, master volume - Esc: pause menu in fights (resume/rematch/select/menu), back-to-title from select/editor/settings - FOITIN_SHOT env hook smoke-tests exported builds end-to-end Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
58beabe4e8
commit
4e2004e740
@ -17,6 +17,8 @@ var msg_label: Label
|
||||
var darken: ColorRect
|
||||
var tick_count := 0
|
||||
var match_over := false
|
||||
var paused := false
|
||||
var pause_ui: Node2D
|
||||
var finish_window := -1 # >0: loser dizzy, finisher available
|
||||
var finisher_running := false
|
||||
|
||||
@ -28,6 +30,8 @@ func _ready() -> void:
|
||||
for arg in OS.get_cmdline_user_args():
|
||||
if arg.begins_with("--shot="):
|
||||
shot_path = arg.trim_prefix("--shot=")
|
||||
if shot_path == "" and OS.get_environment("FOITIN_SHOT") != "":
|
||||
shot_path = ProjectSettings.globalize_path("user://shot.png")
|
||||
_build_stage()
|
||||
_load_fighters()
|
||||
_build_hud()
|
||||
@ -35,8 +39,50 @@ func _ready() -> void:
|
||||
p2.hp = 60 # demo: one special KOs -> exercises the finisher flow
|
||||
overlay = BoxOverlay.new()
|
||||
overlay.engine = self
|
||||
overlay.visible = shot_path != ""
|
||||
overlay.visible = shot_path != "" or Settings.show_boxes
|
||||
add_child(overlay)
|
||||
_build_pause_ui()
|
||||
|
||||
|
||||
func _build_pause_ui() -> void:
|
||||
pause_ui = Node2D.new()
|
||||
pause_ui.visible = false
|
||||
pause_ui.z_index = 100
|
||||
add_child(pause_ui)
|
||||
var dim := ColorRect.new()
|
||||
dim.color = Color(0, 0, 0, 0.6)
|
||||
dim.size = Vector2(1280, 720)
|
||||
pause_ui.add_child(dim)
|
||||
var lab := Label.new()
|
||||
lab.text = "PAUSED"
|
||||
lab.add_theme_font_size_override("font_size", 52)
|
||||
lab.position = Vector2(555, 200)
|
||||
pause_ui.add_child(lab)
|
||||
var entries := [
|
||||
["RESUME (Esc)", func(): _set_paused(false)],
|
||||
["REMATCH", func(): get_tree().reload_current_scene()],
|
||||
["CHARACTER SELECT", func(): get_tree().change_scene_to_file("res://engine/scenes/Select.tscn")],
|
||||
["MAIN MENU", func(): get_tree().change_scene_to_file("res://engine/scenes/Title.tscn")],
|
||||
]
|
||||
for i in entries.size():
|
||||
var b := Button.new()
|
||||
b.text = entries[i][0]
|
||||
b.position = Vector2(510, 300 + i * 66)
|
||||
b.size = Vector2(260, 50)
|
||||
b.add_theme_font_size_override("font_size", 22)
|
||||
b.pressed.connect(entries[i][1])
|
||||
pause_ui.add_child(b)
|
||||
|
||||
|
||||
func _set_paused(v: bool) -> void:
|
||||
paused = v
|
||||
pause_ui.visible = v
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.keycode == KEY_ESCAPE and shot_path == "":
|
||||
_set_paused(not paused)
|
||||
|
||||
|
||||
func _build_stage() -> void:
|
||||
@ -186,7 +232,7 @@ static func _relative(mask: int, facing: int) -> int:
|
||||
## ---------------------------------------------------------------- tick
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if p1 == null or p2 == null:
|
||||
if p1 == null or p2 == null or paused:
|
||||
return
|
||||
if Input.is_key_pressed(KEY_R) and match_over:
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
30
engine/core/settings.gd
Normal file
30
engine/core/settings.gd
Normal file
@ -0,0 +1,30 @@
|
||||
class_name Settings
|
||||
## Persistent app settings (user://settings.json). Apply on boot and on save.
|
||||
|
||||
static var fullscreen := false
|
||||
static var show_boxes := false
|
||||
static var volume := 80.0 # master, 0..100
|
||||
|
||||
static func load_and_apply() -> void:
|
||||
var text := FileAccess.get_file_as_string("user://settings.json")
|
||||
if not text.is_empty():
|
||||
var d = JSON.parse_string(text)
|
||||
if d is Dictionary:
|
||||
fullscreen = bool(d.get("fullscreen", fullscreen))
|
||||
show_boxes = bool(d.get("show_boxes", show_boxes))
|
||||
volume = float(d.get("volume", volume))
|
||||
apply()
|
||||
|
||||
static func save() -> void:
|
||||
var f := FileAccess.open("user://settings.json", FileAccess.WRITE)
|
||||
f.store_string(JSON.stringify({
|
||||
"fullscreen": fullscreen, "show_boxes": show_boxes, "volume": volume,
|
||||
}, " "))
|
||||
f.close()
|
||||
apply()
|
||||
|
||||
static func apply() -> void:
|
||||
DisplayServer.window_set_mode(
|
||||
DisplayServer.WINDOW_MODE_FULLSCREEN if fullscreen
|
||||
else DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
AudioServer.set_bus_volume_db(0, linear_to_db(clampf(volume / 100.0, 0.0, 1.0)))
|
||||
1
engine/core/settings.gd.uid
Normal file
1
engine/core/settings.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bos4wtpw13ign
|
||||
6
engine/scenes/Settings.tscn
Normal file
6
engine/scenes/Settings.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://engine/scenes/settings_menu.gd" id="1"]
|
||||
|
||||
[node name="Settings" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
@ -182,3 +182,8 @@ func _copy_dir(src: String, dst: String) -> void:
|
||||
da.copy(src + "/" + f, dst + "/" + f)
|
||||
for d in da.get_directories():
|
||||
_copy_dir(src + "/" + d, dst + "/" + d)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Title.tscn")
|
||||
|
||||
@ -34,12 +34,8 @@ func _ready() -> void:
|
||||
for path in CharacterData.scan_roster():
|
||||
var d = JSON.parse_string(FileAccess.get_file_as_string(path + "/manifest.json"))
|
||||
var entry := {"path": path, "id": d.get("id", ""), "name": d.get("name", "?"),
|
||||
"color": Color(d.get("color", "#888888")), "portrait": null}
|
||||
var pp := ProjectSettings.globalize_path(path + "/portrait.png")
|
||||
if FileAccess.file_exists(pp):
|
||||
var img := Image.load_from_file(pp)
|
||||
if img:
|
||||
entry.portrait = ImageTexture.create_from_image(img)
|
||||
"color": Color(d.get("color", "#888888")),
|
||||
"portrait": MoveData.load_texture_any(path + "/portrait.png")}
|
||||
roster.append(entry)
|
||||
|
||||
# adaptive grid: shrink cards as the roster grows
|
||||
@ -118,6 +114,9 @@ func _input(event: InputEvent) -> void:
|
||||
if vs_timer >= 0:
|
||||
return
|
||||
match event.keycode:
|
||||
KEY_ESCAPE:
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Title.tscn")
|
||||
return
|
||||
KEY_A: _move(0, -1)
|
||||
KEY_D: _move(0, 1)
|
||||
KEY_J: _lock(0)
|
||||
|
||||
60
engine/scenes/settings_menu.gd
Normal file
60
engine/scenes/settings_menu.gd
Normal file
@ -0,0 +1,60 @@
|
||||
extends Node2D
|
||||
## Settings page: fullscreen, hitbox viewer default, master volume.
|
||||
|
||||
func _ready() -> void:
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color("#14161c")
|
||||
bg.size = Vector2(1280, 720)
|
||||
add_child(bg)
|
||||
var title := Label.new()
|
||||
title.text = "SETTINGS"
|
||||
title.add_theme_font_size_override("font_size", 48)
|
||||
title.position = Vector2(540, 80)
|
||||
add_child(title)
|
||||
|
||||
var fs := CheckBox.new()
|
||||
fs.text = "Fullscreen"
|
||||
fs.button_pressed = Settings.fullscreen
|
||||
fs.position = Vector2(480, 220)
|
||||
fs.add_theme_font_size_override("font_size", 26)
|
||||
fs.toggled.connect(func(v): Settings.fullscreen = v; Settings.save())
|
||||
add_child(fs)
|
||||
|
||||
var boxes := CheckBox.new()
|
||||
boxes.text = "Show hitboxes (training)"
|
||||
boxes.button_pressed = Settings.show_boxes
|
||||
boxes.position = Vector2(480, 280)
|
||||
boxes.add_theme_font_size_override("font_size", 26)
|
||||
boxes.toggled.connect(func(v): Settings.show_boxes = v; Settings.save())
|
||||
add_child(boxes)
|
||||
|
||||
var vol_label := Label.new()
|
||||
vol_label.text = "Master volume"
|
||||
vol_label.position = Vector2(480, 360)
|
||||
vol_label.add_theme_font_size_override("font_size", 26)
|
||||
add_child(vol_label)
|
||||
var vol := HSlider.new()
|
||||
vol.min_value = 0
|
||||
vol.max_value = 100
|
||||
vol.value = Settings.volume
|
||||
vol.position = Vector2(480, 400)
|
||||
vol.size = Vector2(320, 30)
|
||||
vol.value_changed.connect(func(v): Settings.volume = v; Settings.save())
|
||||
add_child(vol)
|
||||
|
||||
var back := Button.new()
|
||||
back.text = "BACK (Esc)"
|
||||
back.position = Vector2(540, 520)
|
||||
back.size = Vector2(200, 52)
|
||||
back.add_theme_font_size_override("font_size", 24)
|
||||
back.pressed.connect(_back)
|
||||
add_child(back)
|
||||
|
||||
|
||||
func _back() -> void:
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Title.tscn")
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||
_back()
|
||||
1
engine/scenes/settings_menu.gd.uid
Normal file
1
engine/scenes/settings_menu.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://crqxe4whatvmu
|
||||
@ -2,6 +2,13 @@ extends Node2D
|
||||
## Title screen: PLAY -> character select, EDITOR -> content editor.
|
||||
|
||||
func _ready() -> void:
|
||||
# export smoke-test hook: FOITIN_SHOT=1 boots straight into a demo fight
|
||||
# that saves user://shot.png and quits (match_engine reads the env too)
|
||||
if OS.get_environment("FOITIN_SHOT") != "":
|
||||
GameState.p1_pick = "kachujin"
|
||||
GameState.p2_pick = "decks"
|
||||
get_tree().change_scene_to_file.call_deferred("res://engine/scenes/Fight.tscn")
|
||||
return
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color("#101218")
|
||||
bg.size = Vector2(1280, 720)
|
||||
@ -19,10 +26,13 @@ func _ready() -> void:
|
||||
sub.position = Vector2(540, 300)
|
||||
add_child(sub)
|
||||
|
||||
_button("PLAY", Vector2(540, 420), func():
|
||||
Settings.load_and_apply()
|
||||
_button("PLAY", Vector2(540, 400), func():
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Select.tscn"))
|
||||
_button("EDITOR", Vector2(540, 500), func():
|
||||
_button("EDITOR", Vector2(540, 470), func():
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Editor.tscn"))
|
||||
_button("SETTINGS", Vector2(540, 540), func():
|
||||
get_tree().change_scene_to_file("res://engine/scenes/Settings.tscn"))
|
||||
|
||||
var hint := Label.new()
|
||||
hint.text = "P1: WASD + J/K P2: arrows + O/P"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user