• 关于修复预制体上UnityEngine.UI引用丢失的一种思路


    在开发项目过程中,如果出现了Unity版本变化,有可能会导致一些预制体上的UI组件丢失,特别是大量UI脚本,明明一看就知道这个是Text组件,但是一个大大的missing出现在预制体上,让人产生了莫名的恐慌。

    一、根据.prefab文件信息,分析引用的UGUI脚本信息

    我们如果此时打开.prefab文件查看,大概可以看到如下信息(ForceText设置可以使得.prefab的显示内容以文本展示而非二进制格式)。

     很多高手对.prefab文件内容并不陌生,但是为了接下来的展开还是解释一下内容,从每个大节点开始讲解(节选部分重要内容):

    1.GameObject

    1. --- !u!1 &165095463815504087
    2. GameObject:
    3.   m_ObjectHideFlags: 0
    4.   m_CorrespondingSourceObject: {fileID: 0}
    5.   m_PrefabInstance: {fileID: 0}
    6.   m_PrefabAsset: {fileID: 0}
    7.   serializedVersion: 6
    8.   m_Component:
    9.   - component: {fileID: 4433197073301798445}
    10.   - component: {fileID: 5990753843237649034}
    11.   - component: {fileID: 5075137301801018474}
    12.   m_Layer: 5
    13.   m_Name: Streak
    14.   m_TagString: Untagged
    15.   m_Icon: {fileID: 0}
    16.   m_NavMeshLayer: 0
    17.   m_StaticEditorFlags: 0
    18.   m_IsActive: 1

    `--- !u!1 &165095463815504087`: 此行表示该配置块(往后一块内容都简称为配置块)在此文件中的私有fileID;

    `GameObject`: 表示一个预制体中的GameObject节点,如果一个预制体由多个GameObject组成,这块仅表示其中一个;

    `m_Component`: 表示该GameObject所引用的私有fileID

    `m_Name`: 顾名思义,这个GameObject的名称,可以在Hierarchy面板中很轻松找到名字,同时也可以方便在后续其他配置块中找到自身所属的GameObject;

    2.RectTransform、CanvasRenderer

    内容略去,该部分一般不会丢失,因为他不隶属于UnityEngine.UI,同时名字可以直接看到

    3.MonoBehaviour

    1. --- !u!114 &5075137301801018474
    2. MonoBehaviour:
    3. m_ObjectHideFlags: 0
    4. m_CorrespondingSourceObject: {fileID: 0}
    5. m_PrefabInstance: {fileID: 0}
    6. m_PrefabAsset: {fileID: 0}
    7. m_GameObject: {fileID: 165095463815504087}
    8. m_Enabled: 1
    9. m_EditorHideFlags: 0
    10. m_Script: {fileID: 11500000, guid: d6072c12dfea5c74897ce48533ec3f2a, type: 3}
    11. m_Name:
    12. m_EditorClassIdentifier:
    13. m_Material: {fileID: 0}
    14. m_Color: {r: 1, g: 1, b: 1, a: 1}
    15. m_RaycastTarget: 1
    16. m_OnCullStateChanged:
    17. m_PersistentCalls:
    18. m_Calls: []
    19. m_Sprite: {fileID: 0}
    20. m_Type: 0
    21. m_PreserveAspect: 0
    22. m_FillCenter: 1
    23. m_FillMethod: 4
    24. m_FillAmount: 1
    25. m_FillClockwise: 1
    26. m_FillOrigin: 0
    27. m_UseSpriteMesh: 0

    敲黑板,该块表示引用了一个MonoBehavior脚本,有可能是自定义的,也有可能是其他继承自Component的脚本,这是我们解析出一个脚本是自定义脚本还是UI脚本的关键;

    m_GameObject: {fileID: 165095463815504087} ,表示该脚本所属的GameObject,根据fileID向文件内索引,对应上文中的`--- !u!1 &165095463815504087`,所以是Streak上挂载的一个脚本

    m_Script: {fileID: 11500000, guid: d6072c12dfea5c74897ce48533ec3f2a, type: 3},表示该脚本的引用信息,fileID表示所属的文件ID,如果是自定义脚本,通常都是11500000(但有例外,我所使用的Unity2019中,UnityEngine.UI中的也是11500000),如果是dll集,则表示dll中某个具体类的引用,guid则表示他在Unity中所归属的具体文件,type略去,暂时不影响复原操作

     这两条配置项是所有配置块都会存在的内容,而我们需要根据接下来的配置项,来推测一个MonoBehavior大概率可能属于哪一类型的UGUI脚本

    1. m_Material: {fileID: 0}
    2. m_Color: {r: 1, g: 1, b: 1, a: 1}
    3. m_RaycastTarget: 1
    4. m_OnCullStateChanged:
    5. m_PersistentCalls:
    6. m_Calls: []
    7. m_Sprite: {fileID: 0}
    8. m_Type: 0
    9. m_PreserveAspect: 0
    10. m_FillCenter: 1
    11. m_FillMethod: 4
    12. m_FillAmount: 1
    13. m_FillClockwise: 1
    14. m_FillOrigin: 0
    15. m_UseSpriteMesh: 0

    其实很容易就发现了一个关键字`m_Sprite`,这大概率就是Image组件所使用的配置项,同时`m_FillCenter`等选项基本可以认定了这就是Image组件,因此,我们只需要根据本工程的Image的fileID,guid,type3个信息,修改该块中`m_Script`项即可复原丢失的引用。

    二、索引出本项目中所有的UGUI信息

    将所有集成自Component且出自程序集UnityEngine.UI的类型,添加到GameObject上,制成预制体,并根据上述分析获得fileID,guid,type,并记录到文件中。

    1. private static void StatisticOrderedScriptGUIDs(string asmName, string fileName)
    2. {
    3. var g = new GameObject();
    4. var asm = Assembly.Load(asmName);
    5. var types = asm.GetTypes();
    6. var guids = new List();
    7. long localId;
    8. string guid;
    9. foreach (var type in types)
    10. {
    11. // 抽象类或不继承自Component的类过滤
    12. if (type.IsAbstract || !type.IsSubclassOf(typeof(Component)))
    13. {
    14. continue;
    15. }
    16. g.name = type.Name;
    17. var component = g.AddComponent(type);
    18. if (component)
    19. {
    20. // 此处设置一个临时路径
    21. var prefabPath = $"Assets/Editor/GUIDPrefab/{type.Name}.prefab";
    22. // 自定义方法,根据文件名判断文件夹是否存在,不存在则创建
    23. FileHelper.CreateDirectoryIfNotExistByFilename(prefabPath);
    24. var success = false;
    25. PrefabUtility.SaveAsPrefabAsset(g, prefabPath, out success);
    26. AssetDatabase.Refresh();
    27. var prefab = AssetDatabase.LoadAssetAtPath(prefabPath, type);
    28. if (!prefab)
    29. {
    30. Debug.LogError($"type:{type} cannot load, path:{prefabPath}");
    31. continue;
    32. }
    33. if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(prefab, out guid, out localId))
    34. {
    35. var fileId = $"&{localId}";
    36. var metaLines = File.ReadAllLines(Path.GetFullPath(prefabPath));
    37. var foundLine = false;
    38. foreach (var line in metaLines)
    39. {
    40. if (line.IndexOf(fileId) > 0)
    41. {
    42. foundLine = true;
    43. continue;
    44. }
    45. if (!foundLine)
    46. {
    47. continue;
    48. }
    49. if (line.IndexOf("m_Script:") > 0)
    50. {
    51. AnalysisScriptLine(line, out long realFileId, out string realGuid, out int fileType);
    52. guids.Add(new FileTypeGUID(type, realFileId, realGuid, fileType));
    53. }
    54. }
    55. }
    56. GameObject.DestroyImmediate(component);
    57. }
    58. }
    59. GameObject.DestroyImmediate(g);
    60. var json = JsonConvert.Serialize(guids, true);
    61. Debug.Log(json);
    62. // 将收集的信息存储下来,为后续分析具体.prefab时供参考
    63. var path = Application.dataPath.Replace("Assets", $"{fileName}.json");
    64. File.WriteAllText(path, json);
    65. }
    66. private static void AnalysisScriptLine(string line, out long fileId, out string guid, out int type)
    67. {
    68. fileId = 0;
    69. guid = string.Empty;
    70. type = 0;
    71. if (!line.Trim().StartsWith("m_Script:"))
    72. {
    73. return;
    74. }
    75. var index = line.IndexOf("{");
    76. var endIndex = line.LastIndexOf("}");
    77. var inside = line.Substring(index + 1, endIndex - index - 1);
    78. var arr = inside.Split(',');
    79. foreach (var item in arr)
    80. {
    81. var pair = item.Split(':');
    82. switch (pair[0].Trim())
    83. {
    84. case "fileID":
    85. long.TryParse(pair[1].Trim(), out fileId);
    86. break;
    87. case "guid":
    88. guid = pair[1].Trim();
    89. break;
    90. case "type":
    91. int.TryParse(pair[1].Trim(), out type);
    92. break;
    93. }
    94. }
    95. }

    至于为什么参数asmName作为传入参数,当然是因为后续更新到了自定义开发脚本也可修复的原因。

    通过上述步骤,我们调用 `StatisticOrderedScriptGUIDs("UnityEngine.UI", "uguiguids");` 获取了项目内所有UGUI组件的guids

    三、动手尝试修复一个.prefab

    这个先把一个预制体文件打开,随后随手修改其中MonoBehavior块中的fileID,guid后,该预制体在Unity中即呈现丢失引用状态。

    然后我们需要编写一些代码来储存如下信息,CharacteristicInfo(UGUI类型的特征信息),FileTypeGUID(fileID, guid, type的相关信息),PrefabContent(整个.prefab文件读取后的可读内容),PrefabChunk(.prefab文件中的配置块),FileTypeGUIDCollection(FileTypeGUID的集合,不是简单用数组处理),

    以下是代码信息,也可以跳过不看,自己尝试开发,因为思路已经都在上边了。

    1. private struct CharacteristicInfo
    2. {
    3. public string Characteristic { get; set; }
    4. public Type Type { get; set; }
    5. public CharacteristicInfo(string c, Type t)
    6. {
    7. Characteristic = c;
    8. Type = t;
    9. }
    10. }
    1. private class FileTypeGUID
    2. {
    3. // 该信息组对应的实际类型
    4. public Type Type { get; set; }
    5. public long FileId { get; set; }
    6. public string Guid { get; set; }
    7. public int FileType { get; set; }
    8. public FileTypeGUID(Type type, long fileId, string guid, int fileType)
    9. {
    10. Type = type;
    11. FileId = fileId;
    12. Guid = guid;
    13. FileType = fileType;
    14. }
    15. public override string ToString()
    16. {
    17. return $"Type:{Type.Name}, FileId:{FileId}, Guid:{Guid}, FileType:{FileType}";
    18. }
    19. }
    1. private class PrefabContent
    2. {
    3. public Object Object { get; private set; }
    4. private readonly List _chunks = new List();
    5. private readonly List<string> _head = new List<string>();
    6. private readonly Dictionary<long, string> _gameObjects = new Dictionary<long, string>();
    7. private PrefabContent(Object belonging)
    8. {
    9. Object = belonging;
    10. }
    11. public void Analysis(TryGetFileGUITypeDelegate tryGetHandler, IsConfusingTypeDelegate isConfusingTypeHandler)
    12. {
    13. var changeCount = 0;
    14. FileTypeGUID[] fileTypeGUIDs;
    15. foreach (var chunk in _chunks)
    16. {
    17. if (tryGetHandler.Invoke(chunk, out var item))
    18. {
    19. if (isConfusingTypeHandler.Invoke(chunk, out fileTypeGUIDs))
    20. {
    21. Debug.LogError("=========== confusing warning ===========");
    22. Debug.LogError($"chunk.Component is a confusing type, should confirm again, {chunk.PrefabContent.Object}", chunk.PrefabContent.Object);
    23. foreach (var guid in fileTypeGUIDs)
    24. {
    25. Debug.LogError($"Type:{guid.Type}, FileID:{guid.FileId}, guid:{guid.Guid}, type:{guid.FileType}");
    26. }
    27. Debug.LogError("=========== end ===========");
    28. }
    29. if (chunk.ModifyM_Script(item.FileId, item.Guid, item.FileType))
    30. {
    31. changeCount++;
    32. }
    33. }
    34. }
    35. Debug.Log($"修改了 {changeCount} 个组件");
    36. }
    37. public string[] GetLines()
    38. {
    39. var list = new List<string>();
    40. list.AddRange(_head);
    41. foreach (var chunk in _chunks)
    42. {
    43. list.AddRange(chunk.Lines);
    44. }
    45. return list.ToArray();
    46. }
    47. public string GetName(long id)
    48. {
    49. _gameObjects.TryGetValue(id, out var name);
    50. return name;
    51. }
    52. public static PrefabContent Parse(Object selected, string[] lines, CharacteristicInfo[] characteristics)
    53. {
    54. var count = lines.Length;
    55. var listInChunk = new List<string>();
    56. var content = new PrefabContent(selected);
    57. var id = 0L;
    58. var foundGameObjectTag = false;
    59. for (int i = 0; i < count; i++)
    60. {
    61. var line = lines[i];
    62. if (line.StartsWith("%"))
    63. {
    64. content._head.Add(line);
    65. continue;
    66. }
    67. if (line.StartsWith("---"))
    68. {
    69. if (i + 1 < count && lines[i + 1].StartsWith("GameObject:"))
    70. {
    71. var andIndex = line.IndexOf('&');
    72. var idString = line.Substring(andIndex + 1);
    73. long.TryParse(idString, out id);
    74. foundGameObjectTag = true;
    75. }
    76. if (listInChunk.Count != 0)
    77. {
    78. var chunk = new PrefabChunk(content, listInChunk.ToArray(), characteristics);
    79. content._chunks.Add(chunk);
    80. listInChunk.Clear();
    81. }
    82. listInChunk.Add(line);
    83. continue;
    84. }
    85. if (foundGameObjectTag)
    86. {
    87. var nameTag = "m_Name:";
    88. var nameIndex = line.IndexOf(nameTag);
    89. if (!string.IsNullOrEmpty(line) && nameIndex != -1)
    90. {
    91. var name = line.Substring(nameIndex + nameTag.Length).Trim();
    92. content._gameObjects[id] = name;
    93. foundGameObjectTag = false;
    94. }
    95. }
    96. listInChunk.Add(line);
    97. }
    98. // 添加剩余的
    99. if (listInChunk.Count != 0)
    100. {
    101. var chunk = new PrefabChunk(content, listInChunk.ToArray(), characteristics);
    102. content._chunks.Add(chunk);
    103. listInChunk.Clear();
    104. }
    105. return content;
    106. }
    107. }
    1. private class PrefabChunk
    2. {
    3. private const string ScriptFormat = " m_Script: {fileID: #FILEID#, guid: #GUID#, type: #TYPE#}";
    4. public string Name { get; private set; }
    5. public PrefabContent PrefabContent { get; private set; }
    6. public Type ComponentType { get; private set; }
    7. public string[] Lines { get; private set; }
    8. public long FileID { get { return _fileID; } }
    9. private long _fileID;
    10. public string GUID { get { return _guid; } }
    11. private string _guid;
    12. public int Type { get { return _type; } }
    13. private int _type;
    14. private int _index;
    15. public PrefabChunk(PrefabContent content, string[] lines, CharacteristicInfo[] characteristics)
    16. {
    17. PrefabContent = content;
    18. Lines = lines;
    19. FindName();
    20. FindType(characteristics);
    21. FindScriptLine();
    22. }
    23. private void FindName()
    24. {
    25. var line = FindLine("m_GameObject:", out var gameObjectIndex);
    26. if (string.IsNullOrEmpty(line) && gameObjectIndex == -1)
    27. {
    28. return;
    29. }
    30. var gameObjectContent = line.Substring(gameObjectIndex);
    31. if (string.IsNullOrEmpty(gameObjectContent))
    32. {
    33. return;
    34. }
    35. gameObjectContent = gameObjectContent.Trim().Trim('{').Trim('}');
    36. var filedIDTag = "fileID:";
    37. var fileIDIndex = gameObjectContent.IndexOf(filedIDTag);
    38. if (fileIDIndex == -1)
    39. {
    40. return;
    41. }
    42. var idString = gameObjectContent.Substring(fileIDIndex + filedIDTag.Length);
    43. long.TryParse(idString, out var id);
    44. if (id == 0)
    45. {
    46. return;
    47. }
    48. Name = PrefabContent.GetName(id);
    49. }
    50. private void FindType(CharacteristicInfo[] characteristics)
    51. {
    52. foreach (var pair in characteristics)
    53. {
    54. if (FindCharacteristic(pair.Characteristic))
    55. {
    56. ComponentType = pair.Type;
    57. }
    58. }
    59. }
    60. private void FindScriptLine()
    61. {
    62. _index = -1;
    63. var count = Lines.Length;
    64. for (int i = 0; i < count; i++)
    65. {
    66. if (Lines[i].Contains("m_Script"))
    67. {
    68. _index = i;
    69. break;
    70. }
    71. }
    72. if (_index == -1)
    73. {
    74. return;
    75. }
    76. AnalysisScriptLine(Lines[_index], out _fileID, out _guid, out _type);
    77. }
    78. public string FindLine(string tag, out int index)
    79. {
    80. index = -1;
    81. foreach (var line in Lines)
    82. {
    83. index = line.IndexOf(tag);
    84. if (index != -1)
    85. {
    86. index += tag.Length;
    87. return line;
    88. }
    89. }
    90. return string.Empty;
    91. }
    92. private bool FindCharacteristic(string tags)
    93. {
    94. var tagArr = tags.Split('&');
    95. var tagCount = tagArr.Length;
    96. var count = Lines.Length;
    97. var matchCount = 0;
    98. for (int tagIndex = 0; tagIndex < tagCount; tagIndex++)
    99. {
    100. var tag = tagArr[tagIndex];
    101. for (int i = 0; i < count; i++)
    102. {
    103. var line = Lines[i];
    104. if (line.Contains(tag))
    105. {
    106. matchCount++;
    107. }
    108. }
    109. }
    110. return matchCount == tagCount;
    111. }
    112. public bool ModifyM_Script(long fileId, string guid, int type)
    113. {
    114. if (_index == -1)
    115. {
    116. return false;
    117. }
    118. if (_fileID == fileId && _guid == guid && _type == type)
    119. {
    120. Debug.Log($"{Name} is using correct reference, needn't to fix");
    121. return false;
    122. }
    123. var line = ScriptFormat.Replace("#FILEID#", fileId.ToString()).Replace("#GUID#", guid).Replace("#TYPE#", type.ToString());
    124. Debug.Log($"{Name}:{ComponentType} ---> fileID:{fileId}, guid:{guid}, type:{type}");
    125. Lines[_index] = line;
    126. return true;
    127. }
    128. }
    1. private class FileTypeGUIDCollection
    2. {
    3. private bool _inited;
    4. protected readonly Dictionary _commonGUIDs = new Dictionary();
    5. protected readonly Dictionary _confusingGUIDs = new Dictionary();
    6. public void Init(string path)
    7. {
    8. if (_inited)
    9. {
    10. return;
    11. }
    12. if (!File.Exists(path))
    13. {
    14. StatisticAllUGUIGUIDs();
    15. }
    16. var json = File.ReadAllText(path);
    17. var guids = JsonConvert.Deserialize(json);
    18. foreach (var item in guids)
    19. {
    20. _commonGUIDs[item.Type] = item;
    21. if (item.Type == typeof(VerticalLayoutGroup) || item.Type == typeof(HorizontalLayoutGroup))
    22. {
    23. _confusingGUIDs[item.Type] = item;
    24. }
    25. }
    26. _inited = true;
    27. }
    28. public virtual bool TryGetValue(PrefabChunk chunk, out FileTypeGUID item)
    29. {
    30. if (chunk.ComponentType == null)
    31. {
    32. item = null;
    33. return false;
    34. }
    35. return _commonGUIDs.TryGetValue(chunk.ComponentType, out item);
    36. }
    37. public virtual bool IsConfusingType(PrefabChunk chunk, out FileTypeGUID[] fileTypeGUIDs)
    38. {
    39. fileTypeGUIDs = null;
    40. if (chunk.ComponentType == null)
    41. {
    42. return false;
    43. }
    44. if (_confusingGUIDs.ContainsKey(chunk.ComponentType))
    45. {
    46. fileTypeGUIDs = _confusingGUIDs.Values.ToArray();
    47. return true;
    48. }
    49. return false;
    50. }
    51. }

    此外还需要2个委托和一个特征信息集合

    1. // 特征字典,根据配置块中特点返回组件类型
    2. private static readonly CharacteristicInfo[] UGUICharacteristics = new CharacteristicInfo[]
    3. {
    4. new CharacteristicInfo("m_Text&m_FontData", typeof(Text)),
    5. new CharacteristicInfo("m_InputType&m_OnEndEdit&m_OnValueChanged", typeof(InputField)),
    6. new CharacteristicInfo("m_Sprite&m_FillCenter", typeof(Image)),
    7. new CharacteristicInfo("m_OnCullStateChanged&m_Texture&m_UVRect", typeof(RawImage)),
    8. new CharacteristicInfo("m_OnClick&m_TargetGraphic&m_SpriteState&m_Interactable", typeof(Button)),
    9. new CharacteristicInfo("m_MovementType&m_Elasticity&m_Viewport&m_OnValueChanged", typeof(ScrollRect)),
    10. new CharacteristicInfo("m_AnimationTriggers&m_Interactable&m_TargetGraphic&m_HandleRectm_NumberOfSteps&m_OnValueChanged", typeof(Scrollbar)),
    11. new CharacteristicInfo("m_ShowMaskGraphic", typeof(Mask)),
    12. new CharacteristicInfo("m_Padding&m_ChildAlignment&m_CellSize&m_Spacing", typeof(GridLayoutGroup)),
    13. new CharacteristicInfo("m_Padding&m_ChildAlignment&m_Spacing&m_ChildForceExpandWidth&m_ChildForceExpandHeight&m_ChildControlWidth&m_ChildControlHeight&m_ChildScaleWidth&m_ChildScaleHeight", typeof(HorizontalLayoutGroup)),
    14. new CharacteristicInfo("m_Padding&m_ChildAlignment&m_Spacing&m_ChildForceExpandWidth&m_ChildForceExpandHeight&m_ChildControlWidth&m_ChildControlHeight", typeof(VerticalLayoutGroup)),
    15. new CharacteristicInfo("m_EffectColor&m_EffectDistance&m_UseGraphicAlpha", typeof(Outline)),
    16. new CharacteristicInfo("m_DynamicPixelsPerUnit&m_ReferenceResolution", typeof(CanvasScaler)),
    17. new CharacteristicInfo("m_IgnoreReversedGraphics&m_BlockingObjects&m_BlockingMask", typeof(GraphicRaycaster)),
    18. new CharacteristicInfo("m_FirstSelected&m_DragThreshold", typeof(EventSystem)),
    19. new CharacteristicInfo("m_HorizontalAxis&m_VerticalAxis&m_SubmitButton&m_CancelButton", typeof(StandaloneInputModule))
    20. };
    21. delegate bool TryGetFileGUITypeDelegate(PrefabChunk chunk, out FileTypeGUID fileTypeGUID);
    22. delegate bool IsConfusingTypeDelegate(PrefabChunk chunk, out FileTypeGUID[] confusingGUIDs);

    随后,使用一段调用代码即可开始修复工作

    1. // 尝试修复引用丢失
    2. [MenuItem("Assets/Try To Fix UGUIComponent Missing")]
    3. private static void TryFixUGUIComponentMissing()
    4. {
    5. var collection = new FileTypeGUIDCollection();
    6. collection.Init(Application.dataPath.Replace("Assets", "uguiguids.json"));
    7. FixComponentMissingBase(collection.TryGetValue, collection.IsConfusingType, UGUICharacteristics);
    8. }
    9. private static void FixComponentMissingBase(TryGetFileGUITypeDelegate tryGetHandler, IsConfusingTypeDelegate isConfusingTypeHandler, CharacteristicInfo[] characteristics)
    10. {
    11. var selected = Selection.activeGameObject;
    12. // 对单个具体的prefab进行引用修复
    13. if (!selected)
    14. {
    15. return;
    16. }
    17. var prefabPath = AssetDatabase.GetAssetPath(selected);
    18. var lines = File.ReadAllLines(prefabPath);
    19. var prefabContent = PrefabContent.Parse(selected, lines, characteristics);
    20. prefabContent.Analysis(tryGetHandler, isConfusingTypeHandler);
    21. lines = prefabContent.GetLines();
    22. File.WriteAllLines(prefabPath, lines);
    23. AssetDatabase.Refresh();
    24. Debug.Log("Done");
    25. }

    上述class为什么为private,是因为都限定在static class PrefabMissingTool中,并不希望外部访问,读者也可以根据自己的需要修改访问范围。

    目前实现了对单个选中的prefab进行修复,也可以自行扩展为对复选或文件夹范围的prefab进行修复。

    =========================== !!重要!! ==========================

    因为`VerticalLayoutGroup`和`HorizontalLayoutGroup`两个类型中的特征信息(配置项)在作者的.prefab配置块中完全一致,因此无法区分,如果后续有发现更新的办法,会更新到文章中,或者也请读者指出更好的做法

    =========================== 扩展 ==========================

    因为可以搜集`UnityEngine.UI`的类型信息,那么同样,也可以收集`Assembly-CSharp`中继承自`Component`的信息,从原版本中收集信息后,到新版本根据匹配结果进行修改,那么也就可以修复在新版本中产生的自定义脚本挂载丢失的情况了。

    后续发现有更好的办法时会及时更新。

    github上可查看源码,已解耦合,仅依赖Newtonsoft,

    DoyoFish/PrefabMissingFixTool: Quick to fix missing component on unity prefab (github.com)

  • 相关阅读:
    真机环境配置教程
    【每日一题Day44】LC1779找到最近的相同X和相同Y的点 | 模拟
    中文编程入门(Lua5.4.6中文版)第十二章 Lua 协程 参考《愿神》游戏
    【Android】Binder的Oneway拦截
    C++ 新特性 | C++ 11 | 移动语义与右值引用
    5.SpringMVC的视图
    LeetCode-637. Average of Levels in Binary Tree [C++][Java]
    实验一 图像基本变换
    1007 Maximum Subsequence Sum
    oracle数据库日志挖掘操作步骤
  • 原文地址:https://blog.csdn.net/DoyoFish/article/details/128172372