using UnityEngine; using UnityEditor; using System.IO; using System.Text.RegularExpressions; using System.Collections.Generic; public class ChineseCodeCollectorUltimate : EditorWindow { private string searchPath = "Assets/Scripts"; private int maxLinesPerFile = 20; private Vector2 scrollPos; private List results = new List(); [MenuItem("Tools/收集中文代码")] static void Open() => GetWindow("中文代码精准收集器"); void OnGUI() { GUILayout.Label("中文硬编码精准收集器", EditorStyles.boldLabel); GUILayout.Label("只排除含有 LogTool./Debug. 的日志行,其他中文照收", EditorStyles.miniLabel); GUILayout.Space(10); GUILayout.BeginHorizontal(); GUILayout.Label("搜索目录:", GUILayout.Width(90)); searchPath = EditorGUILayout.TextField(searchPath); if (GUILayout.Button("浏览", GUILayout.Width(60))) { string p = EditorUtility.OpenFolderPanel("选择目录", searchPath, ""); if (!string.IsNullOrEmpty(p) && p.StartsWith(Application.dataPath)) searchPath = "Assets" + p.Substring(Application.dataPath.Length); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("每文件最多收集:", GUILayout.Width(110)); maxLinesPerFile = EditorGUILayout.IntField(maxLinesPerFile, GUILayout.Width(60)); maxLinesPerFile = Mathf.Max(1, maxLinesPerFile); GUILayout.Label("行"); GUILayout.EndHorizontal(); GUILayout.Space(10); if (GUILayout.Button("开始精准扫描", GUILayout.Height(40))) ScanWithPrecision(); if (results.Count > 0) { GUILayout.Label($"发现 {results.Count} 个文件含有非日志中文", EditorStyles.boldLabel); scrollPos = EditorGUILayout.BeginScrollView(scrollPos); foreach (var info in results) DrawResult(info); EditorGUILayout.EndScrollView(); if (GUILayout.Button("导出报告(TXT)", GUILayout.Height(35))) ExportReport(); } } void ScanWithPrecision() { results.Clear(); if (!Directory.Exists(searchPath)) { EditorUtility.DisplayDialog("错误", "目录不存在:" + searchPath, "OK"); return; } var files = Directory.GetFiles(searchPath, "*.cs", SearchOption.AllDirectories); foreach (var file in files) { // 只排除 Editor 文件夹下的脚本 if (file.Contains("/Editor/") || file.Contains("\\Editor\\")) continue; var info = AnalyzeFilePrecisely(file); if (info != null && info.lines.Count > 0) results.Add(info); } Debug.Log($"精准扫描完成!共 {results.Count} 个文件含有非日志中文"); } ChineseCodeInfo AnalyzeFilePrecisely(string path) { string[] lines = File.ReadAllLines(path); string className = ExtractClassName(lines); var chineseLines = new List(); bool inRegion = false; bool inMultiLineComment = false; for (int i = 0; i < lines.Length; i++) { string raw = lines[i]; string trimmed = raw.Trim(); // 空行跳过 if (string.IsNullOrWhiteSpace(trimmed)) continue; // #region 区域完全跳过 if (trimmed.StartsWith("#region") || trimmed.Contains(" #region ")) inRegion = true; if (trimmed.StartsWith("#endregion") || trimmed.Contains(" #endregion")) { inRegion = false; continue; } if (inRegion) continue; // 多行注释处理 if (inMultiLineComment) { if (trimmed.Contains("*/")) inMultiLineComment = false; continue; } if (trimmed.Contains("/*") && !trimmed.Contains("*/")) { inMultiLineComment = true; continue; } // 单行注释跳过 if (trimmed.StartsWith("//")) continue; // 这行如果包含日志调用,直接跳过这行 if (IsLogLine(trimmed)) continue; // 是否包含中文 if (!ContainsChinese(raw)) continue; chineseLines.Add(new ChineseLine { lineNumber = i + 1, code = raw.TrimEnd() }); if (chineseLines.Count >= maxLinesPerFile) break; } if (chineseLines.Count > 0) { return new ChineseCodeInfo { filePath = path.Replace(Application.dataPath, "Assets"), className = className ?? Path.GetFileNameWithoutExtension(path), lines = chineseLines }; } return null; } // 判断是否为日志行 bool IsLogLine(string line) { // 常见日志调用模式(不区分大小写) string lower = line.ToLower(); return lower.Contains("logtool.") || lower.Contains("debug.log") || lower.Contains("debug.warn") || lower.Contains("debug.error") || lower.Contains("debug.assert") || lower.Contains("debug.draw") || lower.Contains("debug.break") || lower.Contains("logger.") || lower.Contains("log.") && lower.Contains("info") || lower.Contains("log.") && lower.Contains("warning") || lower.Contains("log.") && lower.Contains("error"); } bool ContainsChinese(string text) { return Regex.IsMatch(text, @"[\u4e00-\u9fff]"); } string ExtractClassName(string[] lines) { foreach (string line in lines) { var m = Regex.Match(line, @"class\s+(\w+)"); if (m.Success) return m.Groups[1].Value; } return null; } void DrawResult(ChineseCodeInfo info) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField($"{Path.GetFileName(info.filePath)}", EditorStyles.boldLabel); EditorGUILayout.LabelField($"类名:{info.className}", EditorStyles.miniLabel); EditorGUILayout.SelectableLabel(info.filePath, EditorStyles.miniLabel, GUILayout.Height(16)); foreach (var cl in info.lines) { EditorGUILayout.BeginHorizontal(); GUILayout.Label(cl.lineNumber.ToString("D4"), GUILayout.Width(50)); GUILayout.Label(">", GUILayout.Width(15)); EditorGUILayout.SelectableLabel(cl.code, GUILayout.Height(18)); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); GUILayout.Space(6); } void ExportReport() { string path = EditorUtility.SaveFilePanel("导出中文报告", "", "非日志中文硬编码报告.txt", "txt"); if (string.IsNullOrEmpty(path)) return; using (var sw = new StreamWriter(path, false, System.Text.Encoding.UTF8)) { sw.WriteLine("============================================"); sw.WriteLine(" Unity 非日志中文硬编码精准检测报告"); sw.WriteLine("============================================"); sw.WriteLine($"生成时间:{System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"); sw.WriteLine($"搜索目录:{searchPath}"); sw.WriteLine($"已排除:#region 区域、Editor 文件夹、所有日志行(LogTool./Debug./Log.等)"); sw.WriteLine($"共发现 {results.Count} 个文件含有非日志中文"); sw.WriteLine(); foreach (var r in results) { sw.WriteLine($"文件:{r.filePath}"); sw.WriteLine($"类名:{r.className}"); sw.WriteLine("非日志中文行:"); foreach (var l in r.lines) sw.WriteLine($" 第 {l.lineNumber,4} 行:{l.code}"); sw.WriteLine(new string('-', 70)); } } Debug.Log("精准中文报告已导出:" + path); EditorUtility.RevealInFinder(path); } } // 数据结构 [System.Serializable] public class ChineseCodeInfo { public string filePath; public string className; public List lines; } [System.Serializable] public class ChineseLine { public int lineNumber; public string code; }