Merge remote-tracking branch 'origin/demo'
This commit is contained in:
commit
87da4d2e73
@ -3,6 +3,18 @@ extends Node
|
||||
|
||||
signal archive_loaded
|
||||
|
||||
# Constants
|
||||
const CURRENT_VERSION = 6
|
||||
const ARCHIVE_ID_MIN = 0
|
||||
const ARCHIVE_ID_MAX = 99
|
||||
const ARCHIVE_ID_DIGITS = 3
|
||||
|
||||
# Static paths
|
||||
static var user_root_dir := "user://data/" # must end with "/"
|
||||
static var archive_dir := "user://data/archives/"
|
||||
static var archive_prefix := "save"
|
||||
|
||||
# Archive management
|
||||
var archive: AssembledArchive:
|
||||
set(val):
|
||||
archive = val
|
||||
@ -13,14 +25,6 @@ var archive: AssembledArchive:
|
||||
print_rich("[color=brown] release_stage = %s[/color]" % GlobalConfig.RELEASE_STAGE)
|
||||
|
||||
|
||||
|
||||
# current archive
|
||||
static var user_root_dir := "user://data/" # must end with "/"
|
||||
static var archive_dir := "user://data/archives/"
|
||||
static var archive_prefix := "save"
|
||||
|
||||
const CURRENT_VERSION = 6
|
||||
|
||||
var archives_dict: Dictionary[int, AssembledArchive] = {}
|
||||
var archives_notes_dict: Dictionary[int, String] = {}
|
||||
var autosave_timer := Timer.new()
|
||||
@ -32,24 +36,22 @@ func _ready() -> void:
|
||||
# 禁用默认退出行为,在 _notification 处理 NOTIFICATION_WM_CLOSE_REQUEST 时保存数据
|
||||
get_tree().set_auto_accept_quit(false)
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
if not _check_dirs_and_archives():
|
||||
_handle_load_error("存档目录", "读写")
|
||||
return
|
||||
autosave_timer.timeout.connect(_try_auto_save)
|
||||
autosave_timer.stop()
|
||||
add_child(autosave_timer)
|
||||
|
||||
_setup_autosave_timer()
|
||||
|
||||
# config should be loaded first
|
||||
load_config()
|
||||
|
||||
# 在 debug or editor 模式下,直接保证有 archive
|
||||
if GlobalConfig.DEBUG or Engine.is_editor_hint():
|
||||
if archives_dict.size() == 0:
|
||||
create_and_use_new_archive(0)
|
||||
else:
|
||||
# debug 模式下默认使用 0 号存档
|
||||
GlobalConfigManager.config.current_selected_archive_id = 0
|
||||
_ensure_debug_archive()
|
||||
|
||||
|
||||
func _notification(what):
|
||||
func _notification(what: int) -> void:
|
||||
# handle window close request
|
||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
save_all()
|
||||
@ -59,14 +61,27 @@ func _notification(what):
|
||||
SceneManager.quit_game()
|
||||
|
||||
|
||||
func _on_archive_id_changed():
|
||||
func _setup_autosave_timer() -> void:
|
||||
autosave_timer.timeout.connect(_try_auto_save)
|
||||
autosave_timer.stop()
|
||||
add_child(autosave_timer)
|
||||
|
||||
|
||||
func _ensure_debug_archive() -> void:
|
||||
if archives_dict.is_empty():
|
||||
create_and_use_new_archive(0)
|
||||
else:
|
||||
# debug 模式下默认使用 0 号存档
|
||||
GlobalConfigManager.config.current_selected_archive_id = 0
|
||||
|
||||
|
||||
func _on_archive_id_changed() -> void:
|
||||
var selected_id = GlobalConfigManager.config.current_selected_archive_id
|
||||
if selected_id < 0:
|
||||
return
|
||||
# if archive and selected_id == archive.archive_id:
|
||||
# print("_on_archive_id_changed same id=", selected_id)
|
||||
# return
|
||||
|
||||
print("_on_archive_id_changed id=", selected_id)
|
||||
|
||||
if not archives_dict.has(selected_id):
|
||||
print("新建存档 ", selected_id)
|
||||
create_and_use_new_archive(selected_id)
|
||||
@ -76,16 +91,19 @@ func _on_archive_id_changed():
|
||||
load_archive()
|
||||
|
||||
|
||||
func check_autosave_options():
|
||||
if (
|
||||
GlobalConfigManager.config.auto_save_enabled
|
||||
func check_autosave_options() -> void:
|
||||
var config = GlobalConfigManager.config
|
||||
var should_enable_autosave = (
|
||||
config.auto_save_enabled
|
||||
and archive
|
||||
and GlobalConfigManager.config.auto_save_seconds > 1
|
||||
):
|
||||
and config.auto_save_seconds > 1
|
||||
)
|
||||
|
||||
if should_enable_autosave:
|
||||
# reset left time
|
||||
autosave_timer.stop()
|
||||
autosave_timer.one_shot = false
|
||||
autosave_timer.wait_time = GlobalConfigManager.config.auto_save_seconds
|
||||
autosave_timer.wait_time = config.auto_save_seconds
|
||||
autosave_timer.start()
|
||||
else:
|
||||
autosave_timer.stop()
|
||||
@ -93,15 +111,16 @@ func check_autosave_options():
|
||||
if GlobalConfig.DEBUG:
|
||||
print(
|
||||
"check_autosave_option: ",
|
||||
GlobalConfigManager.config.auto_save_enabled,
|
||||
config.auto_save_enabled,
|
||||
" wait_time=",
|
||||
autosave_timer.wait_time
|
||||
)
|
||||
|
||||
|
||||
func _try_auto_save():
|
||||
func _try_auto_save() -> void:
|
||||
if GlobalConfig.DEBUG:
|
||||
print("Auto save")
|
||||
|
||||
if archive and GlobalConfigManager.config.auto_save_seconds > 1:
|
||||
save_all()
|
||||
# 自动保存成功 [ID:ui_auto_saved]
|
||||
@ -109,71 +128,108 @@ func _try_auto_save():
|
||||
|
||||
|
||||
func _check_dirs_and_archives() -> bool:
|
||||
if !DirAccess.dir_exists_absolute(user_root_dir):
|
||||
DirAccess.make_dir_recursive_absolute(user_root_dir)
|
||||
print("Create user_root_dir:", user_root_dir)
|
||||
# Ensure directories exist
|
||||
_ensure_directory_exists(user_root_dir)
|
||||
_ensure_directory_exists(archive_dir)
|
||||
|
||||
# Check if the archive directory is accessible
|
||||
if !DirAccess.dir_exists_absolute(archive_dir):
|
||||
DirAccess.make_dir_recursive_absolute(archive_dir)
|
||||
print("Create archive_dir:", archive_dir)
|
||||
var archive_dir_access = DirAccess.open(archive_dir)
|
||||
if !archive_dir_access:
|
||||
if not archive_dir_access:
|
||||
_handle_load_error("存档目录", "读取")
|
||||
# TODO pop up a dialog to inform the user
|
||||
return false
|
||||
var files = archive_dir_access.get_files()
|
||||
|
||||
# Load existing archives
|
||||
_load_existing_archives(archive_dir_access)
|
||||
return true
|
||||
|
||||
|
||||
func _ensure_directory_exists(dir_path: String) -> void:
|
||||
if not DirAccess.dir_exists_absolute(dir_path):
|
||||
DirAccess.make_dir_recursive_absolute(dir_path)
|
||||
print("Create directory:", dir_path)
|
||||
|
||||
|
||||
func _load_existing_archives(dir_access: DirAccess) -> void:
|
||||
var files = dir_access.get_files()
|
||||
files.sort()
|
||||
# get archive number
|
||||
|
||||
for file in files:
|
||||
if file.begins_with(archive_prefix) and file.ends_with(GlobalConfig.RES_FILE_FORMAT):
|
||||
# format: save012_xxxxx; save000
|
||||
var id_and_note = file.get_basename().substr(archive_prefix.length()).strip_escapes().split("_", true, 1)
|
||||
var id_str = id_and_note[0]
|
||||
var note_str = id_and_note[1] if id_and_note.size() >= 2 else (archive_prefix + id_str)
|
||||
# 非三位数的 id 会被忽略
|
||||
if id_str.length() != 3:
|
||||
if not _is_valid_archive_filename(file):
|
||||
continue
|
||||
var archive_info = _parse_archive_filename(file)
|
||||
if not archive_info:
|
||||
continue
|
||||
var id = archive_info.id
|
||||
var note = archive_info.note
|
||||
archives_notes_dict[id] = note
|
||||
if not archives_dict.has(id):
|
||||
var archive_resource = _load_archive_resource(archive_dir + file)
|
||||
if archive_resource:
|
||||
archives_dict[id] = archive_resource
|
||||
|
||||
|
||||
func _is_valid_archive_filename(filename: String) -> bool:
|
||||
return filename.begins_with(archive_prefix) and filename.ends_with(GlobalConfig.RES_FILE_FORMAT)
|
||||
|
||||
|
||||
func _parse_archive_filename(filename: String) -> Dictionary:
|
||||
# format: save012_xxxxx; save000
|
||||
var basename = filename.get_basename()
|
||||
var id_and_note = basename.substr(archive_prefix.length()).strip_escapes().split("_", true, 1)
|
||||
var id_str = id_and_note[0]
|
||||
# 非三位数的 id 会被忽略
|
||||
if id_str.length() != ARCHIVE_ID_DIGITS:
|
||||
return {}
|
||||
var id = int(id_str)
|
||||
# 读取范围是 0-99
|
||||
if id < 0 or id > 99:
|
||||
continue
|
||||
archives_notes_dict[id] = note_str
|
||||
var path = archive_dir + file
|
||||
if not archives_dict.has(id):
|
||||
if id < ARCHIVE_ID_MIN or id > ARCHIVE_ID_MAX:
|
||||
return {}
|
||||
var note_str = id_and_note[1] if id_and_note.size() >= 2 else (archive_prefix + id_str)
|
||||
return {
|
||||
"id": id,
|
||||
"note": note_str
|
||||
}
|
||||
|
||||
|
||||
func _load_archive_resource(path: String) -> AssembledArchive:
|
||||
var res = ResourceLoader.load(
|
||||
path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP
|
||||
)
|
||||
if is_instance_valid(res) and res.version >= CURRENT_VERSION:
|
||||
archives_dict[id] = res
|
||||
return res
|
||||
else:
|
||||
printerr("SKIP INVALID ARCHIVE! file=", file)
|
||||
return true
|
||||
printerr("SKIP INVALID ARCHIVE! path=", path)
|
||||
return null
|
||||
|
||||
|
||||
# id = -1 means create a new archive, otherwise create an archive with the given id
|
||||
func create_and_use_new_archive(id := -1) -> void:
|
||||
_check_dirs_and_archives()
|
||||
var archive_path = _get_archive_path(id)
|
||||
|
||||
if id < 0:
|
||||
# 如果 id 小于 0,找到一个新的 id,创建新存档
|
||||
id = 0
|
||||
# find a new id
|
||||
archive_path = _get_archive_path(id)
|
||||
while FileAccess.file_exists(archive_path):
|
||||
id += 1
|
||||
archive_path = _get_archive_path(id)
|
||||
id = _find_next_available_id()
|
||||
_create_and_save_new_archive_resoure(id)
|
||||
else:
|
||||
# 如果 id 大于等于 0,创建指定 id 的存档
|
||||
if FileAccess.file_exists(archive_path):
|
||||
_create_and_save_new_archive_resoure(id, true)
|
||||
else:
|
||||
_create_and_save_new_archive_resoure(id)
|
||||
var archive_path = _get_archive_path(id)
|
||||
var take_over_path = FileAccess.file_exists(archive_path)
|
||||
_create_and_save_new_archive_resoure(id, take_over_path)
|
||||
|
||||
# this will auto trigger signal and load the new archive
|
||||
GlobalConfigManager.config.current_selected_archive_id = id
|
||||
|
||||
|
||||
func _create_and_save_new_archive_resoure(id, take_over_path = false) -> void:
|
||||
func _find_next_available_id() -> int:
|
||||
var id = 0
|
||||
var archive_path = _get_archive_path(id)
|
||||
while FileAccess.file_exists(archive_path) and id <= ARCHIVE_ID_MAX:
|
||||
id += 1
|
||||
archive_path = _get_archive_path(id)
|
||||
return id
|
||||
|
||||
|
||||
func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> void:
|
||||
var archive_path = _get_archive_path(id)
|
||||
archive = AssembledArchive.new() as Resource
|
||||
archive.version = CURRENT_VERSION
|
||||
@ -189,13 +245,7 @@ func _create_and_save_new_archive_resoure(id, take_over_path = false) -> void:
|
||||
|
||||
# 超过 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)
|
||||
var id_str := str(id).pad_zeros(ARCHIVE_ID_DIGITS)
|
||||
return archive_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT
|
||||
|
||||
|
||||
@ -208,41 +258,54 @@ func save_all() -> void:
|
||||
var config = GlobalConfigManager.config
|
||||
if config:
|
||||
ResourceSaver.save(config)
|
||||
|
||||
# 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
|
||||
# save player state
|
||||
_save_player_state()
|
||||
# save archive
|
||||
if archive:
|
||||
ResourceSaver.save(archive)
|
||||
# reset autosave timer
|
||||
check_autosave_options()
|
||||
|
||||
|
||||
func _save_player_state() -> void:
|
||||
if not archive:
|
||||
return
|
||||
var player = SceneManager.get_player() as MainPlayer
|
||||
if player:
|
||||
archive.player_global_position_x = player.global_position.x
|
||||
archive.player_direction = player.facing_direction
|
||||
|
||||
|
||||
func load_config() -> void:
|
||||
if GlobalConfigManager.config:
|
||||
return
|
||||
var path = user_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT
|
||||
if FileAccess.file_exists(path):
|
||||
var config = ResourceLoader.load(path)
|
||||
if is_instance_valid(config) and config.version >= CURRENT_VERSION:
|
||||
GlobalConfigManager.config = config
|
||||
var loaded_config = ResourceLoader.load(path)
|
||||
if is_instance_valid(loaded_config) and loaded_config.version >= CURRENT_VERSION:
|
||||
GlobalConfigManager.config = loaded_config
|
||||
else:
|
||||
printerr("SKIP INVALID CONFIG!")
|
||||
if GlobalConfigManager.config == null:
|
||||
|
||||
if not GlobalConfigManager.config:
|
||||
_create_default_config(path)
|
||||
GlobalConfigManager.config.resource_path = path
|
||||
if not Engine.is_editor_hint():
|
||||
_connect_config_signals()
|
||||
|
||||
|
||||
func _create_default_config(path: String) -> void:
|
||||
var config = GlobalConfig.new()
|
||||
config.version = CURRENT_VERSION
|
||||
GlobalConfigManager.config = config
|
||||
ResourceSaver.save(config, path)
|
||||
GlobalConfigManager.config.resource_path = path
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
# connect signals
|
||||
GlobalConfigManager.config.current_selected_archive_id_changed.connect(_on_archive_id_changed)
|
||||
GlobalConfigManager.config.auto_save_seconds_changed.connect(check_autosave_options)
|
||||
GlobalConfigManager.config.auto_save_enabled_changed.connect(check_autosave_options)
|
||||
|
||||
|
||||
func _connect_config_signals() -> void:
|
||||
var config = GlobalConfigManager.config
|
||||
config.current_selected_archive_id_changed.connect(_on_archive_id_changed)
|
||||
config.auto_save_seconds_changed.connect(check_autosave_options)
|
||||
config.auto_save_enabled_changed.connect(check_autosave_options)
|
||||
|
||||
|
||||
func load_archive() -> void:
|
||||
@ -250,8 +313,7 @@ func load_archive() -> void:
|
||||
var selected_id = 0
|
||||
if GlobalConfigManager.config:
|
||||
selected_id = GlobalConfigManager.config.current_selected_archive_id
|
||||
# if archive and selected_id == archive.archive_id:
|
||||
# return
|
||||
|
||||
print("load_archive ", selected_id)
|
||||
if not archives_dict.has(selected_id):
|
||||
_handle_load_error(str(selected_id) + " 号存档", "查找")
|
||||
@ -262,7 +324,7 @@ func load_archive() -> void:
|
||||
check_autosave_options()
|
||||
|
||||
|
||||
func _handle_load_error(target, action) -> void:
|
||||
func _handle_load_error(target: String, action: String) -> void:
|
||||
var msg = str(target) + " " + str(action) + " failed. Permission Error."
|
||||
SceneManager.pop_notification(msg)
|
||||
printerr(msg)
|
||||
@ -281,9 +343,7 @@ func get_global_value(property: StringName, default = null) -> Variant:
|
||||
printerr("Archive is null, cannot get global value")
|
||||
return default
|
||||
var val = archive.global_data_dict.get(property)
|
||||
if val == null:
|
||||
return default
|
||||
return val
|
||||
return default if val == null else val
|
||||
|
||||
|
||||
func set_chapter_if_greater(c: int) -> void:
|
||||
@ -291,7 +351,9 @@ func set_chapter_if_greater(c: int) -> void:
|
||||
printerr("Archive is null, cannot set chapter")
|
||||
return
|
||||
# 1:序章;2-5:一~四章;6:结尾
|
||||
if c < 1 or c > 6:
|
||||
const MIN_CHAPTER = 1
|
||||
const MAX_CHAPTER = 6
|
||||
if c < MIN_CHAPTER or c > MAX_CHAPTER:
|
||||
printerr("[ArchiveManager] set_chapter_if_greater: invalid chapter value: " + str(c))
|
||||
return
|
||||
if EventManager.get_chapter_stage() >= c:
|
||||
@ -302,14 +364,14 @@ func set_chapter_if_greater(c: int) -> void:
|
||||
|
||||
|
||||
func unlock_memory(id: int) -> void:
|
||||
if archive:
|
||||
if not archive:
|
||||
printerr("Archive is null, cannot unlock memory. id=", id)
|
||||
return
|
||||
if archive.mem_display_dict.get(id):
|
||||
print("memory already unlocked. id=", id)
|
||||
return
|
||||
archive.mem_display_dict[id] = true
|
||||
SceneManager.pop_notification("ui_notify_mem_update")
|
||||
else:
|
||||
printerr("Archive is null, cannot unlock memory. id=", id)
|
||||
|
||||
|
||||
# 供运行时缓存跨场景数据
|
||||
|
@ -15,6 +15,16 @@ const RELEASE_STAGE := 1
|
||||
# .res would be binary encoded, .tres is text encoded
|
||||
const RES_FILE_FORMAT = ".tres"
|
||||
|
||||
# Audio bus names
|
||||
const BUS_MASTER := "Master"
|
||||
const BUS_GAME_SFX := "game_sfx"
|
||||
const BUS_DIALOG := "dialog"
|
||||
|
||||
const LANGUAGE_OPTIONS = ["简体中文", "English"]
|
||||
const LOCALE_PREFIX_MAPPING = {"zh": 0, "en": 1}
|
||||
const CAPTION_OPTIONS_DICT = {0: ["上海话", "普通话"], 1: [""]}
|
||||
const CAPTION_LOCALES_DICT = {0: ["zh_SH", "zh_CN"], 1: ["en"]}
|
||||
|
||||
## layers
|
||||
# 设置
|
||||
const CANVAS_LAYER_SETTINGS = 30
|
||||
@ -103,3 +113,20 @@ signal auto_save_seconds_changed
|
||||
if streamer_mode != val:
|
||||
streamer_mode = val
|
||||
streamer_mode_updated.emit()
|
||||
|
||||
func get_locale_language_name() -> String:
|
||||
language = wrapi(language, 0, LANGUAGE_OPTIONS.size())
|
||||
return LANGUAGE_OPTIONS[language]
|
||||
|
||||
|
||||
func get_locale_caption_name() -> String:
|
||||
language = wrapi(language, 0, LANGUAGE_OPTIONS.size())
|
||||
var caption_options = CAPTION_OPTIONS_DICT.get(language, [""])
|
||||
caption = wrapi(caption, 0, caption_options.size())
|
||||
return caption_options[caption]
|
||||
|
||||
|
||||
func get_locale() -> String:
|
||||
var locales = CAPTION_LOCALES_DICT.get(language, [""])
|
||||
caption = wrapi(caption, 0, locales.size())
|
||||
return locales[caption]
|
||||
|
@ -1,129 +1,165 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
# Constants
|
||||
const TIMER_INTERVAL := 5.0
|
||||
const TIMER_LOG_INTERVAL := 6 # 30秒打印一次 (6 * 5秒)
|
||||
const TIMER_EDITOR_LOG_INTERVAL := 120 # 编辑器中600秒打印一次
|
||||
|
||||
# Static config
|
||||
static var config: GlobalConfig:
|
||||
set = _set_config
|
||||
|
||||
var timer = Timer.new()
|
||||
# Timer for tracking game time
|
||||
var timer := Timer.new()
|
||||
var _timer_tick_counter := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
timer.wait_time = 5.0
|
||||
_setup_timer()
|
||||
|
||||
|
||||
func _setup_timer() -> void:
|
||||
timer.wait_time = TIMER_INTERVAL
|
||||
timer.one_shot = false
|
||||
timer.timeout.connect(_on_timer_timeout)
|
||||
add_child(timer)
|
||||
timer.start()
|
||||
|
||||
|
||||
func _set_config(val: GlobalConfig) -> void:
|
||||
static func _set_config(val: GlobalConfig) -> void:
|
||||
config = val
|
||||
if Engine.is_editor_hint():
|
||||
if not config or Engine.is_editor_hint():
|
||||
return
|
||||
# debug)
|
||||
|
||||
_apply_debug_mode()
|
||||
_apply_window_settings()
|
||||
_apply_audio_settings()
|
||||
_apply_locale_settings()
|
||||
|
||||
|
||||
static func _apply_debug_mode() -> void:
|
||||
if config.debug_mode:
|
||||
GlobalConfig.DEBUG = true
|
||||
print_rich("[color=orange]Debug mode enabled[/color]")
|
||||
# set up window
|
||||
|
||||
|
||||
static func _apply_window_settings() -> void:
|
||||
var window = Engine.get_main_loop().root.get_window()
|
||||
|
||||
if config.window_fullscreen:
|
||||
get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN
|
||||
# get_window().mode = Window.MODE_FULLSCREEN
|
||||
window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN
|
||||
else:
|
||||
get_window().mode = Window.MODE_WINDOWED
|
||||
get_window().always_on_top = config.window_top
|
||||
# set up sound
|
||||
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), config.db_master)
|
||||
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("game_sfx"), config.db_game_sfx)
|
||||
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("dialog"), config.db_dialog)
|
||||
window.mode = Window.MODE_WINDOWED
|
||||
|
||||
window.always_on_top = config.window_top
|
||||
|
||||
|
||||
static func _apply_audio_settings() -> void:
|
||||
AudioServer.set_bus_volume_db(
|
||||
AudioServer.get_bus_index(GlobalConfig.BUS_MASTER), config.db_master
|
||||
)
|
||||
AudioServer.set_bus_volume_db(
|
||||
AudioServer.get_bus_index(GlobalConfig.BUS_GAME_SFX), config.db_game_sfx
|
||||
)
|
||||
AudioServer.set_bus_volume_db(
|
||||
AudioServer.get_bus_index(GlobalConfig.BUS_DIALOG), config.db_dialog
|
||||
)
|
||||
|
||||
prints(
|
||||
"config load volume_db settings (master, sfx, dialog): ",
|
||||
config.db_master,
|
||||
config.db_game_sfx,
|
||||
config.db_dialog
|
||||
)
|
||||
# set locale
|
||||
update_locale(config.language, config.caption)
|
||||
|
||||
|
||||
static func _apply_locale_settings() -> void:
|
||||
var locale = config.get_locale()
|
||||
print("set language to: ", locale)
|
||||
TranslationServer.set_locale(locale)
|
||||
|
||||
|
||||
# -1 null; 0 zh; 1 en
|
||||
var language_options = ["简体中文", "English"]
|
||||
var caption_options_dict = {0: ["上海话", "普通话"], 1: [""]}
|
||||
var caption_loacles_dict = {0: ["zh_SH", "zh_CN"], 1: ["en"]}
|
||||
|
||||
|
||||
# return example: "中文_简体中文" "English_"
|
||||
func update_locale(lang_id: int, caption_id: int) -> void:
|
||||
lang_id = wrapi(lang_id, 0, language_options.size())
|
||||
var caption_loacles = caption_loacles_dict.get(lang_id)
|
||||
caption_id = wrapi(caption_id, 0, caption_loacles.size())
|
||||
var lang = caption_loacles[caption_id]
|
||||
GlobalConfigManager.config.language = lang_id
|
||||
GlobalConfigManager.config.caption = caption_id
|
||||
print("set language to: ", lang)
|
||||
TranslationServer.set_locale(lang)
|
||||
config.language = wrapi(lang_id, 0, GlobalConfig.LANGUAGE_OPTIONS.size())
|
||||
var caption_options = GlobalConfig.CAPTION_OPTIONS_DICT.get(config.language, [""])
|
||||
config.caption = wrapi(caption_id, 0, caption_options.size())
|
||||
_apply_locale_settings()
|
||||
|
||||
|
||||
func get_locale_language_name() -> String:
|
||||
GlobalConfigManager.config.language = wrapi(
|
||||
GlobalConfigManager.config.language, 0, language_options.size()
|
||||
)
|
||||
return language_options[GlobalConfigManager.config.language]
|
||||
if config:
|
||||
return config.get_locale_language_name()
|
||||
return ""
|
||||
|
||||
|
||||
func get_locale_caption_name() -> String:
|
||||
GlobalConfigManager.config.language = wrapi(
|
||||
GlobalConfigManager.config.language, 0, language_options.size()
|
||||
)
|
||||
var caption_options = caption_options_dict.get(GlobalConfigManager.config.language)
|
||||
GlobalConfigManager.config.caption = wrapi(
|
||||
GlobalConfigManager.config.caption, 0, caption_options.size()
|
||||
)
|
||||
return caption_options[GlobalConfigManager.config.caption]
|
||||
if config:
|
||||
return config.get_locale_caption_name()
|
||||
return ""
|
||||
|
||||
|
||||
var _on_timer_timeout_counter := 0
|
||||
|
||||
|
||||
func _on_timer_timeout():
|
||||
var archive := ArchiveManager.archive
|
||||
if archive and config:
|
||||
archive.game_seconds += 5
|
||||
config.game_total_seconds += 5
|
||||
_on_timer_timeout_counter += 1
|
||||
# 30s 打印一次,无需首次打印
|
||||
# ArchiveManager 设置 archive 时会调用 print_global_info
|
||||
if _on_timer_timeout_counter % 6 == 0:
|
||||
# editor 中 600s 打印一次
|
||||
if Engine.is_editor_hint() and _on_timer_timeout_counter % 120 != 0:
|
||||
return
|
||||
print_global_info()
|
||||
|
||||
|
||||
# @warning_ignore("intege")
|
||||
func print_global_info():
|
||||
func _on_timer_timeout() -> void:
|
||||
var archive := ArchiveManager.archive
|
||||
if not archive or not config:
|
||||
return
|
||||
# Update game time
|
||||
archive.game_seconds += int(TIMER_INTERVAL)
|
||||
config.game_total_seconds += int(TIMER_INTERVAL)
|
||||
_timer_tick_counter += 1
|
||||
# Check if should log
|
||||
if _should_log_game_info():
|
||||
print_global_info()
|
||||
|
||||
|
||||
func _should_log_game_info() -> bool:
|
||||
# 30s 打印一次,无需首次打印
|
||||
if _timer_tick_counter % TIMER_LOG_INTERVAL != 0:
|
||||
return false
|
||||
# editor 中 600s 打印一次
|
||||
if Engine.is_editor_hint() and _timer_tick_counter % TIMER_EDITOR_LOG_INTERVAL != 0:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func print_global_info() -> void:
|
||||
var archive := ArchiveManager.archive
|
||||
if not archive or not config:
|
||||
return
|
||||
var game_time_str = _format_game_time(archive.game_seconds)
|
||||
var tick_time_str = _format_tick_time()
|
||||
var round_info = _get_round_info()
|
||||
var scene_info = archive.current_scene
|
||||
prints(
|
||||
"[timemark]",
|
||||
Time.get_datetime_string_from_system(),
|
||||
round_info,
|
||||
scene_info,
|
||||
game_time_str + " " + tick_time_str
|
||||
)
|
||||
|
||||
|
||||
static func _format_game_time(total_seconds: int) -> String:
|
||||
@warning_ignore("integer_division")
|
||||
var hour := archive.game_seconds / 3600
|
||||
var hours := total_seconds / 3600
|
||||
@warning_ignore("integer_division")
|
||||
var minute := (archive.game_seconds % 3600) / 60
|
||||
var second := archive.game_seconds % 60
|
||||
var minutes := (total_seconds % 3600) / 60
|
||||
var seconds := total_seconds % 60
|
||||
return "game:%d:%02d:%02d" % [hours, minutes, seconds]
|
||||
|
||||
|
||||
static func _get_round_info() -> String:
|
||||
# 0:未开始游戏;1:序章;2-5:一~四章;6:结尾
|
||||
var chapter := EventManager.get_chapter_stage()
|
||||
var round_info = "r" + str(config.game_rounds) + "_c" + str(chapter)
|
||||
var game_time_info = "game:" + str(hour) + ":" + str(minute) + ":" + str(second)
|
||||
# get ticks since game app run
|
||||
return "r%d_c%d" % [config.game_rounds, chapter]
|
||||
|
||||
|
||||
@warning_ignore_start("integer_division")
|
||||
static func _format_tick_time() -> String:
|
||||
var ticks = Time.get_ticks_msec()
|
||||
@warning_ignore("integer_division")
|
||||
hour = ticks / 3600000
|
||||
@warning_ignore("integer_division")
|
||||
minute = (ticks % 3600000) / 60000
|
||||
@warning_ignore("integer_division")
|
||||
second = (ticks % 60000) / 1000
|
||||
var msec = ticks % 1000
|
||||
var tick_time_info = (
|
||||
"tick:" + str(hour) + ":" + str(minute) + ":" + str(second) + "." + str(msec)
|
||||
)
|
||||
var time_info = game_time_info + " " + tick_time_info
|
||||
var scene_info = archive.current_scene
|
||||
prints("[timemark]", Time.get_datetime_string_from_system(), round_info, scene_info, time_info)
|
||||
var hours := ticks / 3600000 as int
|
||||
var minutes := (ticks % 3600000) / 60000 as int
|
||||
var seconds := (ticks % 60000) / 1000 as int
|
||||
var msec := ticks % 1000 as int
|
||||
return "tick:%d:%02d:%02d.%03d" % [hours, minutes, seconds, msec]
|
||||
|
@ -242,6 +242,7 @@ locale/fallback="zh"
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
textures/vram_compression/import_etc2_astc=true
|
||||
textures/webp_compression/compression_method=4
|
||||
|
||||
[statistics]
|
||||
|
||||
|
@ -1,6 +1,21 @@
|
||||
@tool
|
||||
class_name Ground2D extends Node2D
|
||||
|
||||
# Constants
|
||||
const DEFAULT_PLAYER_Y := 70
|
||||
const CAMERA_MIN_WIDTH := 564.0
|
||||
const CAMERA_MIN_HEIGHT := 316.0
|
||||
const PLAYER_PADDING_X := 30.0
|
||||
|
||||
const FOOTSTEP_AUDIO = {
|
||||
"ghost": preload("res://config/audio/sfx/footstep_ghost.tres"),
|
||||
"硬地面": preload("res://config/audio/sfx/footstep_硬地面.tres"),
|
||||
"室外": preload("res://config/audio/sfx/footstep_室外.tres"),
|
||||
"crawling": preload("res://config/audio/sfx/footstep_crawling.tres"),
|
||||
"盒子猫": preload("res://config/audio/sfx/footstep_meow.tres"),
|
||||
}
|
||||
|
||||
# Exports
|
||||
@export var scene_name := ""
|
||||
# 用于在 debug 时态下,指定进入的 portal
|
||||
@export_enum("left", "right", "1", "2", "3", "4", "5", "6", "7", "8", "9")
|
||||
@ -12,7 +27,7 @@ var default_portal := "left"
|
||||
player_y_fixed = val
|
||||
if is_node_ready():
|
||||
reset_player_y()
|
||||
@export var player_y := 70:
|
||||
@export var player_y := DEFAULT_PLAYER_Y:
|
||||
set(val):
|
||||
player_y = val
|
||||
if is_node_ready():
|
||||
@ -27,42 +42,24 @@ var default_portal := "left"
|
||||
@export_tool_button("玩家重置到 portal") var _a1 = move_player_to_portal.bind(default_portal)
|
||||
@export_tool_button("玩家重置 y 高度") var _a2 = reset_player_y
|
||||
|
||||
# var main_scene := preload("res://scene/main.tscn") as PackedScene
|
||||
|
||||
@onready var player_line = %PlayerLine2D as Line2D
|
||||
@onready var reenter_lock = %PlayerReenterLock as PlayerReenterLock
|
||||
@onready var player = %MainPlayer as MainPlayer
|
||||
# Nodes
|
||||
@onready var player_line := %PlayerLine2D as Line2D
|
||||
@onready var reenter_lock := %PlayerReenterLock as PlayerReenterLock
|
||||
@onready var player := %MainPlayer as MainPlayer
|
||||
@onready var directional_light := %DirectionalLight2D as DirectionalLight2D
|
||||
@onready var bg_sprite = %BGSprite2D as Sprite2D
|
||||
@onready var foreground = %ParallaxForeground as ParallaxBackground
|
||||
@onready var camera_focus_marker = %CameraFocusMarker as CameraFocusMarker
|
||||
@onready var bg_sprite := %BGSprite2D as Sprite2D
|
||||
@onready var foreground := %ParallaxForeground as ParallaxBackground
|
||||
@onready var camera_focus_marker := %CameraFocusMarker as CameraFocusMarker
|
||||
@onready var footstep_audio := %FootstepAudioPlayer as RandomAudioStreamPlayer
|
||||
|
||||
@onready var footstep_audio = %FootstepAudioPlayer as RandomAudioStreamPlayer
|
||||
|
||||
const FOOTSTEP_AUDIO = {
|
||||
#"wood": preload("res://config/audio/footstep/footstep_wood.tres"),
|
||||
#"carpet": preload("res://config/audio/footstep/footstep_carpet.tres"),
|
||||
# "concrete": preload("res://config/audio/footstep/footstep_concrete.tres"),
|
||||
#"grass": preload("res://config/audio/footstep/footstep_grass.tres"),
|
||||
#"snow": preload("res://config/audio/footstep/footstep_snow.tres"),
|
||||
"ghost": preload("res://config/audio/sfx/footstep_ghost.tres"),
|
||||
"硬地面": preload("res://config/audio/sfx/footstep_硬地面.tres"),
|
||||
"室外": preload("res://config/audio/sfx/footstep_室外.tres"),
|
||||
"crawling": preload("res://config/audio/sfx/footstep_crawling.tres"),
|
||||
# 待替换
|
||||
"盒子猫": preload("res://config/audio/sfx/footstep_meow.tres"),
|
||||
}
|
||||
|
||||
var restarting = false
|
||||
# State
|
||||
var restarting := false
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# 仅在编辑器中调试时,通过 main 场景启动
|
||||
if not Engine.is_editor_hint() and (not get_parent() is GroundLoader):
|
||||
print("restarting... set GlobalConfig.DEBUG = true")
|
||||
restarting = true
|
||||
GlobalConfig.DEBUG = true
|
||||
_restart_from_main()
|
||||
if not Engine.is_editor_hint() and not (get_parent() is GroundLoader):
|
||||
_handle_restart()
|
||||
return
|
||||
if camera_focus_marker:
|
||||
camera_focus_marker.enabled = true
|
||||
@ -72,37 +69,58 @@ func _ready() -> void:
|
||||
if restarting:
|
||||
print("restarting: skip ground _ready()")
|
||||
return
|
||||
foreground.layer = GlobalConfig.CANVAS_LAYER_FG
|
||||
# 检查 scene_name 是否合法
|
||||
scene_name = scene_name.strip_edges()
|
||||
if get_parent().name.begins_with("S") and (not scene_name or scene_name.length() != 7):
|
||||
printerr("scene_name is not valid")
|
||||
_setup_scene()
|
||||
_validate_scene_name()
|
||||
_set_camera_and_player_boundary()
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
# 隐藏 player_line
|
||||
_setup_runtime()
|
||||
|
||||
|
||||
func _handle_restart() -> void:
|
||||
print("restarting... set GlobalConfig.DEBUG = true")
|
||||
restarting = true
|
||||
GlobalConfig.DEBUG = true
|
||||
_restart_from_main()
|
||||
|
||||
|
||||
func _setup_scene() -> void:
|
||||
foreground.layer = GlobalConfig.CANVAS_LAYER_FG
|
||||
player_line.visible = false
|
||||
|
||||
|
||||
func _validate_scene_name() -> void:
|
||||
scene_name = scene_name.strip_edges()
|
||||
if get_parent().name.begins_with("S") and (not scene_name or scene_name.length() != 7):
|
||||
printerr("scene_name is not valid")
|
||||
|
||||
|
||||
func _setup_runtime() -> void:
|
||||
_load_footstep_audio()
|
||||
# marker 默认就在 foucs player 状态
|
||||
# camera_focus_marker.focus_node(player)
|
||||
# %ColorRectTop.visible = true
|
||||
# %ColorRectBottom.visible = true
|
||||
# 如果 debug 模式下不通过 GroundLoader 启动,则插入到 main 以下
|
||||
_setup_player_light()
|
||||
SceneManager.toggle_hud_display(display_hud)
|
||||
|
||||
|
||||
func _restart_from_main():
|
||||
func _restart_from_main() -> void:
|
||||
# _enter_tree, wait for ready
|
||||
await ready
|
||||
_ensure_managers_loaded()
|
||||
_update_archive_for_restart()
|
||||
get_tree().change_scene_to_file.call_deferred("res://scene/main.tscn")
|
||||
|
||||
|
||||
func _ensure_managers_loaded() -> void:
|
||||
if not ArchiveManager.archive:
|
||||
ArchiveManager.load_archive()
|
||||
if not GlobalConfigManager.config:
|
||||
ArchiveManager.load_config()
|
||||
ArchiveManager.archive.current_scene = scene_name
|
||||
ArchiveManager.archive.entrance_portal = default_portal
|
||||
# get_tree().change_scene_to_packed.call_deferred(main_scene)
|
||||
get_tree().change_scene_to_file.call_deferred("res://scene/main.tscn")
|
||||
|
||||
|
||||
func _update_archive_for_restart() -> void:
|
||||
var archive = ArchiveManager.archive
|
||||
if archive:
|
||||
archive.current_scene = scene_name
|
||||
archive.entrance_portal = default_portal
|
||||
|
||||
|
||||
func get_player() -> MainPlayer:
|
||||
@ -121,43 +139,65 @@ func get_camera() -> CameraFocusMarker:
|
||||
return get_node_or_null("CameraFocusMarker") as CameraFocusMarker
|
||||
|
||||
|
||||
func reset_player_y():
|
||||
func reset_player_y() -> void:
|
||||
# 从屏幕下边缘算起
|
||||
if player_y_fixed:
|
||||
get_player().set_y_from_ground(158.0 - player_y)
|
||||
var p = get_player()
|
||||
if p:
|
||||
p.set_y_from_ground(158.0 - player_y)
|
||||
|
||||
|
||||
func _set_camera_and_player_boundary():
|
||||
# set current_boarder by bg size
|
||||
var camera_rect = Rect2(0, -158, 564, 316)
|
||||
var player_rect = Rect2(0, -158, 564, 316)
|
||||
if bg_sprite.texture:
|
||||
var size = bg_sprite.texture.get_size() * bg_sprite.scale
|
||||
# camera rect
|
||||
var camera_size = size
|
||||
camera_size.x += bg_sprite.position.x
|
||||
camera_size = Vector2(max(564.0, camera_size.x), max(camera_size.y, 316.0))
|
||||
var camera_upleft = Vector2(0, -camera_size.y / 2.0)
|
||||
camera_rect = Rect2(camera_upleft, camera_size)
|
||||
# player rect should be set centered, with 30px x padding
|
||||
player_rect.position.x = player_line.get_point_position(0).x + player_line.global_position.x
|
||||
player_rect.size.x = player_line.get_point_position(1).x - player_line.get_point_position(0).x
|
||||
func _set_camera_and_player_boundary() -> void:
|
||||
var camera_rect = _calculate_camera_rect()
|
||||
var player_rect = _calculate_player_rect()
|
||||
if GlobalConfig.DEBUG:
|
||||
print("try to _set_camera_and_player_boundary as:", camera_rect, player_rect)
|
||||
# set_camera_boundary
|
||||
_apply_camera_limits(camera_rect)
|
||||
_apply_player_boundary(player_rect)
|
||||
|
||||
|
||||
func _calculate_camera_rect() -> Rect2:
|
||||
var camera_rect = Rect2(0, -158, CAMERA_MIN_WIDTH, CAMERA_MIN_HEIGHT)
|
||||
if bg_sprite.texture:
|
||||
var size = bg_sprite.texture.get_size() * bg_sprite.scale
|
||||
var camera_size = size
|
||||
camera_size.x += bg_sprite.position.x
|
||||
camera_size = Vector2(
|
||||
max(CAMERA_MIN_WIDTH, camera_size.x), max(camera_size.y, CAMERA_MIN_HEIGHT)
|
||||
)
|
||||
var camera_upleft = Vector2(0, -camera_size.y / 2.0)
|
||||
camera_rect = Rect2(camera_upleft, camera_size)
|
||||
return camera_rect
|
||||
|
||||
|
||||
func _calculate_player_rect() -> Rect2:
|
||||
var player_rect = Rect2(0, -158, CAMERA_MIN_WIDTH, CAMERA_MIN_HEIGHT)
|
||||
if player_line:
|
||||
var line_start = player_line.get_point_position(0)
|
||||
var line_end = player_line.get_point_position(1)
|
||||
player_rect.position.x = line_start.x + player_line.global_position.x
|
||||
player_rect.size.x = line_end.x - line_start.x
|
||||
return player_rect
|
||||
|
||||
|
||||
func _apply_camera_limits(camera_rect: Rect2) -> void:
|
||||
var camera_marker = get_camera()
|
||||
if camera_marker:
|
||||
camera_marker.limit_left = camera_rect.position.x
|
||||
camera_marker.limit_right = camera_rect.position.x + camera_rect.size.x
|
||||
camera_marker.limit_top = camera_rect.position.y
|
||||
camera_marker.limit_bottom = camera_rect.position.y + camera_rect.size.y
|
||||
# set_player_boundary
|
||||
get_player().player_movement_rect = player_rect
|
||||
|
||||
|
||||
func _load_footstep_audio():
|
||||
# foot step sound
|
||||
func _apply_player_boundary(player_rect: Rect2) -> void:
|
||||
var p = get_player()
|
||||
if p:
|
||||
p.player_movement_rect = player_rect
|
||||
|
||||
|
||||
func _load_footstep_audio() -> void:
|
||||
footstep_audio.audio_collections.clear()
|
||||
if footstep_type != "none":
|
||||
if footstep_type != "none" and FOOTSTEP_AUDIO.has(footstep_type):
|
||||
var audio = FOOTSTEP_AUDIO[footstep_type] as AudioStreamCollection
|
||||
footstep_audio.audio_collections.append(audio)
|
||||
|
||||
@ -168,29 +208,44 @@ func play_footstep_sound() -> void:
|
||||
|
||||
|
||||
func move_player_to_portal(portal_name: String) -> void:
|
||||
var node_path = NodePath("DeployLayer/portal_" + portal_name)
|
||||
var portal_node = get_node_or_null(node_path) as Portal2D
|
||||
var portal_node = _get_portal_node(portal_name)
|
||||
var mov_player = get_player() as MainPlayer
|
||||
if portal_node and mov_player:
|
||||
if not portal_node:
|
||||
if mov_player:
|
||||
printerr(scene_name, " portal not found: portal_", portal_name)
|
||||
else:
|
||||
printerr("move_player_to_portal player not ready")
|
||||
return
|
||||
if not mov_player:
|
||||
printerr("move_player_to_portal player not ready")
|
||||
return
|
||||
_position_player_at_portal(mov_player, portal_node, portal_name)
|
||||
reset_player_y()
|
||||
print("[ground] move player to portal:", portal_name, portal_node.global_position)
|
||||
|
||||
|
||||
func _get_portal_node(portal_name: String) -> Portal2D:
|
||||
var node_path = NodePath("DeployLayer/portal_" + portal_name)
|
||||
return get_node_or_null(node_path) as Portal2D
|
||||
|
||||
|
||||
func _position_player_at_portal(
|
||||
mov_player: MainPlayer, portal_node: Portal2D, portal_name: String
|
||||
) -> void:
|
||||
mov_player.global_position.x = portal_node.global_position.x
|
||||
|
||||
if portal_name == "left":
|
||||
mov_player.set_facing_direction(Vector2.RIGHT)
|
||||
elif portal_name == "right":
|
||||
mov_player.set_facing_direction(Vector2.LEFT)
|
||||
reset_player_y()
|
||||
print("[ground] move player to portal:", portal_name, portal_node.global_position)
|
||||
elif mov_player:
|
||||
printerr(scene_name, " portal not found: ", node_path)
|
||||
else:
|
||||
printerr("move_player_to_portal player not ready")
|
||||
|
||||
|
||||
func _setup_player_light():
|
||||
func _setup_player_light() -> void:
|
||||
# 强制显示 directional_light
|
||||
directional_light.visible = true
|
||||
# 设置角色身上光源
|
||||
if directional_light.blend_mode == Light2D.BLEND_MODE_SUB and directional_light.energy > 0.6:
|
||||
player.enable_light = true
|
||||
else:
|
||||
player.enable_light = false
|
||||
var should_enable_light = (
|
||||
directional_light.blend_mode == Light2D.BLEND_MODE_SUB and directional_light.energy > 0.6
|
||||
)
|
||||
player.enable_light = should_enable_light
|
||||
print("_setup_player_light player.enable_light=", player.enable_light)
|
||||
|
@ -1,26 +1,12 @@
|
||||
class_name GroundLoader extends Node2D
|
||||
|
||||
@export_group("Scene")
|
||||
@export var ignore_archive := false
|
||||
@export var current_scene := "c02_s01"
|
||||
@export var entrance_portal := "left"
|
||||
@export var debug_reload := false:
|
||||
set(new_val):
|
||||
debug_reload = false
|
||||
if is_node_ready() and current_scene and entrance_portal:
|
||||
transition_to_scene(current_scene, entrance_portal, 0.0)
|
||||
# 强制覆盖 archive 记录
|
||||
@export var force_archive_scene := ""
|
||||
@export var force_archive_portal := ""
|
||||
# Constants
|
||||
const DEFAULT_TRANSITION_TIME := 1.4
|
||||
const MIN_TRANSITION_TIME := 0.6
|
||||
const EASE_DURATION := 0.3
|
||||
|
||||
@onready var mask_layer := %MaskLayer as CanvasLayer
|
||||
@onready var mask := %Mask as ColorRect
|
||||
|
||||
var has_entered := false
|
||||
var ground: Ground2D
|
||||
|
||||
# 场景名字映射到路径
|
||||
static var GROUND_SCENE_PATH_DICT = {
|
||||
# Scene name to path mapping
|
||||
const GROUND_SCENE_PATH_DICT = {
|
||||
"c01_s05": "uid://dlx5xxbg53rb8",
|
||||
"c01_s06": "uid://bx16c8nn32f40",
|
||||
"c01_s07": "uid://ds2iyfndwamiy",
|
||||
@ -58,107 +44,34 @@ static var GROUND_SCENE_PATH_DICT = {
|
||||
"c03_s09": "uid://dfln301xllqpn", # s09_棺材房
|
||||
}
|
||||
|
||||
# Exports
|
||||
@export var ignore_archive := false
|
||||
@export var current_scene := "c02_s01"
|
||||
@export var entrance_portal := "left"
|
||||
@export var debug_reload := false:
|
||||
set(new_val):
|
||||
debug_reload = false
|
||||
if is_node_ready() and current_scene and entrance_portal:
|
||||
transition_to_scene(current_scene, entrance_portal, 0.0)
|
||||
|
||||
func _ready() -> void:
|
||||
mask.visible = true
|
||||
mask.color.a = 0.0
|
||||
mask_layer.layer = GlobalConfig.CANVAS_LAYER_GROUND_MASK
|
||||
# mask layer 独立的 always 处理模式,可以保证转场正常运行
|
||||
# toggle_mask = mask_layer.toggle_mask
|
||||
ground = get_node_or_null("Ground") as Ground2D
|
||||
if ground:
|
||||
print("GroundLoader remove old ground:", ground.scene_name)
|
||||
# remove_child(ground)
|
||||
ground.queue_free()
|
||||
# load save
|
||||
if not ignore_archive:
|
||||
_load_save()
|
||||
if current_scene and entrance_portal:
|
||||
# 首次进入渐隐效果
|
||||
transition_to_scene(current_scene, entrance_portal)
|
||||
# transition_to_scene(current_scene, entrance_portal, 0.0)
|
||||
|
||||
|
||||
# # var toggle_mask:Callable
|
||||
# func toggle_mask(display: bool, mask_color: Color, wait_time: float) -> Tween:
|
||||
# return mask_layer.toggle_mask(display, mask_color, wait_time)
|
||||
|
||||
var display_start_sec = 0.0
|
||||
|
||||
|
||||
# wait_time 包含 ease in + wait + ease out 完整时长
|
||||
# ease duration = min(ease_min_duration, wait_time * 0.5)
|
||||
func toggle_mask(
|
||||
display: bool, wait_time: float, ease_min_duration := 0.3, mask_color := Color.BLACK
|
||||
) -> Tween:
|
||||
var tween = get_tree().create_tween()
|
||||
mask_color.a = mask.color.a
|
||||
mask.color = mask_color
|
||||
var duration = min(ease_min_duration, wait_time * 0.5)
|
||||
if display:
|
||||
display_start_sec = Time.get_ticks_msec() * 0.001
|
||||
tween.tween_property(mask, "color:a", 1.0, duration).set_trans(Tween.TRANS_CUBIC)
|
||||
else:
|
||||
# 转场至少 0.6s, 除去 0.3s 最后的淡出,需要 0.3s 的等待时间(包含 mask 的淡入)
|
||||
if wait_time:
|
||||
var time = Time.get_ticks_msec() * 0.001
|
||||
wait_time = max(wait_time + display_start_sec - time - 0.3, 0.0)
|
||||
if wait_time:
|
||||
tween.tween_interval(wait_time)
|
||||
tween.tween_property(mask, "color:a", 0.0, duration).set_trans(Tween.TRANS_CUBIC)
|
||||
return tween
|
||||
|
||||
|
||||
func _load_save():
|
||||
# 强制覆盖 archive 记录
|
||||
if force_archive_scene or force_archive_portal:
|
||||
current_scene = force_archive_scene
|
||||
entrance_portal = force_archive_portal
|
||||
return
|
||||
if not Engine.is_editor_hint():
|
||||
if ArchiveManager.archive.current_scene:
|
||||
current_scene = ArchiveManager.archive.current_scene
|
||||
if ArchiveManager.archive.entrance_portal:
|
||||
entrance_portal = ArchiveManager.archive.entrance_portal
|
||||
|
||||
|
||||
func transition_to_scene(scene_name: String, portal: String, wait_time := 1.4) -> void:
|
||||
if ground:
|
||||
print("GroundLoader transition_to_scene: pause prev ground.")
|
||||
# 先发送,再暂停,允许 sfx 等节点执行 ease out
|
||||
SceneManager.ground_transition_pre_paused.emit()
|
||||
ground.set_deferred("process_mode", Node.PROCESS_MODE_DISABLED)
|
||||
# print reenter lock status
|
||||
print("GroundLoader transition_to_scene: reenter lock status: ", ground.reenter_lock)
|
||||
var scene_path = GROUND_SCENE_PATH_DICT.get(scene_name)
|
||||
if scene_path:
|
||||
current_scene = scene_name
|
||||
entrance_portal = portal
|
||||
# 优先更新 archive,使 ground 可以访问自己的 current_scene 键值
|
||||
if not Engine.is_editor_hint():
|
||||
_update_archive()
|
||||
if wait_time > 0.0:
|
||||
# 转场效果,在 _load_ground_node 之前播放
|
||||
var tween = toggle_mask(true, wait_time)
|
||||
tween.tween_callback(_do_transition.call_deferred.bind(scene_name))
|
||||
_allow_ground_start = false
|
||||
# 等到 toggle_mask 结束,再重置 freeze 状态
|
||||
toggle_mask(false, wait_time).tween_callback(func(): _allow_ground_start = true)
|
||||
else:
|
||||
_allow_ground_start = true
|
||||
_do_transition.call_deferred(scene_name)
|
||||
else:
|
||||
print("Scene not found: " + scene_name)
|
||||
# 强制覆盖 archive 记录
|
||||
@export var force_archive_scene := ""
|
||||
@export var force_archive_portal := ""
|
||||
|
||||
# Nodes
|
||||
@onready var mask_layer := %MaskLayer as CanvasLayer
|
||||
@onready var mask := %Mask as ColorRect
|
||||
|
||||
# State
|
||||
var has_entered := false
|
||||
var ground: Ground2D
|
||||
var display_start_sec := 0.0
|
||||
var _frozen_start_time_ms: int
|
||||
var _allow_ground_start := false:
|
||||
set(val):
|
||||
_allow_ground_start = val
|
||||
if ground:
|
||||
if val:
|
||||
if ground and val:
|
||||
if ground.process_mode != Node.PROCESS_MODE_INHERIT:
|
||||
# ground_start 信号
|
||||
SceneManager.ground_start.emit()
|
||||
ground.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
print(
|
||||
@ -166,49 +79,150 @@ var _allow_ground_start := false:
|
||||
Time.get_ticks_msec() - _frozen_start_time_ms
|
||||
)
|
||||
|
||||
# Debug
|
||||
var update_watcher: Timer
|
||||
var last_modify_time := 0
|
||||
|
||||
func _update_archive():
|
||||
ArchiveManager.archive.current_scene = current_scene
|
||||
ArchiveManager.archive.entrance_portal = entrance_portal
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_mask_layer()
|
||||
if not ignore_archive:
|
||||
_load_save()
|
||||
if current_scene and entrance_portal:
|
||||
# 首次进入渐隐效果
|
||||
transition_to_scene(current_scene, entrance_portal)
|
||||
|
||||
|
||||
func _setup_mask_layer() -> void:
|
||||
mask.visible = true
|
||||
mask.color.a = 0.0
|
||||
mask_layer.layer = GlobalConfig.CANVAS_LAYER_GROUND_MASK
|
||||
|
||||
|
||||
func _load_save() -> void:
|
||||
# 强制覆盖 archive 记录
|
||||
if force_archive_scene or force_archive_portal:
|
||||
current_scene = force_archive_scene
|
||||
entrance_portal = force_archive_portal
|
||||
return
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
var archive = ArchiveManager.archive
|
||||
if archive:
|
||||
if archive.current_scene:
|
||||
current_scene = archive.current_scene
|
||||
if archive.entrance_portal:
|
||||
entrance_portal = archive.entrance_portal
|
||||
|
||||
|
||||
func toggle_mask(
|
||||
display: bool, wait_time: float, ease_min_duration := EASE_DURATION, mask_color := Color.BLACK
|
||||
) -> Tween:
|
||||
var tween = get_tree().create_tween()
|
||||
mask_color.a = mask.color.a
|
||||
mask.color = mask_color
|
||||
var duration = min(ease_min_duration, wait_time * 0.5)
|
||||
|
||||
if display:
|
||||
display_start_sec = Time.get_ticks_msec() * 0.001
|
||||
tween.tween_property(mask, "color:a", 1.0, duration).set_trans(Tween.TRANS_CUBIC)
|
||||
else:
|
||||
# 转场至少 0.6s, 除去 0.3s 最后的淡出,需要 0.3s 的等待时间(包含 mask 的淡入)
|
||||
if wait_time > 0.0:
|
||||
var time = Time.get_ticks_msec() * 0.001
|
||||
wait_time = max(wait_time + display_start_sec - time - EASE_DURATION, 0.0)
|
||||
if wait_time > 0.0:
|
||||
tween.tween_interval(wait_time)
|
||||
tween.tween_property(mask, "color:a", 0.0, duration).set_trans(Tween.TRANS_CUBIC)
|
||||
|
||||
return tween
|
||||
|
||||
|
||||
func transition_to_scene(
|
||||
scene_name: String, portal: String, wait_time := DEFAULT_TRANSITION_TIME
|
||||
) -> void:
|
||||
if not GROUND_SCENE_PATH_DICT.has(scene_name):
|
||||
print("Scene not found: " + scene_name)
|
||||
return
|
||||
_pause_current_ground()
|
||||
current_scene = scene_name
|
||||
entrance_portal = portal
|
||||
# 优先更新 archive,使 ground 可以访问自己的 current_scene 键值
|
||||
if not Engine.is_editor_hint():
|
||||
_update_archive()
|
||||
if wait_time > 0.0:
|
||||
_transition_with_effect(scene_name, wait_time)
|
||||
else:
|
||||
_allow_ground_start = true
|
||||
_do_transition.call_deferred(scene_name)
|
||||
|
||||
|
||||
func _pause_current_ground() -> void:
|
||||
if not ground:
|
||||
return
|
||||
print("GroundLoader transition_to_scene: pause prev ground.")
|
||||
# 先发送,再暂停,允许 sfx 等节点执行 ease out
|
||||
SceneManager.ground_transition_pre_paused.emit()
|
||||
ground.set_deferred("process_mode", Node.PROCESS_MODE_DISABLED)
|
||||
# print reenter lock status
|
||||
print("GroundLoader transition_to_scene: reenter lock status: ", ground.reenter_lock)
|
||||
|
||||
|
||||
func _transition_with_effect(scene_name: String, wait_time: float) -> void:
|
||||
# 转场效果,在 _load_ground_node 之前播放
|
||||
var tween = toggle_mask(true, wait_time)
|
||||
tween.tween_callback(_do_transition.call_deferred.bind(scene_name))
|
||||
_allow_ground_start = false
|
||||
# 等到 toggle_mask 结束,再重置 freeze 状态
|
||||
toggle_mask(false, wait_time).tween_callback(func(): _allow_ground_start = true)
|
||||
|
||||
|
||||
func _update_archive() -> void:
|
||||
var archive = ArchiveManager.archive
|
||||
if archive:
|
||||
archive.current_scene = current_scene
|
||||
archive.entrance_portal = entrance_portal
|
||||
|
||||
|
||||
func _do_transition(scene_name: String) -> void:
|
||||
# SceneManager.freeze_player(0)
|
||||
print("GroundLoader Transition to scene:", scene_name, "portal:", entrance_portal)
|
||||
_remove_current_ground()
|
||||
_load_and_setup_ground(scene_name)
|
||||
_add_ground()
|
||||
if _allow_ground_start:
|
||||
SceneManager.ground_start.emit()
|
||||
_post_transition()
|
||||
|
||||
|
||||
func _remove_current_ground() -> void:
|
||||
ground = get_node_or_null("Ground") as Ground2D
|
||||
if ground:
|
||||
# 防止命名冲突
|
||||
remove_child(ground)
|
||||
ground.queue_free()
|
||||
|
||||
|
||||
func _load_and_setup_ground(scene_name: String) -> void:
|
||||
# 先设置 ground,再添加到场景中
|
||||
# 因为 ground 在 enter_tree 时会用到 SceneManager 的方法
|
||||
# 其中间接用到了 GroundLoader 的 ground
|
||||
ground = _load_ground_node(scene_name)
|
||||
|
||||
if not _allow_ground_start:
|
||||
ground.set_deferred("process_mode", Node.PROCESS_MODE_DISABLED)
|
||||
print("GroundLoader not _allow_ground_start: frozen (delayed)")
|
||||
_frozen_start_time_ms = Time.get_ticks_msec()
|
||||
_add_ground()
|
||||
if _allow_ground_start:
|
||||
# 如果不阻塞,直接 ground_start 信号
|
||||
SceneManager.ground_start.emit()
|
||||
# 预先加载邻居场景
|
||||
_post_transition()
|
||||
if GlobalConfig.DEBUG and not Engine.is_editor_hint():
|
||||
_watch_scene_update()
|
||||
|
||||
|
||||
func _add_ground():
|
||||
func _add_ground() -> void:
|
||||
ground.ready.connect(SceneManager.ground_ready.emit.bind(ground))
|
||||
ground.name = "Ground"
|
||||
|
||||
# 在 add child 之前,调整 ground 内部元素属性,在 on ground ready 前设置完成
|
||||
if not Engine.is_editor_hint():
|
||||
# 更新玩家位置
|
||||
if not has_entered:
|
||||
_update_player_position_from_archive()
|
||||
else:
|
||||
# move player to portal
|
||||
ground.move_player_to_portal(entrance_portal)
|
||||
_setup_player_position()
|
||||
add_child(ground)
|
||||
print(
|
||||
"GroundLoader add_ground finished:",
|
||||
@ -222,12 +236,23 @@ func _add_ground():
|
||||
has_entered = true
|
||||
|
||||
|
||||
func _update_player_position_from_archive():
|
||||
func _setup_player_position() -> void:
|
||||
if not has_entered:
|
||||
_update_player_position_from_archive()
|
||||
else:
|
||||
ground.move_player_to_portal(entrance_portal)
|
||||
|
||||
|
||||
func _update_player_position_from_archive() -> void:
|
||||
if ignore_archive or Engine.is_editor_hint():
|
||||
return
|
||||
var archive = ArchiveManager.archive
|
||||
if not archive:
|
||||
return
|
||||
var player = ground.get_player() as MainPlayer
|
||||
player.global_position.x = ArchiveManager.archive.player_global_position_x
|
||||
player.set_facing_direction(ArchiveManager.archive.player_direction)
|
||||
if player:
|
||||
player.global_position.x = archive.player_global_position_x
|
||||
player.set_facing_direction(archive.player_direction)
|
||||
ground.reset_player_y()
|
||||
|
||||
|
||||
@ -235,73 +260,42 @@ func _load_ground_node(scene_name: String) -> Ground2D:
|
||||
if not GROUND_SCENE_PATH_DICT.has(scene_name):
|
||||
return null
|
||||
var path = GROUND_SCENE_PATH_DICT[scene_name]
|
||||
var scene: PackedScene
|
||||
if ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
scene = ResourceLoader.load_threaded_get(path) as PackedScene
|
||||
else:
|
||||
scene = ResourceLoader.load(path) as PackedScene
|
||||
if scene:
|
||||
var scene: PackedScene = _load_scene_resource(path)
|
||||
if not scene:
|
||||
return null
|
||||
var instance = scene.instantiate() as Node2D
|
||||
var ground_node = instance.get_child(0)
|
||||
instance.remove_child(ground_node)
|
||||
ground_node.owner = null
|
||||
instance.queue_free()
|
||||
return ground_node
|
||||
return null
|
||||
|
||||
|
||||
# 读取 portals,预加载邻居场景
|
||||
func _post_transition():
|
||||
if ground:
|
||||
var scene_names = []
|
||||
var deploy_layer = ground.get_node("DeployLayer")
|
||||
if deploy_layer:
|
||||
for node in deploy_layer.get_children():
|
||||
var portal = node as Portal2D
|
||||
if not portal or not portal.target_scene:
|
||||
continue
|
||||
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])
|
||||
if GlobalConfig.DEBUG:
|
||||
print("preload neighbor scenes:", scene_names)
|
||||
func _load_scene_resource(path: String) -> PackedScene:
|
||||
if ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
return ResourceLoader.load_threaded_get(path) as PackedScene
|
||||
else:
|
||||
return ResourceLoader.load(path) as PackedScene
|
||||
|
||||
|
||||
func _post_transition() -> void:
|
||||
if not ground:
|
||||
return
|
||||
_preload_neighbor_scenes()
|
||||
GlobalConfigManager.print_global_info()
|
||||
|
||||
|
||||
var update_watcher: Timer
|
||||
var last_modify_time = 0
|
||||
|
||||
|
||||
# DEBUG 时重新加载资源
|
||||
func _watch_scene_update():
|
||||
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:
|
||||
update_watcher = Timer.new()
|
||||
update_watcher.wait_time = 1
|
||||
update_watcher.one_shot = false
|
||||
add_child(update_watcher)
|
||||
update_watcher.start()
|
||||
else:
|
||||
# remove all connections
|
||||
for c in update_watcher.timeout.get_connections():
|
||||
update_watcher.timeout.disconnect(c.callable)
|
||||
update_watcher.timeout.connect(_check_scene_update.bind(scene_path))
|
||||
|
||||
|
||||
func _check_scene_update(scene_path):
|
||||
var modify = FileAccess.get_modified_time(scene_path)
|
||||
if modify != last_modify_time:
|
||||
last_modify_time = modify
|
||||
_on_resources_reload(scene_path)
|
||||
|
||||
|
||||
func _on_resources_reload(res):
|
||||
print("resources_reload processing:", res)
|
||||
if not Engine.is_editor_hint() and res.ends_with(".tscn"):
|
||||
ArchiveManager.save_all()
|
||||
has_entered = false
|
||||
transition_to_scene.call_deferred(current_scene, entrance_portal, 0.0)
|
||||
func _preload_neighbor_scenes() -> void:
|
||||
var scene_names: Array[String] = []
|
||||
var deploy_layer = ground.get_node_or_null("DeployLayer")
|
||||
if not deploy_layer:
|
||||
return
|
||||
for node in deploy_layer.get_children():
|
||||
var portal = node as Portal2D
|
||||
if portal and portal.target_scene and GROUND_SCENE_PATH_DICT.has(portal.target_scene):
|
||||
scene_names.append(portal.target_scene)
|
||||
if GlobalConfig.DEBUG:
|
||||
print("preload neighbor scenes:", scene_names)
|
||||
for scene_name in scene_names:
|
||||
if GROUND_SCENE_PATH_DICT.has(scene_name):
|
||||
ResourceLoader.load_threaded_request(GROUND_SCENE_PATH_DICT[scene_name])
|
||||
|
153
scene/trailer.gd
153
scene/trailer.gd
@ -1,68 +1,113 @@
|
||||
extends Control
|
||||
|
||||
@onready var video_player = $VideoStreamPlayer
|
||||
@onready var earplug_notice = $"耳机提示" as Control
|
||||
# Constants
|
||||
const EARPLUG_FADE_DURATION := 1.0
|
||||
const EARPLUG_DISPLAY_DURATION := 3.0
|
||||
|
||||
@onready var sfx_click = $SfxClick as Sfx
|
||||
@onready var settings = %"设置界面" as Control
|
||||
@onready var lang_label = %LangLabel as Label
|
||||
@onready var lang_left_btn = %LangLeft as Button
|
||||
@onready var lang_right_btn = %LangRight as Button
|
||||
@onready var caption_box = %CaptionBox as BoxContainer
|
||||
@onready var caption_label = %CaptionLabel as Label
|
||||
@onready var caption_left_btn = %CaptionLeft as Button
|
||||
@onready var caption_right_btn = %CaptionRight as Button
|
||||
@onready var confirm_btn = %ConfirmButton as Button
|
||||
# Nodes
|
||||
@onready var video_player := $VideoStreamPlayer
|
||||
@onready var earplug_notice := $"耳机提示" as Control
|
||||
@onready var sfx_click := $SfxClick as Sfx
|
||||
@onready var settings := %"设置界面" as Control
|
||||
@onready var lang_label := %LangLabel as Label
|
||||
@onready var lang_left_btn := %LangLeft as Button
|
||||
@onready var lang_right_btn := %LangRight as Button
|
||||
@onready var caption_box := %CaptionBox as BoxContainer
|
||||
@onready var caption_label := %CaptionLabel as Label
|
||||
@onready var caption_left_btn := %CaptionLeft as Button
|
||||
@onready var caption_right_btn := %CaptionRight as Button
|
||||
@onready var confirm_btn := %ConfirmButton as Button
|
||||
|
||||
var packed_index_page := preload("uid://c4ycvdsabi7lw")
|
||||
|
||||
# State
|
||||
var first_launching_game := true
|
||||
var earplug_notice_tween: Tween
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
earplug_notice.hide()
|
||||
if GlobalConfigManager.config:
|
||||
if not GlobalConfigManager.config:
|
||||
SceneManager.checkout_index_page(false)
|
||||
return
|
||||
var game_launched_times = GlobalConfigManager.config.game_launched_times
|
||||
GlobalConfigManager.config.game_launched_times = game_launched_times + 1
|
||||
first_launching_game = game_launched_times == 0
|
||||
GlobalConfigManager.config.game_launched_times += 1
|
||||
first_launching_game = (game_launched_times == 0)
|
||||
if GlobalConfigManager.config.skip_trailer:
|
||||
SceneManager.checkout_index_page(false)
|
||||
return
|
||||
_setup_video_player()
|
||||
_setup_language_settings(game_launched_times)
|
||||
_connect_button_signals()
|
||||
|
||||
|
||||
func _setup_video_player() -> void:
|
||||
video_player.play()
|
||||
video_player.finished.connect(_on_video_finished)
|
||||
|
||||
|
||||
func _setup_language_settings(game_launched_times: int) -> void:
|
||||
if game_launched_times == 0:
|
||||
_read_system_locale()
|
||||
_update_language_display()
|
||||
|
||||
|
||||
func _update_language_display() -> void:
|
||||
lang_label.text = GlobalConfigManager.get_locale_language_name()
|
||||
caption_label.text = GlobalConfigManager.get_locale_caption_name()
|
||||
if caption_label.text == "":
|
||||
caption_box.hide()
|
||||
lang_left_btn.pressed.connect(_on_lang_left_btn_pressed)
|
||||
lang_right_btn.pressed.connect(_on_lang_right_btn_pressed)
|
||||
caption_left_btn.pressed.connect(_on_caption_left_btn_pressed)
|
||||
caption_right_btn.pressed.connect(_on_caption_right_btn_pressed)
|
||||
caption_box.visible = (caption_label.text != "")
|
||||
|
||||
|
||||
var earplug_notice_tween: Tween
|
||||
func _connect_button_signals() -> void:
|
||||
lang_left_btn.pressed.connect(_on_lang_change.bind(-1))
|
||||
lang_right_btn.pressed.connect(_on_lang_change.bind(1))
|
||||
caption_left_btn.pressed.connect(_on_caption_change.bind(-1))
|
||||
caption_right_btn.pressed.connect(_on_caption_change.bind(1))
|
||||
|
||||
|
||||
func _read_system_locale() -> void:
|
||||
if not GlobalConfigManager.config:
|
||||
return
|
||||
var locale = OS.get_locale()
|
||||
var prefix = locale.split("_")[0]
|
||||
var config = GlobalConfigManager.config
|
||||
if GlobalConfig.LOCALE_PREFIX_MAPPING.has(prefix):
|
||||
config.language = GlobalConfig.LOCALE_PREFIX_MAPPING.get(prefix)
|
||||
GlobalConfigManager.update_locale(config.language, config.caption)
|
||||
print("[FirstLaunch] System locale: ", locale, " -> Language ID: ", GlobalConfigManager.config.language)
|
||||
|
||||
|
||||
func _on_video_finished() -> void:
|
||||
earplug_notice.visible = true
|
||||
earplug_notice.modulate.a = 0
|
||||
earplug_notice_tween = create_tween()
|
||||
earplug_notice_tween.tween_property(earplug_notice, "modulate:a", 1.0, 1.0)
|
||||
if not first_launching_game:
|
||||
settings.hide()
|
||||
earplug_notice_tween.tween_interval(3.0)
|
||||
earplug_notice_tween.tween_property(earplug_notice, "modulate:a", 0.0, 1.0)
|
||||
earplug_notice_tween.finished.connect(_on_earplug_notice_finished)
|
||||
earplug_notice_tween.tween_property(earplug_notice, "modulate:a", 1.0, EARPLUG_FADE_DURATION)
|
||||
if first_launching_game:
|
||||
_show_first_launch_settings()
|
||||
else:
|
||||
_show_regular_launch_sequence()
|
||||
|
||||
|
||||
func _show_first_launch_settings() -> void:
|
||||
settings.show()
|
||||
confirm_btn.pressed.connect(_on_confirm_btn_pressed)
|
||||
|
||||
|
||||
func _show_regular_launch_sequence() -> void:
|
||||
settings.hide()
|
||||
earplug_notice_tween.tween_interval(EARPLUG_DISPLAY_DURATION)
|
||||
earplug_notice_tween.tween_property(earplug_notice, "modulate:a", 0.0, EARPLUG_FADE_DURATION)
|
||||
earplug_notice_tween.finished.connect(_on_earplug_notice_finished)
|
||||
|
||||
|
||||
func _on_confirm_btn_pressed() -> void:
|
||||
confirm_btn.disabled = true
|
||||
_fade_out_and_continue()
|
||||
|
||||
|
||||
func _fade_out_and_continue() -> void:
|
||||
var tween = create_tween()
|
||||
tween.tween_property(earplug_notice, "modulate:a", 0.0, 1.0)
|
||||
tween.tween_property(earplug_notice, "modulate:a", 0.0, EARPLUG_FADE_DURATION)
|
||||
tween.finished.connect(_on_earplug_notice_finished)
|
||||
|
||||
|
||||
@ -71,49 +116,37 @@ func _on_earplug_notice_finished() -> void:
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("escape") and not first_launching_game:
|
||||
if not event.is_action_pressed("escape") or first_launching_game:
|
||||
return
|
||||
|
||||
if video_player.is_playing():
|
||||
_skip_video()
|
||||
elif earplug_notice_tween and earplug_notice_tween.is_running():
|
||||
_skip_earplug_notice()
|
||||
|
||||
|
||||
func _skip_video() -> void:
|
||||
get_viewport().set_input_as_handled()
|
||||
video_player.stop()
|
||||
_on_video_finished()
|
||||
elif earplug_notice_tween and earplug_notice_tween.is_running():
|
||||
|
||||
|
||||
func _skip_earplug_notice() -> void:
|
||||
get_viewport().set_input_as_handled()
|
||||
earplug_notice_tween.kill()
|
||||
earplug_notice.modulate.a = 0
|
||||
_on_earplug_notice_finished()
|
||||
|
||||
|
||||
func _on_lang_left_btn_pressed() -> void:
|
||||
func _on_lang_change(direction: int) -> void:
|
||||
sfx_click.play()
|
||||
GlobalConfigManager.update_locale(
|
||||
GlobalConfigManager.config.language - 1, GlobalConfigManager.config.caption
|
||||
)
|
||||
lang_label.text = GlobalConfigManager.get_locale_language_name()
|
||||
caption_label.text = GlobalConfigManager.get_locale_caption_name()
|
||||
caption_box.visible = caption_label.text != ""
|
||||
var config = GlobalConfigManager.config
|
||||
GlobalConfigManager.update_locale(config.language + direction, config.caption)
|
||||
_update_language_display()
|
||||
|
||||
|
||||
func _on_lang_right_btn_pressed() -> void:
|
||||
func _on_caption_change(direction: int) -> void:
|
||||
sfx_click.play()
|
||||
GlobalConfigManager.update_locale(
|
||||
GlobalConfigManager.config.language + 1, GlobalConfigManager.config.caption
|
||||
)
|
||||
lang_label.text = GlobalConfigManager.get_locale_language_name()
|
||||
caption_label.text = GlobalConfigManager.get_locale_caption_name()
|
||||
caption_box.visible = caption_label.text != ""
|
||||
|
||||
|
||||
func _on_caption_left_btn_pressed() -> void:
|
||||
sfx_click.play()
|
||||
GlobalConfigManager.update_locale(
|
||||
GlobalConfigManager.config.language, GlobalConfigManager.config.caption - 1
|
||||
)
|
||||
caption_label.text = GlobalConfigManager.get_locale_caption_name()
|
||||
|
||||
|
||||
func _on_caption_right_btn_pressed() -> void:
|
||||
sfx_click.play()
|
||||
GlobalConfigManager.update_locale(
|
||||
GlobalConfigManager.config.language, GlobalConfigManager.config.caption + 1
|
||||
)
|
||||
var config = GlobalConfigManager.config
|
||||
GlobalConfigManager.update_locale(config.language, config.caption + direction)
|
||||
caption_label.text = GlobalConfigManager.get_locale_caption_name()
|
@ -10,6 +10,7 @@ const TWEEN_DURATION = 0.5
|
||||
const SHAKE_FPS = 15.0
|
||||
const SHAKE_DURATION = 0.8
|
||||
const SHAKE_DELTA = 12.0
|
||||
const HUD_FADE_DURATION = 0.3
|
||||
|
||||
@export_group("DebugItem")
|
||||
@export var enable_item := false:
|
||||
@ -63,6 +64,7 @@ var listen_mouse = false
|
||||
var displaying = false
|
||||
var timer := Timer.new()
|
||||
var display_tween: Tween
|
||||
var hud_visibility_tween: Tween
|
||||
var container_tween: Tween
|
||||
var prop_blink: Tween
|
||||
var tween_scroll: Tween
|
||||
@ -174,11 +176,6 @@ func checkout_inventory(character: String) -> void:
|
||||
_reload_cache_and_realign_display()
|
||||
|
||||
|
||||
const HUD_FADE_DURATION = 0.3
|
||||
|
||||
# 在变量定义区域添加
|
||||
var hud_visibility_tween: Tween
|
||||
|
||||
func hide_hud(duration: float = HUD_FADE_DURATION):
|
||||
if not visible:
|
||||
return
|
||||
|
Loading…
Reference in New Issue
Block a user