From c154b11ed27879707c2e71143a86163301af25f5 Mon Sep 17 00:00:00 2001 From: Reed Date: Thu, 15 Jan 2026 17:43:58 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=BB=BA=E7=AB=8Bui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __settings/SettingModel.gd | 108 +++++++++++++++++++ __settings/SettingModel.gd.uid | 1 + __settings/SettingSchema.gd | 22 ++++ __settings/SettingSchema.gd.uid | 1 + __settings/SettingStorage.gd | 22 ++++ __settings/SettingStorage.gd.uid | 1 + __settings/input/InputSchema.gd | 35 +++++++ __settings/input/InputSchema.gd.uid | 1 + __settings/input/InputSettingModel.gd | 121 ++++++++++++++++++++++ __settings/input/InputSettingModel.gd.uid | 1 + _asset/ksw/未命名作品(4).png | Bin 0 -> 5356 bytes _asset/ksw/未命名作品(4).png.import | 40 +++++++ _tileset/level1.tres | 2 +- _ui/main_menu/main_menu.gd | 3 + _ui/main_menu/main_menu.gd.uid | 1 + _ui/main_menu/main_menu.tscn | 10 +- _ui/setting/AudioSettingPanel.tscn | 13 +++ project.godot | 1 + 18 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 __settings/SettingModel.gd create mode 100644 __settings/SettingModel.gd.uid create mode 100644 __settings/SettingSchema.gd create mode 100644 __settings/SettingSchema.gd.uid create mode 100644 __settings/SettingStorage.gd create mode 100644 __settings/SettingStorage.gd.uid create mode 100644 __settings/input/InputSchema.gd create mode 100644 __settings/input/InputSchema.gd.uid create mode 100644 __settings/input/InputSettingModel.gd create mode 100644 __settings/input/InputSettingModel.gd.uid create mode 100644 _asset/ksw/未命名作品(4).png create mode 100644 _asset/ksw/未命名作品(4).png.import create mode 100644 _ui/main_menu/main_menu.gd create mode 100644 _ui/main_menu/main_menu.gd.uid create mode 100644 _ui/setting/AudioSettingPanel.tscn diff --git a/__settings/SettingModel.gd b/__settings/SettingModel.gd new file mode 100644 index 0000000..fb6880e --- /dev/null +++ b/__settings/SettingModel.gd @@ -0,0 +1,108 @@ +# scripts/settings/SettingsModel.gd +extends Node +class_name SettingsModel + +## =============================== +## Dependencies +## =============================== +const SettingsSchema = preload("res://__settings/SettingSchema.gd") +const SettingsStorage = preload("res://__settings/SettingStorage.gd") + +## =============================== +## Signals +## =============================== +signal setting_changed(section: String, key: String, value) +signal settings_applied +signal settings_reverted + +## =============================== +## Internal State +## =============================== +var _data_committed : Dictionary = {} # 已生效 / 已保存 +var _data_working : Dictionary = {} # UI 正在修改 +var _storage := SettingsStorage.new() + +## =============================== +## Lifecycle +## =============================== +func _ready() -> void: + _load() + +## =============================== +## Public API (Read) +## =============================== +func get_setting(section: String, key: String) -> Variant: + return _data_working.get(section, {}).get(key) + +func get_section(section: String) -> Dictionary: + return _data_working.get(section, {}).duplicate(true) + +## =============================== +## Public API (Write - Working Only) +## =============================== +func set_setting(section: String, key: String, value: Variant) -> void: + if not _data_working.has(section): + _data_working[section] = {} + + _data_working[section][key] = value + emit_signal("setting_changed", section, key, value) + +## =============================== +## Typed Getters +## =============================== +func get_bool(section: String, key: String) -> bool: + var value = get_setting(section, key) + return value if typeof(value) == TYPE_BOOL else false + +func get_int(section: String, key: String) -> int: + var value = get_setting(section, key) + return value if typeof(value) == TYPE_INT else 0 + +func get_float(section: String, key: String) -> float: + var value = get_setting(section, key) + if typeof(value) == TYPE_FLOAT or typeof(value) == TYPE_INT: + return float(value) + return 0.0 + +func get_string(section: String, key: String) -> String: + var value = get_setting(section, key) + return value if typeof(value) == TYPE_STRING else "" + +## =============================== +## Transaction Control +## =============================== +func apply() -> void: + _data_committed = _data_working.duplicate(true) + + for section in _data_committed.keys(): + for key in _data_committed[section].keys(): + _storage.set_value(section, key, _data_committed[section][key]) + + _storage.save() + emit_signal("settings_applied") + +func revert() -> void: + _data_working = _data_committed.duplicate(true) + emit_signal("settings_reverted") + +func has_unsaved_changes() -> bool: + return _data_working != _data_committed + +## =============================== +## Internal +## =============================== +func _load() -> void: + _storage.load() + + # 1. Load defaults + _data_committed = SettingsSchema.defaults().duplicate(true) + + # 2. Override with saved values + for section in _data_committed.keys(): + for key in _data_committed[section].keys(): + var default_value = _data_committed[section][key] + var value = _storage.get_value(section, key, default_value) + _data_committed[section][key] = value + + # 3. Init working copy + _data_working = _data_committed.duplicate(true) diff --git a/__settings/SettingModel.gd.uid b/__settings/SettingModel.gd.uid new file mode 100644 index 0000000..bdea12e --- /dev/null +++ b/__settings/SettingModel.gd.uid @@ -0,0 +1 @@ +uid://cmyknlur1bk2d diff --git a/__settings/SettingSchema.gd b/__settings/SettingSchema.gd new file mode 100644 index 0000000..613d1d7 --- /dev/null +++ b/__settings/SettingSchema.gd @@ -0,0 +1,22 @@ +class_name SettingSchema +extends RefCounted + +## =============================== +## 默认设置定义 +## =============================== +static func defaults() -> Dictionary: + return { + "audio": { + "master_volume": 1.0, + "music_volume": 0.8, + "sfx_volume": 0.8, + }, + "graphics": { + "quality": 2, # 0=low,1=mid,2=high + "fullscreen": true, + }, + "input": { + # 这里只是占位,后面可扩展 + "mouse_sensitivity": 1.0, + } + } diff --git a/__settings/SettingSchema.gd.uid b/__settings/SettingSchema.gd.uid new file mode 100644 index 0000000..fd3ce85 --- /dev/null +++ b/__settings/SettingSchema.gd.uid @@ -0,0 +1 @@ +uid://i1nipeyuught diff --git a/__settings/SettingStorage.gd b/__settings/SettingStorage.gd new file mode 100644 index 0000000..57386ec --- /dev/null +++ b/__settings/SettingStorage.gd @@ -0,0 +1,22 @@ +# scripts/settings/SettingsStorage.gd +extends RefCounted +class_name SettingsStorage + +const FILE_PATH := "user://settings.cfg" + +var _config := ConfigFile.new() + +func load() -> void: + _config.load(FILE_PATH) + +func save() -> void: + _config.save(FILE_PATH) + +func has(section: String, key: String) -> bool: + return _config.has_section_key(section, key) + +func get_value(section: String, key: String, default): + return _config.get_value(section, key, default) + +func set_value(section: String, key: String, value) -> void: + _config.set_value(section, key, value) diff --git a/__settings/SettingStorage.gd.uid b/__settings/SettingStorage.gd.uid new file mode 100644 index 0000000..5b40849 --- /dev/null +++ b/__settings/SettingStorage.gd.uid @@ -0,0 +1 @@ +uid://bmnwylphnuio7 diff --git a/__settings/input/InputSchema.gd b/__settings/input/InputSchema.gd new file mode 100644 index 0000000..8df38ea --- /dev/null +++ b/__settings/input/InputSchema.gd @@ -0,0 +1,35 @@ +extends RefCounted +class_name InputSchema + +static func defaults() -> Dictionary: + return { + "move_left": [ + _key(KEY_A), + _key(KEY_LEFT), + ], + "move_right": [ + _key(KEY_D), + _key(KEY_RIGHT), + ], + "jump": [ + _key(KEY_SPACE), + ], + "attack": [ + _mouse(MOUSE_BUTTON_LEFT), + ], + } + +## =============================== +## Helpers (序列化友好) +## =============================== +static func _key(keycode: int) -> Dictionary: + return { + "type": "key", + "keycode": keycode, + } + +static func _mouse(button: int) -> Dictionary: + return { + "type": "mouse", + "button": button, + } diff --git a/__settings/input/InputSchema.gd.uid b/__settings/input/InputSchema.gd.uid new file mode 100644 index 0000000..901abca --- /dev/null +++ b/__settings/input/InputSchema.gd.uid @@ -0,0 +1 @@ +uid://cdf7xcwgtd7jb diff --git a/__settings/input/InputSettingModel.gd b/__settings/input/InputSettingModel.gd new file mode 100644 index 0000000..048f93e --- /dev/null +++ b/__settings/input/InputSettingModel.gd @@ -0,0 +1,121 @@ +extends Node +class_name InputSettingsModel + +## =============================== +## Dependencies +## =============================== +const InputSchema = preload("res://__settings/input/InputSchema.gd") +const SettingsStorage = preload("res://__settings/SettingStorage.gd") + +## =============================== +## Signals +## =============================== +signal binding_changed(action: String) +signal bindings_applied +signal bindings_reverted + +## =============================== +## Internal State +## =============================== +var _bindings_committed : Dictionary = {} +var _bindings_working : Dictionary = {} +var _storage := SettingsStorage.new() + +const SECTION := "input" + +## =============================== +## Lifecycle +## =============================== +func _ready() -> void: + _load() + +## =============================== +## Public API (Read) +## =============================== +func get_bindings(action: String) -> Array: + return _bindings_working.get(action, []).duplicate(true) + +func get_all_bindings() -> Dictionary: + return _bindings_working.duplicate(true) + +## =============================== +## Public API (Write) +## =============================== +func set_bindings(action: String, bindings: Array) -> void: + _bindings_working[action] = bindings + emit_signal("binding_changed", action) + +func add_binding(action: String, binding: Dictionary) -> void: + if not _bindings_working.has(action): + _bindings_working[action] = [] + _bindings_working[action].append(binding) + emit_signal("binding_changed", action) + +func clear_bindings(action: String) -> void: + _bindings_working[action] = [] + emit_signal("binding_changed", action) + +## =============================== +## Transaction +## =============================== +func apply() -> void: + _bindings_committed = _bindings_working.duplicate(true) + _save_to_storage() + _apply_to_input_map() + emit_signal("bindings_applied") + +func revert() -> void: + _bindings_working = _bindings_committed.duplicate(true) + emit_signal("bindings_reverted") + +func has_unsaved_changes() -> bool: + return _bindings_working != _bindings_committed + +## =============================== +## Internal +## =============================== +func _load() -> void: + _storage.load() + + # 1. defaults + _bindings_committed = InputSchema.defaults().duplicate(true) + + # 2. override from storage + for action in _bindings_committed.keys(): + var saved = _storage.get_value(SECTION, action, null) + if saved != null: + _bindings_committed[action] = saved + + _bindings_working = _bindings_committed.duplicate(true) + + # 3. apply to engine at startup + _apply_to_input_map() + +func _save_to_storage() -> void: + for action in _bindings_committed.keys(): + _storage.set_value(SECTION, action, _bindings_committed[action]) + _storage.save() + +func _apply_to_input_map() -> void: + for action in _bindings_committed.keys(): + if not InputMap.has_action(action): + InputMap.add_action(action) + + InputMap.action_erase_events(action) + + for binding in _bindings_committed[action]: + var event = _binding_to_event(binding) + if event: + InputMap.action_add_event(action, event) + +func _binding_to_event(binding: Dictionary) -> InputEvent: + match binding.get("type"): + "key": + var e := InputEventKey.new() + e.keycode = binding.get("keycode", 0) + return e + "mouse": + var e := InputEventMouseButton.new() + e.button_index = binding.get("button", 0) + return e + return null diff --git a/__settings/input/InputSettingModel.gd.uid b/__settings/input/InputSettingModel.gd.uid new file mode 100644 index 0000000..347352e --- /dev/null +++ b/__settings/input/InputSettingModel.gd.uid @@ -0,0 +1 @@ +uid://dytq5bvr0l2rq diff --git a/_asset/ksw/未命名作品(4).png b/_asset/ksw/未命名作品(4).png new file mode 100644 index 0000000000000000000000000000000000000000..69ce227bee0dcac88d044c03889dad7c0d44272e GIT binary patch literal 5356 zcmeAS@N?(olHy`uVBq!ia0y~yU~B+k4mP03lH*(V139Ic&dveZ&hF0nIr&Aw1&PV2 zE}6vzIf<14#taM^6H6!T^*$URa@2oy&;_dwK3N%~PUkC^xXKDMv_)JELReP^&#L3h ztx?eoDijXa{2?E4FtT7nM@T_ofq)CU;N3Y^nmaNo?oAK5rF8H3)4vvTig(5wkPeX4 z{>G&7a@s_nBTKek+4ZElxqV*ns-NCU#EE?dH6vDrcCX`DoRHJ*SU1UDlg#J#T8toFCI7 zE^%eG?eNrDZvCZ(eR@~w+O5qu?#;g%KK-c7tH3jf!fFy-0fCJytPkt|T<){)(=7^5 z2+-?i(mCbgDJZ<@$ma_S6NQWlIUHY2*irntj(_d%lexL?MQ7v|oYTK`%Zb@y?!w7e zZ{Mn5k&t4@d*x#q;{Iykw`WBMesdbkU}}4>u{rVCA;t3QdY%{65C0_de2`@-oBDaZ z^0~KCt@9hT|FVl~X6*@Pb36@<2bN?{=KvmF2F5(*$M1psDX9^jX}-P;T0k}j0~;e7 z12d4t$iM_7ctIqC01$^UN-?m4#TgjV7^UItJVp(K`u0o)7O0vsAkFY>&Z?&q_dcD_ z`(*E&r;{5&LM=ed2&b7AFu`;%HY{L9ut7>KFYaCjq&N#aB8wRqM0`P*amuM)eFg^R zH<=+3B@w}FfdWk9dNvV1jxdk9y3^o;3KxS@gNuokUZcbjYRfVk*ScMgk4HDK@ zQUEI{$+lIB@C{IK&M!(;Fw-;8Gf=YQQczH^DN0GR3UYCSY6tRcl`=|73as??%gf94 z%8m8%i_-NCEiEne4UF`SjC6r2bc-wVN)jt{^NN)rhQQ2mNi9w;$}A|!%+FH*nVXoD zUs__Tqy(}E4j}F<$xK7olvfP(7SMzGAQ^o_Jp+Ag+ES9?&MPP_$^knrC0Rc;Cp9-U zucR2L&k(CV&w`R<6JSWWRsh`t^&ilo$zTJDOH%Za6zCfn7+B~U80i{W>cjm9biNJ9 zWmYcv$)zB>{1S6h?a<=f2JCoLB?!Mm^arID=jW7yf+#dEv&7B_ss~LNU6*ryZb4CM zaWPPvoheicvKYGh2!wfdhQ=^UkVVmT1Z3owZ(7KXDkTc;<#H|9| ztVpc3Ba0&GNJ+LT$t=l91t&*%DgmcU$D9&WQBW*^5>`$oD7{+cB<7{tDplyFq^2d7 z=9DO5#s@?(sv$P|AW>AaZS=7?!v-l)SUDDzl;;;^hk%`m5$`xvLOlwx6DfZ{G9TC_ zz}#oYWup(z$#z^uUos0B82Cy(T^vIy7~ft!$a^S2!1*Bm)Ih7bjh7BtyF~cB3wso) zX?NVYfq{{Ug+oBWVNf#ug+C~C|N7l>zV*GwvOs4>h*!)lK3)H_sA^sE&pMF9Gnm*G z?DP~=a7c(dz;I@M0LV8cjLaAAECG7TKtF-ung0rq7iTpvX5<9{HOvq