Files
mtgodot-poc/project/ui/system_option_ui.gd
T

257 lines
8.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SystemOptionUI (P11) —— 系统设置窗(1:1 迁移 `assets/root/uisystemoption.py` `OptionDialog`)。
#
# var so := preload("res://ui/system_option_ui.gd").new()
# add_child(so)
# so.setup(ui_manager, assets_root, audio_node, func(): return cam, func(): return env)
# so.toggle() # ESC(无其它窗打开时)
#
# 布局走真 `assets/uiscript/uiscript/systemoptiondialog.py`UiScript → UiBuild)。
# 控件绑定逐字对照 uisystemoption.py
# music_volume_controller → snd.SetMusicVolume + systemSetting.SetMusicVolume
# sound_volume_controller → snd.SetSoundVolume + systemSetting.SetSoundVolume
# camera_short / camera_long → systemSetting 镜头最大距离(近/远)
# fog_level0 / 1 / 2 (浓/中/淡) → systemSetting.SetFogLevel(0..2)
# tiling_cpu / tiling_gpu / apply → 原为 CPU/GPU 分块渲染,Godot 渲染器无对应,保留为占位
# 设置持久化到 `user://system_option.cfg`= 原 systemSetting 的配置文件)。
extends Node
const CFG_PATH := "user://system_option.cfg"
const CAMERA_MAX := [11.0, 20.0] # 近 / 远(game_camera.max_dist
const FOG_DENSITY := [0.055, 0.018, 0.004] # 浓 / 中 / 淡(Environment.fog_density
# uiScriptLocale 未接全局 Locale 时的中文兜底
const LABELS := {
"titlename": "系统设置", "music_name": "背景音乐", "sound_name": "音效",
"bgm_button": "更换", "bgm_file": "默认主题", "camera_mode": "镜头距离",
"camera_short": "近", "camera_long": "远", "fog_mode": "雾",
"fog_level0": "浓", "fog_level1": "中", "fog_level2": "淡",
"tiling_mode": "图形内存", "tiling_cpu": "CPU", "tiling_gpu": "GPU", "tiling_apply": "应用",
}
var ui: CanvasLayer
var assets_root := ""
var uiscript_dir := ""
var audio: Node
var _cam_get: Callable = Callable()
var _env_get: Callable = Callable()
var _win: Dictionary = {}
var _cfg := ConfigFile.new()
# --- 状态(= systemSetting 值)---
var music_volume := 0.6
var sound_volume := 0.9
var camera_mode := 1 # 0 近, 1 远
var fog_level := 1 # 0 浓, 1 中, 2 淡
var tiling_mode := 1 # 0 CPU, 1 GPU(占位)
static func config_path() -> String:
var override_dir := OS.get_environment("MT_CONFIG_DIR")
if override_dir != "":
return override_dir.path_join("system_option.cfg")
# Godot headless 在受限执行环境里可能无法写 user://;测试配置放在系统
# 临时目录,正常图形客户端仍使用 user://。
return OS.get_temp_dir().path_join("mtgodot_system_option.cfg") \
if DisplayServer.get_name() == "headless" else CFG_PATH
func setup(ui_manager: CanvasLayer, assets: String, audio_node: Node = null,
cam_getter := Callable(), env_getter := Callable()) -> void:
ui = ui_manager
assets_root = assets
if assets_root == "" and ui and "assets_root" in ui:
assets_root = ui.assets_root
uiscript_dir = assets_root.path_join("uiscript/uiscript")
if not DirAccess.dir_exists_absolute(uiscript_dir):
uiscript_dir = assets_root.path_join("uiscript")
audio = audio_node
_cam_get = cam_getter
_env_get = env_getter
_load_cfg()
_apply_all() # 持久化的设置在开窗前就生效
# --- open / close ---------------------------------------------------
func is_open() -> bool:
return not _win.is_empty() and is_instance_valid(_win.get("root"))
func toggle() -> void:
if is_open(): close()
else: open()
func close() -> void:
if is_open():
ui.close(_win["root"])
_win = {}
func open() -> void:
if is_open():
return
var path := uiscript_dir.path_join("systemoptiondialog.py")
if not FileAccess.file_exists(path):
push_warning("SystemOptionUI: no systemoptiondialog.py at " + path)
return
_win = ui.open_script(path, assets_root)
if not is_open():
return
_relabel()
_wire()
_sync_controls()
func _node(nm: String) -> Control:
if _win.is_empty():
return null
var n = _win.get("nodes", {}).get(nm, null)
return n if n is Control else null
# uiScriptLocale 兜底:把 `<KEY>` 文本换成中文
func _relabel() -> void:
for nm in LABELS:
var n := _node(nm)
if n and n.has_method("set_text"):
n.set_text(LABELS[nm])
# --- wiring (uisystemoption.__Load) ---------------------------------
func _wire() -> void:
var mv := _node("music_volume_controller")
if mv is Range:
mv.min_value = 0.0
mv.max_value = 1.0
mv.step = 0.01
mv.value = music_volume
mv.value_changed.connect(_on_music_volume)
var sv := _node("sound_volume_controller")
if sv is Range:
sv.min_value = 0.0
sv.max_value = 1.0
sv.step = 0.01
sv.value = sound_volume
sv.value_changed.connect(_on_sound_volume)
_bind_radio(["camera_short", "camera_long"], func(i):
camera_mode = i
_apply_camera()
_save())
_bind_radio(["fog_level0", "fog_level1", "fog_level2"], func(i):
fog_level = i
_apply_fog()
_save())
_bind_radio(["tiling_cpu", "tiling_gpu"], func(i):
tiling_mode = i
_save())
var apply_btn := _node("tiling_apply")
if apply_btn is BaseButton:
apply_btn.pressed.connect(func():
# 原 __OnClickTilingApplyButton 会重启客户端应用 CPU/GPU 分块;
# Godot 渲染器无此项,仅记录选择。
_save())
var bgm_btn := _node("bgm_button")
if bgm_btn is BaseButton:
bgm_btn.disabled = true # 换 BGM 需音乐文件选择器,暂缺
func _bind_radio(names: Array, on_pick: Callable) -> void:
var boxes: Array = []
for nm in names:
boxes.append(_node(nm))
for i in boxes.size():
var b = boxes[i]
if not (b is BaseButton):
continue
b.toggle_mode = true
var idx := i
b.pressed.connect(func():
for j in boxes.size():
if boxes[j] is BaseButton:
boxes[j].set_pressed_no_signal(j == idx)
on_pick.call(idx))
# 把当前状态回填到控件(开窗时)
func _sync_controls() -> void:
_set_radio(["camera_short", "camera_long"], camera_mode)
_set_radio(["fog_level0", "fog_level1", "fog_level2"], fog_level)
_set_radio(["tiling_cpu", "tiling_gpu"], tiling_mode)
func _set_radio(names: Array, active: int) -> void:
for i in names.size():
var b = _node(names[i])
if b is BaseButton:
b.set_pressed_no_signal(i == active)
# --- change handlers ----------------------------------------------------
func _on_music_volume(v: float) -> void:
music_volume = clampf(v, 0.0, 1.0)
_apply_music()
_save()
func _on_sound_volume(v: float) -> void:
sound_volume = clampf(v, 0.0, 1.0)
_apply_sound()
_save()
# --- apply to engine --------------------------------------------------
func _apply_all() -> void:
_apply_music()
_apply_sound()
_apply_camera()
_apply_fog()
func _apply_music() -> void:
if audio == null:
return
if "master_bgm" in audio:
audio.master_bgm = music_volume
# 已在播放的 BGM 声道即时跟随
if "_bgm" in audio and "_bgm_cur" in audio:
var pl = audio._bgm[audio._bgm_cur]
if is_instance_valid(pl):
pl.volume_db = linear_to_db(maxf(music_volume, 0.0001))
func _apply_sound() -> void:
if audio and "master_sfx" in audio:
audio.master_sfx = sound_volume # play_ui / play_at 每次读取
func _apply_camera() -> void:
if not _cam_get.is_valid():
return
var c = _cam_get.call()
if c == null:
return
var m: float = CAMERA_MAX[clampi(camera_mode, 0, 1)]
if "max_dist" in c:
c.max_dist = m
if "dist" in c:
c.dist = minf(c.dist, m)
func _apply_fog() -> void:
if not _env_get.is_valid():
return
var e = _env_get.call()
if not (e is Environment):
return
e.fog_enabled = true
e.fog_density = FOG_DENSITY[clampi(fog_level, 0, 2)]
# --- persistence (= systemSetting 配置文件) --------------------------
func _load_cfg() -> void:
if _cfg.load(config_path()) != OK:
return
music_volume = float(_cfg.get_value("audio", "music_volume", music_volume))
sound_volume = float(_cfg.get_value("audio", "sound_volume", sound_volume))
camera_mode = int(_cfg.get_value("video", "camera_mode", camera_mode))
fog_level = int(_cfg.get_value("video", "fog_level", fog_level))
tiling_mode = int(_cfg.get_value("video", "tiling_mode", tiling_mode))
func _save() -> void:
_cfg.set_value("audio", "music_volume", music_volume)
_cfg.set_value("audio", "sound_volume", sound_volume)
_cfg.set_value("video", "camera_mode", camera_mode)
_cfg.set_value("video", "fog_level", fog_level)
_cfg.set_value("video", "tiling_mode", tiling_mode)
var err := _cfg.save(config_path())
if err != OK:
push_warning("SystemOptionUI: save failed (%s): %s" % [err, config_path()])