Merge remote-tracking branch 'origin/demo'

This commit is contained in:
cakipaul 2025-07-16 23:26:58 +08:00
commit 2f431cff52
12 changed files with 528 additions and 71 deletions

View File

@ -10,8 +10,8 @@ const ARCHIVE_ID_MAX = 99
const ARCHIVE_ID_DIGITS = 3 const ARCHIVE_ID_DIGITS = 3
# Static paths # Static paths
static var user_root_dir := "user://data/" # must end with "/" static var user_data_root_dir := "user://data/" # must end with "/"
static var archive_dir := "user://data/archives/" static var user_archives_dir := "user://data/archives/"
static var archive_prefix := "save" static var archive_prefix := "save"
# Archive management # Archive management
@ -19,6 +19,8 @@ var archive: AssembledArchive:
set(val): set(val):
archive = val archive = val
if archive: if archive:
# emit signal
archive_loaded.emit()
GlobalConfigManager.print_global_info() GlobalConfigManager.print_global_info()
print("use archive ", archive.resource_path) print("use archive ", archive.resource_path)
archive.event_stage["release_stage"] = GlobalConfig.RELEASE_STAGE archive.event_stage["release_stage"] = GlobalConfig.RELEASE_STAGE
@ -129,11 +131,11 @@ func _try_auto_save() -> void:
func _check_dirs_and_archives() -> bool: func _check_dirs_and_archives() -> bool:
# Ensure directories exist # Ensure directories exist
_ensure_directory_exists(user_root_dir) _ensure_directory_exists(user_data_root_dir)
_ensure_directory_exists(archive_dir) _ensure_directory_exists(user_archives_dir)
# Check if the archive directory is accessible # Check if the archive directory is accessible
var archive_dir_access = DirAccess.open(archive_dir) var archive_dir_access = DirAccess.open(user_archives_dir)
if not archive_dir_access: if not archive_dir_access:
_handle_load_error("存档目录", "读取") _handle_load_error("存档目录", "读取")
return false return false
@ -163,7 +165,7 @@ func _load_existing_archives(dir_access: DirAccess) -> void:
var note = archive_info.note var note = archive_info.note
archives_notes_dict[id] = note archives_notes_dict[id] = note
if not archives_dict.has(id): if not archives_dict.has(id):
var archive_resource = _load_archive_resource(archive_dir + file) var archive_resource = _load_archive_resource(user_archives_dir + file)
if archive_resource: if archive_resource:
archives_dict[id] = archive_resource archives_dict[id] = archive_resource
@ -212,7 +214,7 @@ func create_and_use_new_archive(id := -1) -> void:
_create_and_save_new_archive_resoure(id) _create_and_save_new_archive_resoure(id)
else: else:
# 如果 id 大于等于 0创建指定 id 的存档 # 如果 id 大于等于 0创建指定 id 的存档
var archive_path = _get_archive_path(id) var archive_path = get_archive_path(id)
var take_over_path = FileAccess.file_exists(archive_path) var take_over_path = FileAccess.file_exists(archive_path)
_create_and_save_new_archive_resoure(id, take_over_path) _create_and_save_new_archive_resoure(id, take_over_path)
@ -222,15 +224,15 @@ func create_and_use_new_archive(id := -1) -> void:
func _find_next_available_id() -> int: func _find_next_available_id() -> int:
var id = 0 var id = 0
var archive_path = _get_archive_path(id) var archive_path = get_archive_path(id)
while FileAccess.file_exists(archive_path) and id <= ARCHIVE_ID_MAX: while FileAccess.file_exists(archive_path) and id <= ARCHIVE_ID_MAX:
id += 1 id += 1
archive_path = _get_archive_path(id) archive_path = get_archive_path(id)
return id return id
func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> void: func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> void:
var archive_path = _get_archive_path(id) var archive_path = get_archive_path(id)
archive = AssembledArchive.new() as Resource archive = AssembledArchive.new() as Resource
archive.version = CURRENT_VERSION archive.version = CURRENT_VERSION
if take_over_path: if take_over_path:
@ -244,9 +246,9 @@ func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> v
# 超过 999 个存档会出问题;不过这个游戏不会有这么多存档 # 超过 999 个存档会出问题;不过这个游戏不会有这么多存档
func _get_archive_path(id: int) -> String: func get_archive_path(id: int) -> String:
var id_str := str(id).pad_zeros(ARCHIVE_ID_DIGITS) var id_str := str(id).pad_zeros(ARCHIVE_ID_DIGITS)
return archive_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT return user_archives_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT
func allow_resume(id := 1) -> bool: func allow_resume(id := 1) -> bool:
@ -279,7 +281,7 @@ func _save_player_state() -> void:
func load_config() -> void: func load_config() -> void:
if GlobalConfigManager.config: if GlobalConfigManager.config:
return return
var path = user_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT var path = user_data_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT
if FileAccess.file_exists(path): if FileAccess.file_exists(path):
var loaded_config = ResourceLoader.load(path) var loaded_config = ResourceLoader.load(path)
if is_instance_valid(loaded_config) and loaded_config.version >= CURRENT_VERSION: if is_instance_valid(loaded_config) and loaded_config.version >= CURRENT_VERSION:
@ -289,7 +291,6 @@ func load_config() -> void:
if not GlobalConfigManager.config: if not GlobalConfigManager.config:
_create_default_config(path) _create_default_config(path)
GlobalConfigManager.config.resource_path = path
if not Engine.is_editor_hint(): if not Engine.is_editor_hint():
_connect_config_signals() _connect_config_signals()
@ -297,6 +298,7 @@ func load_config() -> void:
func _create_default_config(path: String) -> void: func _create_default_config(path: String) -> void:
var config = GlobalConfig.new() var config = GlobalConfig.new()
config.version = CURRENT_VERSION config.version = CURRENT_VERSION
config.resource_path = path
GlobalConfigManager.config = config GlobalConfigManager.config = config
ResourceSaver.save(config, path) ResourceSaver.save(config, path)
@ -319,8 +321,6 @@ func load_archive() -> void:
_handle_load_error(str(selected_id) + " 号存档", "查找") _handle_load_error(str(selected_id) + " 号存档", "查找")
return return
archive = archives_dict[selected_id] archive = archives_dict[selected_id]
# emit signal
archive_loaded.emit()
check_autosave_options() check_autosave_options()

