From 675ae26d6ab26c08adc53bff2be32abb3e7dfc37 Mon Sep 17 00:00:00 2001 From: cakipaul Date: Tue, 21 Jan 2025 20:41:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AD=98=E6=A1=A3=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E9=80=BB=E8=BE=91=E4=B8=8E=E5=91=BD=E5=90=8D=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=EF=BC=9B=E8=AF=BB=E5=8F=96=E5=AD=98=E6=A1=A3=E5=90=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- addons/dialogue_manager/dialogue_manager.gd | 3 +- manager/archive_manager/archive_manager.gd | 77 +++++--- manager/archive_manager/assembled_archive.gd | 19 +- manager/config_manager/global_config.gd | 1 - manager/scene/scene_manager.gd | 28 +-- scene/dialog/balloon_debug.gd | 188 +++++++++++++++++++ scene/dialog/balloon_debug.tscn | 7 +- scene/ground/ground_loader.gd | 49 +++-- scene/ground/scene/c01/s05_animation.gd | 24 ++- scene/index_page.gd | 2 +- scene/prop/prop_bag.gd | 23 ++- scene/prop/prop_bag.tscn | 42 ++--- scene/prop/prop_hud.tscn | 1 + scene/settings/settings.gd | 5 +- 15 files changed, 367 insertions(+), 112 deletions(-) create mode 100644 scene/dialog/balloon_debug.gd diff --git a/README.md b/README.md index 01cae2c2..7cbd1692 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,14 @@ ## 存档结构 -- 开发阶段存档:save0 -- 正常继续游戏存档:save1 +- 存档命名为:"save"+三位数字 +- 如果不足三位数字,则会忽略;超过三位数字不会忽略,可以正常读取 +- 这是为了方便多存档功能进行文本排序等 +- 开发阶段 debug 存档:save000 +- 开发阶段 index 页面进入的游戏存档:save001 +- 发行后,玩家游戏过程中,自动保存的存档编号范围(最多 10 个,循环保存):000-019 +- 发行后,玩家游戏过程中,手动保存的存档编号范围(最多 10 个栏位):050-059 +- 发行后,玩家游戏过程中最多有以上 20 个存档 每个场景都有一份 GroundArchive 存档,通过 ArchiveManager.archive.ground_archive() 可以获得。 diff --git a/addons/dialogue_manager/dialogue_manager.gd b/addons/dialogue_manager/dialogue_manager.gd index e3439b1b..1ceede8d 100644 --- a/addons/dialogue_manager/dialogue_manager.gd +++ b/addons/dialogue_manager/dialogue_manager.gd @@ -285,7 +285,8 @@ func show_dialogue_balloon_scene(balloon_scene, resource: DialogueResource, titl balloon_scene = balloon_scene.instantiate() var balloon: Node = balloon_scene - get_current_scene.call().add_child(balloon) + if balloon.get_parent() == null: + get_current_scene.call().add_child(balloon) if balloon.has_method(&"start"): balloon.start(resource, title, extra_game_states) elif balloon.has_method(&"Start"): diff --git a/manager/archive_manager/archive_manager.gd b/manager/archive_manager/archive_manager.gd index 78ed14c4..66d2aa5a 100644 --- a/manager/archive_manager/archive_manager.gd +++ b/manager/archive_manager/archive_manager.gd @@ -9,7 +9,7 @@ static var user_root_dir := "user://data/" # must end with "/" static var archive_dir := "user://data/archives/" static var archive_prefix := "save" -var archives: Array[int] # archive id list in ascending order +var archives := {} var autosave_timer := Timer.new() @@ -30,7 +30,8 @@ func _ready() -> void: if archives.size() == 0: create_and_use_new_archive() else: - GlobalConfigManager.config.current_selected_archive_id = archives[0] + # debug 模式下默认使用 0 号存档 + GlobalConfigManager.config.current_selected_archive_id = 0 func _notification(what): @@ -48,34 +49,35 @@ func _notification(what): func _on_archive_id_changed(): var selected_id = GlobalConfigManager.config.current_selected_archive_id + if selected_id < 0: + return if archive: if selected_id != archive.archive_id: ResourceSaver.save(archive) archive = null else: return - if selected_id < 0: - return - var path = archive_dir + archive_prefix + str(selected_id) + GlobalConfig.RES_FILE_FORMAT - archive = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE_DEEP) - if !archive: + if not archives.has(selected_id): create_and_use_new_archive(selected_id) SceneManager.pop_notification("已创建新存档") else: load_archive() - # emit signal - archive_loaded.emit() func check_autosave_options(): - if not GlobalConfigManager.config.auto_save_enabled: - autosave_timer.stop() - elif archive and GlobalConfigManager.config.auto_save_seconds > 1: + if ( + GlobalConfigManager.config.auto_save_enabled + and archive + and GlobalConfigManager.config.auto_save_seconds > 1 + ): # reset left time autosave_timer.stop() autosave_timer.one_shot = false autosave_timer.wait_time = GlobalConfigManager.config.auto_save_seconds autosave_timer.start() + else: + autosave_timer.stop() + if GlobalConfig.DEBUG: print( "check_autosave_option: ", @@ -106,15 +108,24 @@ func _check_dirs_and_archives() -> bool: _handle_load_error("存档目录", "读取") # TODO pop up a dialog to inform the user return false - archives.clear() var files = archive_dir_access.get_files() + files.sort() # get archive number for file in files: - if file.begins_with(archive_prefix): - var id_str = file.substr(archive_prefix.length()) + if file.begins_with(archive_prefix) and file.ends_with(GlobalConfig.RES_FILE_FORMAT): + var id_str = file.get_basename().substr(archive_prefix.length()) + # 低于三位数的 id 会被忽略 + if id_str.length() < 3: + continue var id = int(id_str) - archives.append(id) - archives.sort() + # 读取范围是 0-99 + if id < 0 or id > 99: + continue + var path = archive_dir + file + if not archives.has(id): + archives[id] = ResourceLoader.load( + path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP + ) return true @@ -122,23 +133,35 @@ func _check_dirs_and_archives() -> bool: func create_and_use_new_archive(id := -1) -> void: _check_dirs_and_archives() archive = AssembledArchive.new() - var archive_path = archive_dir + archive_prefix + str(id) + GlobalConfig.RES_FILE_FORMAT + var archive_path = _get_archive_path(id) if id < 0: id = 0 # find a new id - archive_path = (archive_dir + archive_prefix + str(id) + GlobalConfig.RES_FILE_FORMAT) + archive_path = _get_archive_path(id) while FileAccess.file_exists(archive_path): id += 1 - archive_path = (archive_dir + archive_prefix + str(id) + GlobalConfig.RES_FILE_FORMAT) + archive_path = _get_archive_path(id) archive.resource_path = archive_path archive.archive_id = id archive.created_time = Time.get_datetime_string_from_system(false, true) ResourceSaver.save(archive, archive_path) - archives.append(id) + archives[id] = archive # this will auto trigger signal and load the new archive GlobalConfigManager.config.current_selected_archive_id = id +# 超过 999 个存档会出问题;不过这个游戏不会有这么多存档 +func _get_archive_path(id: int) -> String: + var id_str := "" + if id < 10: + id_str = "00" + str(id) + elif id < 100: + id_str = "0" + str(id) + else: + id_str = str(id) + return archive_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT + + func save_all() -> void: # save config var config = GlobalConfigManager.config @@ -147,6 +170,7 @@ func save_all() -> void: # player_global_position var player = SceneManager.get_player() as MainPlayer + # 在此处保存 player 的位置信息 if archive and player: archive.player_global_position_x = player.global_position.x archive.player_direction = player.facing_direction @@ -179,15 +203,14 @@ func load_config() -> void: func load_archive() -> void: _check_dirs_and_archives() var selected_id = GlobalConfigManager.config.current_selected_archive_id + if archive and selected_id == archive.archive_id: + return if not archives.has(selected_id): _handle_load_error(str(selected_id) + " 号存档", "查找") return - var path = archive_dir + archive_prefix + str(selected_id) + GlobalConfig.RES_FILE_FORMAT - archive = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE_DEEP) - if !archive: - _handle_load_error(str(selected_id) + " 号存档", "加载") - return - archive.resource_path = path + archive = archives[selected_id] + # emit signal + archive_loaded.emit() check_autosave_options() diff --git a/manager/archive_manager/assembled_archive.gd b/manager/archive_manager/assembled_archive.gd index 3e02ab0b..ced18e06 100644 --- a/manager/archive_manager/assembled_archive.gd +++ b/manager/archive_manager/assembled_archive.gd @@ -1,13 +1,22 @@ class_name AssembledArchive extends Resource @export var archive_id := 0 -@export var entrance_portal := "" -@export var current_scene := "c00_s00": +# TODO: 注意设置游戏起点 +@export var entrance_portal := "left" +@export var current_scene := "c01_s05": set(val): current_scene = val - if val and val.length() == 7: - current_chapter = int(val.substr(1, 2)) - current_section = int(val.substr(5)) + if val and val.length() != 7: + printerr("[AssembledArchive] current_scene is not valid: " + val) + return + current_chapter = int(val.substr(1, 2)) + current_section = int(val.substr(5)) + # 尝试后台预先加载该场景 + if GroundLoader.GROUND_SCENE_PATH_DICT.has(val): + var path = GroundLoader.GROUND_SCENE_PATH_DICT[val] + if GlobalConfig.DEBUG: + print("[AssembledArchive] preload scene: " + path) + ResourceLoader.load_threaded_request(path, "PackedScene") # current_chapter and current_section are derived from current_scene @export var current_chapter := 0 @export var current_section := 0 diff --git a/manager/config_manager/global_config.gd b/manager/config_manager/global_config.gd index 83aaf4e3..d2b206aa 100644 --- a/manager/config_manager/global_config.gd +++ b/manager/config_manager/global_config.gd @@ -19,7 +19,6 @@ const CANVAS_LAYER_HD_ENTITY = 1 const CHARACTER_COLOR_MAP = { "default": Color.LIGHT_SEA_GREEN, - "音效": Color.DARK_VIOLET, # 非 bug 模式时不显示 "吕萍": Color.ORANGE, "雾": Color.MEDIUM_SEA_GREEN, "获得": Color.WHITE, diff --git a/manager/scene/scene_manager.gd b/manager/scene/scene_manager.gd index 91282e03..36a3c313 100644 --- a/manager/scene/scene_manager.gd +++ b/manager/scene/scene_manager.gd @@ -9,14 +9,6 @@ enum VIBE { @export var first_entered = true -var prop_bag_instance = preload("res://scene/prop/prop_bag.tscn").instantiate() - - -func _ready() -> void: - add_child(prop_bag_instance) - prop_bag_instance.visible = false - - #### Ground and Loader #### @@ -133,16 +125,19 @@ func set_player_boundary(rect: Rect2) -> void: printerr("Player node not found") +var balloon_node + + func pop_debug_dialog_info(character: String, content: String): if GlobalConfig.DEBUG: + if not is_instance_valid(balloon_node): + balloon_node = preload("res://scene/dialog/balloon_debug.tscn").instantiate() var title = "title" var body = "~ " + title + "\n" body += character + ": " + content + "\n" body += "=> END" var res = DialogueManager.create_resource_from_text(body) - DialogueManager.show_dialogue_balloon_scene( - preload("res://scene/dialog/balloon_debug.tscn"), res, title - ) + DialogueManager.show_dialogue_balloon_scene(balloon_node, res, title) #### Prop #### @@ -219,14 +214,11 @@ func checkout_index_page(): get_tree().change_scene_to_packed(preload("res://scene/index_page.tscn")) +var prop_bag = preload("res://scene/prop/prop_bag.tscn") + + func show_bag(): - freeze_player(0) - prop_bag_instance.visible = true - - -func hide_bag(): - release_player() - prop_bag_instance.visible = false + get_node("/root/Main").add_child(prop_bag.instantiate()) func quit_game(): diff --git a/scene/dialog/balloon_debug.gd b/scene/dialog/balloon_debug.gd new file mode 100644 index 00000000..aba575c9 --- /dev/null +++ b/scene/dialog/balloon_debug.gd @@ -0,0 +1,188 @@ +extends CanvasLayer + +@export var force_locale :String: + set(val): + force_locale = val + if force_locale: + TranslationServer.set_locale(force_locale) + +@export var auto_play := true + +## The action to use for advancing the dialogue +@export var next_action: StringName = &"interact" + +## The action to use to skip typing the dialogue +@export var skip_action: StringName = &"" +@onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer + +@onready var balloon: Control = %Balloon +@onready var character_label: RichTextLabel = %CharacterLabel +@onready var dialogue_label: DialogueLabel = %DialogueLabel +@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu + +## The dialogue resource +var resource: DialogueResource + +## Temporary game states +var temporary_game_states: Array = [] + +## See if we are waiting for the player +var is_waiting_for_input: bool = false + +## See if we are running a long mutation and should hide the balloon +var will_hide_balloon: bool = false + +const CHARACTER_COLOR_MAP = { + "音效": Color.DARK_VIOLET, + "default": Color.LIGHT_SALMON, +} + +## The current line +var dialogue_line: DialogueLine: + set(next_dialogue_line): + is_waiting_for_input = false + balloon.focus_mode = Control.FOCUS_ALL + balloon.grab_focus() + # The dialogue has finished so close the balloon + if not next_dialogue_line: + queue_free() + return + # If the node isn't ready yet then none of the labels will be ready yet either + if not is_node_ready(): + await ready + dialogue_line = next_dialogue_line + character_label.visible = not dialogue_line.character.is_empty() + character_label.text = tr(dialogue_line.character, "dialogue") + #主要角色颜色 + if CHARACTER_COLOR_MAP.has(dialogue_line.character): + character_label.modulate = CHARACTER_COLOR_MAP[dialogue_line.character] + else: + character_label.modulate = CHARACTER_COLOR_MAP["default"] + + #print(dialogue_line.character,character_label.modulate) + + dialogue_label.hide() + _setup_content_text() + dialogue_label.dialogue_line = dialogue_line + + responses_menu.hide() + responses_menu.set_responses(dialogue_line.responses) + + # Show our balloon + balloon.show() + will_hide_balloon = false + + dialogue_label.show() + if not dialogue_line.text.is_empty(): + dialogue_label.type_out() + + is_waiting_for_input = true + balloon.focus_mode = Control.FOCUS_ALL + balloon.grab_focus() + await get_tree().create_timer(2.0).timeout + next(next_dialogue_line.next_id) + + # 如果当前 line 运行结束,则 queue free 释放资源 + if dialogue_line == next_dialogue_line: + queue_free() + +func _ready() -> void: + layer = GlobalConfig.CANVAS_LAYER_DIALOG + balloon.hide() + Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated) + + # If the responses menu doesn't have a next action set, use this one + if responses_menu.next_action.is_empty(): + responses_menu.next_action = next_action + + +# 自定义获得文本,从 tags 中获取备注参数 +func _setup_content_text() -> void: + var translation_key = dialogue_line.translation_key + var text + if translation_key: + text = tr(translation_key, "dialogue") + if text == translation_key: + text = dialogue_line.text + else: + text = dialogue_line.text + if dialogue_line.tags.has("shake"): + # eg. [shake rate=20 level=10][/shake] + text = "[shake rate=20 level=6]" + text + "[/shake]" + character_label.text = "[shake rate=20 level=6]" + character_label.text + "[/shake]" + if dialogue_line.tags.has("wave"): + # eg. [wave amp=25 freq=5][/wave] + text = "[wave amp=15 freq=5]" + text + "[/wave]" + character_label.text = "[wave amp=15 freq=5]" + character_label.text + "[/wave]" + if dialogue_line.tags.has("item"): + # orange color + text = "[color=orange]" + text + "[/color]" + dialogue_line.text = text + + +func _notification(what: int) -> void: + # Detect a change of locale and update the current dialogue line to show the new language + if what == NOTIFICATION_TRANSLATION_CHANGED and is_instance_valid(dialogue_label): + var visible_ratio = dialogue_label.visible_ratio + self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id) + if visible_ratio < 1: + dialogue_label.skip_typing() + +## Start some dialogue +func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void: + temporary_game_states = [self] + extra_game_states + is_waiting_for_input = false + resource = dialogue_resource + self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states) + +## Go to the next line +func next(next_id: String) -> void: + self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states) + +#region Signals + +func _on_mutated(_mutation: Dictionary) -> void: + is_waiting_for_input = false + will_hide_balloon = true + get_tree().create_timer(0.1).timeout.connect(func(): + if will_hide_balloon: + will_hide_balloon = false + balloon.hide() + ) + +# debug balloon 无需接管输入事件 + +# func _unhandled_input(_event: InputEvent) -> void: +# # Only the balloon is allowed to handle input while it's showing +# get_viewport().set_input_as_handled() + + +# func _on_balloon_gui_input(event: InputEvent) -> void: +# # See if we need to skip typing of the dialogue +# if dialogue_label.is_typing: +# if event.is_action_pressed("interact") or event.is_action_pressed("cancel"): +# dialogue_label.skip_typing() +# get_viewport().set_input_as_handled() +# return + +# # if not is_waiting_for_input: return +# if dialogue_line.responses.size() > 0: return + +# # When there are no response options the balloon itself is the clickable thing +# # get_viewport().set_input_as_handled() + +# #if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: +# #next(dialogue_line.next_id) +# #elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon: +# #next(dialogue_line.next_id) + +# if event.is_action_pressed("interact") or event.is_action_pressed("cancel"): +# # if event.is_action_pressed("interact") and get_viewport().gui_get_focus_owner() == balloon: +# get_viewport().set_input_as_handled() +# manually_skipped_line.emit() +# next(dialogue_line.next_id) + +func _on_responses_menu_response_selected(response: DialogueResponse) -> void: + next(response.next_id) + +#endregion diff --git a/scene/dialog/balloon_debug.tscn b/scene/dialog/balloon_debug.tscn index 2408a8d2..cc576ecb 100644 --- a/scene/dialog/balloon_debug.tscn +++ b/scene/dialog/balloon_debug.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=10 format=3 uid="uid://bni3dt3xcb72o"] -[ext_resource type="Script" path="res://scene/dialog/balloon.gd" id="1_vvk1n"] +[ext_resource type="Script" path="res://scene/dialog/balloon_debug.gd" id="1_jfh0c"] [ext_resource type="FontFile" uid="uid://dr8bp6p7byb37" path="res://asset/font/字体/方正楷体简体.TTF" id="2_qwe3r"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="3_jo1vi"] [ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="4_4netn"] @@ -58,7 +58,7 @@ Panel/styles/panel = SubResource("StyleBoxEmpty_jydvi") [node name="Balloon" type="CanvasLayer"] layer = 100 -script = ExtResource("1_vvk1n") +script = ExtResource("1_jfh0c") metadata/_edit_vertical_guides_ = [-78.0] metadata/_edit_horizontal_guides_ = [276.0] @@ -134,7 +134,8 @@ theme_override_constants/shadow_offset_x = 1 theme_override_font_sizes/normal_font_size = 9 text = "Dialogue..." autowrap_mode = 0 -seconds_per_step = 0.06 +seconds_per_step = 0.01 +seconds_per_pause_step = 0.01 [node name="Responses" type="MarginContainer" parent="Balloon"] layout_mode = 1 diff --git a/scene/ground/ground_loader.gd b/scene/ground/ground_loader.gd index 511b6356..50341964 100644 --- a/scene/ground/ground_loader.gd +++ b/scene/ground/ground_loader.gd @@ -21,18 +21,31 @@ var first_entered := true var ground: Ground2D var display_mask_time = 0.0 -var scenes_dir = "res://scene/ground/scene/" + +static func _static_init() -> void: + _read_grounds() + # 场景名字映射到路径 -var ground_scene_path_dict = {} +static var GROUND_SCENE_PATH_DICT = {} + + +static func _read_grounds() -> void: + var scenes_dir = "res://scene/ground/scene/" + # read grounds + var dir = DirAccess.open(scenes_dir) + for c_dir in dir.get_directories(): + var c_path = scenes_dir + c_dir + "/" + for s_file in DirAccess.open(c_path).get_files(): + if s_file.ends_with(".tscn"): + var s_path = c_path + s_file + GROUND_SCENE_PATH_DICT[c_dir.substr(0, 3) + "_" + s_file.substr(0, 3)] = s_path func _ready() -> void: mask_layer.layer = GlobalConfig.CANVAS_LAYER_GROUND_MASK mask.visible = true mask.color.a = 0.0 - # grounds - _read_grounds() ground = get_node_or_null("Ground") as Ground2D if ground: ground.queue_free() @@ -43,22 +56,6 @@ func _ready() -> void: transition_to_scene(current_scene, entrance_portal, true) -func _read_grounds() -> void: - # read grounds - var dir = DirAccess.open(scenes_dir) - for c_dir in dir.get_directories(): - var c_path = scenes_dir + c_dir + "/" - for s_file in DirAccess.open(c_path).get_files(): - if s_file.ends_with(".tscn"): - var s_path = c_path + s_file - ground_scene_path_dict[c_dir.substr(0, 3) + "_" + s_file.substr(0, 3)] = s_path - # # 确保每个 ground 都初始化 archive - # for key in ground_scene_path_dict.keys(): - # if GlobalConfig.DEBUG: - # print("check ground_archive:", key) - # ArchiveManager.archive.ground_archive(key) - - func _load_save(): # 强制覆盖 archive 记录 if force_archive_scene or force_archive_portal: @@ -89,7 +86,7 @@ func _toggle_mask(display: bool, _immediately: bool) -> Tween: func transition_to_scene(scene_name: String, portal: String, immediately: bool) -> void: - var scene_path = ground_scene_path_dict.get(scene_name) + var scene_path = GROUND_SCENE_PATH_DICT.get(scene_name) if scene_path: current_scene = scene_name entrance_portal = portal @@ -153,9 +150,9 @@ func _update_player_position_from_archive(): func _load_ground_node(scene_name: String) -> Node2D: - if not ground_scene_path_dict.has(scene_name): + if not GROUND_SCENE_PATH_DICT.has(scene_name): return null - var path = ground_scene_path_dict[scene_name] + var path = GROUND_SCENE_PATH_DICT[scene_name] var scene = ResourceLoader.load(path) as PackedScene if scene: var instance = scene.instantiate() as Node2D @@ -177,11 +174,11 @@ func _post_transition(): var portal = node as Portal2D if not portal or not portal.target_scene: continue - if ground_scene_path_dict.has(portal.target_scene): + if GROUND_SCENE_PATH_DICT.has(portal.target_scene): scene_names.append(portal.target_scene) if scene_names: for scene_name in scene_names: - ResourceLoader.load_threaded_request(ground_scene_path_dict[scene_name]) + ResourceLoader.load_threaded_request(GROUND_SCENE_PATH_DICT[scene_name]) if GlobalConfig.DEBUG: print("preload neighbor scenes:", scene_names) @@ -192,7 +189,7 @@ var last_modify_time = 0 # DEBUG 时重新加载资源 func _watch_scene_update(): - var scene_path = ground_scene_path_dict[current_scene] + var scene_path = GROUND_SCENE_PATH_DICT[current_scene] if scene_path: last_modify_time = FileAccess.get_modified_time(scene_path) if not update_watcher: diff --git a/scene/ground/scene/c01/s05_animation.gd b/scene/ground/scene/c01/s05_animation.gd index 46182d3c..a048fc15 100644 --- a/scene/ground/scene/c01/s05_animation.gd +++ b/scene/ground/scene/c01/s05_animation.gd @@ -8,6 +8,7 @@ var paper: Interactable2D var right_door: Portal2D var piano: Interactable2D + # 覆盖该方法 func _default_data() -> Dictionary: return {} @@ -44,7 +45,7 @@ func _on_deploy_layer_ready() -> void: # 画框未正位时,首先允许互动相框 frame.read_note.connect(_on_note_read, CONNECT_ONE_SHOT) ambush.triggered.connect(_on_ambush_triggered) - + # 纸片状态 if frame_relocated: # 画框已经正位,纸片已经被拾取 @@ -53,7 +54,7 @@ func _on_deploy_layer_ready() -> void: paper.enabled = false right_door.enabled = true else: - # 画框已经正位,纸片未被拾取,直接掉落 + # 画框已经正位,纸片未被拾取,直接掉落 paper.visible = true paper.enabled = true paper.interacted.connect(_on_paper_interacted, CONNECT_ONE_SHOT) @@ -94,5 +95,22 @@ func _on_paper_interacted(): SceneManager.pop_debug_dialog_info("音效", "开门声") $"../sfx_door_open".play() + +# 钢琴音效,每次按下播放不同音符 +# 最长间隔时间 +var piano_time_epsilon := 2.0 +# 钢琴音符最大编号 +var piano_id_max := 10 +var piano_last_played_time := 0.0 +var piano_id := 0 + + func _on_piano_interacted(): - SceneManager.freeze_and_play(0.0, "钢琴") \ No newline at end of file + # 播放音符 + var now = Time.get_ticks_msec() + if now - piano_last_played_time < piano_time_epsilon * 1000: + piano_id = wrapi(piano_id + 1, 0, piano_id_max) + else: + piano_id = 0 + piano_last_played_time = now + SceneManager.pop_debug_dialog_info("音效", "钢琴声: " + str(piano_id)) diff --git a/scene/index_page.gd b/scene/index_page.gd index 73219aac..8f9185a6 100644 --- a/scene/index_page.gd +++ b/scene/index_page.gd @@ -51,8 +51,8 @@ func _on_resume_pressed(): if GlobalConfig.DEBUG: print("Resume") if ArchiveManager.archives.has(1): + # 设置 current_selected_archive_id 后,存档会自动加载 GlobalConfigManager.config.current_selected_archive_id = 1 - ArchiveManager.load_archive() else: ArchiveManager.create_and_use_new_archive(1) _enter_main_scene() diff --git a/scene/prop/prop_bag.gd b/scene/prop/prop_bag.gd index 6e73d500..3fbcb98e 100644 --- a/scene/prop/prop_bag.gd +++ b/scene/prop/prop_bag.gd @@ -1,5 +1,24 @@ -extends CanvasLayer +extends Panel func _ready(): - layer = GlobalConfig.CANVAS_LAYER_BAG \ No newline at end of file + get_parent().layer = GlobalConfig.CANVAS_LAYER_BAG + _load_inventory() + SceneManager.lock_player() + get_tree().paused = true + + +func _load_inventory(): + pass + + +func _unhandled_input(event: InputEvent) -> void: + if ( + event.is_action_pressed("bag") + or event.is_action_pressed("cancel") + or event.is_action_pressed("escape") + ): + get_viewport().set_input_as_handled() + get_tree().paused = false + SceneManager.unlock_player() + queue_free() diff --git a/scene/prop/prop_bag.tscn b/scene/prop/prop_bag.tscn index 9a104957..938eefd3 100644 --- a/scene/prop/prop_bag.tscn +++ b/scene/prop/prop_bag.tscn @@ -4,17 +4,17 @@ [ext_resource type="Texture2D" uid="uid://dsj3l0baqg1g7" path="res://asset/art/ui/小蝶笔记.png" id="2_3s314"] [node name="PropBag" type="CanvasLayer"] -script = ExtResource("1_f3hpu") +process_mode = 3 -[node name="Control" type="Control" parent="."] -layout_mode = 3 +[node name="Bag" type="Panel" parent="."] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +script = ExtResource("1_f3hpu") -[node name="TextureRect" type="TextureRect" parent="Control"] +[node name="TextureRect" type="TextureRect" parent="Bag"] layout_mode = 1 anchors_preset = 14 anchor_top = 0.5 @@ -28,7 +28,7 @@ texture = ExtResource("2_3s314") expand_mode = 5 stretch_mode = 2 -[node name="GridContainer" type="GridContainer" parent="Control"] +[node name="GridContainer" type="GridContainer" parent="Bag"] custom_minimum_size = Vector2(270, 120) layout_mode = 1 anchors_preset = 8 @@ -44,66 +44,66 @@ grow_horizontal = 2 grow_vertical = 2 columns = 9 -[node name="Panel" type="Panel" parent="Control/GridContainer"] +[node name="Panel" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel2" type="Panel" parent="Control/GridContainer"] +[node name="Panel2" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel3" type="Panel" parent="Control/GridContainer"] +[node name="Panel3" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel4" type="Panel" parent="Control/GridContainer"] +[node name="Panel4" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel5" type="Panel" parent="Control/GridContainer"] +[node name="Panel5" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel6" type="Panel" parent="Control/GridContainer"] +[node name="Panel6" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel7" type="Panel" parent="Control/GridContainer"] +[node name="Panel7" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel8" type="Panel" parent="Control/GridContainer"] +[node name="Panel8" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel9" type="Panel" parent="Control/GridContainer"] +[node name="Panel9" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel10" type="Panel" parent="Control/GridContainer"] +[node name="Panel10" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel11" type="Panel" parent="Control/GridContainer"] +[node name="Panel11" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel12" type="Panel" parent="Control/GridContainer"] +[node name="Panel12" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel13" type="Panel" parent="Control/GridContainer"] +[node name="Panel13" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel14" type="Panel" parent="Control/GridContainer"] +[node name="Panel14" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel15" type="Panel" parent="Control/GridContainer"] +[node name="Panel15" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 -[node name="Panel16" type="Panel" parent="Control/GridContainer"] +[node name="Panel16" type="Panel" parent="Bag/GridContainer"] custom_minimum_size = Vector2(30, 30) layout_mode = 2 diff --git a/scene/prop/prop_hud.tscn b/scene/prop/prop_hud.tscn index 3ce01353..86368d71 100644 --- a/scene/prop/prop_hud.tscn +++ b/scene/prop/prop_hud.tscn @@ -11,6 +11,7 @@ [ext_resource type="Texture2D" uid="uid://d03la4d2swk0k" path="res://asset/art/ui/hud/pressed_right.png" id="11_a512b"] [node name="PropHUD" type="Control"] +process_mode = 3 custom_minimum_size = Vector2(600, 500) layout_mode = 3 anchors_preset = 0 diff --git a/scene/settings/settings.gd b/scene/settings/settings.gd index 78596acb..fb0b6f75 100644 --- a/scene/settings/settings.gd +++ b/scene/settings/settings.gd @@ -87,8 +87,9 @@ func _on_autosave_box_toggled(is_pressed: bool) -> void: func _on_autosave_time_edit_text_submitted(_text = null) -> void: var seconds = autosave_time_edit.text.to_int() - if seconds < 0: - seconds = 0 + # limit the value, at least 10 seconds + if seconds < 10: + seconds = 10 autosave_time_edit.text = str(seconds) GlobalConfigManager.config.auto_save_seconds = seconds