#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEngine; public class AxiProjectToolsStatistics : EditorWindow { static string cachecfgPath = "Assets/AxiStatisticsDatas.asset"; static Dictionary dictTempData = new Dictionary(); static void ClearTempData() { dictTempData.Clear(); } static string GetRootTempKey(int type, string rootName) { return type + "_" + rootName; } // 添加Hierarchy右键菜单项 [MenuItem("GameObject/AxiStatistics/GetAxiNodeHash", false, 10)] public static void GetAxiNodeHash() { // 获取当前右键选中的Transform Transform selectedTransform = Selection.activeTransform; if (selectedTransform != null) { Debug.Log("选中的对象"+selectedTransform.name+",Hash=>"+ GetNodeDataHash(selectedTransform,true)); } } static int GetNodeDataHash(Transform trans,bool bLog = false) { long hashplus = 0; hashplus += trans.position.GetHashCode(); hashplus += trans.localPosition.GetHashCode(); #if UNITY_2017_1_OR_NEWER int count = trans.childCount; #else int count = trans.GetChildCount(); #endif hashplus += count; for (int i = 0; i < count; i++) { hashplus += trans.GetChild(i).name.GetHashCode(); } if (bLog) { //Debug.Log("trans.position.GetHashCode()=>" + trans.position.GetHashCode()); //Debug.Log("trans.localPosition.GetHashCode()=>" + trans.localPosition.GetHashCode()); //Debug.Log("childCount =>" + count); //Debug.Log("hashplus =>" + hashplus); //Debug.Log("hashplus.GetHashCode() =>" + hashplus.GetHashCode()); } return hashplus.GetHashCode(); } static int GetNodeLinkListHash(List nodes) { string hashplus = string.Empty; foreach (var node in nodes) { hashplus += node.Name; hashplus += node.Idx.ToString(); } return hashplus.GetHashCode(); } static string GetNodeLinkListStr(List nodes) { string linkstr = string.Empty; foreach (var node in nodes) { linkstr += "/"; linkstr += node.Name; linkstr += "[" + node.Idx + "]"; } return linkstr; } static void AddComponentData(int _type, string _rootPath, AxiStatistics_Node_Component _comdata, string _nodepath, Component lastcom) { string rootKey = GetRootTempKey(_type, _rootPath); if (!dictTempData.ContainsKey(rootKey)) { dictTempData[rootKey] = new AxiStatisticsDatas() { type = _type, FullPath = _rootPath, nodes = new List() }; } AxiStatisticsDatas rootData = dictTempData[rootKey]; List link = new List(); if (lastcom.transform.parent != null) { Transform currNode = lastcom.transform; while (currNode != null) { //最顶层了 if (currNode.parent == null) { link.Insert(0, new AxiStatistics_Node_Link() { NodeHash = GetNodeDataHash(currNode), Idx = 0, OnlyOne = true, Name = currNode.gameObject.name }); break; } int thisNameAllCount = 0; int thisNodeIdx = -1; #if UNITY_2017_1_OR_NEWER int count = currNode.parent.childCount; #else int count = currNode.parent.GetChildCount(); #endif bool bFind = false; for (int i = 0; i < count; i++) { GameObject checkGobj = currNode.parent.GetChild(i).gameObject; if (checkGobj.name == currNode.name) { thisNameAllCount++; if (checkGobj == currNode.gameObject) { thisNodeIdx = thisNameAllCount - 1; bFind = true; } } } if (bFind) { link.Insert(0, new AxiStatistics_Node_Link() { NodeHash = GetNodeDataHash(currNode), Idx = thisNodeIdx, OnlyOne = thisNameAllCount == 1, Name = currNode.gameObject.name }); currNode = currNode.parent; } else break; } } else { link.Insert(0, new AxiStatistics_Node_Link() { NodeHash = GetNodeDataHash(lastcom.transform), Idx = 0, OnlyOne = true, Name = lastcom.gameObject.name }); } int linkhash = GetNodeLinkListHash(link); AxiStatistics_Node nodeData = rootData.nodes.Where(w => w.LinkHash == linkhash).FirstOrDefault(); if (nodeData == null) { nodeData = new AxiStatistics_Node(); nodeData.Name = Path.GetFileName(_nodepath); //nodeData.NodeFullPath = _nodepath; nodeData.components = new List(); //nodeData.NodeIdx = thisNodeIdx; //nodeData.NodeIdxOnlyOne = bNodeIdxOnlyOne; nodeData.link = link; nodeData.LinkHash = linkhash; nodeData.LinkFullStr = GetNodeLinkListStr(link); rootData.nodes.Add(nodeData); } nodeData.components.Add(_comdata); } static bool CheckCom(Component[] allcoms, int comRealIdx, int _type, string _rootPath, Component com, string nodepath) { if (com is BoxCollider2D) { BoxCollider2D bc = com as BoxCollider2D; #if UNITY_2017_1_OR_NEWER Debug.Log(nodepath + "BoxCollider2D->center=>(" + bc.offset.x + "," + bc.offset.y + ") size=>(" + bc.size.x + "," + bc.size.y + ""); #else Debug.Log(nodepath +"BoxCollider2D->center=>("+ bc.center.x+","+bc.center.y+") size=>("+ bc.size.x+","+bc.size.y+""); #endif AxiStatistics_Node_Component _com = new AxiStatistics_Node_Component(); _com.type = typeof(BoxCollider2D).ToString(); #if UNITY_2017_1_OR_NEWER _com.center = bc.offset; #else _com.center = bc.center; #endif _com.size = bc.size; SetCompnentIdxNum(_com, allcoms, comRealIdx, bc); AddComponentData(_type, _rootPath, _com, nodepath, com); } if (com is Rigidbody2D) { Rigidbody2D rig2d = com as Rigidbody2D; Debug.Log(_rootPath + "Rigidbody2D->simulated=>(" + rig2d.simulated + ")"); Debug.Log(_rootPath + "Rigidbody2D->IsSleeping=>(" + rig2d.isKinematic.ToString() + ")"); AxiStatistics_Node_Component _com = new AxiStatistics_Node_Component(); _com.type = typeof(Rigidbody2D).ToString(); _com.isKinematic = rig2d.isKinematic; _com.simulated = rig2d.simulated; _com.gravityScale = rig2d.gravityScale; SetCompnentIdxNum(_com, allcoms, comRealIdx, rig2d); AddComponentData(_type, _rootPath, _com, nodepath, com); } return true; } /// /// 找出同类Idx /// /// /// /// /// /// static void SetCompnentIdxNum(AxiStatistics_Node_Component _comData, Component[] allcoms, int comRealIdx, T com) where T : Component { int ComIdxNum; bool ComTypeIsOnlyOne = false; int TCount = com.transform.GetComponents().Length; if (TCount == 1) { ComIdxNum = 0; ComTypeIsOnlyOne = true; } else if (TCount < 1) { Debug.LogError("找不到,不应该"); ComIdxNum = -1; } ComIdxNum = -1; for (int i = 0; i < allcoms.Length; i++) { //他自己自然是了 if (i == comRealIdx) { ComIdxNum++; break; } if (allcoms[i] is T) ComIdxNum++; } _comData.ComIdxNum = ComIdxNum; _comData.ComTypeOnlyOne = ComTypeIsOnlyOne; } [MenuItem("Axibug移植工具/Statistics/[1]统计所有预制体和场景下的Collider和RigBody")] public static void StatisticsCollider() { ClearTempData(); StatisticsCollider(); StatisticsCollider(); AxiStatisticsCache cache = ScriptableObject.CreateInstance(); foreach (var data in dictTempData) cache.caches.Add(data.Value); AssetDatabase.CreateAsset(cache, cachecfgPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } public static void StatisticsCollider() where T : Component { AxiProjectTools.GoTAxiProjectToolsSence(); string[] sceneGuids = AssetDatabase.FindAssets("t:scene"); foreach (string guid in sceneGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); if (path.Contains(AxiProjectTools.toolSenceName)) continue; #if UNITY_4_6 EditorApplication.OpenScene(path); #else UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path); #endif // 创建一个列表来存储根节点 List rootNodes = new List(); // 遍历场景中的所有对象 GameObject[] allObjects = FindObjectsOfType(); foreach (GameObject obj in allObjects) { // 检查对象是否有父对象 if (obj.transform.parent == null) { // 如果没有父对象,则它是一个根节点 rootNodes.Add(obj); } } foreach (var node in rootNodes) LoopPrefabNode(0, path, path, node, 0); } string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); foreach (string guid in prefabGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); GetPrefab(path); } AxiProjectTools.GoTAxiProjectToolsSence(); Debug.Log("处理完毕 统计所有预制体和场景下的" + typeof(T).FullName + ""); } static void GetPrefab(string path) where T : Component { #if UNITY_4_6 GameObject prefab = AssetDatabase.LoadAssetAtPath(path,typeof(GameObject)) as GameObject; #else GameObject prefab = AssetDatabase.LoadAssetAtPath(path); #endif LoopPrefabNode(1, path, path, prefab.gameObject, 0); } static void LoopPrefabNode(int _type, string _rootPath, string noderootPath, GameObject trans, int depth) where T : Component { // #if UNITY_2018_4_OR_NEWER string nodename = noderootPath + "/" + trans.name; GameObject prefabRoot = trans.gameObject; Component[] components = prefabRoot.GetComponents(); for (int i = 0; i < components.Length; i++) { var com = components[i]; if (com == null) continue; T comobj = com as T; if (comobj == null) continue; if (CheckCom(components, i, _type, _rootPath, comobj, nodename)) continue; } //遍历 foreach (Transform child in trans.transform) LoopPrefabNode(_type, _rootPath, nodename, child.gameObject, depth + 1); //#else // Debug.Log("低版本不要执行本函数"); //#endif } #if UNITY_2017_1_OR_NEWER [MenuItem("Axibug移植工具/Statistics/[2]通过记录,对组件进行修补")] public static void RepairRigBodyByStatistics() { List errLog = new List(); List doneLog = new List(); List> NeedRepair = new List>(); List> FinishRepair = new List>(); string CurrScenePath = string.Empty; AxiProjectTools.GoTAxiProjectToolsSence(); #if UNITY_4_6 AxiStatisticsCache data = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(AxiStatisticsCache)) as AxiStatisticsCache; #else AxiStatisticsCache data = AssetDatabase.LoadAssetAtPath(cachecfgPath); #endif string[] sceneGuids = AssetDatabase.FindAssets("t:scene"); List ScenePath = new List(); List SceneName = new List(); foreach (string guid in sceneGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); if (path.Contains(AxiProjectTools.toolSenceName)) continue; ScenePath.Add(path); SceneName.Add(Path.GetFileName(path)); } string[] prefabGuids = AssetDatabase.FindAssets("t:prefab"); List prefabPath = new List(); List prefabName = new List(); foreach (string guid in prefabGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); prefabPath.Add(path); prefabName.Add(Path.GetFileName(path)); } foreach (var cache in data.caches.OrderBy(w => w.type)) { //场景 if (cache.type == 0) { #region 场景加载 string targetName = Path.GetFileName(cache.FullPath); int Idx = SceneName.IndexOf(targetName); if (Idx < 0) { Debug.LogError(targetName + "[Repair]找不到对应资源"); continue; } string targetpath = ScenePath[Idx]; //保证场景切换 if (!string.Equals(CurrScenePath, targetpath)) { #if UNITY_4_6 EditorApplication.OpenScene(targetpath); #else UnityEditor.SceneManagement.EditorSceneManager.OpenScene(targetpath); #endif } CurrScenePath = targetpath; #endregion int DirtyCount = 0; foreach (var node in cache.nodes) { string errStr; GameObject targetNodePathObj = GetNodeByLink(cache.FullPath, node.link, out errStr); if (targetNodePathObj == null) { errLog.Add(errStr); continue; } /* string targetNodePath = node.NodeFullPath.Substring(cache.FullPath.Length, node.NodeFullPath.Length - cache.FullPath.Length); //GameObject targetNodePathObj = GameObject.Find(targetNodePath); GameObject targetNodePathObj = GetNodeByIdx(node, targetNodePath); if (targetNodePathObj == null) { string err = "[Repair]" + node.NodeFullPath + "找不到对应节点"; errLog.Add(err); Debug.LogError(err); continue; } */ foreach (var com in node.components) { if (RepairComponent(node.LinkFullStr, targetNodePathObj, com, out var errlog)) { NeedRepair.Add(new ValueTuple($"{cache.FullPath}:{node.LinkFullStr}", $"{com.type}[{com.ComIdxNum}]")); DirtyCount++; } errLog.AddRange(errlog); } } if (DirtyCount > 0) { Debug.Log($"[Repair][场景处理]{cache.FullPath}共{DirtyCount}个需要处理"); // 获取当前打开的场景 var activeScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene(); // 保存场景 UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene); Debug.Log("场景已保存: " + activeScene.path); string donestr = $"[Repair][场景处理成功]{targetpath},共{DirtyCount}个"; doneLog.Add(donestr); } } else if (cache.type == 1) { string targetpath = cache.FullPath; //来到空场景 if (!string.IsNullOrEmpty(CurrScenePath)) { AxiProjectTools.GoTAxiProjectToolsSence(); CurrScenePath = string.Empty; } GameObject prefabInstance = AssetDatabase.LoadAssetAtPath(targetpath); if (prefabInstance == null) { Debug.LogError($"[Repair]Failed to load prefab at path: {prefabPath}"); return; } var obj = GameObject.Instantiate(prefabInstance, null); int DirtyCount = 0; foreach (var node in cache.nodes) { GameObject targetNodePathObj = GetNodeByLink(cache.FullPath, node.link, out string errStr, obj); if (targetNodePathObj == null) { errLog.Add(errStr); continue; } //if (node.NodeFullPath == targetpath + "/" + Path.GetFileNameWithoutExtension(targetpath)) //{ // //预制体自己就是目标 // targetNodePathObj = obj; //} //else //{ // string targetNodePath = node.NodeFullPath.Substring(cache.FullPath.Length + prefabInstance.name.Length + 2, node.NodeFullPath.Length - cache.FullPath.Length - prefabInstance.name.Length - 2); // //targetNodePathObj = obj.transform.Find(targetNodePath)?.gameObject; // targetNodePathObj = GetNodeByIdx(node, targetNodePath, obj); // if (targetNodePathObj == null) // { // Debug.LogError("[Repair]" + targetNodePath + "找不到对应节点"); // continue; // } //} foreach (var com in node.components) { if (RepairComponent(node.LinkFullStr, targetNodePathObj, com, out var errlog)) { NeedRepair.Add(new ValueTuple($"{cache.FullPath}:{node.LinkFullStr}", $"{com.type}[{com.ComIdxNum}]")); DirtyCount++; } errLog.AddRange(errlog); } } if (DirtyCount > 0) { Debug.Log($"[Repair][预制体处理]{targetpath}共{DirtyCount}个需要处理"); PrefabUtility.SaveAsPrefabAsset(obj, targetpath); string donestr = $"[Repair][预制体处理成功]{targetpath},共{DirtyCount}个"; doneLog.Add(donestr); } GameObject.DestroyImmediate(obj); } } AxiProjectTools.GoTAxiProjectToolsSence(); StringBuilder sb = new StringBuilder(); sb.AppendLine("[Repair][统计]:"); sb.AppendLine("----处理成功----"); foreach (var val in doneLog.OrderBy(w => w)) { sb.AppendLine(val); } sb.AppendLine("----异常统计----"); foreach (var val in errLog.OrderBy(w => w)) { sb.AppendLine(val); } sb.AppendLine("----需要处理----"); foreach (var val in NeedRepair.OrderBy(w => w.Item1)) { sb.AppendLine($"{val.Item1}=>{val.Item2}"); } Debug.Log($"{sb}"); File.WriteAllText("Assets/AxiNeedRepair.txt", sb.ToString()); } // static GameObject GetNodeByIdx(AxiStatistics_Node nodedata, string targetNodePath, GameObject root = null) // { // GameObject targetNodePathObj; // if (root == null) // targetNodePathObj = GameObject.Find(targetNodePath); // else // targetNodePathObj = root.transform.Find(targetNodePath)?.gameObject; // if (targetNodePathObj == null) // return null; // string targetName = targetNodePathObj.name; // int currIdx = -1; // if (!nodedata.NodeIdxOnlyOne) // { // if (targetNodePathObj.transform.parent != null) // { //#if UNITY_2017_1_OR_NEWER // int count = targetNodePathObj.transform.parent.childCount; //#else // int count = targetNodePathObj.transform.parent.GetChildCount(); //#endif // for (int i = 0; i < count; i++) // { // GameObject checkGobj = targetNodePathObj.transform.parent.GetChild(i).gameObject; // if (checkGobj.name == targetName) // { // currIdx++; // if (nodedata.NodeIdx == currIdx) // { // targetNodePathObj = checkGobj; // break; // } // } // } // } // } // return targetNodePathObj; // } static GameObject GetNodeByLink(string rootPath, List linklist, out string errStr, GameObject PrefabRoot = null) { List temp_useddlink = new List(); if (linklist.Count < 1) { errStr = $"[Repair] Link 为空"; Debug.LogError(errStr); return null; } temp_useddlink.Add(linklist[0]); GameObject currRoot; if (PrefabRoot == null) currRoot = GameObject.Find(linklist[0].Name); else { currRoot = PrefabRoot; //currRoot = PrefabRoot.transform.Find(linklist[0].Name)?.gameObject; } if (currRoot == null) { errStr = $"[Repair] 根节点找不到{rootPath}:{GetNodeLinkListStr(linklist)} => null"; Debug.LogError(errStr); return null; } for (int link_i = 1; link_i < linklist.Count; link_i++) { AxiStatistics_Node_Link targetLink = linklist[link_i]; temp_useddlink.Add(targetLink); GameObject findNode = null; #if UNITY_2017_1_OR_NEWER int count = currRoot.transform.childCount; #else int count = currNode.transform.GetChildCount(); #endif if (targetLink.OnlyOne) { for (int i = 0; i < count; i++) { GameObject checkGobj = currRoot.transform.GetChild(i).gameObject; if (checkGobj.name == targetLink.Name) { findNode = checkGobj; break; } } } else { Dictionary tempHash2Node = new Dictionary(); List tempGobjList = new List(); bool HashDrity = false; for (int i = 0; i < count; i++) { GameObject checkGobj = currRoot.transform.GetChild(i).gameObject; if (checkGobj.name == targetLink.Name) { int temphash = GetNodeDataHash(checkGobj.transform); if (!tempHash2Node.ContainsKey(temphash)) { tempHash2Node.Add(GetNodeDataHash(checkGobj.transform), checkGobj); } else { HashDrity = true; } tempGobjList.Add(checkGobj); } } //Hash严格模式 if (!HashDrity && tempHash2Node.TryGetValue(targetLink.NodeHash, out var val)) { findNode = val; } //下标模式 else { if (targetLink.Idx < 0 || tempGobjList.Count == 0 || (tempGobjList.Count != 0 && targetLink.Idx >= tempGobjList.Count)) { errStr = $"[Repair]link 下标模式 找不到=>{rootPath}:{GetNodeLinkListStr(temp_useddlink)}[{targetLink.Idx}] => 完整链路{rootPath}:{GetNodeLinkListStr(linklist)}"; Debug.LogError(errStr); return null; } else { findNode = tempGobjList[targetLink.Idx]; } } } currRoot = findNode; if (currRoot == null) break; } if (currRoot == null) { errStr = $"[Repair]link 找不到[{rootPath}:{GetNodeLinkListStr(temp_useddlink)}] => 完整链路{rootPath}:{GetNodeLinkListStr(linklist)}"; Debug.LogError(errStr); return null; } else { errStr = string.Empty; return currRoot; } } static bool RepairComponent(string NodePath, GameObject targetNodePathObj, AxiStatistics_Node_Component comdata, out List Errlog) { Errlog = new List(); string err; bool Dirty = false; if (comdata.type == typeof(Rigidbody2D).ToString()) { Rigidbody2D rg2d = GetCompnentById(targetNodePathObj, comdata); if (rg2d == null) { err = $"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] == null"; Debug.LogError(err); Errlog.Add(err); Dirty = false; } /* 与新版Unity的差异​ ​无BodyType选项​:Unity 4.6.7中所有Rigidbody2D默认等效于新版的Dynamic类型(受重力影响),但无法设置为Static或Kinematic。 ​无Simulated选项​:只要物体启用了Rigidbody2D组件且Gravity Scale > 0,就会受重力作用。 所以,一旦老版本gravityScale > 0,就受重力作用,直接设置新版本这边:simulated = true;bodyType = RigidbodyType2D.Dynamic; */ if (rg2d.gravityScale != comdata.gravityScale) { Debug.Log($"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] simulated:{rg2d.gravityScale} != :{comdata.gravityScale} rg2d.bodyType => {rg2d.bodyType} "); rg2d.gravityScale = comdata.gravityScale; Dirty = true; } //if (rg2d.gravityScale > 0 && (!rg2d.simulated || rg2d.bodyType != RigidbodyType2D.Dynamic)) if (!rg2d.simulated || rg2d.bodyType != RigidbodyType2D.Dynamic) { Debug.Log($"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] simulated:{rg2d.simulated} != :{comdata.simulated} rg2d.bodyType => {rg2d.bodyType} "); rg2d.simulated = true; rg2d.bodyType = RigidbodyType2D.Dynamic; Dirty = true; } } else if (comdata.type == typeof(BoxCollider2D).ToString()) { BoxCollider2D bc = GetCompnentById(targetNodePathObj, comdata); if (bc == null) { err = $"[Repair]{NodePath}=> BoxCollider2D[{comdata.ComIdxNum}] == null"; Debug.LogError(err); Errlog.Add(err); Dirty = false; } else { if (bc.size != comdata.size) { Debug.Log($"[Repair]{NodePath} BoxCollider2D[{comdata.ComIdxNum}] => size:{bc.size} != {comdata.size} "); bc.size = comdata.size; Dirty = true; } if (bc.offset != comdata.center) { Debug.Log($"[Repair]{NodePath} BoxCollider2D[{comdata.ComIdxNum}] => offset:{bc.offset} != center{comdata.center} "); bc.offset = comdata.center; Dirty = true; } if (Dirty) { bc.size = comdata.size; bc.offset = comdata.center; } } } return Dirty; } static T GetCompnentById(GameObject gobj, AxiStatistics_Node_Component node) where T : Component { if (node.ComIdxNum == 0) return gobj.GetComponent(); else if (node.ComIdxNum > 0) { T[] coms = gobj.GetComponents(); if (node.ComIdxNum < coms.Length) return coms[node.ComIdxNum]; } return null; } #endif } #endif