View File

@ -11,8 +11,8 @@ class_name AssembledArchive extends Resource
printerr("[AssembledArchive] current_scene is not valid: " + val) printerr("[AssembledArchive] current_scene is not valid: " + val)
return return
# 尝试后台预先加载该场景 # 尝试后台预先加载该场景
if GroundLoader.GROUND_SCENE_PATH_DICT.has(val): var path = GroundLoader.get_ground_scene_uid(val)
var path = GroundLoader.GROUND_SCENE_PATH_DICT[val] if path:
if GlobalConfig.DEBUG: if GlobalConfig.DEBUG:
print("[AssembledArchive] current_scene: " + current_scene) print("[AssembledArchive] current_scene: " + current_scene)
ResourceLoader.load_threaded_request(path, "PackedScene") ResourceLoader.load_threaded_request(path, "PackedScene")

View File

@ -0,0 +1,314 @@
extends Control
# UI References
@onready var quit_debug_button: Button = %QuitDebugModeButton
@onready var archive_grid: GridContainer = %ArchiveGrid
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var current_archive_label: Label = %CurrentArchiveLabel
@onready var name_input: LineEdit = %NameInput
@onready var save_button: Button = %SaveButton
@onready var refresh_button: Button = %RefreshButton
# Constants
const MAX_MANUAL_ARCHIVES = 99
const GRID_COLUMNS = 4
# Variables
var manual_archives: Dictionary = {} # {id: {name: String, path: String, time: String}}
var next_available_id: int = 2 # Start from 2 since 1 is reserved for main archive
func _ready() -> void:
get_parent().layer = GlobalConfig.CANVAS_LAYER_SETTINGS
# Setup UI
archive_grid.columns = GRID_COLUMNS
# Connect signals
quit_debug_button.pressed.connect(_on_quit_debug_button_pressed)
save_button.pressed.connect(_on_save_button_pressed)
refresh_button.pressed.connect(_refresh_archive_list)
name_input.text_submitted.connect(_on_name_submitted)
# Set default name
_update_default_name()
# Initial load
_refresh_archive_list()
_update_current_archive_label()
func _update_current_archive_label() -> void:
current_archive_label.text = "当前使用存档1号存档主存档"
func _update_default_name() -> void:
name_input.placeholder_text = "输入存档名称"
var chapter_name = EventManager.get_chapter_stage()
if chapter_name == 1:
chapter_name = "序章"
elif chapter_name <= 5:
chapter_name = "%s" % (chapter_name - 1)
elif chapter_name == 6:
chapter_name = "结尾"
else:
chapter_name = "未知"
var scene_name = SceneManager.get_current_scene_name()
var saving_name = chapter_name + "_" + scene_name
name_input.text = _get_unique_archive_name(saving_name)
func _on_name_submitted(_text: String) -> void:
_on_save_button_pressed()
func _on_quit_debug_button_pressed() -> void:
# 不写入配置
GlobalConfig.DEBUG = false
quit()
func _on_save_button_pressed() -> void:
# Check limit
if manual_archives.size() >= MAX_MANUAL_ARCHIVES:
_show_notification("已达到最大存档数量限制99个")
return
# Get and validate name
var archive_name = name_input.text.strip_edges()
if archive_name.is_empty():
archive_name = "未命名存档_" + Time.get_datetime_string_from_system()
# Save current progress
ArchiveManager.save_all()
# Get unique name
archive_name = _get_unique_archive_name(archive_name)
# Copy current archive
_copy_current_archive(archive_name)
# Reset input field
_update_default_name()
name_input.select_all()
func _get_unique_archive_name(base_name: String) -> String:
var final_name = base_name
var counter = 1
# Check if name already exists
var name_exists = true
while name_exists:
name_exists = false
for data in manual_archives.values():
if data.name == final_name:
name_exists = true
final_name = base_name + "_" + str(counter)
counter += 1
break
return final_name
func _copy_current_archive(archive_name: String) -> void:
# Get current archive path
var current_archive = ArchiveManager.archive
if not current_archive:
_show_notification("当前没有活动存档")
return
# Find next available ID
while manual_archives.has(next_available_id) and next_available_id <= MAX_MANUAL_ARCHIVES + 1:
next_available_id += 1
if next_available_id > MAX_MANUAL_ARCHIVES + 1:
_show_notification("无法创建更多存档")
return
# Create new archive path
var new_archive_path = (
ArchiveManager.user_archives_dir
+ "manual_"
+ str(next_available_id)
+ "_"
+ archive_name.validate_filename()
+ GlobalConfig.RES_FILE_FORMAT
)
# Copy the archive file
var source_path = current_archive.resource_path
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
var error = dir.copy(source_path, new_archive_path)
print("Copying archive from: ", source_path, " to: ", new_archive_path)
if error == OK:
# Save manual archive info
manual_archives[next_available_id] = {
"name": archive_name,
"path": new_archive_path,
"time": Time.get_datetime_string_from_system(false, true)
}
# Save manual archives data
_save_manual_archives_data()
# Refresh UI
_refresh_archive_list()
_show_notification("存档已保存:" + archive_name)
next_available_id += 1
else:
_show_notification("存档复制失败:" + error_string(error))
else:
_show_notification("无法访问存档目录")
func _refresh_archive_list() -> void:
# refresh savings name
_update_default_name()
# Clear existing items
for child in archive_grid.get_children():
child.queue_free()
# Load manual archives data
_load_manual_archives_data()
# Create UI items for each manual archive
var sorted_ids = manual_archives.keys()
sorted_ids.sort()
for id in sorted_ids:
var data = manual_archives[id]
_create_archive_item(id, data)
func _create_archive_item(id: int, data: Dictionary) -> void:
# Create container for the archive item
var item_container = PanelContainer.new()
item_container.custom_minimum_size = Vector2(200, 60)
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 4)
item_container.add_child(vbox)
# Archive name (editable)
var name_edit = LineEdit.new()
name_edit.text = data.name
name_edit.tooltip_text = "创建时间:" + data.time
vbox.add_child(name_edit)
# Time label
var time_label = Label.new()
time_label.text = data.time
time_label.add_theme_font_size_override("font_size", 12)
time_label.modulate.a = 0.7
vbox.add_child(time_label)
# Action buttons container
var button_container = HBoxContainer.new()
button_container.add_theme_constant_override("separation", 4)
# Load button
var load_btn = Button.new()
load_btn.text = "加载"
load_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_container.add_child(load_btn)
# Delete button
var delete_btn = Button.new()
delete_btn.text = "删除"
delete_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_container.add_child(delete_btn)
vbox.add_child(button_container)
# Connect signals
name_edit.text_changed.connect(func(new_text): _on_archive_renamed(id, new_text))
load_btn.pressed.connect(func(): _load_manual_archive(id, data))
delete_btn.pressed.connect(func(): _delete_manual_archive(id))
archive_grid.add_child(item_container)
func _on_archive_renamed(id: int, new_name: String) -> void:
if new_name.strip_edges().is_empty():
return
# Update name in data
manual_archives[id].name = new_name.strip_edges()
# Save changes
_save_manual_archives_data()
func _load_manual_archive(_id: int, data: Dictionary) -> void:
# Save current state first
ArchiveManager.save_all()
# Copy manual archive to archive 1
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
var saving_archive = load(data.path)
if saving_archive:
var target_path = ArchiveManager.get_archive_path(1)
print("Loading archive from: ", data.path, " to: ", target_path)
GlobalConfigManager.config.current_selected_archive_id = 1
if ArchiveManager.archive:
saving_archive.take_over_path(target_path)
else:
saving_archive.resource_path = target_path
ArchiveManager.archives_dict[1] = saving_archive
ArchiveManager.archive = saving_archive
print("Loading archive from: ", data.path, " to: ", target_path)
# Reload current scene
get_tree().reload_current_scene()
else:
_show_notification("加载存档失败:" + data.name)
func _delete_manual_archive(id: int) -> void:
var data = manual_archives[id]
# Delete file
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
dir.remove(data.path)
# Remove from dictionary
manual_archives.erase(id)
# Save changes
_save_manual_archives_data()
# Refresh UI
_refresh_archive_list()
_show_notification("已删除存档:" + data.name)
func _save_manual_archives_data() -> void:
var save_path = ArchiveManager.user_data_root_dir + "test_manual_archives.dat"
var file = FileAccess.open(save_path, FileAccess.WRITE)
if file:
file.store_var(manual_archives)
file.close()
func _load_manual_archives_data() -> void:
var save_path = ArchiveManager.user_data_root_dir + "test_manual_archives.dat"
if FileAccess.file_exists(save_path):
var file = FileAccess.open(save_path, FileAccess.READ)
if file:
manual_archives = file.get_var()
file.close()
# Find next available ID
next_available_id = 2
for id in manual_archives.keys():
if id >= next_available_id:
next_available_id = id + 1
func _show_notification(message: String) -> void:
SceneManager.pop_notification(message)
func _enter_tree() -> void:
SceneManager.toggle_pause_counter(true, "savings")
func _exit_tree() -> void:
SceneManager.toggle_pause_counter(false, "savings")
func quit() -> void:
queue_free()
func _unhandled_input(event: InputEvent) -> void:
# savings 界面接受所有输入事件
get_viewport().set_input_as_handled()
if (
event.is_action_pressed("savings")
or event.is_action_pressed("cancel")
or event.is_action_pressed("escape")
):
quit()

