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

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

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

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

1.GameObject

--- !u!1 &165095463815504087
GameObject:m_ObjectHideFlags: 0m_CorrespondingSourceObject: {fileID: 0}m_PrefabInstance: {fileID: 0}m_PrefabAsset: {fileID: 0}serializedVersion: 6m_Component:- component: {fileID: 4433197073301798445}- component: {fileID: 5990753843237649034}- component: {fileID: 5075137301801018474}m_Layer: 5m_Name: Streakm_TagString: Untaggedm_Icon: {fileID: 0}m_NavMeshLayer: 0m_StaticEditorFlags: 0m_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

--- !u!114 &5075137301801018474
MonoBehaviour:m_ObjectHideFlags: 0m_CorrespondingSourceObject: {fileID: 0}m_PrefabInstance: {fileID: 0}m_PrefabAsset: {fileID: 0}m_GameObject: {fileID: 165095463815504087}m_Enabled: 1m_EditorHideFlags: 0m_Script: {fileID: 11500000, guid: d6072c12dfea5c74897ce48533ec3f2a, type: 3}m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0}m_Color: {r: 1, g: 1, b: 1, a: 1}m_RaycastTarget: 1m_OnCullStateChanged:m_PersistentCalls:m_Calls: []m_Sprite: {fileID: 0}m_Type: 0m_PreserveAspect: 0m_FillCenter: 1m_FillMethod: 4m_FillAmount: 1m_FillClockwise: 1m_FillOrigin: 0m_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脚本

  m_Material: {fileID: 0}m_Color: {r: 1, g: 1, b: 1, a: 1}m_RaycastTarget: 1m_OnCullStateChanged:m_PersistentCalls:m_Calls: []m_Sprite: {fileID: 0}m_Type: 0m_PreserveAspect: 0m_FillCenter: 1m_FillMethod: 4m_FillAmount: 1m_FillClockwise: 1m_FillOrigin: 0m_UseSpriteMesh: 0

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

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

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

    private static void StatisticOrderedScriptGUIDs(string asmName, string fileName){var g = new GameObject();var asm = Assembly.Load(asmName);var types = asm.GetTypes();var guids = new List<FileTypeGUID>();long localId;string guid;foreach (var type in types){// 抽象类或不继承自Component的类过滤if (type.IsAbstract || !type.IsSubclassOf(typeof(Component))){continue;}g.name = type.Name;var component = g.AddComponent(type);if (component){// 此处设置一个临时路径var prefabPath = $"Assets/Editor/GUIDPrefab/{type.Name}.prefab";// 自定义方法,根据文件名判断文件夹是否存在,不存在则创建FileHelper.CreateDirectoryIfNotExistByFilename(prefabPath);var success = false;PrefabUtility.SaveAsPrefabAsset(g, prefabPath, out success);AssetDatabase.Refresh();var prefab = AssetDatabase.LoadAssetAtPath(prefabPath, type);if (!prefab){Debug.LogError($"type:{type} cannot load, path:{prefabPath}");continue;}if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(prefab, out guid, out localId)){var fileId = $"&{localId}";var metaLines = File.ReadAllLines(Path.GetFullPath(prefabPath));var foundLine = false;foreach (var line in metaLines){if (line.IndexOf(fileId) > 0){foundLine = true;continue;}if (!foundLine){continue;}if (line.IndexOf("m_Script:") > 0){AnalysisScriptLine(line, out long realFileId, out string realGuid, out int fileType);guids.Add(new FileTypeGUID(type, realFileId, realGuid, fileType));}}}GameObject.DestroyImmediate(component);}}GameObject.DestroyImmediate(g);var json = JsonConvert.Serialize(guids, true);Debug.Log(json);// 将收集的信息存储下来,为后续分析具体.prefab时供参考var path = Application.dataPath.Replace("Assets", $"{fileName}.json");File.WriteAllText(path, json);}private static void AnalysisScriptLine(string line, out long fileId, out string guid, out int type){fileId = 0;guid = string.Empty;type = 0;if (!line.Trim().StartsWith("m_Script:")){return;}var index = line.IndexOf("{");var endIndex = line.LastIndexOf("}");var inside = line.Substring(index + 1, endIndex - index - 1);var arr = inside.Split(',');foreach (var item in arr){var pair = item.Split(':');switch (pair[0].Trim()){case "fileID":long.TryParse(pair[1].Trim(), out fileId);break;case "guid":guid = pair[1].Trim();break;case "type":int.TryParse(pair[1].Trim(), out type);break;}}}

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

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

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

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

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

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

    private struct CharacteristicInfo{public string Characteristic { get; set; }public Type Type { get; set; }public CharacteristicInfo(string c, Type t){Characteristic = c;Type = t;}}
    private class FileTypeGUID{// 该信息组对应的实际类型public Type Type { get; set; }public long FileId { get; set; }public string Guid { get; set; }public int FileType { get; set; }public FileTypeGUID(Type type, long fileId, string guid, int fileType){Type = type;FileId = fileId;Guid = guid;FileType = fileType;}public override string ToString(){return $"Type:{Type.Name}, FileId:{FileId}, Guid:{Guid}, FileType:{FileType}";}}
    private class PrefabContent{public Object Object { get; private set; }private readonly List<PrefabChunk> _chunks = new List<PrefabChunk>();private readonly List<string> _head = new List<string>();private readonly Dictionary<long, string> _gameObjects = new Dictionary<long, string>();private PrefabContent(Object belonging){Object = belonging;}public void Analysis(TryGetFileGUITypeDelegate tryGetHandler, IsConfusingTypeDelegate isConfusingTypeHandler){var changeCount = 0;FileTypeGUID[] fileTypeGUIDs;foreach (var chunk in _chunks){if (tryGetHandler.Invoke(chunk, out var item)){if (isConfusingTypeHandler.Invoke(chunk, out fileTypeGUIDs)){Debug.LogError("=========== confusing warning ===========");Debug.LogError($"chunk.Component is a confusing type, should confirm again, {chunk.PrefabContent.Object}", chunk.PrefabContent.Object);foreach (var guid in fileTypeGUIDs){Debug.LogError($"Type:{guid.Type}, FileID:{guid.FileId}, guid:{guid.Guid}, type:{guid.FileType}");}Debug.LogError("=========== end ===========");}if (chunk.ModifyM_Script(item.FileId, item.Guid, item.FileType)){changeCount++;}}}Debug.Log($"修改了 {changeCount} 个组件");}public string[] GetLines(){var list = new List<string>();list.AddRange(_head);foreach (var chunk in _chunks){list.AddRange(chunk.Lines);}return list.ToArray();}public string GetName(long id){_gameObjects.TryGetValue(id, out var name);return name;}public static PrefabContent Parse(Object selected, string[] lines, CharacteristicInfo[] characteristics){var count = lines.Length;var listInChunk = new List<string>();var content = new PrefabContent(selected);var id = 0L;var foundGameObjectTag = false;for (int i = 0; i < count; i++){var line = lines[i];if (line.StartsWith("%")){content._head.Add(line);continue;}if (line.StartsWith("---")){if (i + 1 < count && lines[i + 1].StartsWith("GameObject:")){var andIndex = line.IndexOf('&');var idString = line.Substring(andIndex + 1);long.TryParse(idString, out id);foundGameObjectTag = true;}if (listInChunk.Count != 0){var chunk = new PrefabChunk(content, listInChunk.ToArray(), characteristics);content._chunks.Add(chunk);listInChunk.Clear();}listInChunk.Add(line);continue;}if (foundGameObjectTag){var nameTag = "m_Name:";var nameIndex = line.IndexOf(nameTag);if (!string.IsNullOrEmpty(line) && nameIndex != -1){var name = line.Substring(nameIndex + nameTag.Length).Trim();content._gameObjects[id] = name;foundGameObjectTag = false;}}listInChunk.Add(line);}// 添加剩余的if (listInChunk.Count != 0){var chunk = new PrefabChunk(content, listInChunk.ToArray(), characteristics);content._chunks.Add(chunk);listInChunk.Clear();}return content;}}
    private class PrefabChunk{private const string ScriptFormat = "  m_Script: {fileID: #FILEID#, guid: #GUID#, type: #TYPE#}";public string Name { get; private set; }public PrefabContent PrefabContent { get; private set; }public Type ComponentType { get; private set; }public string[] Lines { get; private set; }public long FileID { get { return _fileID; } }private long _fileID;public string GUID { get { return _guid; } }private string _guid;public int Type { get { return _type; } }private int _type;private int _index;public PrefabChunk(PrefabContent content, string[] lines, CharacteristicInfo[] characteristics){PrefabContent = content;Lines = lines;FindName();FindType(characteristics);FindScriptLine();}private void FindName(){var line = FindLine("m_GameObject:", out var gameObjectIndex);if (string.IsNullOrEmpty(line) && gameObjectIndex == -1){return;}var gameObjectContent = line.Substring(gameObjectIndex);if (string.IsNullOrEmpty(gameObjectContent)){return;}gameObjectContent = gameObjectContent.Trim().Trim('{').Trim('}');var filedIDTag = "fileID:";var fileIDIndex = gameObjectContent.IndexOf(filedIDTag);if (fileIDIndex == -1){return;}var idString = gameObjectContent.Substring(fileIDIndex + filedIDTag.Length);long.TryParse(idString, out var id);if (id == 0){return;}Name = PrefabContent.GetName(id);}private void FindType(CharacteristicInfo[] characteristics){foreach (var pair in characteristics){if (FindCharacteristic(pair.Characteristic)){ComponentType = pair.Type;}}}private void FindScriptLine(){_index = -1;var count = Lines.Length;for (int i = 0; i < count; i++){if (Lines[i].Contains("m_Script")){_index = i;break;}}if (_index == -1){return;}AnalysisScriptLine(Lines[_index], out _fileID, out _guid, out _type);}public string FindLine(string tag, out int index){index = -1;foreach (var line in Lines){index = line.IndexOf(tag);if (index != -1){index += tag.Length;return line;}}return string.Empty;}private bool FindCharacteristic(string tags){var tagArr = tags.Split('&');var tagCount = tagArr.Length;var count = Lines.Length;var matchCount = 0;for (int tagIndex = 0; tagIndex < tagCount; tagIndex++){var tag = tagArr[tagIndex];for (int i = 0; i < count; i++){var line = Lines[i];if (line.Contains(tag)){matchCount++;}}}return matchCount == tagCount;}public bool ModifyM_Script(long fileId, string guid, int type){if (_index == -1){return false;}if (_fileID == fileId && _guid == guid && _type == type){Debug.Log($"{Name} is using correct reference, needn't to fix");return false;}var line = ScriptFormat.Replace("#FILEID#", fileId.ToString()).Replace("#GUID#", guid).Replace("#TYPE#", type.ToString());Debug.Log($"{Name}:{ComponentType} ---> fileID:{fileId}, guid:{guid}, type:{type}");Lines[_index] = line;return true;}}
    private class FileTypeGUIDCollection{private bool _inited;protected readonly Dictionary<Type, FileTypeGUID> _commonGUIDs = new Dictionary<Type, FileTypeGUID>();protected readonly Dictionary<Type, FileTypeGUID> _confusingGUIDs = new Dictionary<Type, FileTypeGUID>();public void Init(string path){if (_inited){return;}if (!File.Exists(path)){StatisticAllUGUIGUIDs();}var json = File.ReadAllText(path);var guids = JsonConvert.Deserialize<FileTypeGUID[]>(json);foreach (var item in guids){_commonGUIDs[item.Type] = item;if (item.Type == typeof(VerticalLayoutGroup) || item.Type == typeof(HorizontalLayoutGroup)){_confusingGUIDs[item.Type] = item;}}_inited = true;}public virtual bool TryGetValue(PrefabChunk chunk, out FileTypeGUID item){if (chunk.ComponentType == null){item = null;return false;}return _commonGUIDs.TryGetValue(chunk.ComponentType, out item);}public virtual bool IsConfusingType(PrefabChunk chunk, out FileTypeGUID[] fileTypeGUIDs){fileTypeGUIDs = null;if (chunk.ComponentType == null){return false;}if (_confusingGUIDs.ContainsKey(chunk.ComponentType)){fileTypeGUIDs = _confusingGUIDs.Values.ToArray();return true;}return false;}}

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

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

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

    // 尝试修复引用丢失[MenuItem("Assets/Try To Fix UGUIComponent Missing")]private static void TryFixUGUIComponentMissing(){var collection = new FileTypeGUIDCollection();collection.Init(Application.dataPath.Replace("Assets", "uguiguids.json"));FixComponentMissingBase(collection.TryGetValue, collection.IsConfusingType, UGUICharacteristics);}private static void FixComponentMissingBase(TryGetFileGUITypeDelegate tryGetHandler, IsConfusingTypeDelegate isConfusingTypeHandler, CharacteristicInfo[] characteristics){var selected = Selection.activeGameObject;// 对单个具体的prefab进行引用修复if (!selected){return;}var prefabPath = AssetDatabase.GetAssetPath(selected);var lines = File.ReadAllLines(prefabPath);var prefabContent = PrefabContent.Parse(selected, lines, characteristics);prefabContent.Analysis(tryGetHandler, isConfusingTypeHandler);lines = prefabContent.GetLines();File.WriteAllLines(prefabPath, lines);AssetDatabase.Refresh();Debug.Log("Done");}

上述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)

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

  1. Unity 运行状态下动态保存 预制体/预制体上脚本参数

    前言:在Unity游戏制作过程中为了方便策划调试保存 通常会让策划可以在游戏运行时直接保存调整好的预制体 在此背景下,出现了以下代码: 一.运行状态下动态保存预制体 代码: public class ...

  2. AB上的脚本引用丢失,The referenced script is missing!

    今天工作的时候队友遇到了一个BUG.说是脚本引用丢失. 一个Prefab打包AB后,修改了AB上绑定的脚本. 然后加载AB后出现了 警告 引用丢失. 项目的EditorSetting设置的是 Visi ...

  3. Cocos Creator用cc.loader加载预制体资源和删除预制体资源

    对于小游戏来说,单个场景的页面可以事先写好然后用active的方式来开关. 但是这会导致一个严重的问题就是每次载入这个场景的时候就会很卡,而如果是让场景绑定预制体资源的时候这个场景同时也会加载对应的预 ...

  4. Unity的使用(四):预制体,创建地形和地形导航

    前面介绍了Unity游戏引擎的基础功能,现在终于要进入到游戏开发中了.那么,一款游戏开发要有资源,这个一般是由美术提供的,我们只需要负责程序方面的事.那么,怎么将获得的资源应用起来呢? 一. 导入资源 ...

  5. Unity预制体Prefab及其实例化(Instantiate)

    简介: 在Unity3D工程建设中,Prefabs(预设)是很常用的一种资源类型,是一种可以被重复使用的游戏对象 可以被置入多个场景中,也可以在一个场景中多次置入 在场景中增加一个Prefab,就是实 ...

  6. unity3d 预制体

    首先要说明一下什么是预制体? 在Unity3D里面我们叫它Prefab:我们也可以这样理解:当制作好了游戏组件(场景中的任意一个gameobject ),我们希望将它制作成一个组件模版,用于批量的套用 ...

  7. Unity使用c#开发遇上的问题(六)(3dmax围绕指定中心旋转,unity中动态调用预制体并根据模型旋转指定角度)

    文章目录 前言 一.3dmax创建子弹.炮塔及武器库 1.相关模型 2.炮塔模型引入unity,无法绕旋转球旋转,重新调整 1.3dmax中默认炮管的中心点 2.选择层次界面 3.选择编辑工作轴 4. ...

  8. 【Unity编辑器扩展实践】、查找所有引用该图片的预制体

    上一篇Unity编辑器扩展实践二.通过代码查找所有预制中已经查到到所有的预制体了. 然后我们就可以用这些预制体做一些其他的操作了,比如查找该预制的资源引用.可以直接遍历预制,找到预制里面的所有Imag ...

  9. Unity 拓展编辑器 - 导出UI预制体lua文件

    使用oldin插件 + Unity自带拓展编辑器编写 实现效果: 界面配置基类:UIDialogConfig using Sirenix.OdinInspector; using UnityEngin ...

最新文章

  1. Linq To Sql中实现Left Join与Inner Join使用Linq语法与lambda表达式
  2. 漫谈C++ Builder多线程编程技术
  3. 软件架构设计的6大原则
  4. ANSIBLE--handlers的概念
  5. 安装10gR2的硬件要求
  6. err=etherbase address must be explicitly specified
  7. 最短路大大大跟着合集
  8. jsp/servlet/mysql/linux基本概念和操作
  9. SPI总线接口与简单配置
  10. word2019加载mathtype
  11. Java方法——方法的重载
  12. 泰晤士报华科计算机排名,最新USNews中国内地高校计算机学科排名,北大第7,华科第2?...
  13. 使用Xbrowser远程连接REHL6.5
  14. 构建知识体系(3):建立体系6个步骤
  15. 微信小程序授权登录和账号登录
  16. Scene…… couldn‘t be loaded because it has not been added to the build settings or the AssetBundle...
  17. 短距离激光测距仪方案模块
  18. 平板电脑 中柏4s pro 重装win10 系统
  19. Flink-flink原理解读
  20. 长沙医学院计算机科学与技术专业怎么样,长沙医学院有哪些专业及什么专业好...

热门文章

  1. 这份算法攻略,我拿到了5个大厂的offer
  2. 明年完成百余城高精地图绘制,详解晶众的营收之道
  3. 无人机动力测试台-15公斤级-Flight Stand 15
  4. 怎样做精美的思维导图,简单方法介绍
  5. [JS]笔记19_AJAX跨域(demo)
  6. 测试分享之抽奖【一】
  7. 转 LUA语言学习教程
  8. 姿态估计:人体骨骼关键点检测的方法论
  9. [230]连接Redis后执行命令错误 MISCONF Redis is configured to save RDB snapshots
  10. 队列的入队/出队操作