using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Managers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using Utils; namespace Configs { /// /// 配置管理器,采用单例模式,负责读取、修改、存储应用程序配置。 /// 继承自 Utils.Singleton<ConfigManager> 和 ILaunchManager。 /// public class ConfigManager : Singleton, ILaunchManager { /// /// 配置文件名 /// private const string CONFIG_FILE_NAME = "app_config.json"; /// /// 完整的配置文件路径,文件位于程序的可执行文件同级目录。 /// 在编辑器中:通常是 Assets 文件夹的父目录。 /// 在构建后:与可执行文件(例如 .exe)在同一目录下。 /// private readonly string _configFilePath; /// /// 存储程序级的默认配置数据,这些值是硬编码在程序中的。 /// private readonly Dictionary _programmaticDefaults; /// /// 存储配置数据的字典。使用 JToken 允许存储任意 JSON 类型(基本类型、对象、数组)。 /// 这些是用户实际加载和修改的配置数据。 /// private Dictionary _configData; /// /// 标记配置数据是否被修改,需要保存。 /// private bool _isDirty; /// /// 私有构造函数,由 Singleton 基类调用。 /// public ConfigManager() { var appDirectory = Path.GetDirectoryName(Application.dataPath); if (string.IsNullOrEmpty(appDirectory)) // Fallback for editor if GetDirectoryName returns null for some reason appDirectory = Application.dataPath; _configFilePath = Path.Combine(appDirectory, CONFIG_FILE_NAME); _configData = new Dictionary(); _programmaticDefaults = new Dictionary(); // 初始化默认配置字典 _isDirty = false; // 初始状态为未修改 InitializeProgrammaticDefaults(); // 调用新方法来填充默认配置 Debug.Log($"ConfigManager 初始化。配置文件路径: {_configFilePath}"); } public bool Completed { get; set; } public string StepDescription => "载入配置文件"; /// /// 初始化配置管理器,加载配置文件。 /// 如果配置文件不存在,将创建一个默认配置。 /// public Task Init() { LoadConfig(); Completed = true; return Task.CompletedTask; } /// /// 只是用于热重载,这里不适用重新加载 /// public void Clear() { } /// /// 初始化程序内置的默认配置项。 /// private void InitializeProgrammaticDefaults() { // 使用 JToken.FromObject 直接填充 _programmaticDefaults _programmaticDefaults["playerAffiliation"] = JToken.FromObject("player"); _programmaticDefaults["OutsideDimension"] = JToken.FromObject("DefaultOutsideDimension"); _programmaticDefaults["InsideDimension"] = JToken.FromObject("DefaultInsideDimension"); _programmaticDefaults["BaseDimension"] = JToken.FromObject("DefaultBaseDimension"); _programmaticDefaults["CoinItem"] = JToken.FromObject("Coin"); _programmaticDefaults["configVersion"] = JToken.FromObject(0.2); _programmaticDefaults["OpenShopCost"] = JToken.FromObject(5); _programmaticDefaults["ShoppingCost"] = JToken.FromObject(100); _programmaticDefaults["NanorobotsAffiliation"]=JToken.FromObject("nanorobot"); // 可以在此处添加更多默认配置项 } /// /// 从配置文件加载配置数据到内存。 /// 如果文件不存在或无效,则创建默认配置并保存。 /// 如果文件存在且有效,则在加载后合并程序内置的默认配置。 /// private void LoadConfig() { var needsToInitializeDefaults = false; string jsonContent = null; if (File.Exists(_configFilePath)) { try { jsonContent = File.ReadAllText(_configFilePath); if (string.IsNullOrWhiteSpace(jsonContent)) { Debug.LogWarning($"ConfigManager: 配置文件 '{_configFilePath}' 为空或仅包含空白字符。正在用程序默认配置初始化。"); needsToInitializeDefaults = true; } else { _configData = JsonConvert.DeserializeObject>(jsonContent); if (_configData == null) // 如果 JSON 是 "null" 或其他无效结构 { Debug.LogWarning( $"ConfigManager: 配置文件 '{_configFilePath}' 包含无效结构或反序列化结果为 null。正在用程序默认配置初始化。"); needsToInitializeDefaults = true; } } } catch (JsonException ex) { Debug.LogError($"ConfigManager: 反序列化配置文件 '{_configFilePath}' 失败。正在用程序默认配置初始化。错误: {ex.Message}"); needsToInitializeDefaults = true; } catch (IOException ex) { Debug.LogError($"ConfigManager: 读取配置文件 '{_configFilePath}' 失败。正在用程序默认配置初始化。错误: {ex.Message}"); needsToInitializeDefaults = true; } } else { Debug.Log($"ConfigManager: 配置文件 '{_configFilePath}' 未找到。正在用程序默认配置初始化。"); needsToInitializeDefaults = true; } if (needsToInitializeDefaults) { // 如果需要从头创建配置,则将程序默认配置复制到 _configData _configData.Clear(); // 清空,确保是全新的 foreach (var pair in _programmaticDefaults) _configData[pair.Key] = pair.Value.DeepClone(); // 使用 DeepClone 确保 JToken 实例是独立的 _isDirty = true; // 既然是新建的,肯定需要保存 Debug.Log("ConfigManager: 已从程序默认配置初始化配置数据。"); SaveConfig(); // 立刻保存到文件 } else { // 如果配置文件成功加载,则合并程序默认配置中缺失的项 MergeProgrammaticDefaultsIntoConfigData(); } } /// /// 合并程序内置的默认配置到当前内存中的配置数据中。 /// 如果有新的默认配置项被添加,_isDirty 将被设置为 true,并会调用 SaveConfig()。 /// private void MergeProgrammaticDefaultsIntoConfigData() { var defaultsAdded = false; foreach (var pair in _programmaticDefaults) // 只添加不存在的键,已存在的键保持不变(用户配置优先) if (!_configData.ContainsKey(pair.Key)) { _configData[pair.Key] = pair.Value.DeepClone(); // 确保 JToken 实例是独立的 defaultsAdded = true; Debug.Log($"ConfigManager: 添加新的默认配置项 '{pair.Key}'。"); } if (defaultsAdded) { _isDirty = true; // 至少有一个默认值被添加,需要保存 Debug.Log("ConfigManager: 发现并添加了新的默认配置项,正在保存。"); SaveConfig(); // 保存合并后的配置 } } /// /// 将内存中的配置数据保存到配置文件。 /// 只有当配置数据被修改时(_isDirty 为 true)才会执行实际的写入操作。 /// public void SaveConfig() { if (!_isDirty) // Debug.Log($"ConfigManager: 配置数据未修改,无需保存。"); // 可以根据需要开启 return; try { var json = JsonConvert.SerializeObject(_configData, Formatting.Indented); File.WriteAllText(_configFilePath, json); _isDirty = false; // 成功保存后,重置脏标记 Debug.Log($"ConfigManager: 配置已保存到 '{_configFilePath}'。"); } catch (JsonException ex) { Debug.LogError($"ConfigManager: 序列化配置数据失败。错误: {ex.Message}"); } catch (IOException ex) { Debug.LogError($"ConfigManager: 写入配置文件 '{_configFilePath}' 失败。请检查权限。错误: {ex.Message}"); } catch (UnauthorizedAccessException ex) { Debug.LogError($"ConfigManager: 写入配置文件 '{_configFilePath}' 权限被拒绝。请以管理员身份运行或更改文件夹权限。错误: {ex.Message}"); } } /// /// 获取指定键的配置值。 /// 优先从用户配置读取,如果不存在,则从程序内置默认配置中查找并添加。 /// 如果两者都不存在,则返回传入的默认值。 /// /// 值的目标类型。 /// 配置项的键。 /// 如果键在用户配置和内置默认配置中都不存在,返回的默认值。 /// 配置值或默认值。 public T GetValue(string key, T defaultValue = default) { if (_configData.TryGetValue(key, out var userJToken)) // 1. 在用户配置中找到 try { // Debug.Log($"ConfigManager: 键 '{key}' 从用户配置中获取。"); // 避免日志过多 return userJToken.ToObject(); } catch (JsonException ex) { Debug.LogWarning( $"ConfigManager: 键 '{key}' 的用户配置值 '{userJToken}' 转换为类型 '{typeof(T).Name}' 失败。" + $"正在尝试获取程序默认值。错误: {ex.Message}"); // fall through to programmatic defaults } catch (FormatException ex) { Debug.LogWarning( $"ConfigManager: 键 '{key}' 的用户配置值 '{userJToken}' 转换为类型 '{typeof(T).Name}' 时格式错误。" + $"正在尝试获取程序默认值。错误: {ex.Message}"); // fall through to programmatic defaults } // else 走到这里,说明用户配置中没有,或者转换失败,尝试从程序默认配置中查找 if (_programmaticDefaults.TryGetValue(key, out var defaultJToken)) { // 2. 在程序默认配置中找到 _configData[key] = defaultJToken.DeepClone(); // 将默认值添加到用户配置中,以便下次保存 _isDirty = true; Debug.Log($"ConfigManager: 键 '{key}' 未在用户配置中找到,已从程序默认配置中获取并添加到用户配置。"); try { return defaultJToken.ToObject(); } catch (JsonException ex) { Debug.LogWarning( $"ConfigManager: 键 '{key}' 的程序默认值 '{defaultJToken}' 转换为类型 '{typeof(T).Name}' 失败。" + $"返回传入的默认值 '{defaultValue}'。错误: {ex.Message}"); return defaultValue; } catch (FormatException ex) { Debug.LogWarning( $"ConfigManager: 键 '{key}' 的程序默认值 '{defaultJToken}' 转换为类型 '{typeof(T).Name}' 时格式错误。" + $"返回传入的默认值 '{defaultValue}'。错误: {ex.Message}"); return defaultValue; } } // 3. 在任何地方都未找到 Debug.LogWarning($"ConfigManager: 未在用户配置或程序默认配置中找到键 '{key}'。返回传入的默认值 '{defaultValue}'。"); return defaultValue; } /// /// 设置指定键的配置值。 /// /// 值的类型。 /// 配置项的键。 /// 要设置的值。 public void SetValue(string key, T value) { if (string.IsNullOrEmpty(key)) { Debug.LogError("ConfigManager: 键不能为空。"); return; } try { var newValue = JToken.FromObject(value); // 只有当值实际发生变化时才标记为脏,避免不必要的脏标记和保存 if (!_configData.TryGetValue(key, out var existingValue) || !JToken.DeepEquals(existingValue, newValue)) { _configData[key] = newValue; _isDirty = true; Debug.Log($"ConfigManager: 键 '{key}' 的值设置为 '{value}'。请调用 SaveConfig() 持久化更改。"); } // Debug.Log($"ConfigManager: 键 '{key}' 的值未改变。"); // 避免日志过多 } catch (JsonException ex) { Debug.LogError( $"ConfigManager: 键 '{key}' 的值 '{value}' 转换为 JToken 失败。错误: {ex.Message}"); } } /// /// 检查配置中是否存在某个键。 /// /// 要检查的键。 /// 如果存在该键,则为 true;否则为 false。 public bool ContainsKey(string key) { // 检查用户配置和程序默认配置 return _configData.ContainsKey(key) || _programmaticDefaults.ContainsKey(key); } /// /// 移除指定键的配置项。 /// /// 要移除的键。 /// 如果成功移除,则为 true;否则为 false。 public bool RemoveKey(string key) { if (_configData.Remove(key)) { _isDirty = true; // 配置被修改,需要保存 Debug.Log($"ConfigManager: 键 '{key}' 已移除。请调用 SaveConfig() 持久化更改。"); return true; } Debug.LogWarning($"ConfigManager: 尝试移除不存在的键 '{key}'。"); return false; } /// /// 直接获取原始的 JToken 值,适用于处理复杂或嵌套的 JSON 结构。 /// 优先从用户配置获取,其次从程序默认配置获取。 /// /// 配置项的键。 /// 对应的 JToken;如果键不存在,则为 null。 public JToken GetRawJToken(string key) { // 优先返回用户配置,不存在则返回默认配置,这两个都没有则返回 null if (_configData.TryGetValue(key, out var userJToken)) return userJToken; _programmaticDefaults.TryGetValue(key, out var defaultJToken); return defaultJToken; // 可能会返回 null } } }