using System; using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace TyphoonUI { [InitializeOnLoad] public static class UINodeCopyProcessor { [Serializable] public class LinkNode { [JsonIgnore] public GameObject Obj; [JsonIgnore] public UIObject Node; [JsonIgnore] public List ComponentOrObjectBeExport; [JsonProperty("obj")] public string ObjName => Obj.name; [JsonProperty("node")] public string NodeName => Node.gameObject.name; [JsonProperty("export")] public List ComponentOrObjectBeExportNames { get { var result = new List(); foreach (var o in ComponentOrObjectBeExport) { result.Add($"{o.name}[{o.GetType().Name}]-->{Node.gameObject.name}"); } return result; } } public LinkNode(GameObject obj, UIObject node, List componentOrObjectBeExport) { Obj = obj; Node = node; ComponentOrObjectBeExport = componentOrObjectBeExport; } //创建导出详情 public List CalculateExportDetail() { var export = ComponentOrObjectBeExport; var types = new HashSet(); for (int i = 0; i < export.Count; i++) { var element = export[i]; types.Add(element.GetType()); } var map = new Dictionary>(); var goType = typeof(GameObject); foreach (var type in types) { map.Add(type, new List()); if (type == goType) { map[type].Add(Obj.gameObject); } else { map[type].AddRange(Obj.GetComponents(type)); } } var result = new List(); foreach (var element in export) { var type = element.GetType(); if (type == goType) { result.Add(new ElementExportDetail(Node, Obj, type, -1)); } else { var list = map[type]; var index = list.IndexOf(element); result.Add(new ElementExportDetail(Node, Obj, type, index)); } } return result; } } //元素导出详情 public class ElementExportDetail { public UIObject Node; public GameObject Obj; public Type Type; public int ComponentIndex; public ElementExportDetail(UIObject node, GameObject obj, Type type, int componentIndex) { Node = node; Obj = obj; Type = type; ComponentIndex = componentIndex; } public ExportRemapResult Remap(GameObject target) { if (Type == typeof(GameObject)) { return new ExportRemapResult(Node, target); } else { var com = target.GetComponents(Type); var export = com[ComponentIndex]; return new ExportRemapResult(Node, export); } } } //映射信息 [Serializable] public class ExportRemapResult { [JsonIgnore] public UIObject Node; [JsonIgnore] public Object ExportObject; [JsonProperty("node")] public string PreviewNode => Node.name; [JsonProperty("exportObj")] public string PreviewExportObject => $"{ExportObject.name}[{ExportObject.GetType().Name}]"; public ExportRemapResult(UIObject node, Object exportObject) { Node = node; ExportObject = exportObject; } } static List CalculateLinkNodes(GameObject obj) { var result = new List(); var nodes = obj.GetComponentsInParent(true).ToList(); var own = new HashSet(); own.AddRange(obj.GetComponents()); own.Add(obj); foreach (var node in nodes) { var nodeExport = new List(); nodeExport.AddRange(node.Nodes); //取导出的节点 var matches = nodeExport.Intersect(own).ToList(); if (matches.Count > 0) { //创建关联关系 result.Add(new LinkNode(obj, node, matches)); } } return result; } //触发复制 private static bool _TriggerDuplicate = false; private static List _DuplicateBuffer; static UINodeCopyProcessor() { EditorApplication.hierarchyWindowItemOnGUI += HandleHierarchyWindowItemOnGUI; EditorApplication.hierarchyChanged += OnHierarchyChanged; } static List CalculateLinkNodes(List objs) { var hash = new List(); var insideNodes = new HashSet(); foreach (var obj in objs) { var children = obj.transform.GetComponentsInChildren(); insideNodes.AddRange(obj.GetComponentsInChildren(true)); foreach (var child in children) { hash.Add(child.gameObject); } } //创建关联节点 var links = new List(); foreach (var element in hash) { links.AddRange(CalculateLinkNodes(element)); } // var sb = new StringBuilder(); // sb.AppendLine("原始:"); // sb.AppendLine(JsonConvert.SerializeObject(links)); //过滤掉可以忽略的uinode for (int i = links.Count - 1; i >= 0; i--) { var link = links[i]; if (insideNodes.Contains(link.Node)) { links.RemoveAt(i); } } // sb.AppendLine("过滤后"); // sb.AppendLine(JsonConvert.SerializeObject(links)); // Debug.Log(sb.ToString()); return links; } static void OnHierarchyChanged() { if (_TriggerDuplicate && _DuplicateBuffer != null && Selection.gameObjects != null) { // Debug.Log("变动"); var from = _DuplicateBuffer.ToList(); from.Sort((a, b) => a.GetHierarchySiblingIndices().CompareTo(b.GetHierarchySiblingIndices())); var to = Selection.gameObjects.ToList(); to.Sort((a, b) => a.GetHierarchySiblingIndices().CompareTo(b.GetHierarchySiblingIndices())); var links = CalculateLinkNodes(from); //TODO//比对映射关系 if (from.Count == to.Count) { //创建映射关系表 var childrenCopy = GetAllChildrenWithSortHierarchyIndex(_DuplicateBuffer); var childrenPaste = GetAllChildrenWithSortHierarchyIndex(to); var exportDetail = new List(); foreach (var link in links) { exportDetail.AddRange(link.CalculateExportDetail()); } var remapResults = new List(); //映射到新元素 foreach (var detail in exportDetail) { var index = childrenCopy.IndexOf(detail.Obj); var remapTarget = childrenPaste[index]; remapResults.Add(detail.Remap(remapTarget)); } var node = new HashSet(); //重新映射 foreach (var element in remapResults) { element.Node.Add(element.ExportObject, false); node.Add(element.Node); } foreach (var element in node) { element.Apply(); } // var json = JsonConvert.SerializeObject(remapResults); // Debug.Log($"最终映射结果:\n{json}"); } _TriggerDuplicate = false; _DuplicateBuffer = null; } } static List GetAllChildrenWithSortHierarchyIndex(List go) { var hash = new HashSet(); foreach (var element in go) { hash.AddRange(GetAllChildren(element)); } var result = hash.ToList(); result.Sort((a, b) => a.GetHierarchySiblingIndices().CompareTo(b.GetHierarchySiblingIndices())); return result; } static List GetAllChildren(GameObject go) { var result = new List(); var children = go.GetComponentsInChildren(true); foreach (var child in children) { result.Add(child.gameObject); } return result; } static void HandleHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect) { if (EditorApplication.isPlaying) { return; } if (Event.current.type == EventType.ExecuteCommand && Event.current.commandName == "Duplicate") { var selects2 = Selection.gameObjects; _DuplicateBuffer = CalculateValidCopySelect(selects2); _TriggerDuplicate = true; return; } } //筛选深拷贝的UINode static List FliterDeepCopyUINode(List input) { var result = new HashSet(); foreach (var node in input) { if (!IsInsideOtherPrefab(node)) { result.Add(node); } } return result.ToList(); } private static void AddRange(this HashSet hashSet, IList list) { foreach (var element in list) { hashSet.Add(element); } } //是其它预制物的内嵌节点 static bool IsInsideOtherPrefab(GameObject go) { var corresponding = PrefabUtility.GetCorrespondingObjectFromOriginalSource(go); if (corresponding == null) { return false; } var isMain = AssetDatabase.IsMainAsset(corresponding); if (isMain) { return true; } return false; } //计算最终复制的对象 static List CalculateValidCopySelect(GameObject[] selects) { var hasSet = ArrToHashSet(selects); var ignore = new List(); foreach (var o in hasSet) { var parents = GetHierarchyParents(o); var same = CalculateSameCount(hasSet, parents); if (same > 1) { //如果父节点有两项,剔除,该选择无效,不用复制 ignore.Add(o); } } foreach (var element in ignore) { hasSet.Remove(element); } return hasSet.ToList(); } static HashSet GetHierarchyParents(GameObject target) { var result = new HashSet(); var parents = target.GetComponentsInParent(true); foreach (var element in parents) { result.Add(element.gameObject); } return result; } //计算相同的数量 static int CalculateSameCount(HashSet a, HashSet b) { var count = 0; foreach (var element in a) { if (b.Contains(element)) { count += 1; } } return count; } static HashSet ArrToHashSet(T[] objects) { var result = new HashSet(); foreach (var o in objects) { if (o != null) { result.Add(o); } } return result; } static bool CheckIfNestedPrefab(GameObject rootPrefab, GameObject checkObject) { PrefabAssetType assetType = PrefabUtility.GetPrefabAssetType(checkObject); if (assetType == PrefabAssetType.Regular || assetType == PrefabAssetType.Variant) { return checkObject == rootPrefab; // 如果检查的对象是根预制物,则返回false } Transform parent = checkObject.transform.parent; if (parent == null) { return false; // 检查到达顶层,未找到根预制物 } return CheckIfNestedPrefab(rootPrefab, parent.gameObject); } } }