150 lines
5.5 KiB
GDScript
150 lines
5.5 KiB
GDScript
## SettingsManager — 全局设置 + 难度(Autoload: SettingsManager)
|
||
## 权威来源:development_plan.md S6 P1(难度三档持久化)、certification_checklist.md ST-03(清除本地数据)
|
||
## 持久化 user://save_data.json:{ difficulty:int, locale:String, master_volume:float }
|
||
extends Node
|
||
|
||
const SAVE_PATH: String = "user://save_data.json"
|
||
|
||
enum Difficulty { BEGINNER = 0, STANDARD = 1, CHALLENGE = 2 }
|
||
|
||
var difficulty: int = Difficulty.STANDARD
|
||
var locale: String = "zh_CN"
|
||
var master_volume: float = 1.0
|
||
var ftue_done: bool = false # 首次启动难度引导是否已完成(FTUE)
|
||
|
||
func _ready() -> void:
|
||
_load()
|
||
_load_balance()
|
||
_apply_audio()
|
||
if Locale:
|
||
Locale.set_locale(locale)
|
||
|
||
## 是否需要首启难度引导(FTUE)
|
||
func needs_ftue() -> bool:
|
||
return not ftue_done
|
||
|
||
## 完成 FTUE:设定难度并持久化标志
|
||
func complete_ftue(chosen_difficulty: int) -> void:
|
||
difficulty = clampi(chosen_difficulty, 0, 2)
|
||
ftue_done = true
|
||
_save()
|
||
|
||
## 难度乘子 + Boss 血量:纯数据驱动,唯一来源 data/balance.json(游戏设计器「平衡」面板维护)
|
||
## get() 的默认仅为防崩溃,非内容副本
|
||
var _MULTS: Dictionary = {} # key → [初学者, 标准, 挑战]
|
||
var _BOSS_HP: Dictionary = {} # 原型→基础血量
|
||
|
||
const BALANCE_JSON: String = "res://data/balance.json"
|
||
|
||
func _mult(key: String) -> float:
|
||
var arr: Array = _MULTS.get(key, [1.0, 1.0, 1.0])
|
||
return float(arr[clampi(difficulty, 0, arr.size() - 1)])
|
||
|
||
func enemy_hp_mult() -> float: return _mult("enemy_hp")
|
||
func player_dmg_taken_mult() -> float: return _mult("player_dmg")
|
||
func wave_count_mult() -> float: return _mult("wave_count")
|
||
func boss_hp_mult() -> float: return _mult("boss_hp")
|
||
|
||
## Boss 基础血量(WaveManager._spawn_boss 查询;balance.json 可调)
|
||
func get_boss_base_hp(boss_type: int) -> float:
|
||
return float(_BOSS_HP.get(str(boss_type), 500.0))
|
||
|
||
## 数据驱动:加载 balance.json(难度乘子 + Boss 血量)
|
||
func _load_balance() -> void:
|
||
if not FileAccess.file_exists(BALANCE_JSON):
|
||
push_error("SettingsManager: 缺失 res://data/balance.json(难度乘子/Boss血量)")
|
||
return
|
||
var data = JSON.parse_string(FileAccess.get_file_as_string(BALANCE_JSON))
|
||
if not (data is Dictionary):
|
||
push_error("SettingsManager: balance.json 格式错误")
|
||
return
|
||
if data.has("difficulty_mults") and data["difficulty_mults"] is Dictionary:
|
||
for k in data["difficulty_mults"]:
|
||
_MULTS[k] = data["difficulty_mults"][k]
|
||
if data.has("boss_hp") and data["boss_hp"] is Dictionary:
|
||
_BOSS_HP = data["boss_hp"]
|
||
|
||
func difficulty_name() -> String:
|
||
return ["DIFF_BEGINNER", "DIFF_STANDARD", "DIFF_CHALLENGE"][difficulty]
|
||
|
||
# ── 设置变更(即时保存)──────────────────────────────────────
|
||
func set_difficulty(d: int) -> void:
|
||
difficulty = clampi(d, 0, 2)
|
||
_save()
|
||
|
||
func cycle_difficulty() -> int:
|
||
set_difficulty((difficulty + 1) % 3)
|
||
return difficulty
|
||
|
||
func set_locale(loc: String) -> void:
|
||
locale = loc
|
||
if Locale:
|
||
Locale.set_locale(loc)
|
||
_save()
|
||
|
||
func set_master_volume(v: float) -> void:
|
||
master_volume = clampf(v, 0.0, 1.0)
|
||
_apply_audio()
|
||
_save()
|
||
|
||
func _apply_audio() -> void:
|
||
var bus: int = AudioServer.get_bus_index("Master")
|
||
if bus >= 0:
|
||
AudioServer.set_bus_volume_db(bus, linear_to_db(maxf(0.0001, master_volume)))
|
||
|
||
# ── ST-03:清除所有本地数据 ─────────────────────────────────
|
||
func clear_all_local_data() -> void:
|
||
var files: Array = [
|
||
"user://crash_log.txt", "user://crash_log_prev.txt",
|
||
"user://run_a.json", "user://run_b.json",
|
||
"user://endless_records.json", "user://save_data.json",
|
||
]
|
||
for path in files:
|
||
if FileAccess.file_exists(path):
|
||
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
|
||
_remove_dir_recursive("user://runs")
|
||
# 重置内存态
|
||
difficulty = Difficulty.STANDARD
|
||
master_volume = 1.0
|
||
ftue_done = false # 清数据后重新触发首启难度引导
|
||
# 用路径引用避免编译时 autoload 依赖(打破 SettingsManager→ProfileManager→WaveManager→SettingsManager 循环)
|
||
var pm: Node = get_node_or_null("/root/ProfileManager")
|
||
if pm and pm.has_method("clear_run"):
|
||
pm.clear_run()
|
||
_apply_audio()
|
||
|
||
func _remove_dir_recursive(path: String) -> void:
|
||
var d := DirAccess.open(path)
|
||
if d == null:
|
||
return
|
||
d.list_dir_begin()
|
||
var fn := d.get_next()
|
||
while fn != "":
|
||
if not d.current_is_dir():
|
||
DirAccess.remove_absolute(ProjectSettings.globalize_path(path + "/" + fn))
|
||
fn = d.get_next()
|
||
d.list_dir_end()
|
||
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
|
||
|
||
# ── 持久化 ──────────────────────────────────────────────────
|
||
func _save() -> void:
|
||
var data: Dictionary = {
|
||
"difficulty": difficulty, "locale": locale,
|
||
"master_volume": master_volume, "ftue_done": ftue_done,
|
||
}
|
||
var f := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||
if f:
|
||
f.store_string(JSON.stringify(data))
|
||
f.close()
|
||
|
||
func _load() -> void:
|
||
if not FileAccess.file_exists(SAVE_PATH):
|
||
return
|
||
var parsed = JSON.parse_string(FileAccess.get_file_as_string(SAVE_PATH))
|
||
if not (parsed is Dictionary):
|
||
return
|
||
difficulty = clampi(int(parsed.get("difficulty", 1)), 0, 2)
|
||
locale = String(parsed.get("locale", "zh_CN"))
|
||
master_volume = clampf(float(parsed.get("master_volume", 1.0)), 0.0, 1.0)
|
||
ftue_done = bool(parsed.get("ftue_done", false))
|