fix:1、更换项目,使用winter来创建

This commit is contained in:
2026-04-22 11:13:16 +08:00
parent 173cfb2dc9
commit 83ff9f71ad
7375 changed files with 209752 additions and 157557 deletions
+70
View File
@@ -0,0 +1,70 @@
# ⚙️ ConfigLoader 配置加载模块
此模块负责配置文件的加载与检测,依赖于 `Common` 通用模块,请确保已安装该模块。
### 主要功能
- 📂 配置文件加载
- 🔍 配置字段缺失检测
- ❌ 配置字段冗余检测
- ⚙️ 自动化插件安装
- 📝 日志标准化输出
------
## ⚠️ 注意事项
导入项目后,会弹出插件安装提示框。点击“确定”后,插件将自动安装;若点击“取消”,脚本重新编译时仍会再次弹出提示。
- 插件安装需要一定时间,请耐心等待。安装完成后会有弹窗提示。
- 安装完成后,会自动生成配置文件夹 `Configs`,请将游戏配置文件复制到此文件夹中。
- **请勿修改配置文件名**。如需替换配置文件,请先删除旧文件,确保文件夹中每个配置只有一份。
- 游戏首次运行时,会自动将该文件夹复制到游戏的数据存储位置。
------
## 🚀 使用指南
在登录成功后,调用 `ConfigLoader``Init` 方法传入所需参数,具体参数请查看方法注释。
> 注意:
>
> - **List 类型**的配置需继承 `ConfigModel<T, T0>`,不再需要之前的 `IDataList` 接口。
> - **非 List 类型**配置继承 `ConfigModel<T>`。
### 初始化示例
```csharp
var loginModel = NetworkKit.Instance.GetLoginModel();
ConfigLoader.Instance.Init(
loginModel.setting,
loginModel.cdn_url,
new List<ConfigModel> {
new CommonModel("Common"),
new SignDailyRewardModel("SignDailyReward"),
new TurntableModel("turntable"),
// 其它配置...
},
state => {
Debug.Log($"配置加载状态: {state}");
},
(errorName, message) => {
Debug.LogError($"配置解析错误: {errorName},错误信息:{message}");
});
```
------
## 🔍 读取配置示例
```csharp
// 读取基础配置
var model = ConfigLoader.Instance.GetConfig<CommonModel>();
// 解析自定义配置
ConfigLoader.Instance.ParesPersonalizedConfig(new PrizeWheelDataModel("PrizeWheelData"), "prize");
// 获取自定义配置
var personalizedConfig = ConfigLoader.Instance.GetPersonalizedConfig<PrizeWheelDataModel>("prize");
```
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b25e03cedceda0f4db9aebc261a5bf0a
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2c34977ebbc9d4440826289429a2adc3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c03491d9ce1584a408c18056b93f91d2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SGModule.Common.Helper;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
#if USE_ADDRESSABLES
using UnityEditor.AddressableAssets;
#endif
namespace SGModule.Editor {
[InitializeOnLoad]
public static class AddressablesManager {
private const string AddressablesPackageName = "com.unity.addressables";
private const string AddressablesSymbol = "USE_ADDRESSABLES";
private static AddRequest _addRequest;
// 安装标记,避免重复弹窗
private static bool _isInstallingAddressables;
static AddressablesManager() {
EditorApplication.projectChanged += OnProjectChanged; // 监听项目变更
CheckAndSetupAddressables();
}
private static string DefaultConfigsPath => "Assets/Configs"; // 默认的 Addressables 配置路径
private static void CheckAndSetupAddressables() {
if (IsAddressablesInstalled()) {
Log.Info("ConfigLoader", "Addressables 已安装,正在初始化...");
AddScriptingDefineSymbol(AddressablesSymbol);
#if USE_ADDRESSABLES
EnsureAddressablesConfigured();
#endif
}
else {
RemoveScriptingDefineSymbol(AddressablesSymbol);
if (!_isInstallingAddressables) {
PromptToInstallAddressables();
}
}
}
private static bool IsAddressablesInstalled() {
return PackageInfo.GetAllRegisteredPackages().Any(package => package.name == AddressablesPackageName);
}
private static void PromptToInstallAddressables() {
var install = EditorUtility.DisplayDialog(
"Addressables 未安装",
"当前插件依赖 Addressables 功能,请安装以确保正常使用。\n\n是否立即安装 Addressables",
"安装",
"取消");
if (install) {
InstallAddressables();
}
else {
Log.Warning("ConfigLoader", "用户取消安装 Addressables,部分功能可能无法正常使用!");
}
}
private static void InstallAddressables() {
if (_isInstallingAddressables) {
return; // 防止重复安装
}
Log.Info("ConfigLoader", "开始安装 Addressables...");
_isInstallingAddressables = true; // 标记正在安装,避免重复弹窗
_addRequest = Client.Add(AddressablesPackageName);
EditorApplication.update += MonitorAddRequest;
}
private static void MonitorAddRequest() {
if (_addRequest == null || !_addRequest.IsCompleted) {
return;
}
EditorApplication.update -= MonitorAddRequest;
if (_addRequest.Status == StatusCode.Success) {
Log.Info("ConfigLoader", "Addressables 安装成功!");
EditorUtility.DisplayDialog("安装完成", "Addressables 安装成功,请稍候等待 Unity 刷新。", "确定");
CheckAndSetupAddressables();
}
else if (_addRequest.Status >= StatusCode.Failure) {
Log.Error("ConfigLoader", $"安装 Addressables 失败:{_addRequest.Error.message}");
EditorUtility.DisplayDialog("安装失败", $"安装 Addressables 失败:{_addRequest.Error.message}", "确定");
}
_isInstallingAddressables = false; // 重置标记
}
private static void AddScriptingDefineSymbol(string symbol) {
var symbols = new List<string>(PlayerSettings
.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'));
if (symbols.Contains(symbol)) {
return;
}
symbols.Add(symbol);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", symbols));
Log.Info("ConfigLoader", $"已添加脚本定义符号: {symbol}");
}
private static void RemoveScriptingDefineSymbol(string symbol) {
var symbols = new List<string>(PlayerSettings
.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'));
if (symbols.Contains(symbol)) {
symbols.Remove(symbol);
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", symbols));
Log.Info("ConfigLoader", $"已移除脚本定义符号: {symbol}");
}
}
private static void OnProjectChanged() {
Log.Info("ConfigLoader", "检测到项目变更,重新检查 Addressables 状态...");
CheckAndSetupAddressables();
}
#if USE_ADDRESSABLES
private static void EnsureAddressablesConfigured() {
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null) {
Log.Info("ConfigLoader", "正在初始化 Addressables 设置...");
settings = AddressableAssetSettingsDefaultObject.GetSettings(true); // 自动创建配置
}
// 确保默认的 Configs 文件夹存在并被添加到 Addressables
var needRefresh = false;
if (!Directory.Exists(DefaultConfigsPath)) {
Directory.CreateDirectory(DefaultConfigsPath);
Log.Info("ConfigLoader", $"已创建文件夹: {DefaultConfigsPath}");
needRefresh = true;
}
if (!IsFolderInAddressables(DefaultConfigsPath)) {
var guid = AssetDatabase.AssetPathToGUID(DefaultConfigsPath);
var group = settings.DefaultGroup;
settings.CreateOrMoveEntry(guid, group)?.SetLabel("config", true, true);
needRefresh = true;
}
if (needRefresh) {
AssetDatabase.Refresh();
}
}
private static bool IsFolderInAddressables(string folderPath) {
var settings = AddressableAssetSettingsDefaultObject.Settings;
foreach (var group in settings.groups)
{
if (group)
{
foreach (var entry in group.entries)
{
if (AssetDatabase.GUIDToAssetPath(entry.guid) == folderPath)
{
return true;
}
}
}
}
return false;
}
#endif
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 383ac67f695b4c6e94ba988c9303cd5b
timeCreated: 1733734050
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 34c8928adac69ec44ac63b385ef330d1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 53a14b320d93d7c439c59c8190a874ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SGModule.Common.Helper;
namespace SGModule.ConfigLoader {
public class ConfigDataManager {
private readonly Dictionary<Type, object> _configData = new();
private readonly HashSet<string> _usedKeys = new();
private Dictionary<string, object> _jsonDictionary = new();
public void SetJsonDictionary(string json) {
_jsonDictionary = SerializeHelper.ToObject<Dictionary<string, object>>(json);
_usedKeys.Clear();
}
public void AddConfig(Type type, object instance) {
_configData[type] = instance;
}
public void AddConfig(object configModel) {
var type = configModel.GetType();
_configData[type] = configModel;
}
public T GetConfig<T>() where T : class {
if (_configData.TryGetValue(typeof(T), out var obj)) {
return obj as T;
}
Log.ConfigLoader.Error($"未找到类型 {typeof(T).FullName} 的配置");
return null;
}
public bool TryGetJsonValue(string key, out object value) {
if (!_jsonDictionary.TryGetValue(key, out value)) {
return false;
}
_usedKeys.Add(key);
return true;
}
public IEnumerable<string> GetUnusedKeys() {
return _jsonDictionary.Keys.Except(_usedKeys);
}
public IEnumerable<string> GetJsonKeys() {
return _jsonDictionary.Keys;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 223b606b799d46c2a7e684cba7c5df6a
timeCreated: 1753950649
@@ -0,0 +1,167 @@
using System;
using System.IO;
using SGModule.Common.Helper;
using UnityEngine;
namespace SGModule.ConfigLoader {
public class ConfigFileManager {
private const int MaxErrorCount = 6;
private const string ConfigFileNameKey = "ConfigFileName";
private const string FirstLaunchKey = "FirstLaunch";
private int _initConfigErrorCount;
private bool HasExceededMaxErrors => _initConfigErrorCount > MaxErrorCount;
private static bool IsFirstLaunch => !PlayerPrefs.HasKey(FirstLaunchKey);
private static string SavedConfigFileName => PlayerPrefs.GetString(ConfigFileNameKey);
private static void SetFirstLaunch() {
PlayerPrefs.SetInt(FirstLaunchKey, 1);
}
private static void SetSavedConfigFileName(string name) {
PlayerPrefs.SetString(ConfigFileNameKey, name);
}
private void IncrementErrorCount() {
_initConfigErrorCount++;
}
// 检查是否需要下载新配置,基于传入的期望配置文件名setting
private static bool HasNewConfig(string expectedSetting) {
if (string.IsNullOrEmpty(SavedConfigFileName)) {
return true;
}
return !SavedConfigFileName.Equals(expectedSetting);
}
private void FirstLaunchCopyConfig(Action<bool> callback) {
SetFirstLaunch();
FileNetworkManager.Instance.CopyStreamingAssetsToPersistentDataPath(result => {
var isSuccess = false;
if (result) {
var names = FileNetworkManager.Instance.GetFileNamesFromPersistentDataPath(FileNetworkManager.FolderName);
if (names.Length > 0) {
SetSavedConfigFileName(names[0]);
isSuccess = true;
}
}
else {
Log.ConfigLoader.Error("拷贝文件失败 请检查Configs文件夹是否有配置文件");
}
if (!isSuccess) {
IncrementErrorCount();
}
callback?.Invoke(isSuccess);
});
}
private void DownloadConfig(string cdnUrl, string setting, Action<bool, string> callback) {
var url = $"{cdnUrl}/config/{setting}";
Log.ConfigLoader.Info($"开始下载配置文件 {url}", false);
FileNetworkManager.Instance.ReadData(url, result => {
if (!string.IsNullOrEmpty(result)) {
SetSavedConfigFileName(setting);
FileNetworkManager.Instance.WriteToPersistentData(FileNetworkManager.Instance.GetConfigFOlderPath(), setting, result);
callback?.Invoke(true, result);
}
else {
Log.ConfigLoader.Error("下载配置文件失败");
IncrementErrorCount();
callback?.Invoke(false, null);
}
});
}
private void ReadLocalConfig(Action<bool, string> callback) {
var savedCfgName = SavedConfigFileName;
Log.ConfigLoader.Info($"开始读取本地配置文件 {savedCfgName}", false);
if (string.IsNullOrEmpty(savedCfgName)) {
IncrementErrorCount();
callback?.Invoke(false, null);
return;
}
var path = Path.Combine(FileNetworkManager.Instance.GetConfigFOlderPath(), savedCfgName);
FileNetworkManager.Instance.ReadData(path, result => {
if (!string.IsNullOrEmpty(result)) {
callback?.Invoke(true, result);
}
else {
Log.ConfigLoader.Error("读取本地数据异常");
IncrementErrorCount();
callback?.Invoke(false, null);
}
});
}
/// <summary>
/// 配置文件检查与加载流程
/// </summary>
public void CheckAndLoadConfig(ConfigInitOptions initOptions, Action<string> onConfigLoaded, Action<ConfigLoaderState> callback) {
if (HasExceededMaxErrors) {
callback?.Invoke(ConfigLoaderState.Failed);
return;
}
if (IsFirstLaunch) {
FirstLaunchCopyConfig(success => {
if (!success) {
IncrementErrorCount();
}
CheckAndLoadConfig(initOptions, onConfigLoaded, callback);
});
return;
}
if (HasNewConfig(initOptions.Setting)) {
DownloadConfig(initOptions.CdnUrl, initOptions.Setting, (success, json) => {
if (success) {
ReloadConfig(json, onConfigLoaded, callback);
}
else {
ReadLocalConfig((readSuccess, localJson) => {
if (readSuccess) {
ReloadConfig(localJson, onConfigLoaded, callback);
}
else {
callback?.Invoke(ConfigLoaderState.Failed);
}
});
}
});
return;
}
ReadLocalConfig((success, json) => {
if (success) {
ReloadConfig(json, onConfigLoaded, callback);
}
else {
FirstLaunchCopyConfig(result => {
if (!result) {
IncrementErrorCount();
}
CheckAndLoadConfig(initOptions, onConfigLoaded, callback);
});
}
});
}
private static void ReloadConfig(string json, Action<string> onConfigLoaded, Action<ConfigLoaderState> callback) {
if (string.IsNullOrWhiteSpace(json)) {
callback?.Invoke(ConfigLoaderState.JsonEmptyError);
return;
}
onConfigLoaded?.Invoke(json);
callback?.Invoke(ConfigLoaderState.Successful);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ecda816ab1fc4edaa3948be9e79fbdd2
timeCreated: 1753950339
@@ -0,0 +1,42 @@
using System;
namespace SGModule.ConfigLoader {
public enum ConfigLoaderState {
None,
Failed,
JsonEmptyError,
Successful
}
public class ConfigInitOptions {
// 配置文件名
public string Setting {
get;
set;
}
// 下载配置的地址
public string CdnUrl {
get;
set;
}
// 配置加载回调
public Action<ConfigLoaderState> OnComplete {
get;
set;
}
// 配置解析错误回调
public Action<string, string> OnError {
get;
set;
}
// 处理未标记的配置
public Action OnHandleUnmarkedConfig {
get;
set;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f04e729bc4b34a4c8a44785e9aef732b
timeCreated: 1753946665
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using SGModule.Common;
using SGModule.Common.Base;
using SGModule.Common.Helper;
namespace SGModule.ConfigLoader {
public class ConfigLoader : SingletonMonoBehaviour<ConfigLoader> {
private readonly ConfigDataManager _dataManager = new();
private readonly ConfigFileManager _fileManager = new();
private ConfigInitOptions _initOptions;
private bool _isConfigLoaded;
public void Init(ConfigInitOptions options) {
_initOptions = options ?? throw new ArgumentNullException(nameof(options));
IsInitComplete = true;
Log.ConfigLoader.Info("Setting: " + _initOptions.Setting);
_fileManager.CheckAndLoadConfig(_initOptions, ReloadConfig, state => {
if (state == ConfigLoaderState.Successful) {
Log.ConfigLoader.Info($"Data read successfully: {SerializeHelper.ToJsonIndented(GetJsonKeys())}", false);
ConfigParser.ParseAllConfigs(new ParseConfigOptions {
OnParsed = (type, instance) => _dataManager.AddConfig(type, instance),
OnError = _initOptions.OnError,
OnHandleUnmarkedConfig = _initOptions.OnHandleUnmarkedConfig
});
}
_initOptions.OnComplete?.Invoke(state);
});
}
private void ReloadConfig(string json) {
if (string.IsNullOrWhiteSpace(json)) {
return;
}
if (!json.StartsWith("{")) {
json = Cryptor.Decrypt(json, ConfigManager.GameConfig.packageName);
}
_dataManager.SetJsonDictionary(json);
_isConfigLoaded = true;
}
public T GetConfig<T>() where T : class {
return _dataManager.GetConfig<T>();
}
public void AddConfig(object configModel) {
_dataManager.AddConfig(configModel);
}
public bool TryGetJsonValue(string key, out object value) {
return _dataManager.TryGetJsonValue(key, out value);
}
public IEnumerable<string> GetUnusedKeys() {
return _dataManager.GetUnusedKeys();
}
public IEnumerable<string> GetJsonKeys() {
return _dataManager.GetJsonKeys();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9336c81edc514b209f5d055056dbee52
timeCreated: 1753950686
@@ -0,0 +1,235 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SGModule.Common.Base;
using SGModule.Common.Helper;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if USE_ADDRESSABLES
#endif
namespace SGModule.ConfigLoader {
public class FileNetworkManager : SingletonMonoBehaviour<FileNetworkManager> {
public const string FolderName = "Configs";
private const string FolderLabel = "config";
private string _configFolderPath;
protected override void Awake() {
base.Awake();
_configFolderPath = Path.Combine(Application.persistentDataPath, FolderName);
}
public string GetConfigFOlderPath() {
return _configFolderPath;
}
// 示例解析文件列表方法(你可以根据实际需求实现)
private List<string> ParseFileList(string data) {
// 假设 data 是文件列表的文本,解析文件名
// 根据实际情况解析,例如通过换行符分割
return data.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
public void CopyStreamingAssetsToPersistentDataPath(Action<bool> onComplete = null) {
// 如果目标文件夹不存在,创建它
if (!Directory.Exists(_configFolderPath)) {
Directory.CreateDirectory(_configFolderPath);
}
StartCoroutine(CopyFile(onComplete));
}
private void HandleInitializationError() {
// 检查文件是否存在
var path = $"{Application.dataPath}/Library/com.unity.addressables/aa/Android/settings.json";
if (!File.Exists(path)) {
Log.ConfigLoader.Warning(
$"Settings file not found at: {path}. Rebuilding Addressables may be required.");
}
// 提示用户或执行其他逻辑
// 比如:显示弹窗或退出程序
}
private IEnumerator CopyFile(Action<bool> onComplete = null) {
#if USE_ADDRESSABLES
var handle = Addressables.LoadResourceLocationsAsync(FolderLabel);
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded) {
// 查找以 ".json" 结尾的文件
var jsonLocation = handle.Result.FirstOrDefault(loc => loc.PrimaryKey.EndsWith(".json"));
if (jsonLocation != null) {
var jsonFileName = Path.GetFileName(jsonLocation.PrimaryKey);
// 加载 JSON 文件
var textAssetAsync = Addressables.LoadAssetAsync<TextAsset>(jsonLocation);
yield return textAssetAsync;
if (textAssetAsync.Status == AsyncOperationStatus.Succeeded) {
var jsonFile = textAssetAsync.Result;
// Log.ConfigLoader.Info($"Loaded JSON Name:{jsonFileName} content: " + jsonFile.text);
var destFilePath = Path.Combine(_configFolderPath, jsonFileName);
File.WriteAllBytes(destFilePath, jsonFile.bytes);
onComplete?.Invoke(true);
}
else {
Log.ConfigLoader.Error("Failed to load JSON file");
onComplete?.Invoke(false);
}
}
else {
Log.ConfigLoader.Error("No JSON file found in folder");
onComplete?.Invoke(false);
}
}
else {
Log.ConfigLoader.Error("Failed to load folder resources");
onComplete?.Invoke(false);
}
#else
Log.Error( "没有 Addressables 插件,请检查 ");
yield break;
#endif
}
private IEnumerator CopyFile(string sourceFile, string destFile, Action<bool> onComplete = null) {
var filePath = "file://" + sourceFile;
using (var www = UnityWebRequest.Get(filePath)) {
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success) {
File.WriteAllBytes(destFile, www.downloadHandler.data);
Log.ConfigLoader.Info($"File copied to {destFile}");
onComplete?.Invoke(true);
}
else {
Log.ConfigLoader.Error("Failed to copy file: " + www.error);
onComplete?.Invoke(false);
}
}
}
/// <summary>
/// 读取或下载
/// </summary>
/// <param name="path"></param>
/// <param name="callback"></param>
public void ReadData(string path, Action<string> callback) {
StartCoroutine(ReadDataEnumerator(path, callback));
}
/// <summary>
/// 读取或下载
/// </summary>
/// <param name="path"></param>
/// <param name="callback"></param>
/// <returns></returns>
private IEnumerator ReadDataEnumerator(string path, Action<string> callback) {
string fullPath;
// 判断是网络URL还是本地文件路径
if (path.StartsWith("http://") || path.StartsWith("https://")) {
fullPath = path; // 网络URL
#if UNITY_EDITOR
if (path.StartsWith("http://") && PlayerSettings.insecureHttpOption == InsecureHttpOption.NotAllowed) {
Log.ConfigLoader.Error("发起了 HTTP 链接,但设置了不允许非Https连接,请检查设置!!!");
callback?.Invoke(null);
yield break;
}
#endif
}
else {
// 本地文件路径
fullPath = "file://" + path;
}
// 使用UnityWebRequest读取数据
using (var webRequest = UnityWebRequest.Get(fullPath)) {
yield return webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.ConnectionError ||
webRequest.result == UnityWebRequest.Result.ProtocolError) {
Log.ConfigLoader.Error($"Error while reading file: {webRequest.error} path: {fullPath}");
callback?.Invoke(null); // 返回null表示出错
}
else {
var data = webRequest.downloadHandler.text;
callback?.Invoke(data); // 通过回调传出数据
}
}
}
/// <summary>
/// 将内容写入指定文件,并删除原有文件。
/// </summary>
/// <param name="folderName">文件夹名称</param>
/// <param name="fileName">文件名称</param>
/// <param name="content">要写入的内容</param>
public void WriteToPersistentData(string folderName, string fileName, string content) {
// 获取持久化数据路径
var folderPath = Path.Combine(Application.persistentDataPath, folderName);
// 如果文件夹不存在,则创建它
if (!Directory.Exists(folderPath)) {
Directory.CreateDirectory(folderPath);
}
// 删除原有文件
var existingFiles = Directory.GetFiles(folderPath);
foreach (var file in existingFiles) {
File.Delete(file);
}
// 完整文件路径
var filePath = Path.Combine(folderPath, fileName);
// 写入新文件
File.WriteAllText(filePath, content);
Log.ConfigLoader.Info($"File written to: {filePath}");
}
/// <summary>
/// 提取指定目录下所有文件文件名
/// </summary>
/// <param name="subdirectory"></param>
/// <returns></returns>
public string[] GetFileNamesFromPersistentDataPath(string subdirectory) {
var directoryPath = Path.Combine(Application.persistentDataPath, subdirectory);
if (Directory.Exists(directoryPath))
// 获取目录中的所有文件名
{
return Directory.GetFiles(directoryPath)
.Select(Path.GetFileName) // 只提取文件名
.ToArray();
}
Log.ConfigLoader.Warning($"Directory does not exist: {directoryPath}");
return Array.Empty<string>(); // 返回空数组
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 35da25a8aaf547b8accde3b52c38a887
timeCreated: 1731982304
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f15ea40a5b634081a8b21a4af531f8b5
timeCreated: 1753869849
@@ -0,0 +1,19 @@
using System;
namespace SGModule.ConfigLoader {
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ConfigKeyAttribute : Attribute {
public ConfigKeyAttribute(string key, bool isList = true) {
Key = key;
IsList = isList;
}
public string Key {
get;
}
public bool IsList {
get;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 93c57a91da9d46baba3feb3a46719bb2
timeCreated: 1753869869
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SGModule.Common.Helper;
namespace SGModule.ConfigLoader {
public static class ConfigParser {
public static void ParseAllConfigs(ParseConfigOptions options) {
var onParsed = options.OnParsed;
var errorCallback = options.OnError;
var onHandleUnmarkedConfig = options.OnHandleUnmarkedConfig;
var configTypes = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.GetCustomAttribute<ConfigKeyAttribute>() != null);
foreach (var type in configTypes) {
var attr = type.GetCustomAttribute<ConfigKeyAttribute>();
var key = attr.Key;
if (!ConfigLoader.Instance.TryGetJsonValue(key, out var jsonData)) {
errorCallback?.Invoke(key, "未找到对应配置数据");
continue;
}
try {
var json = jsonData.ToString();
var targetType = attr.IsList ? typeof(List<>).MakeGenericType(type) : type;
var parsedInstance = JsonConvert.DeserializeObject(json, targetType);
if (parsedInstance == null) {
errorCallback?.Invoke(key, "解析返回空");
continue;
}
#if !GAME_RELEASE
RunFieldCheck(json, type, key, attr.IsList);
#endif
// 把结果交还给调用方
onParsed?.Invoke(targetType, parsedInstance);
}
catch (Exception ex) {
errorCallback?.Invoke(key, $"反序列化异常: {ex.Message}");
Log.ConfigLoader.Error($"配置 {key} 反序列化异常: {ex}");
}
}
// 处理未标记的配置
onHandleUnmarkedConfig?.Invoke();
// 打印未使用的 jsonDict 中的 key
var unusedKeys = ConfigLoader.Instance.GetUnusedKeys().ToList();
if (unusedKeys.Count > 0) {
Log.ConfigLoader.Warning($"未使用的配置表: {string.Join(", ", unusedKeys)};", false);
}
}
#if !GAME_RELEASE
private static void RunFieldCheck(string json, Type type, string key, bool isList) {
var objectsToCheck = new List<JObject>();
try {
if (isList) {
var jsonArray = JArray.Parse(json);
objectsToCheck.AddRange(jsonArray.OfType<JObject>());
}
else {
var singleObj = JObject.Parse(json);
objectsToCheck.Add(singleObj);
}
}
catch (Exception ex) {
Log.ConfigLoader.Warning($"[{key}] JSON 解析失败: {ex.Message}");
return;
}
var classFields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Select(f => f.Name)
.ToHashSet();
var allMissingFields = new HashSet<string>();
var allExtraFields = new HashSet<string>();
foreach (var obj in objectsToCheck) {
var jsonKeys = obj.Properties().Select(p => p.Name).ToHashSet();
allMissingFields.UnionWith(classFields.Except(jsonKeys));
allExtraFields.UnionWith(jsonKeys.Except(classFields));
}
if (allMissingFields.Count > 0 || allExtraFields.Count > 0) {
var msg = $"[{key}]";
if (allMissingFields.Count > 0) {
msg += $" 缺少字段: {string.Join(", ", allMissingFields.OrderBy(f => f))};";
}
if (allExtraFields.Count > 0) {
msg += $" 多余字段: {string.Join(", ", allExtraFields.OrderBy(f => f))};";
}
var shortJson = json.Length > 1000 ? json.Substring(0, 1000) + "..." : json;
Log.ConfigLoader.Warning($"[{key}] Json: {shortJson}", false);
Log.ConfigLoader.Warning(msg, false);
}
}
#endif
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ddad7a16d91d4f48901b7217aa6af0e9
timeCreated: 1753870514
@@ -0,0 +1,20 @@
using System;
namespace SGModule.ConfigLoader {
public class ParseConfigOptions {
public Action<Type, object> OnParsed {
get;
set;
}
public Action<string, string> OnError {
get;
set;
}
public Action OnHandleUnmarkedConfig {
get;
set;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c27fb301f6224b778cdfd006ca12d7e5
timeCreated: 1753947404