View File

@ -0,0 +1 @@
uid://cirf1nw72l315

View File

@ -0,0 +1,97 @@
[gd_scene load_steps=2 format=3 uid="uid://d4jeeteyq8kk3"]
[ext_resource type="Script" uid="uid://cirf1nw72l315" path="res://manager/archive_manager/savings_panel.gd" id="1_oo2ip"]
[node name="SavingsLayer" type="CanvasLayer"]
[node name="TestArchivePanel" type="PanelContainer" parent="."]
process_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 59.0
offset_top = 21.0
offset_right = 395.0
offset_bottom = 262.0
grow_horizontal = 2
grow_vertical = 2
scale = Vector2(0.5, 0.5)
script = ExtResource("1_oo2ip")
[node name="VBoxContainer" type="VBoxContainer" parent="TestArchivePanel"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TestArchivePanel/VBoxContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="TestArchivePanel/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 24
text = "存档测试管理器(测试专用)"
horizontal_alignment = 1
[node name="QuitDebugModeButton" type="Button" parent="TestArchivePanel/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "关闭 Debug 模式"
[node name="存档管理" type="VBoxContainer" parent="TestArchivePanel/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="CurrentArchiveLabel" type="Label" parent="TestArchivePanel/VBoxContainer/存档管理"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.7, 0.9, 0.7, 1)
text = "当前使用存档1号存档主存档"
[node name="HSeparator" type="HSeparator" parent="TestArchivePanel/VBoxContainer/存档管理"]
layout_mode = 2
[node name="SaveContainer" type="HBoxContainer" parent="TestArchivePanel/VBoxContainer/存档管理"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="NameInput" type="LineEdit" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "输入存档名称"
[node name="SaveButton" type="Button" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
text = "保存当前进度"
[node name="RefreshButton" type="Button" parent="TestArchivePanel/VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "刷新"
[node name="HSeparator2" type="HSeparator" parent="TestArchivePanel/VBoxContainer/存档管理"]
layout_mode = 2
[node name="Label" type="Label" parent="TestArchivePanel/VBoxContainer/存档管理"]
layout_mode = 2
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
text = "手动存档列表:"
[node name="ScrollContainer" type="ScrollContainer" parent="TestArchivePanel/VBoxContainer/存档管理"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 380)
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
vertical_scroll_mode = 2
[node name="ArchiveGrid" type="GridContainer" parent="TestArchivePanel/VBoxContainer/存档管理/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 10
theme_override_constants/v_separation = 10
columns = 4

View File

@ -2,16 +2,12 @@ class_name GlobalConfig extends Resource
signal streamer_mode_updated signal streamer_mode_updated
#const DEBUG = true
static var DEBUG = false static var DEBUG = false
# 影响事件的 release_stage # 影响事件的 release_stage
# 0:demo # 0:demo
# 1:v1.0正式版 # 1:v1.0正式版
const RELEASE_STAGE := 1 const RELEASE_STAGE := 1
# 与 Editor 编辑器有 Debugger 连接
# static var EDITOR = false
# .res would be binary encoded, .tres is text encoded # .res would be binary encoded, .tres is text encoded
const RES_FILE_FORMAT = ".tres" const RES_FILE_FORMAT = ".tres"
@ -26,7 +22,7 @@ const CAPTION_OPTIONS_DICT = {0: ["上海话", "普通话"], 1: [""]}
const CAPTION_LOCALES_DICT = {0: ["zh_SH", "zh_CN"], 1: ["en"]} const CAPTION_LOCALES_DICT = {0: ["zh_SH", "zh_CN"], 1: ["en"]}
## layers ## layers
# 设置 # 设置, DebugPanel
const CANVAS_LAYER_SETTINGS = 30 const CANVAS_LAYER_SETTINGS = 30
# note # note
const CANVAS_LAYER_NOTE = 25 const CANVAS_LAYER_NOTE = 25
@ -74,9 +70,9 @@ signal current_selected_archive_id_changed
signal auto_save_enabled_changed signal auto_save_enabled_changed
signal auto_save_seconds_changed signal auto_save_seconds_changed
@export var version: int #存档版本
@export var debug_mode := false # 开启 debug 模式 @export var debug_mode := false # 开启 debug 模式
@export var skip_trailer := false # 跳过 trailer @export var skip_trailer := false # 跳过 trailer
@export var version: int #存档版本
@export var game_launched_times := 0 # 启动游戏次数 @export var game_launched_times := 0 # 启动游戏次数
@export var game_total_seconds := 0 # 游戏总时长 @export var game_total_seconds := 0 # 游戏总时长
@export var game_rounds := 1 # 当前周目数 @export var game_rounds := 1 # 当前周目数

View File

@ -31,7 +31,6 @@ static func _set_config(val: GlobalConfig) -> void:
config = val config = val
if not config or Engine.is_editor_hint(): if not config or Engine.is_editor_hint():
return return
_apply_debug_mode() _apply_debug_mode()
_apply_window_settings() _apply_window_settings()
_apply_audio_settings() _apply_audio_settings()
@ -46,12 +45,10 @@ static func _apply_debug_mode() -> void:
static func _apply_window_settings() -> void: static func _apply_window_settings() -> void:
var window = Engine.get_main_loop().root.get_window() var window = Engine.get_main_loop().root.get_window()
if config.window_fullscreen: if config.window_fullscreen:
window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN
else: else:
window.mode = Window.MODE_WINDOWED window.mode = Window.MODE_WINDOWED
window.always_on_top = config.window_top window.always_on_top = config.window_top
@ -65,7 +62,6 @@ static func _apply_audio_settings() -> void:
AudioServer.set_bus_volume_db( AudioServer.set_bus_volume_db(
AudioServer.get_bus_index(GlobalConfig.BUS_DIALOG), config.db_dialog AudioServer.get_bus_index(GlobalConfig.BUS_DIALOG), config.db_dialog
) )
prints( prints(
"config load volume_db settings (master, sfx, dialog): ", "config load volume_db settings (master, sfx, dialog): ",
config.db_master, config.db_master,

View File

@ -53,6 +53,13 @@ func get_ground() -> Ground2D:
return null return null
func get_current_scene_name() -> String:
var ground = get_ground()
if ground:
return GroundLoader.get_ground_scene_readable_name(ground.scene_name)
return ""
func get_camera_marker() -> CameraFocusMarker: func get_camera_marker() -> CameraFocusMarker:
var ground = get_ground() var ground = get_ground()
if ground: if ground:
@ -356,6 +363,15 @@ func enter_main_scene() -> void:
get_tree().paused = false get_tree().paused = false
#### Savings
var savings_scene = preload("uid://d4jeeteyq8kk3")
func show_savings() -> void:
get_tree().current_scene.add_child(savings_scene.instantiate())
#### UX: settings; panel: note, bag, memory #### #### UX: settings; panel: note, bag, memory ####
var panel_scene = preload("uid://ddlwnsccsmr8u") var panel_scene = preload("uid://ddlwnsccsmr8u")
@ -394,7 +410,7 @@ func show_settings() -> void:
#### 游戏场景树暂停计数器设置、memory、bag 等菜单都会导致 pause #### 游戏场景树暂停计数器设置、memory、bag 等菜单都会导致 pause
## 目前有(5类): settings, panel, bag, note, memory ## 目前有(6类): settings, panel, bag, note, memory, savings
var pause_counter_arr: Array[String] = [] var pause_counter_arr: Array[String] = []
var pause_counter_mutex := Mutex.new() var pause_counter_mutex := Mutex.new()

View File

@ -211,6 +211,11 @@ panel={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
] ]
} }
savings={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":96,"key_label":0,"unicode":96,"location":0,"echo":false,"script":null)
]
}
[internationalization] [internationalization]

View File

@ -69,11 +69,11 @@ func _ready() -> void:
if restarting: if restarting:
print("restarting: skip ground _ready()") print("restarting: skip ground _ready()")
return return
_setup_scene()
_validate_scene_name() _validate_scene_name()
_set_camera_and_player_boundary() _set_camera_and_player_boundary()
if Engine.is_editor_hint(): if Engine.is_editor_hint():
return return
_setup_scene()
_setup_runtime() _setup_runtime()

View File

@ -6,42 +6,42 @@ const MIN_TRANSITION_TIME := 0.6
const EASE_DURATION := 0.3 const EASE_DURATION := 0.3
# Scene name to path mapping # Scene name to path mapping
const GROUND_SCENE_PATH_DICT = { const GROUND_SCENE_PATH_DICT: Dictionary[String, Dictionary] = {
"c01_s05": "uid://dlx5xxbg53rb8", "c01_s05": {"path": "uid://dlx5xxbg53rb8", "name": "院长房间"},
"c01_s06": "uid://bx16c8nn32f40", "c01_s06": {"path": "uid://bx16c8nn32f40", "name": "孤儿院长廊"},
"c01_s07": "uid://ds2iyfndwamiy", "c01_s07": {"path": "uid://ds2iyfndwamiy", "name": "书店外"},
"c01_s08": "uid://cwu4dhayra8pg", "c01_s08": {"path": "uid://cwu4dhayra8pg", "name": "书店"},
"c01_s09": "uid://c777lv8mjojcw", "c01_s09": {"path": "uid://c777lv8mjojcw", "name": "公寓楼外"},
"c01_s10": "uid://be57l2o3vxxtm", "c01_s10": {"path": "uid://be57l2o3vxxtm", "name": "公寓楼道"},
"c01_s11": "uid://coiumaaenimbc", "c01_s11": {"path": "uid://coiumaaenimbc", "name": "黄包车"},
"c01_s12": "uid://bol5hl68pbpgq", "c01_s12": {"path": "uid://bol5hl68pbpgq", "name": "诡异书店外"},
"c02_s01": "uid://bbs7yy5aofw1v", "c02_s01": {"path": "uid://bbs7yy5aofw1v", "name": "公寓门口"},
"c02_s02": "uid://brck77w81fhvc", "c02_s02": {"path": "uid://brck77w81fhvc", "name": "公寓楼道"},
"c02_s03": "uid://djc2uaefhmu7", "c02_s03": {"path": "uid://djc2uaefhmu7", "name": "一楼院子"},
"c02_s04": "uid://bivc5cdap370p", "c02_s04": {"path": "uid://bivc5cdap370p", "name": "一楼保卫科"},
"c02_s05": "uid://cp8d3ag5nbjq0", "c02_s05": {"path": "uid://cp8d3ag5nbjq0", "name": "一楼内侧楼道"},
"c02_s06": "uid://cootarwb44vvh", "c02_s06": {"path": "uid://cootarwb44vvh", "name": "二楼楼道"},
"c02_s07": "uid://t4xjt774ngwh", "c02_s07": {"path": "uid://t4xjt774ngwh", "name": "二楼内侧楼道"},
"c02_s08": "uid://ce2vyyg2reg52", "c02_s08": {"path": "uid://ce2vyyg2reg52", "name": "瞎子卧室"},
"c02_s09": "uid://ryups1dnwdto", "c02_s09": {"path": "uid://ryups1dnwdto", "name": "裂缝空间"},
"c02_s10": "uid://dny21yhtuteap", "c02_s10": {"path": "uid://dny21yhtuteap", "name": "空房间"},
"c02_s11": "uid://dq41rvwl5hyrk", # 注:该场景合并在了 c02_s03 院子中 "c02_s11": {"path": "uid://dq41rvwl5hyrk", "name": "一楼火灾"},
"c02_s12": "uid://da4cuf2i3nwpj", "c02_s12": {"path": "uid://da4cuf2i3nwpj", "name": "盒子猫安全屋"},
"c02_s13": "uid://bvjutch6jex0v", "c02_s13": {"path": "uid://bvjutch6jex0v", "name": "盒子猫二楼"},
"c02_s14": "uid://d0p4x5st2r315", "c02_s14": {"path": "uid://d0p4x5st2r315", "name": "盒子猫二楼内侧"},
"c02_s15": "uid://b21p53g42j2nt", "c02_s15": {"path": "uid://b21p53g42j2nt", "name": "盒子猫一楼内侧"},
"c02_s16": "uid://22hc3oe8t0id", "c02_s16": {"path": "uid://22hc3oe8t0id", "name": "盒子猫三楼内侧"},
"c02_s17": "uid://cbr6gbgrl2wb1", "c02_s17": {"path": "uid://cbr6gbgrl2wb1", "name": "盒子猫三楼"},
"c02_s18": "uid://d27gv3pbkn4b8", "c02_s18": {"path": "uid://d27gv3pbkn4b8", "name": "盒子猫一楼"},
"c03_s01": "uid://dlrbhfvnd3cs0", # s01_三楼 "c03_s01": {"path": "uid://dlrbhfvnd3cs0", "name": "三楼楼道"},
"c03_s02": "uid://ctwy1ubhm68la", # s03_瞎子卧室 "c03_s02": {"path": "uid://ctwy1ubhm68la", "name": "瞎子卧室"},
"c03_s03": "uid://bsqt2c061fmin", # s02_瞎子理发店 "c03_s03": {"path": "uid://bsqt2c061fmin", "name": "瞎子理发店"},
"c03_s04": "uid://c7c88hg2cl1j7", # s04_李癞房间 "c03_s04": {"path": "uid://c7c88hg2cl1j7", "name": "李癞房间"},
"c03_s05": "uid://6ehb3ux2kilu", # s05_肉铺 "c03_s05": {"path": "uid://6ehb3ux2kilu", "name": "胖子肉铺"},
"c03_s06": "uid://cxacrp8mrrbry", # s06_胖子卧室 "c03_s06": {"path": "uid://cxacrp8mrrbry", "name": "胖子卧室"},
"c03_s07": "uid://c67732f2we13j", # s07_屠宰间 "c03_s07": {"path": "uid://c67732f2we13j", "name": "屠宰间"},
"c03_s08": "uid://bixdbbyhroepi", # s08_囚室 "c03_s08": {"path": "uid://bixdbbyhroepi", "name": "囚室"},
"c03_s09": "uid://dfln301xllqpn", # s09_棺材房 "c03_s09": {"path": "uid://dfln301xllqpn", "name": "棺材房"},
} }
# Exports # Exports
@ -117,6 +117,20 @@ func _load_save() -> void:
entrance_portal = archive.entrance_portal entrance_portal = archive.entrance_portal
static func get_ground_scene_uid(scene_name: String) -> String:
if GROUND_SCENE_PATH_DICT.has(scene_name):
return GROUND_SCENE_PATH_DICT[scene_name]["path"]
printerr("GroundLoader get_ground_scene_uid: scene not found:", scene_name)
return ""
static func get_ground_scene_readable_name(scene_name: String) -> String:
if GROUND_SCENE_PATH_DICT.has(scene_name):
return GROUND_SCENE_PATH_DICT[scene_name]["name"]
printerr("GroundLoader get_ground_scene_readable_name: scene not found:", scene_name)
return scene_name
func toggle_mask( func toggle_mask(
display: bool, wait_time: float, ease_min_duration := EASE_DURATION, mask_color := Color.BLACK display: bool, wait_time: float, ease_min_duration := EASE_DURATION, mask_color := Color.BLACK
) -> Tween: ) -> Tween:
@ -259,7 +273,7 @@ func _update_player_position_from_archive() -> void:
func _load_ground_node(scene_name: String) -> Ground2D: func _load_ground_node(scene_name: String) -> Ground2D:
if not GROUND_SCENE_PATH_DICT.has(scene_name): if not GROUND_SCENE_PATH_DICT.has(scene_name):
return null return null
var path = GROUND_SCENE_PATH_DICT[scene_name] var path = get_ground_scene_uid(scene_name)
var scene: PackedScene = _load_scene_resource(path) var scene: PackedScene = _load_scene_resource(path)
if not scene: if not scene:
return null return null
@ -298,4 +312,4 @@ func _preload_neighbor_scenes() -> void:
print("preload neighbor scenes:", scene_names) print("preload neighbor scenes:", scene_names)
for scene_name in scene_names: for scene_name in scene_names:
if GROUND_SCENE_PATH_DICT.has(scene_name): if GROUND_SCENE_PATH_DICT.has(scene_name):
ResourceLoader.load_threaded_request(GROUND_SCENE_PATH_DICT[scene_name]) ResourceLoader.load_threaded_request(get_ground_scene_uid(scene_name))

View File

@ -16,6 +16,10 @@ func _ready() -> void:
# settings.exited.connect(grab_focus) # settings.exited.connect(grab_focus)
var debug_button_last_press_msec := 0
var debug_button_pressed_count := 0
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("escape"): if event.is_action_pressed("escape"):
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
@ -33,6 +37,23 @@ func _unhandled_input(event: InputEvent) -> void:
elif event.is_action_pressed("panel"): elif event.is_action_pressed("panel"):
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
SceneManager.show_panel() SceneManager.show_panel()
elif event.is_action_pressed("savings"):
if GlobalConfig.DEBUG:
get_viewport().set_input_as_handled()
SceneManager.show_savings()
else:
# 连续按 5 次开启 debug mode
var time = Time.get_ticks_msec()
if time - debug_button_last_press_msec < 500:
debug_button_pressed_count += 1
else:
debug_button_pressed_count = 1
debug_button_last_press_msec = time
if debug_button_pressed_count >= 5:
get_viewport().set_input_as_handled()
# 不写入配置
GlobalConfig.DEBUG = true
SceneManager.show_savings()
var vignette_tween: Tween var vignette_tween: Tween
@ -48,6 +69,3 @@ func tween_vignette(ratio := 0.5, duration := 0.3, color := Color.RED):
vignette_tween.parallel().tween_property( vignette_tween.parallel().tween_property(
vignette.material, "shader_parameter/vignette_rgb", color, duration vignette.material, "shader_parameter/vignette_rgb", color, duration
) )