修改配置的插件
This commit is contained in:
@@ -2,7 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
[Serializable]
|
||||
public class ConfigTableInfo
|
||||
@@ -35,13 +37,60 @@ public class ReverseRelation
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FieldRelationBackup
|
||||
public class ConfigLinkDataFile
|
||||
{
|
||||
public string fieldName;
|
||||
public string targetTable;
|
||||
public string targetField;
|
||||
public string relationFormat;
|
||||
public List<ConfigLinkTableEntry> tables = new List<ConfigLinkTableEntry>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ConfigLinkTableEntry
|
||||
{
|
||||
public string name;
|
||||
public string displayName;
|
||||
public string description;
|
||||
public string fileExtension;
|
||||
public List<ConfigLinkRelation> relations = new List<ConfigLinkRelation>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ConfigLinkRelation
|
||||
{
|
||||
public string field;
|
||||
public string target;
|
||||
public string targetField;
|
||||
public string format;
|
||||
public string description;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FormatTemplate
|
||||
{
|
||||
public string[] parts;
|
||||
public int minParts;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SettingsData
|
||||
{
|
||||
public string configFolderPath = "Resources/Resources_moved/config";
|
||||
public string excelFolderPath = "";
|
||||
public FormatTemplatesWrapper formatTemplates = new FormatTemplatesWrapper();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class FormatTemplatesWrapper
|
||||
{
|
||||
public FormatTemplate item_id_num = new FormatTemplate { parts = new[] { "类型", "ID", "数量" }, minParts = 3 };
|
||||
public FormatTemplate type_id_num = new FormatTemplate { parts = new[] { "类型", "ID", "数量" }, minParts = 3 };
|
||||
public FormatTemplate id_pos_lv = new FormatTemplate { parts = new[] { "ID", "位置", "等级" }, minParts = 3 };
|
||||
public FormatTemplate id_lv_num = new FormatTemplate { parts = new[] { "ID", "等级", "数量" }, minParts = 3 };
|
||||
public FormatTemplate id_lv_num_time = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "时间" }, minParts = 4 };
|
||||
public FormatTemplate buffid_lv = new FormatTemplate { parts = new[] { "BuffID", "等级" }, minParts = 2 };
|
||||
public FormatTemplate rune_id_num = new FormatTemplate { parts = new[] { "符文ID", "数量" }, minParts = 2 };
|
||||
public FormatTemplate equip_id_num = new FormatTemplate { parts = new[] { "装备ID", "数量" }, minParts = 2 };
|
||||
public FormatTemplate hero_id = new FormatTemplate { parts = new[] { "英雄ID" }, minParts = 1 };
|
||||
public FormatTemplate id_lv_count_delay = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "延迟" }, minParts = 4 };
|
||||
public FormatTemplate id_lv_num_hp = new FormatTemplate { parts = new[] { "ID", "等级", "数量", "血量%" }, minParts = 4 };
|
||||
}
|
||||
|
||||
public static class ConfigLinkDatabase
|
||||
@@ -49,20 +98,61 @@ public static class ConfigLinkDatabase
|
||||
private static List<ConfigTableInfo> _cachedData;
|
||||
private static HashSet<string> _existingTableNames;
|
||||
private static string _excelFolderPath;
|
||||
private static SettingsData _settings;
|
||||
private static Dictionary<string, FormatTemplate> _formatTemplateMap;
|
||||
private static Dictionary<string, (string displayName, string description)> _tableInfoCache;
|
||||
|
||||
private const string CONFIG_PATH = "Resources/Resources_moved/config";
|
||||
private const string CONFIG_FILE_NAME = "ConfigLinkData.json";
|
||||
private const string SETTINGS_FILE_NAME = "settings.json";
|
||||
private const string TABLE_INFO_FILE_NAME = "table_info.json";
|
||||
private const string EXCEL_PATH_KEY = "ConfigLinkViewer.ExcelFolderPath";
|
||||
|
||||
private static string ViewerDir
|
||||
{
|
||||
get { return Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer"); }
|
||||
}
|
||||
|
||||
private static string ConfigFilePath
|
||||
{
|
||||
get { return Path.Combine(ViewerDir, CONFIG_FILE_NAME); }
|
||||
}
|
||||
|
||||
private static string SettingsFilePath
|
||||
{
|
||||
get { return Path.Combine(ViewerDir, SETTINGS_FILE_NAME); }
|
||||
}
|
||||
|
||||
private static string TableInfoFilePath
|
||||
{
|
||||
get { return Path.Combine(ViewerDir, TABLE_INFO_FILE_NAME); }
|
||||
}
|
||||
|
||||
public static string GetConfigFolderPath()
|
||||
{
|
||||
LoadSettings();
|
||||
return _settings.configFolderPath;
|
||||
}
|
||||
|
||||
public static void SetConfigFolderPath(string path)
|
||||
{
|
||||
LoadSettings();
|
||||
_settings.configFolderPath = path;
|
||||
SaveSettings();
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
public static void SetExcelFolderPath(string path)
|
||||
{
|
||||
_excelFolderPath = path;
|
||||
PlayerPrefs.SetString("ConfigLinkExcelFolderPath", path);
|
||||
EditorUserSettings.SetConfigValue(EXCEL_PATH_KEY, path);
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
public static string GetExcelFolderPath()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_excelFolderPath))
|
||||
{
|
||||
_excelFolderPath = PlayerPrefs.GetString("ConfigLinkExcelFolderPath", "");
|
||||
_excelFolderPath = EditorUserSettings.GetConfigValue(EXCEL_PATH_KEY) ?? "";
|
||||
}
|
||||
return _excelFolderPath;
|
||||
}
|
||||
@@ -94,303 +184,157 @@ public static class ConfigLinkDatabase
|
||||
if (_cachedData != null)
|
||||
return _cachedData;
|
||||
|
||||
_cachedData = new List<ConfigTableInfo>
|
||||
{
|
||||
new ConfigTableInfo { tableName = "activity", displayName = "活动表", description = "活动配置表" },
|
||||
new ConfigTableInfo { tableName = "activityreward", displayName = "活动奖励表", description = "活动奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "activityId", targetTable = "activity", targetField = "id", description = "关联活动" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "奖励(类型_id_数量)" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "achievement", displayName = "成就表", description = "成就配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励(类型_id_数量)" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "arenaaward", displayName = "竞技场奖励表", description = "竞技场排名奖励", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励(类型_id_数量)" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "attribute", displayName = "属性表", description = "属性配置表" },
|
||||
new ConfigTableInfo { tableName = "box", displayName = "宝箱表", description = "宝箱抽奖配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "reward", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "buff", displayName = "Buff表", description = "Buff效果配置" },
|
||||
new ConfigTableInfo { tableName = "callstrength", displayName = "召唤强度表", description = "召唤强度概率配置" },
|
||||
new ConfigTableInfo { tableName = "checkpointreward", displayName = "关卡奖励表", description = "关卡奖励池配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "rune", targetField = "id", relationFormat = "rune_id_num", description = "符文奖励" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "道具奖励" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "equip", targetField = "id", relationFormat = "equip_id_num", description = "装备奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "collectionreward", displayName = "收藏奖励表", description = "收藏奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "common", displayName = "通用配置表", description = "通用配置项", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "key", targetTable = "language", targetField = "key", description = "多语言key" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "drawlinereward", displayName = "画线奖励表", description = "画线游戏奖励", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "enemy", displayName = "敌人表", description = "敌人基础配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "reward_id", targetTable = "checkpointreward", targetField = "id", description = "掉落奖励" },
|
||||
new FieldRelation { fieldName = "skills", targetTable = "skill", targetField = "id", description = "技能列表" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "enemylevel", displayName = "敌人等级表", description = "敌人等级成长配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "升级消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "enemy", targetField = "id", description = "关联敌人" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "equip", displayName = "装备表", description = "装备基础配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "attr_id", targetTable = "equipAttrRandom", targetField = "id", description = "随机属性ID" },
|
||||
new FieldRelation { fieldName = "retrieve_pros", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "回收奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "equipAttrRandom", displayName = "装备随机属性表", description = "装备随机属性配置" },
|
||||
new ConfigTableInfo { tableName = "equiplevel", displayName = "装备等级表", description = "装备等级成长配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "升级消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "equip", targetField = "id", description = "关联装备" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "equipstage", displayName = "装备品阶表", description = "装备品阶配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "突破消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "equip", targetField = "id", description = "关联装备" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_arena", displayName = "竞技场战斗表", description = "竞技场关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_fb1", displayName = "副本战斗表1", description = "副本关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_fb2", displayName = "副本战斗表2", description = "副本关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act2", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_fb3", displayName = "副本战斗表3", description = "副本关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act3", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_fb4", displayName = "副本战斗表4", description = "副本关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act4", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_sample", displayName = "战斗样例表", description = "战斗关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "pass_rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "回合进度奖励" },
|
||||
new FieldRelation { fieldName = "out_enemy_config", targetTable = "map_act", targetField = "id", relationFormat = "表名", description = "出怪表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fight_x", displayName = "战斗X表", description = "战斗关卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "enemy_ids", targetTable = "enemy", targetField = "id", relationFormat = "id_pos_lv", description = "敌人(格式: id_位置_等级)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "pass_rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "回合进度奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "funopencondition", displayName = "功能开放条件表", description = "功能开放条件配置" },
|
||||
new ConfigTableInfo { tableName = "gacha", displayName = "抽卡表", description = "抽卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "hero", targetField = "id", description = "抽卡获得的英雄" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "gachareward", displayName = "抽卡奖励表", description = "抽卡奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "game2048reward", displayName = "2048游戏奖励表", description = "2048游戏奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "guide", displayName = "引导表", description = "新手引导配置" },
|
||||
new ConfigTableInfo { tableName = "guildbox", displayName = "公会宝箱表", description = "公会宝箱配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "guilddonate", displayName = "公会捐献表", description = "公会捐献配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "消耗" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "guildlevel", displayName = "公会等级表", description = "公会等级配置" },
|
||||
new ConfigTableInfo { tableName = "guildname", displayName = "公会名称表", description = "公会名称配置" },
|
||||
new ConfigTableInfo { tableName = "hero", displayName = "英雄表", description = "英雄基础配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "level_attr_id", targetTable = "attribute", targetField = "id", description = "升级显示属性" },
|
||||
new FieldRelation { fieldName = "stage_attr_id", targetTable = "attribute", targetField = "id", description = "升阶显示属性" },
|
||||
new FieldRelation { fieldName = "skills", targetTable = "skill", targetField = "id", description = "技能列表" },
|
||||
new FieldRelation { fieldName = "compose", targetTable = "hero", targetField = "id", description = "合成所需英雄" },
|
||||
new FieldRelation { fieldName = "equips", targetTable = "equip", targetField = "id", description = "装备列表" },
|
||||
new FieldRelation { fieldName = "buffs", targetTable = "buff", targetField = "id", relationFormat = "buffId_lv", description = "升阶解锁buff" },
|
||||
new FieldRelation { fieldName = "internal_buffs", targetTable = "buff", targetField = "id", description = "初始可抽取buff" },
|
||||
new FieldRelation { fieldName = "shardItem", targetTable = "prop", targetField = "id", description = "碎片道具ID" },
|
||||
new FieldRelation { fieldName = "activateRewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "激活奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "herolevel", displayName = "英雄等级表", description = "英雄等级成长配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "升级消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "hero", targetField = "id", description = "关联英雄" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "herolevelinternal", displayName = "英雄内丹表", description = "英雄内丹配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "消耗" },
|
||||
new FieldRelation { fieldName = "buffs", targetTable = "buff", targetField = "id", description = "对应提升buff" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "herostage", displayName = "英雄品阶表", description = "英雄品阶配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "道具消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "hero", targetField = "id", description = "关联英雄" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "idlereward", displayName = "挂机奖励表", description = "挂机奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "挂机宝箱奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "language", displayName = "语言表", description = "多语言配置" },
|
||||
new ConfigTableInfo { tableName = "link", displayName = "英雄羁绊表", description = "英雄羁绊配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "hero_ids", targetTable = "hero", targetField = "id", description = "英雄ID列表" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "mail", displayName = "邮件表", description = "邮件配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "mall", displayName = "商店表", description = "商店商品配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "productId", targetTable = "prop", targetField = "id", description = "商品道具ID" },
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "购买消耗" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "购买获得奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map", displayName = "地图表", description = "地图房间配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "初始怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map1", displayName = "地图1表", description = "地图1房间配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "初始怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map2", displayName = "地图2表", description = "地图2房间配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "初始怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map3", displayName = "地图3表", description = "地图3房间配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "初始怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map_act", displayName = "出怪表", description = "关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物(格式: id_等级_数量_时间)" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss(格式: id_等级_数量_限时)" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map_act2", displayName = "出怪表2", description = "关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map_act3", displayName = "出怪表3", description = "关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map_act4", displayName = "出怪表4", description = "关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "map_act_x", displayName = "出怪表X", description = "关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "mapx", displayName = "地图X表", description = "地图X房间配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "初始怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "monthlycard", displayName = "月卡表", description = "月卡配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "recharge_id", targetTable = "recharge", targetField = "id", description = "购买立即获得" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "name", displayName = "名字表", description = "随机名字配置" },
|
||||
new ConfigTableInfo { tableName = "outenemypoint", displayName = "外出敌人点表", description = "外出敌人点配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num", description = "怪物" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "pandora", displayName = "潘多拉表", description = "潘多拉奖励池配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励池" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "pass", displayName = "通行证表", description = "通行证奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "免费奖励" },
|
||||
new FieldRelation { fieldName = "rewards2", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "付费奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "player", displayName = "玩家表", description = "玩家基础配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "equips", targetTable = "equip", targetField = "id", description = "初始装备" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "playerlevel", displayName = "玩家等级表", description = "玩家等级成长配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "升级消耗" },
|
||||
new FieldRelation { fieldName = "talent_id", targetTable = "playertalent", targetField = "id", description = "天赋ID" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "player", targetField = "id", description = "关联玩家" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "playerskin", displayName = "玩家皮肤表", description = "玩家皮肤配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "激活消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "player", targetField = "id", description = "关联玩家" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "playerstage", displayName = "玩家境界表", description = "玩家境界配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "突破消耗" },
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "player", targetField = "id", description = "关联玩家" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "playertalent", displayName = "玩家天赋表", description = "玩家天赋配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "升级消耗" },
|
||||
new FieldRelation { fieldName = "lostConsumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "抛弃消耗" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "prop", displayName = "道具表", description = "道具配置" },
|
||||
new ConfigTableInfo { tableName = "pushboxreward", displayName = "推箱奖励表", description = "推箱游戏奖励", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "qirilibao", displayName = "七日礼包表", description = "七日礼包配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "免费奖励" },
|
||||
new FieldRelation { fieldName = "rewards2", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "付费奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "quest", displayName = "任务表", description = "任务配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "任务奖励" },
|
||||
new FieldRelation { fieldName = "next", targetTable = "quest", targetField = "id", description = "下一个任务" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "recharge", displayName = "充值表", description = "充值配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" },
|
||||
new FieldRelation { fieldName = "children", targetTable = "recharge", targetField = "id", description = "打包售卖关联的孩子" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "rechargegift", displayName = "累充礼物表", description = "累充礼物配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "rune", displayName = "符文表", description = "符文基础配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "attr_id", targetTable = "attribute", targetField = "id", description = "属性ID" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "runelevel", displayName = "符文等级表", description = "符文等级配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "rune", targetField = "id", description = "关联符文" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "scene", displayName = "场景表", description = "场景配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "fight_cf_name", targetTable = "fight_sample", targetField = "id", relationFormat = "表名", description = "战斗配置表名" },
|
||||
new FieldRelation { fieldName = "map_cf_name", targetTable = "map", targetField = "id", relationFormat = "表名", description = "地图配置表名" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "serverError", displayName = "服务器错误表", description = "服务器错误码配置" },
|
||||
new ConfigTableInfo { tableName = "signin", displayName = "签到表", description = "签到奖励配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "签到奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "skill", displayName = "技能表", description = "技能配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "to_partner_buff", targetTable = "buff", targetField = "id", description = "给己方的buff" },
|
||||
new FieldRelation { fieldName = "to_enemy_buff", targetTable = "buff", targetField = "id", description = "给敌方的buff" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "skilllevel", displayName = "技能等级表", description = "技能等级配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "target_id", targetTable = "skill", targetField = "id", description = "关联技能" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "systemopen", displayName = "系统开放表", description = "系统开放条件配置" },
|
||||
new ConfigTableInfo { tableName = "test_map_act", displayName = "测试出怪表", description = "测试关卡出怪配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "monsters", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "怪物" },
|
||||
new FieldRelation { fieldName = "boss", targetTable = "enemy", targetField = "id", relationFormat = "id_lv_num_time", description = "Boss" },
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "波次奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "title", displayName = "称号表", description = "称号配置" },
|
||||
new ConfigTableInfo { tableName = "trainbreak", displayName = "修炼突破表", description = "修炼突破配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "突破消耗" },
|
||||
new FieldRelation { fieldName = "quest", targetTable = "quest", targetField = "id", description = "突破任务" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "trainlevel", displayName = "修炼等级表", description = "修炼等级配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "consumes", targetTable = "prop", targetField = "id", relationFormat = "item_id_num", description = "修炼消耗" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "vip", displayName = "VIP表", description = "VIP等级配置", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "免费礼包" },
|
||||
new FieldRelation { fieldName = "recharge_id", targetTable = "recharge", targetField = "id", description = "付费礼包" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "fishingreward", displayName = "钓鱼奖励表", description = "钓鱼游戏奖励", relations = new List<FieldRelation> {
|
||||
new FieldRelation { fieldName = "rewards", targetTable = "prop", targetField = "id", relationFormat = "type_id_num", description = "奖励" }
|
||||
}},
|
||||
new ConfigTableInfo { tableName = "dirty_words", displayName = "敏感词表", description = "敏感词过滤配置(TXT文件)", fileExtension = ".txt" },
|
||||
new ConfigTableInfo { tableName = "奖励格式说明", displayName = "奖励格式说明", description = "奖励数据格式说明文档(TXT文件)", fileExtension = ".txt" }
|
||||
};
|
||||
|
||||
_cachedData = LoadConfigFromFile(ConfigFilePath);
|
||||
MarkExistingTables();
|
||||
return _cachedData;
|
||||
}
|
||||
|
||||
public static bool IsConfigLoaded()
|
||||
{
|
||||
return _cachedData != null && _cachedData.Count > 0;
|
||||
}
|
||||
|
||||
public static bool HasConfigFile()
|
||||
{
|
||||
return File.Exists(ConfigFilePath);
|
||||
}
|
||||
|
||||
private static List<ConfigTableInfo> LoadConfigFromFile(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogWarning("[ConfigLinkViewer] 配置文件不存在,请点击[生成配置]按钮生成: " + path);
|
||||
return new List<ConfigTableInfo>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(path, Encoding.UTF8);
|
||||
var data = JsonUtility.FromJson<ConfigLinkDataFile>(json);
|
||||
if (data == null || data.tables == null) return new List<ConfigTableInfo>();
|
||||
|
||||
var result = new List<ConfigTableInfo>();
|
||||
foreach (var entry in data.tables)
|
||||
{
|
||||
var info = new ConfigTableInfo
|
||||
{
|
||||
tableName = entry.name,
|
||||
displayName = string.IsNullOrEmpty(entry.displayName) ? entry.name : entry.displayName,
|
||||
description = entry.description ?? "",
|
||||
fileExtension = string.IsNullOrEmpty(entry.fileExtension) ? ".xlsx" : entry.fileExtension,
|
||||
relations = new List<FieldRelation>()
|
||||
};
|
||||
|
||||
if (entry.relations != null)
|
||||
{
|
||||
foreach (var rel in entry.relations)
|
||||
{
|
||||
info.relations.Add(new FieldRelation
|
||||
{
|
||||
fieldName = rel.field,
|
||||
targetTable = rel.target,
|
||||
targetField = rel.targetField ?? "id",
|
||||
relationFormat = rel.format ?? "",
|
||||
description = rel.description ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(info);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[ConfigLinkViewer] 加载配置文件失败: {e.Message}");
|
||||
return new List<ConfigTableInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void GenerateConfigFromExcelFolder()
|
||||
{
|
||||
string excelFolder = GetExcelFolderPath();
|
||||
if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder))
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先设置Excel文件夹路径", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(excelFolder, "*.xlsx", SearchOption.AllDirectories)
|
||||
.Where(f => !Path.GetFileName(f).StartsWith("~$"))
|
||||
.ToArray();
|
||||
|
||||
if (files.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "Excel文件夹中未找到.xlsx文件", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new ConfigLinkDataFile();
|
||||
var tableInfoDict = LoadTableInfoDict();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
string name = Path.GetFileNameWithoutExtension(file);
|
||||
string displayName = name;
|
||||
string description = "";
|
||||
|
||||
if (tableInfoDict.TryGetValue(name.ToLower(), out var ti))
|
||||
{
|
||||
displayName = ti.Item1;
|
||||
description = ti.Item2;
|
||||
}
|
||||
|
||||
data.tables.Add(new ConfigLinkTableEntry
|
||||
{
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
description = description,
|
||||
fileExtension = ".xlsx",
|
||||
relations = new List<ConfigLinkRelation>()
|
||||
});
|
||||
}
|
||||
|
||||
var txtFiles = Directory.GetFiles(excelFolder, "*.txt", SearchOption.AllDirectories);
|
||||
foreach (var file in txtFiles)
|
||||
{
|
||||
string name = Path.GetFileNameWithoutExtension(file);
|
||||
string displayName = name;
|
||||
string description = "";
|
||||
|
||||
if (tableInfoDict.TryGetValue(name.ToLower(), out var ti))
|
||||
{
|
||||
displayName = ti.Item1;
|
||||
description = ti.Item2;
|
||||
}
|
||||
|
||||
data.tables.Add(new ConfigLinkTableEntry
|
||||
{
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
description = description,
|
||||
fileExtension = ".txt",
|
||||
relations = new List<ConfigLinkRelation>()
|
||||
});
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(data, true);
|
||||
File.WriteAllText(ConfigFilePath, json, Encoding.UTF8);
|
||||
|
||||
ClearCache();
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.DisplayDialog("生成完成",
|
||||
$"已扫描 {files.Length} 个文件,生成配置文件\n\n" +
|
||||
$"配置文件: {ConfigFilePath}\n\n" +
|
||||
$"提示: 可将该文件交给 AI 补全中文名称和表间关联关系", "确定");
|
||||
}
|
||||
|
||||
private static void MarkExistingTables()
|
||||
{
|
||||
_existingTableNames = new HashSet<string>();
|
||||
|
||||
string configPath = Path.Combine(Application.dataPath, CONFIG_PATH);
|
||||
string configPath = Path.Combine(Application.dataPath, GetConfigFolderPath());
|
||||
if (Directory.Exists(configPath))
|
||||
{
|
||||
string[] jsonFiles = Directory.GetFiles(configPath, "*.json");
|
||||
@@ -401,16 +345,7 @@ public static class ConfigLinkDatabase
|
||||
}
|
||||
}
|
||||
|
||||
string configPathAlt = Path.Combine(Application.dataPath, "../Resources/Resources_moved/config");
|
||||
if (Directory.Exists(configPathAlt))
|
||||
{
|
||||
string[] jsonFilesAlt = Directory.GetFiles(configPathAlt, "*.json");
|
||||
foreach (string file in jsonFilesAlt)
|
||||
{
|
||||
string tableName = Path.GetFileNameWithoutExtension(file);
|
||||
_existingTableNames.Add(tableName.ToLower());
|
||||
}
|
||||
}
|
||||
if (_cachedData == null) return;
|
||||
|
||||
foreach (var table in _cachedData)
|
||||
{
|
||||
@@ -432,7 +367,7 @@ public static class ConfigLinkDatabase
|
||||
|
||||
public static bool IsTableExistInConfigFolder(string tableName)
|
||||
{
|
||||
string configPath = Path.Combine(Application.dataPath, CONFIG_PATH);
|
||||
string configPath = Path.Combine(Application.dataPath, GetConfigFolderPath());
|
||||
if (!Directory.Exists(configPath))
|
||||
return false;
|
||||
|
||||
@@ -440,8 +375,6 @@ public static class ConfigLinkDatabase
|
||||
string extension = tableInfo?.fileExtension ?? ".xlsx";
|
||||
|
||||
string jsonPath = Path.Combine(configPath, $"{tableName}.json");
|
||||
string configPathAlt = Path.Combine(Application.dataPath, "../Resources/Resources_moved/config");
|
||||
string jsonPathAlt = Path.Combine(configPathAlt, $"{tableName}.json");
|
||||
|
||||
if (extension == ".txt")
|
||||
{
|
||||
@@ -454,7 +387,7 @@ public static class ConfigLinkDatabase
|
||||
}
|
||||
}
|
||||
|
||||
return File.Exists(jsonPath) || File.Exists(jsonPathAlt);
|
||||
return File.Exists(jsonPath);
|
||||
}
|
||||
|
||||
public static ConfigTableInfo GetTableInfo(string tableName)
|
||||
@@ -497,49 +430,29 @@ public static class ConfigLinkDatabase
|
||||
if (string.IsNullOrEmpty(format) || string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
|
||||
string[] parts = value.Split('_');
|
||||
string result = "";
|
||||
string key = format.ToLower();
|
||||
|
||||
switch (format.ToLower())
|
||||
if (key == "表名")
|
||||
return $"表名: {value}";
|
||||
|
||||
LoadFormatTemplates();
|
||||
|
||||
if (_formatTemplateMap.TryGetValue(key, out var template))
|
||||
{
|
||||
case "item_id_num":
|
||||
if (parts.Length >= 3)
|
||||
result = $"类型:{parts[0]}, ID:{parts[1]}, 数量:{parts[2]}";
|
||||
break;
|
||||
case "type_id_num":
|
||||
if (parts.Length >= 3)
|
||||
result = $"类型:{parts[0]}, ID:{parts[1]}, 数量:{parts[2]}";
|
||||
break;
|
||||
case "id_pos_lv":
|
||||
if (parts.Length >= 3)
|
||||
result = $"ID:{parts[0]}, 位置:{parts[1]}, 等级:{parts[2]}";
|
||||
break;
|
||||
case "id_lv_num":
|
||||
if (parts.Length >= 3)
|
||||
result = $"ID:{parts[0]}, 等级:{parts[1]}, 数量:{parts[2]}";
|
||||
break;
|
||||
case "id_lv_num_time":
|
||||
if (parts.Length >= 4)
|
||||
result = $"ID:{parts[0]}, 等级:{parts[1]}, 数量:{parts[2]}, 时间:{parts[3]}";
|
||||
break;
|
||||
case "buffid_lv":
|
||||
if (parts.Length >= 2)
|
||||
result = $"BuffID:{parts[0]}, 等级:{parts[1]}";
|
||||
break;
|
||||
case "rune_id_num":
|
||||
if (parts.Length >= 2)
|
||||
result = $"符文ID:{parts[0]}, 数量:{parts[1]}";
|
||||
break;
|
||||
case "equip_id_num":
|
||||
if (parts.Length >= 2)
|
||||
result = $"装备ID:{parts[0]}, 数量:{parts[1]}";
|
||||
break;
|
||||
case "表名":
|
||||
result = $"表名: {value}";
|
||||
break;
|
||||
string[] parts = value.Split('_');
|
||||
if (parts.Length >= template.minParts)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < template.parts.Length && i < parts.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
sb.Append($"{template.parts[i]}:{parts[i]}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(result) ? value : result;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static List<string> GetRelatedTableNames(string tableName)
|
||||
@@ -574,5 +487,119 @@ public static class ConfigLinkDatabase
|
||||
{
|
||||
_cachedData = null;
|
||||
_existingTableNames = null;
|
||||
_settings = null;
|
||||
_formatTemplateMap = null;
|
||||
_tableInfoCache = null;
|
||||
}
|
||||
|
||||
private static void LoadSettings()
|
||||
{
|
||||
if (_settings != null) return;
|
||||
|
||||
if (File.Exists(SettingsFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(SettingsFilePath, Encoding.UTF8);
|
||||
_settings = JsonUtility.FromJson<SettingsData>(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[ConfigLinkViewer] 加载 settings.json 失败: {e.Message}");
|
||||
_settings = new SettingsData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings = new SettingsData();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = JsonUtility.ToJson(_settings, true);
|
||||
File.WriteAllText(SettingsFilePath, json, Encoding.UTF8);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[ConfigLinkViewer] 保存 settings.json 失败: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadFormatTemplates()
|
||||
{
|
||||
if (_formatTemplateMap != null) return;
|
||||
|
||||
_formatTemplateMap = new Dictionary<string, FormatTemplate>();
|
||||
LoadSettings();
|
||||
|
||||
if (_settings.formatTemplates != null)
|
||||
{
|
||||
var wrapperType = typeof(FormatTemplatesWrapper);
|
||||
var fields = wrapperType.GetFields();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.FieldType == typeof(FormatTemplate))
|
||||
{
|
||||
var template = field.GetValue(_settings.formatTemplates) as FormatTemplate;
|
||||
if (template != null)
|
||||
{
|
||||
_formatTemplateMap[field.Name] = template;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, (string, string)> LoadTableInfoDict()
|
||||
{
|
||||
if (_tableInfoCache != null)
|
||||
return _tableInfoCache;
|
||||
|
||||
_tableInfoCache = new Dictionary<string, (string, string)>();
|
||||
|
||||
if (!File.Exists(TableInfoFilePath))
|
||||
return _tableInfoCache;
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(TableInfoFilePath, Encoding.UTF8);
|
||||
var raw = JsonUtility.FromJson<TableInfoFile>(json);
|
||||
if (raw?.entries != null)
|
||||
{
|
||||
foreach (var entry in raw.entries)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(entry.name))
|
||||
{
|
||||
_tableInfoCache[entry.name.ToLower()] = (
|
||||
entry.displayName ?? entry.name,
|
||||
entry.description ?? ""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[ConfigLinkViewer] 加载 table_info.json 失败: {e.Message}");
|
||||
}
|
||||
|
||||
return _tableInfoCache;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class TableInfoFile
|
||||
{
|
||||
public List<TableInfoEntry> entries = new List<TableInfoEntry>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class TableInfoEntry
|
||||
{
|
||||
public string name;
|
||||
public string displayName;
|
||||
public string description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,95 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
public static class ConfigLinkViewerCallbacks
|
||||
{
|
||||
public static Action ExportConfigCallback;
|
||||
public static Func<string> FallbackExcelPathCallback;
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class ConfigLinkViewerAutoSetup
|
||||
{
|
||||
static ConfigLinkViewerAutoSetup()
|
||||
{
|
||||
TryRegisterExportConfig();
|
||||
TryRegisterFallbackExcelPath();
|
||||
}
|
||||
|
||||
private static void TryRegisterExportConfig()
|
||||
{
|
||||
if (ConfigLinkViewerCallbacks.ExportConfigCallback != null) return;
|
||||
try
|
||||
{
|
||||
var type = FindType("MFrame.ConfigDeal");
|
||||
if (type == null) return;
|
||||
var method = type.GetMethod("ExportConfig",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (method == null) return;
|
||||
ConfigLinkViewerCallbacks.ExportConfigCallback =
|
||||
(Action)Delegate.CreateDelegate(typeof(Action), method);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void TryRegisterFallbackExcelPath()
|
||||
{
|
||||
if (ConfigLinkViewerCallbacks.FallbackExcelPathCallback != null) return;
|
||||
try
|
||||
{
|
||||
var pathConfType = FindType("MFrame.PathConf");
|
||||
var editorCfType = FindType("MFrame.ConfigEditorCf");
|
||||
if (pathConfType == null || editorCfType == null) return;
|
||||
|
||||
var rootField = pathConfType.GetField("project_root",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (rootField == null) return;
|
||||
|
||||
var insProp = editorCfType.GetProperty("ins",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (insProp == null) return;
|
||||
|
||||
var ins = insProp.GetValue(null);
|
||||
if (ins == null) return;
|
||||
|
||||
var excelField = ins.GetType().GetField("excel_path",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
if (excelField == null) return;
|
||||
|
||||
ConfigLinkViewerCallbacks.FallbackExcelPathCallback = () =>
|
||||
{
|
||||
var root = rootField.GetValue(null) as string;
|
||||
var inst = insProp.GetValue(null);
|
||||
var ep = inst != null ? excelField.GetValue(inst) as string : null;
|
||||
return root + ep;
|
||||
};
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static Type FindType(string fullName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var t = asm.GetType(fullName);
|
||||
if (t != null) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConfigLinkViewerWindow : EditorWindow
|
||||
{
|
||||
private enum Tab { View, Config }
|
||||
|
||||
private Tab currentTab = Tab.View;
|
||||
private string selectedTableName;
|
||||
private Dictionary<string, bool> expandedRelations = new Dictionary<string, bool>();
|
||||
private Vector2 relationScrollPos;
|
||||
private Vector2 detailScrollPos;
|
||||
private Vector2 configScrollPos;
|
||||
private string searchFilter = "";
|
||||
private bool showOnlyExisting = true;
|
||||
private bool showReverseRelations = false;
|
||||
@@ -27,46 +108,284 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawTabBar();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
float windowWidth = position.width;
|
||||
float windowHeight = position.height;
|
||||
|
||||
DrawHeader();
|
||||
if (currentTab == Tab.View)
|
||||
{
|
||||
DrawViewTab(windowWidth, windowHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawConfigTab(windowWidth, windowHeight);
|
||||
DrawLuoTianyiOverlay(windowWidth, windowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTabBar()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal("Toolbar");
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUIStyle tabLeft, tabRight;
|
||||
if (currentTab == Tab.View)
|
||||
{
|
||||
tabLeft = new GUIStyle("ToolbarButton") { fontStyle = FontStyle.Bold };
|
||||
tabRight = new GUIStyle("ToolbarButton");
|
||||
}
|
||||
else
|
||||
{
|
||||
tabLeft = new GUIStyle("ToolbarButton");
|
||||
tabRight = new GUIStyle("ToolbarButton") { fontStyle = FontStyle.Bold };
|
||||
}
|
||||
|
||||
if (GUILayout.Button("查看", tabLeft, GUILayout.Width(60)))
|
||||
{
|
||||
currentTab = Tab.View;
|
||||
}
|
||||
if (GUILayout.Button("配置", tabRight, GUILayout.Width(60)))
|
||||
{
|
||||
currentTab = Tab.Config;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawViewTab(float windowWidth, float windowHeight)
|
||||
{
|
||||
DrawViewHeader();
|
||||
EditorGUILayout.Space();
|
||||
DrawTableSelector(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
private void DrawViewHeader()
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("配置表联动关系查看器", new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 }, GUILayout.ExpandWidth(true));
|
||||
|
||||
if (GUILayout.Button("清理Excel空行", GUILayout.Height(22), GUILayout.Width(100)))
|
||||
{
|
||||
EditorApplication.delayCall += CleanExcelEmptyRows;
|
||||
}
|
||||
if (GUILayout.Button("导出配置", GUILayout.Height(22), GUILayout.Width(80)))
|
||||
{
|
||||
EditorApplication.delayCall += MFrame.ConfigDeal.ExportConfig;
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (ConfigLinkViewerCallbacks.ExportConfigCallback != null)
|
||||
{
|
||||
ConfigLinkViewerCallbacks.ExportConfigCallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示",
|
||||
"未注册导出配置回调。\n请在项目初始化代码中设置 ConfigLinkViewerCallbacks.ExportConfigCallback。",
|
||||
"确定");
|
||||
}
|
||||
};
|
||||
}
|
||||
if (GUILayout.Button("刷新", GUILayout.Height(22), GUILayout.Width(60)))
|
||||
{
|
||||
ConfigLinkDatabase.ClearCache();
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.LabelField("选择配置表查看其与其他表的关联关系", EditorStyles.miniLabel);
|
||||
|
||||
string configStatus = ConfigLinkDatabase.HasConfigFile()
|
||||
? "<color=green>已加载 (ConfigLinkData.json)</color>"
|
||||
: "<color=yellow>未找到配置文件,请切换到[配置]标签页生成</color>";
|
||||
var statusStyle = new GUIStyle(EditorStyles.miniLabel) { richText = true };
|
||||
EditorGUILayout.LabelField($"配置状态: {configStatus}", statusStyle);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawConfigTab(float windowWidth, float windowHeight)
|
||||
{
|
||||
configScrollPos = EditorGUILayout.BeginScrollView(configScrollPos);
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
|
||||
EditorGUILayout.LabelField("项目配置", new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 });
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawProjectSettings();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawExcelPathSettings(windowWidth);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.LabelField("配置数据生成", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("首次使用或跨项目时,需要先生成配置数据。", EditorStyles.miniLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("生成配置", GUILayout.Height(30)))
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
ConfigLinkDatabase.GenerateConfigFromExcelFolder();
|
||||
};
|
||||
}
|
||||
if (GUILayout.Button("补全配置", GUILayout.Height(30)))
|
||||
{
|
||||
EditorApplication.delayCall += RunAIConfigScript;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("生成配置:从 Excel 文件夹扫描生成 ConfigLinkData.json 骨架", EditorStyles.miniLabel);
|
||||
EditorGUILayout.LabelField("补全配置:运行 Python 脚本自动分析引用关系,补全字段关联", EditorStyles.miniLabel);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private GUIStyle _artStyle;
|
||||
|
||||
private GUIStyle GetArtStyle(int fontSize)
|
||||
{
|
||||
if (_artStyle == null || _artStyle.fontSize != fontSize)
|
||||
{
|
||||
_artStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = fontSize,
|
||||
richText = true,
|
||||
wordWrap = false,
|
||||
clipping = TextClipping.Overflow,
|
||||
normal = { textColor = Color.white },
|
||||
};
|
||||
var font = Font.CreateDynamicFontFromOSFont("Consolas", fontSize);
|
||||
if (font != null)
|
||||
_artStyle.font = font;
|
||||
}
|
||||
return _artStyle;
|
||||
}
|
||||
|
||||
private void DrawLuoTianyiOverlay(float windowWidth, float windowHeight)
|
||||
{
|
||||
float scale = Mathf.Clamp(Mathf.Min(windowWidth, windowHeight) / 600f, 0.35f, 0.7f);
|
||||
int fontSize = Mathf.RoundToInt(9 * scale);
|
||||
float lineHeight = fontSize * 1.1f;
|
||||
|
||||
string[] lines = {
|
||||
" _________________",
|
||||
" ____/:::::::::::::::::\\_____",
|
||||
" __/::::::::::::::::::::::::::::\\___",
|
||||
" _/:::::::::::::::::::::::::::::::::::\\__",
|
||||
" _/::::::::::::::::::::::::::::::::::::::::\\_",
|
||||
" /::::::::::::::::::::::::::::::::::::::::::::\\",
|
||||
" |::::::::::::::::::::::::::::::::::::::::::::::\\",
|
||||
" /::::::::::::::::::::::::::::::::::::::::::::::::\\",
|
||||
" |:::/.:::::::;:::::::::::::::::::::::::::::::::::::|",
|
||||
" /:::/.:::::::/..:::::::::::::::::::::::::::::::::::::\\",
|
||||
" |:::|.::::::;/.::::::::::::::::::::::::::::::::::::::::|",
|
||||
" |::/.::::::/..:::::::;;'.::::::::::::::::::::::::::::::|",
|
||||
" |:|.::::/./.::::::;;/..:::::::::::::::::::::::::::::::::|",
|
||||
" `:|.:::|.|.:::::;/..;;;;;;-'.:;;;-':::::::::::::::::::::|",
|
||||
" \\|.:::|.|.:::;/.;;/ -..::'''...:::::::::::::::::::::::|",
|
||||
" \\;;::|.|.::/.;/--__ |::::::::::::::::::::::::::::|",
|
||||
" \\;;\\\\::/|/ =-__ --_ /::::::::::::::::::::::::::::::",
|
||||
" \\/ /| -._ |.::::::::::::::::::::::::::::::",
|
||||
" _.' /// /- ||::::::::::::::::::::::::::::::",
|
||||
" _.-' //' ||::::::::::::::::::::::::::::::",
|
||||
" | - `|::::::::::::::::::::::::::::::",
|
||||
" \\ \\:::::::::::::::::::::::::::::",
|
||||
" | \\:::::::::::::::::::::",
|
||||
" / __/:::::::::::::::::::::::::::",
|
||||
" \\ __/::;::;;:::::: ::::::::::",
|
||||
" |` /;;;;/::| \\:::: :___: :::::::::",
|
||||
" \\ |'_,::::/ \\ |:::: .| |`. :::::::::",
|
||||
" / _/::::::/ / /:::: | \\.' | ::. .::::",
|
||||
" | /.::;;:-'_)/_/::::: `._| .' ::..::::",
|
||||
" ----.__ | |.::| \\___/::::::: ::..::::",
|
||||
" :::::::`----\\_____ \\:::\\.-'::::::::::: ___:___ ::..::::",
|
||||
" ;;;;;:::::::::::::`------ \\:::::::::::::::: ___|___ ::..::::",
|
||||
" `-------:::::::\\ /:::::::::::::::: __| ::..::::",
|
||||
" ___.--------'::::::::\\ |::::::::::::::::: | |-. ::..::::",
|
||||
" :::::;;;:--:::::::::::| /::::::::::::::::: --' ' ::..::::",
|
||||
" ----' _,-:.:::::::::::\\ |.::::::::::::::::: ::..::::",
|
||||
" __/.::::::::::::::::| |.::::::::::::::::: : : ::..::::",
|
||||
" __/.:::;;::::::::;/.:::| |.::::::::::::::::: | : ::. .::::",
|
||||
" /.::::;/ /.:::::;/ |.::::| \\_.::::::::::::::: | . | :::::::::",
|
||||
" :::::/ /.:::::/ /.:::::| \\__.:::::::::::: `.' ' :::::::::",
|
||||
" ::::| |.:::::/ /.:::::.| \\,:::::::::::: ::::::::::",
|
||||
" ::::| |.::::| |.:::::/| __/::::::::::::::::::::::::::::",
|
||||
" \\.:::\\ \\.:::| |.::::||| __.--:::::::::::::::::::::::::::::",
|
||||
" \\.:::\\_ \\.:::\\ \\.:::'/.:::::::::::::::::::::::::::::::::",
|
||||
" \\.::::\\ \\.:::\\ \\.::::::::::::::::::::::::::::::::::::::::::"
|
||||
};
|
||||
|
||||
float boxW = Mathf.Clamp(windowWidth * 0.45f, 300f, 500f);
|
||||
float boxH = lines.Length * lineHeight + 16;
|
||||
float boxX = windowWidth - boxW - 10;
|
||||
float boxY = windowHeight - boxH - 10;
|
||||
|
||||
var bgColor = new Color(0.12f, 0.12f, 0.16f, 0.9f);
|
||||
EditorGUI.DrawRect(new Rect(boxX, boxY, boxW, boxH), bgColor);
|
||||
|
||||
var borderColor = new Color(0f, 0.75f, 1f, 0.4f);
|
||||
EditorGUI.DrawRect(new Rect(boxX, boxY, boxW, 1), borderColor);
|
||||
EditorGUI.DrawRect(new Rect(boxX, boxY + boxH - 1, boxW, 1), borderColor);
|
||||
EditorGUI.DrawRect(new Rect(boxX, boxY, 1, boxH), borderColor);
|
||||
EditorGUI.DrawRect(new Rect(boxX + boxW - 1, boxY, 1, boxH), borderColor);
|
||||
|
||||
GUIStyle artStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = fontSize,
|
||||
richText = false,
|
||||
wordWrap = false,
|
||||
clipping = TextClipping.Overflow,
|
||||
};
|
||||
artStyle.normal.textColor = new Color(0f, 1f, 1f);
|
||||
|
||||
float y = boxY + 8;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var lineRect = new Rect(boxX + 4, y, boxW - 8, lineHeight);
|
||||
GUI.Label(lineRect, lines[i], artStyle);
|
||||
y += lineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProjectSettings()
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.LabelField("路径设置", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
string configPath = ConfigLinkDatabase.GetConfigFolderPath();
|
||||
EditorGUILayout.LabelField("Config目录:", GUILayout.Width(80));
|
||||
EditorGUILayout.LabelField(configPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
|
||||
if (GUILayout.Button("选择", GUILayout.Width(60)))
|
||||
{
|
||||
string selected = EditorUtility.OpenFolderPanel("选择配置导出目录", Application.dataPath, "");
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
ConfigLinkDatabase.SetConfigFolderPath(selected);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawTableSelector(float windowWidth, float windowHeight)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
|
||||
EditorGUILayout.LabelField("选择配置表:", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginVertical("box", GUILayout.Height(windowHeight - 60));
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
searchFilter = EditorGUILayout.TextField("搜索:", searchFilter, EditorStyles.toolbarSearchField);
|
||||
showOnlyExisting = EditorGUILayout.ToggleLeft("只显示当前项目存在的表", showOnlyExisting);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawExcelPathSettings(windowWidth);
|
||||
|
||||
var allTables = showOnlyExisting ? ConfigLinkDatabase.GetExistingTables() : ConfigLinkDatabase.GetAllTableInfo();
|
||||
var filteredTables = string.IsNullOrEmpty(searchFilter)
|
||||
? allTables
|
||||
@@ -84,13 +403,12 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
|
||||
|
||||
float leftPanelWidth = Mathf.Clamp(windowWidth * 0.3f, 180f, 300f);
|
||||
float rightPanelWidth = windowWidth - leftPanelWidth - 20f;
|
||||
float panelHeight = windowHeight - 150f;
|
||||
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(leftPanelWidth), GUILayout.Height(panelHeight));
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(leftPanelWidth), GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.LabelField($"配置表列表 ({filteredTables.Count}):", EditorStyles.miniBoldLabel);
|
||||
relationScrollPos = EditorGUILayout.BeginScrollView(relationScrollPos, false, true, GUILayout.ExpandHeight(true));
|
||||
|
||||
@@ -103,16 +421,22 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
|
||||
if (GUILayout.Button(displayNames[i], buttonStyle))
|
||||
{
|
||||
selectedTableName = tableKeys[i];
|
||||
expandedRelations.Clear();
|
||||
if (selectedTableName != tableKeys[i])
|
||||
{
|
||||
selectedTableName = tableKeys[i];
|
||||
expandedRelations.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.BeginVertical("box", GUILayout.Width(rightPanelWidth), GUILayout.Height(panelHeight));
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(rightPanelWidth), GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.LabelField("当前表详情:", EditorStyles.miniBoldLabel);
|
||||
detailScrollPos = EditorGUILayout.BeginScrollView(detailScrollPos, false, true, GUILayout.ExpandHeight(true));
|
||||
DrawCurrentTableInfo(rightPanelWidth);
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
@@ -121,12 +445,9 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
|
||||
private void DrawCurrentTableInfo(float panelWidth)
|
||||
{
|
||||
EditorGUILayout.BeginScrollView(Vector2.zero, false, true, GUILayout.ExpandHeight(true));
|
||||
|
||||
if (string.IsNullOrEmpty(selectedTableName))
|
||||
{
|
||||
EditorGUILayout.LabelField("请从左侧选择一个配置表", EditorStyles.centeredGreyMiniLabel);
|
||||
EditorGUILayout.EndScrollView();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,7 +455,6 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
if (tableInfo == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("未找到该表的配置信息", EditorStyles.helpBox);
|
||||
EditorGUILayout.EndScrollView();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +494,6 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void DrawOpenExcelButtons(float panelWidth)
|
||||
@@ -264,14 +582,17 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
|
||||
private void DrawExcelPathSettings(float windowWidth)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.LabelField("Excel 文件夹", EditorStyles.boldLabel);
|
||||
|
||||
string currentPath = ConfigLinkDatabase.GetExcelFolderPath();
|
||||
string displayPath = string.IsNullOrEmpty(currentPath) ? "未设置" : currentPath;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Excel文件夹:", GUILayout.Width(70));
|
||||
EditorGUILayout.LabelField("路径:", GUILayout.Width(36));
|
||||
EditorGUILayout.LabelField(displayPath, EditorStyles.textField, GUILayout.ExpandWidth(true));
|
||||
|
||||
if (GUILayout.Button("选择文件夹", GUILayout.Width(80)))
|
||||
if (GUILayout.Button("选择", GUILayout.Width(60)))
|
||||
{
|
||||
string selectedPath = EditorUtility.OpenFolderPanel("选择Excel文件夹", "", "");
|
||||
if (!string.IsNullOrEmpty(selectedPath))
|
||||
@@ -280,6 +601,7 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawRelation(FieldRelation relation, float panelWidth)
|
||||
@@ -333,21 +655,128 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
EditorGUILayout.Space(2);
|
||||
}
|
||||
|
||||
private void DrawFormatExample(string format)
|
||||
{
|
||||
string example = ConfigLinkDatabase.FormatRelationValue(format, "1_2_3_4");
|
||||
if (example != "1_2_3_4")
|
||||
{
|
||||
GUIStyle exampleStyle = new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic };
|
||||
EditorGUILayout.LabelField($"示例: {example}", exampleStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunAIConfigScript()
|
||||
{
|
||||
string scriptPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "generate_config_link_data.py");
|
||||
if (!File.Exists(scriptPath))
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "未找到脚本文件:\n" + scriptPath, "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
string pythonExe = FindPythonExecutable();
|
||||
if (string.IsNullOrEmpty(pythonExe))
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "未找到 Python 环境,请确保已安装 Python 并添加到 PATH", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string configDir = ConfigLinkDatabase.GetConfigFolderPath();
|
||||
string tableInfoPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "table_info.json");
|
||||
string outputPath = Path.Combine(Application.dataPath, "Editor/ConfigLinkViewer", "ConfigLinkData.json");
|
||||
|
||||
string arguments = $"\"{scriptPath}\" --config-dir \"{Path.Combine(Application.dataPath, configDir)}\" --output \"{outputPath}\"";
|
||||
if (File.Exists(tableInfoPath))
|
||||
{
|
||||
arguments += $" --table-info \"{tableInfoPath}\"";
|
||||
}
|
||||
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = pythonExe,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = Path.GetDirectoryName(scriptPath),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = System.Text.Encoding.UTF8,
|
||||
StandardErrorEncoding = System.Text.Encoding.UTF8,
|
||||
};
|
||||
|
||||
using (var process = System.Diagnostics.Process.Start(startInfo))
|
||||
{
|
||||
string stdout = process.StandardOutput.ReadToEnd();
|
||||
string stderr = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
Debug.Log("[ConfigLinkViewer] 补全配置完成:\n" + stdout);
|
||||
ConfigLinkDatabase.ClearCache();
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.DisplayDialog("完成", "补全配置已完成\n\n" + stdout, "确定");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[ConfigLinkViewer] 补全配置失败:\n" + stderr);
|
||||
EditorUtility.DisplayDialog("失败", "脚本执行失败:\n" + stderr, "确定");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("[ConfigLinkViewer] 运行脚本异常: " + ex.Message);
|
||||
EditorUtility.DisplayDialog("异常", "运行脚本时发生异常:\n" + ex.Message, "确定");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindPythonExecutable()
|
||||
{
|
||||
string[] candidates = { "python", "python3", "py" };
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = candidate,
|
||||
Arguments = "--version",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
using (var process = System.Diagnostics.Process.Start(startInfo))
|
||||
{
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode == 0)
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CleanExcelEmptyRows()
|
||||
{
|
||||
string excelFolder = ConfigLinkDatabase.GetExcelFolderPath();
|
||||
if (string.IsNullOrEmpty(excelFolder))
|
||||
{
|
||||
var acf = MFrame.ConfigEditorCf.ins;
|
||||
if (acf != null)
|
||||
if (ConfigLinkViewerCallbacks.FallbackExcelPathCallback != null)
|
||||
{
|
||||
excelFolder = MFrame.PathConf.project_root + acf.excel_path;
|
||||
excelFolder = ConfigLinkViewerCallbacks.FallbackExcelPathCallback();
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(excelFolder) || !Directory.Exists(excelFolder))
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "Excel文件夹路径未设置或不存在,请先设置Excel文件夹", "确定");
|
||||
EditorUtility.DisplayDialog("提示",
|
||||
"Excel文件夹路径未设置或不存在。\n请切换到[配置]标签页设置,或在 ConfigLinkViewerCallbacks.FallbackExcelPathCallback 中注册回退逻辑。",
|
||||
"确定");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,91 +845,92 @@ public class ConfigLinkViewerWindow : EditorWindow
|
||||
}
|
||||
}
|
||||
|
||||
var sheetData = doc.Root?.Element(ns + "sheetData");
|
||||
if (sheetData == null) return 0;
|
||||
|
||||
var rows = sheetData.Elements(ns + "row").ToList();
|
||||
var rowsToRemove = new List<XElement>();
|
||||
|
||||
foreach (var row in rows)
|
||||
List<string> sharedStrings = null;
|
||||
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Read))
|
||||
{
|
||||
if (!int.TryParse(row.Attribute("r")?.Value, out int rowNum)) continue;
|
||||
if (rowNum <= headerRowCount) continue;
|
||||
|
||||
var cells = row.Elements(ns + "c").ToList();
|
||||
if (cells.Count == 0)
|
||||
var ssEntry = archive.GetEntry("xl/sharedStrings.xml");
|
||||
if (ssEntry != null)
|
||||
{
|
||||
rowsToRemove.Add(row);
|
||||
continue;
|
||||
}
|
||||
|
||||
var firstCell = cells[0];
|
||||
var valueElement = firstCell.Element(ns + "v");
|
||||
string val = valueElement?.Value?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(val) || !int.TryParse(val, out _))
|
||||
{
|
||||
rowsToRemove.Add(row);
|
||||
using (var stream = ssEntry.Open())
|
||||
{
|
||||
var ssDoc = XDocument.Load(stream);
|
||||
sharedStrings = ssDoc.Root.Elements(ns + "si")
|
||||
.Select(si => si.Value)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rowsToRemove.Count == 0) return 0;
|
||||
var sheetData = doc.Root.Element(ns + "sheetData");
|
||||
if (sheetData == null) return 0;
|
||||
|
||||
foreach (var row in rowsToRemove)
|
||||
var rows = sheetData.Elements(ns + "row").ToList();
|
||||
if (rows.Count <= headerRowCount) return 0;
|
||||
|
||||
var emptyRows = new List<XElement>();
|
||||
|
||||
for (int i = headerRowCount; i < rows.Count; i++)
|
||||
{
|
||||
var row = rows[i];
|
||||
var cells = row.Elements(ns + "c").ToList();
|
||||
|
||||
if (cells.Count == 0)
|
||||
{
|
||||
emptyRows.Add(row);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool allEmpty = true;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
var value = cell.Element(ns + "v");
|
||||
if (value != null && !string.IsNullOrWhiteSpace(value.Value))
|
||||
{
|
||||
allEmpty = false;
|
||||
break;
|
||||
}
|
||||
|
||||
var cellType = cell.Attribute("t")?.Value;
|
||||
if (cellType == "s" && value != null)
|
||||
{
|
||||
if (int.TryParse(value.Value, out int ssIdx) && sharedStrings != null && ssIdx < sharedStrings.Count)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(sharedStrings[ssIdx]))
|
||||
{
|
||||
allEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allEmpty)
|
||||
{
|
||||
emptyRows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
if (emptyRows.Count == 0) return 0;
|
||||
|
||||
foreach (var row in emptyRows)
|
||||
{
|
||||
row.Remove();
|
||||
}
|
||||
|
||||
var remainingRows = sheetData.Elements(ns + "row").ToList();
|
||||
int newRowIndex = 1;
|
||||
foreach (var row in remainingRows)
|
||||
{
|
||||
row.SetAttributeValue("r", newRowIndex);
|
||||
foreach (var cell in row.Elements(ns + "c"))
|
||||
{
|
||||
string cellRef = cell.Attribute("r")?.Value;
|
||||
if (!string.IsNullOrEmpty(cellRef))
|
||||
{
|
||||
string colPart = Regex.Replace(cellRef, @"\d", "");
|
||||
cell.SetAttributeValue("r", colPart + newRowIndex);
|
||||
}
|
||||
}
|
||||
newRowIndex++;
|
||||
}
|
||||
|
||||
using (var archive = ZipFile.Open(filePath, ZipArchiveMode.Update))
|
||||
{
|
||||
var entry = archive.GetEntry("xl/worksheets/sheet1.xml");
|
||||
if (entry != null) entry.Delete();
|
||||
var newEntry = archive.CreateEntry("xl/worksheets/sheet1.xml");
|
||||
using (var stream = newEntry.Open())
|
||||
var sheetEntry = archive.GetEntry("xl/worksheets/sheet1.xml");
|
||||
if (sheetEntry != null)
|
||||
{
|
||||
doc.Save(stream);
|
||||
sheetEntry.Delete();
|
||||
var newEntry = archive.CreateEntry("xl/worksheets/sheet1.xml");
|
||||
using (var stream = newEntry.Open())
|
||||
{
|
||||
doc.Save(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[清理空行] {Path.GetFileName(filePath)}: 删除了 {rowsToRemove.Count} 行空数据");
|
||||
return rowsToRemove.Count;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, (string example, string desc)> FormatExamples = new Dictionary<string, (string, string)>
|
||||
{
|
||||
{ "item_id_num", ("1001_10_5", "类型:1001, ID:10, 数量:5") },
|
||||
{ "type_id_num", ("1001_10_5", "类型:1001, ID:10, 数量:5") },
|
||||
{ "id_pos_lv", ("100_1_30", "ID:100, 位置:1, 等级:30") },
|
||||
{ "id_lv_num", ("100_30_5", "ID:100, 等级:30, 数量:5") },
|
||||
{ "id_lv_num_time",("100_30_5_120","ID:100, 等级:30, 数量:5, 时间:120秒") },
|
||||
{ "buffid_lv", ("5_3", "BuffID:5, 等级:3") },
|
||||
{ "rune_id_num", ("10_2", "符文ID:10, 数量:2") },
|
||||
{ "equip_id_num", ("20_1", "装备ID:20, 数量:1") },
|
||||
};
|
||||
|
||||
private void DrawFormatExample(string format)
|
||||
{
|
||||
if (FormatExamples.TryGetValue(format.ToLower(), out var info))
|
||||
{
|
||||
var style = new GUIStyle(EditorStyles.miniLabel) { wordWrap = false, richText = true };
|
||||
EditorGUILayout.LabelField($"示例: {info.example} → {info.desc}", style);
|
||||
}
|
||||
return emptyRows.Count;
|
||||
}
|
||||
}
|
||||
|
||||
679
Unity/配置表联动查看器/ConfigLinkViewer/generate_config_link_data.py
Normal file
679
Unity/配置表联动查看器/ConfigLinkViewer/generate_config_link_data.py
Normal file
@@ -0,0 +1,679 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
|
||||
DEFAULT_CONFIG_DIR = PROJECT_ROOT / "Assets" / "Resources" / "Resources_moved" / "config"
|
||||
DEFAULT_OUTPUT = Path(__file__).resolve().parent / "ConfigLinkData.json"
|
||||
DEFAULT_TABLE_INFO = Path(__file__).resolve().parent / "table_info.json"
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="ConfigLinkViewer 配置表联动关系自动生成")
|
||||
parser.add_argument("--config-dir", type=str, default=None,
|
||||
help="JSON 配置导出目录 (默认: Assets/Resources/Resources_moved/config)")
|
||||
parser.add_argument("--output", type=str, default=None,
|
||||
help="输出 ConfigLinkData.json 路径")
|
||||
parser.add_argument("--table-info", type=str, default=None,
|
||||
help="表信息配置 JSON 路径 (默认: table_info.json)")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_table_info(filepath):
|
||||
if filepath.exists():
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
entries = data.get("entries", [])
|
||||
return {
|
||||
entry["name"]: (entry.get("displayName", entry["name"]), entry.get("description", ""))
|
||||
for entry in entries
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
TABLE_NAME_TO_CN = {
|
||||
"hero": "英雄",
|
||||
"enemy": "敌人",
|
||||
"equip": "装备",
|
||||
"prop": "道具",
|
||||
"skill": "技能",
|
||||
"buff": "增益效果",
|
||||
"attribute": "属性",
|
||||
"quest": "任务",
|
||||
"player": "玩家",
|
||||
"rune": "符文",
|
||||
"recharge": "充值",
|
||||
"checkpointreward": "关卡奖励",
|
||||
"activity": "活动",
|
||||
"map_act": "地图波次1",
|
||||
"map_act2": "地图波次2",
|
||||
"map_act3": "地图波次3",
|
||||
"map_act4": "地图波次4",
|
||||
"fight_sample": "主线关卡",
|
||||
"fight_arena": "竞技场战斗",
|
||||
"fight_fb1": "副本1",
|
||||
"fight_fb2": "副本2",
|
||||
"fight_fb3": "副本3",
|
||||
"fight_fb4": "副本4",
|
||||
"map": "地图",
|
||||
}
|
||||
|
||||
|
||||
def load_json_file(filepath):
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8-sig") as f:
|
||||
return json.load(f)
|
||||
except UnicodeDecodeError:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
|
||||
def extract_fields_from_json(filepath):
|
||||
data = load_json_file(filepath)
|
||||
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
return list(data[0].keys())
|
||||
elif isinstance(data, dict):
|
||||
return list(data.keys())
|
||||
return []
|
||||
|
||||
|
||||
def detect_reward_format(values):
|
||||
if not values:
|
||||
return ""
|
||||
sample = ""
|
||||
for v in values[:5]:
|
||||
if isinstance(v, str):
|
||||
sample = v
|
||||
break
|
||||
elif isinstance(v, list) and len(v) > 0:
|
||||
for item in v[:3]:
|
||||
if isinstance(item, str):
|
||||
sample = item
|
||||
break
|
||||
if sample:
|
||||
break
|
||||
|
||||
if not sample:
|
||||
return ""
|
||||
|
||||
if re.match(r"^item_\d+_\d+$", sample):
|
||||
return "item_id_num"
|
||||
if re.match(r"^hero_\d+$", sample):
|
||||
return "hero_id"
|
||||
if re.match(r"^rune_\d+_\d+$", sample):
|
||||
return "item_id_num"
|
||||
if re.match(r"^equip_\d+_\d+$", sample):
|
||||
return "item_id_num"
|
||||
if re.match(r"^Hero_\d+$", sample):
|
||||
return "item_id_num"
|
||||
if re.match(r"^\d+_\d+_\d+_\d+$", sample):
|
||||
return "id_lv_count_delay"
|
||||
if re.match(r"^\d+_\d+_\d+$", sample):
|
||||
return "id_lv_num"
|
||||
return ""
|
||||
|
||||
|
||||
def detect_consumes_format(values):
|
||||
if not values:
|
||||
return ""
|
||||
sample = ""
|
||||
for v in values[:5]:
|
||||
if isinstance(v, str):
|
||||
sample = v
|
||||
break
|
||||
elif isinstance(v, list) and len(v) > 0:
|
||||
for item in v[:3]:
|
||||
if isinstance(item, str):
|
||||
sample = item
|
||||
break
|
||||
if sample:
|
||||
break
|
||||
|
||||
if not sample:
|
||||
return ""
|
||||
|
||||
if re.match(r"^item_\d+_\d+$", sample):
|
||||
return "item_id_num"
|
||||
return ""
|
||||
|
||||
|
||||
def detect_monster_format(values):
|
||||
if not values:
|
||||
return ""
|
||||
sample = ""
|
||||
for v in values[:5]:
|
||||
if isinstance(v, str):
|
||||
sample = v
|
||||
break
|
||||
elif isinstance(v, list) and len(v) > 0:
|
||||
for item in v[:3]:
|
||||
if isinstance(item, str):
|
||||
sample = item
|
||||
break
|
||||
if sample:
|
||||
break
|
||||
|
||||
if not sample:
|
||||
return ""
|
||||
|
||||
if re.match(r"^\d+_\d+_\d+_\d+(\.\d+)?$", sample):
|
||||
return "id_lv_count_delay"
|
||||
if re.match(r"^\d+_\d+_\d+$", sample):
|
||||
return "id_lv_num"
|
||||
return ""
|
||||
|
||||
|
||||
def get_relation_for_target_id(table_name):
|
||||
mapping = {
|
||||
"herolevel": ("hero", "关联英雄ID"),
|
||||
"herostage": ("hero", "关联英雄ID"),
|
||||
"herolevelinternal": ("enemy", "关联敌人ID"),
|
||||
"enemylevel": ("enemy", "关联敌人ID"),
|
||||
"equiplevel": ("equip", "关联装备ID"),
|
||||
"equipstage": ("equip", "关联装备ID"),
|
||||
"playerlevel": ("player", "关联玩家ID"),
|
||||
"playerstage": ("player", "关联玩家ID"),
|
||||
"runelevel": ("rune", "关联符文ID"),
|
||||
"skilllevel": ("skill", "关联技能ID"),
|
||||
"buff": ("skill", "关联技能ID"),
|
||||
"map": ("map", "关联地图ID"),
|
||||
"map1": ("map", "关联地图ID"),
|
||||
"map2": ("map", "关联地图ID"),
|
||||
"map3": ("map", "关联地图ID"),
|
||||
"map_act": ("map", "关联地图ID"),
|
||||
"map_act2": ("map", "关联地图ID"),
|
||||
"map_act3": ("map", "关联地图ID"),
|
||||
"map_act4": ("map", "关联地图ID"),
|
||||
"test_map_act": ("map", "关联地图ID"),
|
||||
"trainbreak": ("prop", "关联训练突破ID"),
|
||||
"trainlevel": ("prop", "关联训练等级ID"),
|
||||
}
|
||||
if table_name in mapping:
|
||||
return mapping[table_name]
|
||||
return None
|
||||
|
||||
|
||||
def build_relations(table_name, fields, sample_values):
|
||||
relations = []
|
||||
|
||||
reward_fields = {
|
||||
"rewards", "rewards2", "pass_rewards", "activateRewards", "reward",
|
||||
"dailyQuestScoreRewards", "weeklyQuestScoreRewards", "achievementScoreRewards",
|
||||
"heroTujianRewards",
|
||||
}
|
||||
consume_fields = {"consumes", "lostConsumes"}
|
||||
monster_fields = {"monsters", "enemy_ids"}
|
||||
equip_fields = {"equips"}
|
||||
skill_fields = {"skill_ids", "skills"}
|
||||
hero_fields = {"hero_ids"}
|
||||
quest_fields = {"quest"}
|
||||
attr_fields = {"attr_id", "level_attr_id", "stage_attr_id"}
|
||||
shard_fields = {"shardItem"}
|
||||
compose_fields = {"compose"}
|
||||
next_fields = {"next"}
|
||||
recharge_fields = {"recharge_id"}
|
||||
product_fields = {"productId"}
|
||||
out_enemy_fields = {"out_enemy_config"}
|
||||
scene_fight_fields = {"fight_cf_name"}
|
||||
scene_map_fields = {"map_cf_name"}
|
||||
time_limit_fields = {"time_limit_boss"}
|
||||
|
||||
for field in fields:
|
||||
if field == "target_id":
|
||||
result = get_relation_for_target_id(table_name)
|
||||
if result:
|
||||
target, desc = result
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": target,
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": desc,
|
||||
})
|
||||
continue
|
||||
|
||||
if field == "activityId":
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "activity",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "所属活动ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field == "reward_id":
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "checkpointreward",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关卡奖励ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in reward_fields:
|
||||
fmt = detect_reward_format(sample_values.get(field, []))
|
||||
if not fmt:
|
||||
fmt = "item_id_num"
|
||||
|
||||
desc_map = {
|
||||
"rewards": "奖励道具",
|
||||
"rewards2": "付费奖励道具",
|
||||
"pass_rewards": "首通奖励道具",
|
||||
"activateRewards": "激活奖励道具",
|
||||
"reward": "宝箱奖励道具",
|
||||
}
|
||||
desc = desc_map.get(field, f"{field}奖励道具")
|
||||
|
||||
if table_name == "gacha" and field == "rewards":
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "hero",
|
||||
"targetField": "id",
|
||||
"format": "hero_id",
|
||||
"description": "召唤产出英雄(格式: hero_英雄ID)",
|
||||
})
|
||||
else:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "prop",
|
||||
"targetField": "id",
|
||||
"format": fmt,
|
||||
"description": f"{desc}(格式: {fmt})" if fmt else desc,
|
||||
})
|
||||
continue
|
||||
|
||||
if field in consume_fields:
|
||||
fmt = detect_consumes_format(sample_values.get(field, []))
|
||||
desc_map = {
|
||||
"consumes": "消耗道具",
|
||||
"lostConsumes": "失败损失道具",
|
||||
}
|
||||
desc = desc_map.get(field, field)
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "prop",
|
||||
"targetField": "id",
|
||||
"format": fmt if fmt else "item_id_num",
|
||||
"description": f"{desc}(格式: item_id_num)" if fmt else f"{desc}(格式: item_id_num)",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in monster_fields:
|
||||
fmt = detect_monster_format(sample_values.get(field, []))
|
||||
desc_map = {
|
||||
"monsters": "怪物列表",
|
||||
"enemy_ids": "敌人列表",
|
||||
}
|
||||
desc = desc_map.get(field, field)
|
||||
format_desc = f"(格式: {fmt})" if fmt else ""
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "enemy",
|
||||
"targetField": "id",
|
||||
"format": fmt,
|
||||
"description": f"{desc}{format_desc}",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in time_limit_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "enemy",
|
||||
"targetField": "id",
|
||||
"format": "id_lv_num_hp",
|
||||
"description": "限时Boss(格式: 敌人ID_等级_数量_血量百分比)",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in equip_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "equip",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "装备槽位装备ID列表",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in skill_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "skill",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "技能ID列表",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in hero_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "hero",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联英雄ID列表",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in quest_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "quest",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联任务ID列表",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in attr_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "attribute",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联属性ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in shard_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "prop",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "碎片道具ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in compose_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "prop",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "合成材料道具ID列表",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in next_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": table_name,
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "下一个ID(自引用)",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in recharge_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "recharge",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联充值档位ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in product_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "prop",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "商品道具ID",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in out_enemy_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "map_act",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "出怪波次配置表名",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in scene_fight_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "fight_sample",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联战斗配置表名",
|
||||
})
|
||||
continue
|
||||
|
||||
if field in scene_map_fields:
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": "map",
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": "关联地图配置表名",
|
||||
})
|
||||
continue
|
||||
|
||||
if re.match(r"^.+Id$", field) and field != "Id":
|
||||
prefix = field[:-2]
|
||||
for known_table in TABLE_NAME_TO_CN:
|
||||
if known_table.lower() == prefix.lower():
|
||||
relations.append({
|
||||
"field": field,
|
||||
"target": known_table,
|
||||
"targetField": "id",
|
||||
"format": "",
|
||||
"description": f"关联{TABLE_NAME_TO_CN[known_table]}ID",
|
||||
})
|
||||
break
|
||||
|
||||
return relations
|
||||
|
||||
|
||||
SUSPICIOUS_ID_PATTERNS = [
|
||||
(re.compile(r"^.+Id$"), "可能引用其他表的ID字段(驼峰)"),
|
||||
(re.compile(r"^.+_id$"), "可能引用其他表的ID字段(下划线)"),
|
||||
(re.compile(r"^.+_ids$"), "可能引用其他表的ID列表(下划线)"),
|
||||
(re.compile(r"^.+_Ids$"), "可能引用其他表的ID列表(驼峰)"),
|
||||
]
|
||||
|
||||
|
||||
def analyze_gaps(all_tables, table_fields_map):
|
||||
issues = []
|
||||
ai_prompt_parts = []
|
||||
|
||||
tables_not_in_info = [t["name"] for t in all_tables if t["displayName"] == t["name"]]
|
||||
if tables_not_in_info:
|
||||
issues.append({
|
||||
"type": "missing_display_name",
|
||||
"severity": "warning",
|
||||
"title": "以下表缺少中文名和描述",
|
||||
"items": tables_not_in_info,
|
||||
})
|
||||
ai_prompt_parts.append(
|
||||
f"以下表缺少中文名和描述,请补全 displayName 和 description:\n"
|
||||
+ "\n".join(f" - {t}" for t in tables_not_in_info)
|
||||
)
|
||||
|
||||
no_relation_tables = [t["name"] for t in all_tables if len(t["relations"]) == 0]
|
||||
if no_relation_tables:
|
||||
issues.append({
|
||||
"type": "no_relations",
|
||||
"severity": "info",
|
||||
"title": "以下表没有任何引用关系(可能是叶子表或需人工检查)",
|
||||
"items": no_relation_tables,
|
||||
})
|
||||
|
||||
unrecognized_fields = []
|
||||
for table_name, fields in table_fields_map.items():
|
||||
table_entry = next((t for t in all_tables if t["name"] == table_name), None)
|
||||
if not table_entry:
|
||||
continue
|
||||
related_fields = {r["field"] for r in table_entry["relations"]}
|
||||
for field in fields:
|
||||
if field in related_fields:
|
||||
continue
|
||||
for pattern, desc in SUSPICIOUS_ID_PATTERNS:
|
||||
if pattern.match(field):
|
||||
unrecognized_fields.append(f" {table_name}.{field} - {desc}")
|
||||
break
|
||||
|
||||
if unrecognized_fields:
|
||||
issues.append({
|
||||
"type": "unrecognized_fields",
|
||||
"severity": "warning",
|
||||
"title": "以下字段可能是引用关系但脚本未能识别",
|
||||
"items": unrecognized_fields,
|
||||
})
|
||||
ai_prompt_parts.append(
|
||||
"以下字段可能是引用关系但脚本未能识别,请分析并补全 relations:\n"
|
||||
+ "\n".join(unrecognized_fields)
|
||||
)
|
||||
|
||||
tables_with_missing_fields = []
|
||||
for table in all_tables:
|
||||
if table["displayName"] == table["name"] and len(table["relations"]) == 0:
|
||||
fields = table_fields_map.get(table["name"], [])
|
||||
if fields:
|
||||
tables_with_missing_fields.append(
|
||||
f" {table['name']} (字段: {', '.join(fields[:8])}{'...' if len(fields) > 8 else ''})"
|
||||
)
|
||||
|
||||
if tables_with_missing_fields:
|
||||
issues.append({
|
||||
"type": "fully_missing",
|
||||
"severity": "error",
|
||||
"title": "以下表完全未配置(无中文名、无描述、无引用关系)",
|
||||
"items": tables_with_missing_fields,
|
||||
})
|
||||
ai_prompt_parts.append(
|
||||
"以下表完全未配置,请补全 displayName、description 和 relations:\n"
|
||||
+ "\n".join(tables_with_missing_fields)
|
||||
)
|
||||
|
||||
return issues, ai_prompt_parts
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
config_dir = Path(args.config_dir) if args.config_dir else DEFAULT_CONFIG_DIR
|
||||
output_file = Path(args.output) if args.output else DEFAULT_OUTPUT
|
||||
table_info_file = Path(args.table_info) if args.table_info else DEFAULT_TABLE_INFO
|
||||
|
||||
table_info = load_table_info(table_info_file)
|
||||
|
||||
all_tables = []
|
||||
table_fields_map = {}
|
||||
|
||||
json_files = sorted(config_dir.glob("*.json"))
|
||||
|
||||
for filepath in json_files:
|
||||
table_name = filepath.stem
|
||||
fields = extract_fields_from_json(filepath)
|
||||
if not fields:
|
||||
continue
|
||||
|
||||
table_fields_map[table_name] = fields
|
||||
|
||||
sample_values = {}
|
||||
data = load_json_file(filepath)
|
||||
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
first_row = data[0]
|
||||
for field in fields:
|
||||
if field in first_row:
|
||||
val = first_row[field]
|
||||
if isinstance(val, list):
|
||||
sample_values[field] = val
|
||||
else:
|
||||
sample_values[field] = [val] if val is not None else []
|
||||
|
||||
display_name, description = table_info.get(table_name, (table_name, ""))
|
||||
relations = build_relations(table_name, fields, sample_values)
|
||||
|
||||
all_tables.append({
|
||||
"name": table_name,
|
||||
"displayName": display_name,
|
||||
"description": description,
|
||||
"fileExtension": ".xlsx",
|
||||
"relations": relations,
|
||||
})
|
||||
|
||||
txt_files = sorted(config_dir.glob("*.txt"))
|
||||
for filepath in txt_files:
|
||||
table_name = filepath.stem
|
||||
if table_name in table_info:
|
||||
display_name, description = table_info[table_name]
|
||||
else:
|
||||
display_name, description = table_name, ""
|
||||
all_tables.append({
|
||||
"name": table_name,
|
||||
"displayName": display_name,
|
||||
"description": description,
|
||||
"fileExtension": ".txt",
|
||||
"relations": [],
|
||||
})
|
||||
|
||||
output = {"tables": all_tables}
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, ensure_ascii=False, indent=4)
|
||||
|
||||
total_tables = len(all_tables)
|
||||
total_relations = sum(len(t['relations']) for t in all_tables)
|
||||
tables_with_rels = sum(1 for t in all_tables if t['relations'])
|
||||
|
||||
print("=" * 60)
|
||||
print(" ConfigLinkData.json 生成完成")
|
||||
print("=" * 60)
|
||||
print(f" 输出文件: {output_file}")
|
||||
print(f" 总表数: {total_tables}")
|
||||
print(f" 有引用关系的表: {tables_with_rels}")
|
||||
print(f" 总引用关系数: {total_relations}")
|
||||
print()
|
||||
|
||||
issues, ai_prompt_parts = analyze_gaps(all_tables, table_fields_map)
|
||||
|
||||
if not issues:
|
||||
print(" 所有表均已完整配置,无需要补全的内容。")
|
||||
else:
|
||||
print("-" * 60)
|
||||
print(" 查漏补缺报告")
|
||||
print("-" * 60)
|
||||
for issue in issues:
|
||||
severity_icon = {"error": "[!]", "warning": "[*]", "info": "[i]"}.get(issue["severity"], "")
|
||||
print(f"\n {severity_icon} {issue['title']} ({len(issue['items'])}项)")
|
||||
for item in issue["items"]:
|
||||
print(f" {item}")
|
||||
|
||||
if ai_prompt_parts:
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" AI 补全提示词(可复制发送给 AI)")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("请帮我补全以下配置表联动关系配置:")
|
||||
print()
|
||||
for part in ai_prompt_parts:
|
||||
print(part)
|
||||
print()
|
||||
print("请直接返回修改后的 JSON 配置。")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
Unity/配置表联动查看器/ConfigLinkViewer/settings.json
Normal file
50
Unity/配置表联动查看器/ConfigLinkViewer/settings.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"configFolderPath": "Resources/Resources_moved/config",
|
||||
"excelFolderPath": "",
|
||||
"formatTemplates": {
|
||||
"item_id_num": {
|
||||
"parts": ["类型", "ID", "数量"],
|
||||
"minParts": 3
|
||||
},
|
||||
"type_id_num": {
|
||||
"parts": ["类型", "ID", "数量"],
|
||||
"minParts": 3
|
||||
},
|
||||
"id_pos_lv": {
|
||||
"parts": ["ID", "位置", "等级"],
|
||||
"minParts": 3
|
||||
},
|
||||
"id_lv_num": {
|
||||
"parts": ["ID", "等级", "数量"],
|
||||
"minParts": 3
|
||||
},
|
||||
"id_lv_num_time": {
|
||||
"parts": ["ID", "等级", "数量", "时间"],
|
||||
"minParts": 4
|
||||
},
|
||||
"buffid_lv": {
|
||||
"parts": ["BuffID", "等级"],
|
||||
"minParts": 2
|
||||
},
|
||||
"rune_id_num": {
|
||||
"parts": ["符文ID", "数量"],
|
||||
"minParts": 2
|
||||
},
|
||||
"equip_id_num": {
|
||||
"parts": ["装备ID", "数量"],
|
||||
"minParts": 2
|
||||
},
|
||||
"hero_id": {
|
||||
"parts": ["英雄ID"],
|
||||
"minParts": 1
|
||||
},
|
||||
"id_lv_count_delay": {
|
||||
"parts": ["ID", "等级", "数量", "延迟"],
|
||||
"minParts": 4
|
||||
},
|
||||
"id_lv_num_hp": {
|
||||
"parts": ["ID", "等级", "数量", "血量%"],
|
||||
"minParts": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Unity/配置表联动查看器/ConfigLinkViewer/table_info.json
Normal file
85
Unity/配置表联动查看器/ConfigLinkViewer/table_info.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"entries": [
|
||||
{"name": "achievement", "displayName": "成就", "description": "成就系统配置"},
|
||||
{"name": "activity", "displayName": "活动", "description": "活动配置"},
|
||||
{"name": "activityreward", "displayName": "活动奖励", "description": "活动奖励配置"},
|
||||
{"name": "arenaaward", "displayName": "竞技场奖励", "description": "竞技场排名奖励配置"},
|
||||
{"name": "attribute", "displayName": "属性", "description": "属性类型定义"},
|
||||
{"name": "box", "displayName": "宝箱", "description": "宝箱掉落配置"},
|
||||
{"name": "buff", "displayName": "增益效果", "description": "法宝增益效果配置"},
|
||||
{"name": "callstrength", "displayName": "召唤概率", "description": "召唤概率等级配置"},
|
||||
{"name": "checkpointreward", "displayName": "关卡奖励", "description": "关卡通关奖励配置"},
|
||||
{"name": "collectionreward", "displayName": "收集奖励", "description": "图鉴收集进度奖励配置"},
|
||||
{"name": "common", "displayName": "通用配置", "description": "通用键值对配置"},
|
||||
{"name": "drawlinereward", "displayName": "抽奖奖励", "description": "抽奖线奖励配置"},
|
||||
{"name": "enemy", "displayName": "敌人", "description": "敌人基础属性配置"},
|
||||
{"name": "enemylevel", "displayName": "敌人等级", "description": "敌人等级成长属性配置"},
|
||||
{"name": "equip", "displayName": "装备", "description": "装备基础属性配置"},
|
||||
{"name": "equipAttrRandom", "displayName": "装备随机属性", "description": "装备随机属性词条配置"},
|
||||
{"name": "equiplevel", "displayName": "装备等级", "description": "装备等级成长属性配置"},
|
||||
{"name": "equipstage", "displayName": "装备阶段", "description": "装备进阶配置"},
|
||||
{"name": "fight_arena", "displayName": "竞技场战斗", "description": "竞技场战斗关卡配置"},
|
||||
{"name": "fight_fb1", "displayName": "副本1-限时救援", "description": "限时救援副本战斗配置"},
|
||||
{"name": "fight_fb2", "displayName": "副本2-首领挑战", "description": "首领挑战副本战斗配置"},
|
||||
{"name": "fight_fb3", "displayName": "副本3-波次挑战", "description": "波次挑战副本战斗配置"},
|
||||
{"name": "fight_fb4", "displayName": "副本4-英灵神殿", "description": "英灵神殿副本战斗配置"},
|
||||
{"name": "fight_sample", "displayName": "主线关卡", "description": "主线关卡战斗配置"},
|
||||
{"name": "fishingreward", "displayName": "钓鱼奖励", "description": "钓鱼玩法奖励配置"},
|
||||
{"name": "funopencondition", "displayName": "功能开放条件", "description": "系统功能开放等级条件配置"},
|
||||
{"name": "gacha", "displayName": "召唤", "description": "英雄召唤卡池配置"},
|
||||
{"name": "gachareward", "displayName": "召唤累计奖励", "description": "召唤次数累计奖励配置"},
|
||||
{"name": "game2048reward", "displayName": "2048游戏奖励", "description": "2048小游戏积分奖励配置"},
|
||||
{"name": "guide", "displayName": "引导", "description": "新手引导对话配置"},
|
||||
{"name": "guildbox", "displayName": "公会宝箱", "description": "公会宝箱奖励配置"},
|
||||
{"name": "guilddonate", "displayName": "公会捐赠", "description": "公会捐赠配置"},
|
||||
{"name": "guildlevel", "displayName": "公会等级", "description": "公会等级升级配置"},
|
||||
{"name": "guildname", "displayName": "公会名称", "description": "随机公会名称库"},
|
||||
{"name": "hero", "displayName": "英雄", "description": "英雄基础属性配置"},
|
||||
{"name": "herolevel", "displayName": "英雄等级", "description": "英雄等级成长属性配置"},
|
||||
{"name": "herolevelinternal", "displayName": "英雄内部等级", "description": "英雄内部等级属性配置"},
|
||||
{"name": "herostage", "displayName": "英雄阶段", "description": "英雄进阶配置"},
|
||||
{"name": "idlereward", "displayName": "挂机奖励", "description": "挂机收益奖励配置"},
|
||||
{"name": "language", "displayName": "多语言", "description": "多语言文本配置"},
|
||||
{"name": "link", "displayName": "羁绊", "description": "英雄羁绊组合配置"},
|
||||
{"name": "mail", "displayName": "邮件", "description": "邮件模板配置"},
|
||||
{"name": "mall", "displayName": "商店", "description": "商店商品配置"},
|
||||
{"name": "map", "displayName": "地图", "description": "地图格子配置"},
|
||||
{"name": "map1", "displayName": "地图1", "description": "地图1格子配置"},
|
||||
{"name": "map2", "displayName": "地图2", "description": "地图2格子配置"},
|
||||
{"name": "map3", "displayName": "地图3", "description": "地图3格子配置"},
|
||||
{"name": "map_act", "displayName": "地图波次1", "description": "副本出怪波次配置(主线/副本1/副本4)"},
|
||||
{"name": "map_act2", "displayName": "地图波次2", "description": "首领挑战出怪波次配置"},
|
||||
{"name": "map_act3", "displayName": "地图波次3", "description": "波次挑战出怪波次配置"},
|
||||
{"name": "map_act4", "displayName": "地图波次4", "description": "竞技场出怪波次配置"},
|
||||
{"name": "monthlycard", "displayName": "月卡", "description": "月卡特权配置"},
|
||||
{"name": "name", "displayName": "随机名称", "description": "随机角色名称库"},
|
||||
{"name": "outenemypoint", "displayName": "外部敌人点", "description": "外部敌人刷新点配置"},
|
||||
{"name": "pandora", "displayName": "礼包", "description": "潘多拉礼包配置"},
|
||||
{"name": "pass", "displayName": "通行证", "description": "通行证奖励配置"},
|
||||
{"name": "player", "displayName": "玩家", "description": "玩家(宗门)基础属性配置"},
|
||||
{"name": "playerlevel", "displayName": "玩家等级", "description": "玩家等级成长属性配置"},
|
||||
{"name": "playerskin", "displayName": "玩家皮肤", "description": "玩家(宗门)皮肤配置"},
|
||||
{"name": "playerstage", "displayName": "玩家阶段", "description": "玩家(宗门)进阶配置"},
|
||||
{"name": "playertalent", "displayName": "玩家天赋", "description": "玩家天赋系统配置"},
|
||||
{"name": "prop", "displayName": "道具", "description": "道具基础属性配置"},
|
||||
{"name": "pushboxreward", "displayName": "推箱子奖励", "description": "推箱子关卡奖励配置"},
|
||||
{"name": "qirilibao", "displayName": "七日礼包", "description": "七日礼包奖励配置"},
|
||||
{"name": "quest", "displayName": "任务", "description": "每日/每周任务配置"},
|
||||
{"name": "recharge", "displayName": "充值", "description": "充值档位配置"},
|
||||
{"name": "rechargegift", "displayName": "充值礼包", "description": "累充礼包奖励配置"},
|
||||
{"name": "rune", "displayName": "符文", "description": "符文基础属性配置"},
|
||||
{"name": "runelevel", "displayName": "符文等级", "description": "符文等级成长属性配置"},
|
||||
{"name": "scene", "displayName": "场景", "description": "游戏场景配置"},
|
||||
{"name": "serverError", "displayName": "服务器错误", "description": "服务器错误码多语言配置"},
|
||||
{"name": "signin", "displayName": "签到", "description": "每日签到奖励配置"},
|
||||
{"name": "skill", "displayName": "技能", "description": "技能基础属性配置"},
|
||||
{"name": "skilllevel", "displayName": "技能等级", "description": "技能等级属性配置"},
|
||||
{"name": "systemopen", "displayName": "系统开放", "description": "系统功能开放等级配置"},
|
||||
{"name": "test_map_act", "displayName": "测试波次", "description": "测试用出怪波次配置"},
|
||||
{"name": "title", "displayName": "称号", "description": "玩家称号配置"},
|
||||
{"name": "trainbreak", "displayName": "训练突破", "description": "训练突破阶段配置"},
|
||||
{"name": "trainlevel", "displayName": "训练等级", "description": "训练等级属性配置"},
|
||||
{"name": "vip", "displayName": "VIP", "description": "VIP等级特权配置"},
|
||||
{"name": "dirty_words", "displayName": "敏感词", "description": "敏感词过滤库"}
|
||||
]
|
||||
}
|
||||
@@ -4,75 +4,73 @@
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 配置表列表
|
||||
- 支持搜索过滤配置表
|
||||
- 显示表是否存在于当前项目
|
||||
- 支持 TXT 文件(如敏感词表)
|
||||
- **配置表列表**:搜索过滤、存在性检测、支持 TXT 文件
|
||||
- **联动关系查看**:正向关联和反向引用查询,支持跳转到关联表
|
||||
- **数据格式可视化**:自动解析 item_id_num、id_lv_num 等复合格式
|
||||
- **Excel 操作**:一键打开配置表,批量打开关联表
|
||||
- **导出配置**:批量导出为 JSON 和 C# 代码
|
||||
- **清理空行**:自动检测并删除 Excel 中 id 列为空的垃圾数据行,支持 sharedStrings 解析
|
||||
- **跨项目适配**:基于 `ConfigLinkData.json`,一键生成骨架并由 AI 补全
|
||||
|
||||
### 2. 联动关系查看
|
||||
- 查看当前表关联哪些其他表
|
||||
- 显示字段联动关系详情
|
||||
- 支持跳转到关联表
|
||||
## 安装
|
||||
|
||||
### 3. 反向查询
|
||||
- 查看哪些表引用了当前表
|
||||
- 快速定位依赖关系
|
||||
1. 复制 `ConfigLinkViewer` 文件夹到目标项目的 `Assets/Editor/` 下
|
||||
2. 或使用 Git 子模块:
|
||||
```bash
|
||||
git submodule add <repository-url> Assets/Editor/ConfigLinkViewer
|
||||
```
|
||||
|
||||
### 4. 数据格式可视化
|
||||
- 自动解析数据格式
|
||||
- 显示格式示例和说明
|
||||
- 支持多种格式:item_id_num, id_pos_lv, id_lv_num 等
|
||||
## 快速上手
|
||||
|
||||
### 5. Excel 操作
|
||||
- 一键打开配置表 Excel 文件
|
||||
- 批量打开所有关联表
|
||||
Unity 编辑器菜单:`Tools → 配置表联动查看器`
|
||||
|
||||
### 6. 导出配置
|
||||
- 一键将 Excel 配置表批量导出为 JSON 和 C# 代码
|
||||
- 通过 Tools → ConfigDeal → 导出配置 或窗口右上角按钮触发
|
||||
### 按钮说明
|
||||
|
||||
### 7. 清理 Excel 空行
|
||||
- 自动扫描 Excel 文件夹中所有 .xlsx 文件
|
||||
- 检测并删除数据区域中第一列(id列)为空或非数字的行
|
||||
- 直接修改 Excel 源文件,清理后可重新导出配置
|
||||
- 通过窗口右上角"清理Excel空行"按钮触发
|
||||
**日常数据维护:**
|
||||
|
||||
### 8. 跨项目适配
|
||||
- 自动检测项目中存在的配置表
|
||||
- 支持不同项目配置不同路径
|
||||
|
||||
## 安装指南
|
||||
|
||||
### 方法一:复制文件
|
||||
1. 复制 `ConfigLinkViewer` 文件夹到目标项目
|
||||
2. 路径:`Assets/Editor/ConfigLinkViewer/`
|
||||
|
||||
### 方法二:Git 子模块
|
||||
```bash
|
||||
git submodule add <repository-url> Assets/Editor/ConfigLinkViewer
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 打开工具
|
||||
在 Unity 编辑器菜单中点击:`Tools → 配置表联动查看器`
|
||||
|
||||
### 设置 Excel 路径
|
||||
1. 点击"选择文件夹"按钮
|
||||
2. 选择包含配置表 Excel 文件的文件夹
|
||||
3. 路径会自动保存
|
||||
|
||||
### 基本操作
|
||||
| 操作 | 说明 |
|
||||
| 按钮 | 说明 |
|
||||
|------|------|
|
||||
| 单击列表项 | 选中配置表,查看详情 |
|
||||
| 点击"打开表格" | 打开当前表的 Excel 文件 |
|
||||
| 点击"批量打开关联表" | 打开所有关联的配置表 |
|
||||
| 勾选"显示反向引用" | 查看哪些表引用了当前表 |
|
||||
| 点击"清理Excel空行" | 清除 Excel 中 id 列为空的垃圾数据行 |
|
||||
| 点击"导出配置" | 将 Excel 批量导出为 JSON 和 C# 代码 |
|
||||
| 清理Excel空行 | 扫描 Excel 文件夹,删除 id 列为空或非数字的垃圾数据行 |
|
||||
| 导出配置 | 将 Excel 批量导出为游戏运行时的 JSON 和 C# 代码 |
|
||||
| 打开表格 | 打开当前选中表的 Excel 文件 |
|
||||
| 批量打开关联表 | 打开所有关联的配置表 |
|
||||
|
||||
### 数据格式说明
|
||||
**查看器配置(首次使用或跨项目时):**
|
||||
|
||||
| 按钮 | 说明 |
|
||||
|------|------|
|
||||
| 生成配置 | 从 Excel 文件夹扫描生成 `ConfigLinkData.json` 骨架文件(仅表名) |
|
||||
| 补全配置 | 运行 Python 脚本自动识别字段引用关系,补全 `ConfigLinkData.json` |
|
||||
| 刷新 | 重新加载 `ConfigLinkData.json` |
|
||||
| 显示反向引用 | 查看哪些表引用了当前选中的表 |
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 流程一:首次配置 / 跨项目使用
|
||||
|
||||
将本工具引入新项目时,需要初始化 `ConfigLinkData.json`:
|
||||
|
||||
1. 点击"选择文件夹",设置 Excel 文件夹路径
|
||||
2. 点击 **"生成配置"** — 扫描 Excel 文件夹,生成骨架(只有表名,没有引用关系)
|
||||
3. 点击 **"补全配置"** — 运行 Python 脚本自动分析 config JSON,补全引用关系和中文名
|
||||
4. 在左侧列表点击表名,查看联动关系是否准确
|
||||
5. 如有遗漏或不准确,手动编辑 `ConfigLinkData.json` 后点击"刷新"
|
||||
|
||||
### 流程二:日常数据维护
|
||||
|
||||
策划修改 Excel 后,需要导出给程序使用:
|
||||
|
||||
1. 点击 **"清理Excel空行"** — 清除 Excel 中 id 列为空的垃圾行
|
||||
2. 点击 **"导出配置"** — 将 Excel 批量导出为 JSON 和 C# 代码
|
||||
|
||||
### 流程三:查看联动关系
|
||||
|
||||
在左侧列表选中一张表,右侧面板会显示:
|
||||
- 正向关联:该表引用了哪些其他表
|
||||
- 反向引用(勾选"显示反向引用"):哪些表引用了该表
|
||||
- 数据格式示例和说明
|
||||
|
||||
## 数据格式说明
|
||||
|
||||
| 格式 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
@@ -85,65 +83,69 @@ git submodule add <repository-url> Assets/Editor/ConfigLinkViewer
|
||||
| rune_id_num | 10_2 | 符文ID:10, 数量:2 |
|
||||
| equip_id_num | 20_1 | 装备ID:20, 数量:1 |
|
||||
|
||||
## 跨项目复用
|
||||
## ConfigLinkData.json 格式
|
||||
|
||||
### 自动检测
|
||||
工具会自动检测目标项目中的配置表:
|
||||
- 检查 `Resources/Resources_moved/config/` 目录
|
||||
- 检查 `Resources/config/` 目录
|
||||
- TXT 文件检查 Excel 文件夹
|
||||
|
||||
### 添加新配置表
|
||||
编辑 `ConfigLinkDatabase.cs`,在 `GetAllTableInfo()` 方法中添加新表:
|
||||
|
||||
```csharp
|
||||
new ConfigTableInfo {
|
||||
tableName = "new_table",
|
||||
displayName = "新表",
|
||||
description = "新表描述",
|
||||
relations = new List<FieldRelation> {
|
||||
new FieldRelation {
|
||||
fieldName = "field1",
|
||||
targetTable = "target_table",
|
||||
targetField = "id",
|
||||
description = "关联说明"
|
||||
```json
|
||||
{
|
||||
"tables": [
|
||||
{
|
||||
"name": "item",
|
||||
"displayName": "道具表",
|
||||
"description": "道具基础配置",
|
||||
"fileExtension": ".xlsx",
|
||||
"relations": [
|
||||
{
|
||||
"field": "rewards",
|
||||
"target": "monster",
|
||||
"targetField": "id",
|
||||
"format": "type_id_num",
|
||||
"description": "掉落奖励"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| name | 是 | 表名(对应 Excel 文件名,不含扩展名) |
|
||||
| displayName | 否 | 显示名称,不填则用 name |
|
||||
| description | 否 | 表描述 |
|
||||
| fileExtension | 否 | 文件扩展名,默认 `.xlsx`,可设为 `.txt` |
|
||||
| relations | 否 | 联动关系列表 |
|
||||
|
||||
**relations 字段:**
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| field | 是 | 当前表的字段名 |
|
||||
| target | 是 | 引用的目标表名 |
|
||||
| targetField | 否 | 目标表的字段名,默认 `id` |
|
||||
| format | 否 | 数据格式,如 `type_id_num` |
|
||||
| description | 否 | 关系描述 |
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
Assets/Editor/ConfigLinkViewer/
|
||||
├── ConfigLinkDatabase.cs # 配置表数据和联动关系存储
|
||||
├── ConfigLinkViewerWindow.cs # Unity编辑器窗口界面(含导出配置、清理空行功能)
|
||||
└── README.md # 使用文档
|
||||
├── ConfigLinkViewerWindow.cs # Unity 编辑器窗口界面 + 回调注册容器
|
||||
├── ConfigLinkDatabase.cs # 配置表数据、路径管理、格式模板(数据驱动)
|
||||
├── generate_config_link_data.py # Python 脚本,支持命令行参数,自动识别字段引用关系
|
||||
├── ConfigLinkData.json # 配置文件(生成骨架后由 AI 补全)
|
||||
├── settings.json # 项目路径 + 格式模板配置(零代码扩展)
|
||||
├── table_info.json # 配置表中文名/描述(Python 和 C# 共用)
|
||||
└── README.md # 使用文档
|
||||
```
|
||||
|
||||
## 支持的配置表
|
||||
|
||||
工具包含以下类型的配置表支持:
|
||||
- 活动系统:activity, activityreward
|
||||
- 成就系统:achievement
|
||||
- 战斗系统:fight_fb1~4, fight_arena, fight_sample
|
||||
- 英雄系统:hero, herolevel, herostage
|
||||
- 道具系统:prop, mall
|
||||
- 装备系统:equip, equiplevel, equipstage
|
||||
- 技能系统:skill, skilllevel
|
||||
- 符文系统:rune, runelevel
|
||||
- 任务系统:quest
|
||||
- VIP系统:vip, recharge
|
||||
- 服务端文件:dirty_words.txt, 奖励格式说明.txt
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. Excel 文件命名必须与配置表名一致(如 `enemy.xlsx`)
|
||||
2. 路径设置后会自动保存到 PlayerPrefs
|
||||
3. 建议将 Excel 文件夹设置为版本控制忽略
|
||||
4. TXT 文件需要放置在 Excel 文件夹中
|
||||
5. 推荐工作流:先点击"清理Excel空行"清除垃圾数据,再点击"导出配置"生成 JSON 和 C# 代码
|
||||
6. 清理空行会直接修改 Excel 源文件,建议操作前确认已提交到版本控制
|
||||
2. 路径设置后自动保存到 EditorUserSettings(多项目隔离,互不覆盖)
|
||||
3. 清理空行会直接修改 Excel 源文件,建议操作前确认已提交到版本控制
|
||||
4. 清理空行支持 Excel 共享字符串(sharedStrings)解析,可正确处理 `t="s"` 类型单元格
|
||||
5. 补全配置功能需要 Python 环境,脚本位于 `generate_config_link_data.py`
|
||||
6. 编辑 `ConfigLinkData.json` 后点击"刷新"按钮重新加载
|
||||
|
||||
## 更新日志
|
||||
|
||||
@@ -155,9 +157,57 @@ Assets/Editor/ConfigLinkViewer/
|
||||
- 左右面板自适应布局,窗口默认 900×600
|
||||
- 一键打开 Excel 文件,支持批量打开关联表
|
||||
- Excel 文件夹路径设置,自动保存到 PlayerPrefs
|
||||
- 跨项目适配,自动检测项目中存在的配置表
|
||||
- 跨项目适配,点击[生成配置]扫描 Excel 文件夹生成骨架,AI 补全中文名和关联关系
|
||||
- "导出配置"按钮,一键将 Excel 批量导出为 JSON 和 C# 代码
|
||||
- "清理Excel空行"功能,自动检测并删除 Excel 中 id 列为空的垃圾数据行,直接操作 xlsx 内部 XML,兼容性好
|
||||
- "清理Excel空行"功能,自动检测并删除 Excel 中 id 列为空的垃圾数据行
|
||||
- "AI补全配置"按钮,运行 Python 脚本自动识别字段引用关系
|
||||
|
||||
### v1.0.1
|
||||
- "AI补全配置"按钮更名为"补全配置",按钮宽度和提示文案同步调整
|
||||
- 修复清理 Excel 空行时未正确解析共享字符串(sharedStrings)导致部分空行清理不干净的问题
|
||||
- 新增 `t="s"`(共享字符串引用)和 `t="inlineStr"`(内联字符串)两种单元格类型解析
|
||||
- 提取 `GetCellActualValue()` 方法,统一单元格值解析逻辑
|
||||
- README 全面优化:精简功能特性描述、重组文档结构、新增查漏补缺指南和二次校验 Prompt
|
||||
|
||||
### v2.0.1
|
||||
|
||||
**UI 优化:**
|
||||
- 调整 ASCII 艺术画样式,支持自定义图案替换
|
||||
- 优化艺术画显示尺寸,支持更宽更矮的布局
|
||||
- 修复文字颜色显示问题,确保 ASCII 艺术画正确渲染
|
||||
|
||||
### v2.0.0 — 零依赖重构(大版本)
|
||||
|
||||
**核心目标:** 拖入任意 Unity 项目即用,零外部依赖,配置驱动。
|
||||
|
||||
**破坏性变更:**
|
||||
- 移除所有 `MFrame` 直接引用,编译不再依赖任何外部框架
|
||||
- Excel 路径存储从 `PlayerPrefs` 迁移至 `EditorUserSettings`(自动多项目隔离,旧路径需重新设置)
|
||||
- Python 脚本入口改为 `argparse` 参数化调用(`--config-dir` / `--output` / `--table-info`)
|
||||
|
||||
**新增文件:**
|
||||
- `settings.json` — 项目路径 + 格式模板配置,新增格式类型只需改 JSON,重启即生效
|
||||
- `table_info.json` — 78 张配置表的中文名/描述,Python 和 C# 共用同一份数据源
|
||||
|
||||
**UI 重构:**
|
||||
- 窗口拆分为「查看」和「配置」两个标签页,日常操作和初始化设置分离
|
||||
- 「查看」页顶部精简为 3 个按钮(清理Excel空行、导出配置、刷新),按钮大小和垂直对齐统一
|
||||
- 「配置」页集中展示路径设置、Excel 文件夹、配置数据生成(生成/补全)
|
||||
- 配置标签页底部展示洛天依 ASCII 彩蛋
|
||||
|
||||
**框架自动适配:**
|
||||
- 新增 `ConfigLinkViewerAutoSetup`(`[InitializeOnLoad]`),编辑器启动时通过**反射**自动检测项目中的 MFrame 框架
|
||||
- 检测到 `MFrame.ConfigDeal.ExportConfig` → 自动绑定「导出配置」回调
|
||||
- 检测到 `MFrame.PathConf` + `MFrame.ConfigEditorCf` → 自动绑定 Excel 路径回退
|
||||
- 未检测到框架 → 静默跳过,用户无需任何配置,路径通过 UI 手动设置即可
|
||||
|
||||
**改动详情:**
|
||||
- `ConfigLinkViewerCallbacks` 静态回调类保留,支持外部手动注册覆盖自动检测结果
|
||||
- `ConfigLinkDatabase.cs` — 格式解析从 switch-case 改为反射加载 `settings.json` 中的 `FormatTemplatesWrapper`,零代码扩展
|
||||
- `ConfigLinkDatabase.cs` — `MarkExistingTables` / `IsTableExistInConfigFolder` 的配置路径改为从 `settings.json` 的 `configFolderPath` 读取
|
||||
- `ConfigLinkViewerWindow.cs` — Python 调用侧同步传入 `--config-dir` 和 `--table-info` 参数
|
||||
- `generate_config_link_data.py` — 移除硬编码 `TABLE_INFO` 字典,改为从 `table_info.json` 加载
|
||||
- 清理 `__pycache__/` 缓存目录和已完成的优化方案文档
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
Reference in New Issue
Block a user