Unity 编辑器开发实战【Editor Window】- 构建公司内部的PackageManager

简介: Unity 编辑器开发实战【Editor Window】- 构建公司内部的PackageManager

Unity中的资源包管理器Package Manager为我们提供了模块、工具包的集中管理功能,可在其中下载、升级相应的资源包,本文介绍如何构建公司内部的Package Manager资源包管理器。

image.gif

首先我们需要一个局域网服务器,将我们的资源包放到服务器内提供下载。构建该服务器有很多途径,可以让公司后端人员进行开发,并定制下载等相关接口,我们只需要调用接口。如果没有该条件,可以使用一些相关的软件,比如HFS,一个简易的HTTP服务器软件,下载链接:HFS ~ Http File Server

image.gif

或者使用phpstudy软件,链接:PhpStudy 让天下没有难配的服务器环境 具体的使用教程不在此进行介绍,可以参考一些相关的博客,例如:【Unity3D日常开发】Unity3D中实现向Web服务器上传图片以及下载图片功能

image.gif

有了服务器环境后,开始在Unity中创建编辑器,创建一个编辑器窗口首先需要继承Editor Window类,在往期的博客中也有介绍:四、Unity编辑器开发之EditorWindow

using UnityEditor;
namespace SK.Framework
{
    public class PackageManagerInternal : EditorWindow
    {
        [MenuItem("Window/Package Manager Internal", priority = 1500)]
        private static void Open()
        {
             GetWindow<PackageManagerInternal>("Package Manager Internal").Show();
        }
    }
}

image.gif

image.gif

定义资源包的数据结构,参考Unity中的Package Manager,一个资源包包含的信息大概有:名称、作者、版本、发布日期、简介、依赖项、示例等:

usingSystem;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
publicclassPackageManagerInternal : EditorWindow    {
        [MenuItem("Window/Package Manager Internal", priority=1500)]
privatestaticvoidOpen()
        {
varwindow=GetWindow<PackageManagerInternal>("Package Manager Internal");
//设置窗口的最小尺寸window.minSize=newVector2(600f, 400f);
window.Show();
        }
/// <summary>/// 资源包数据结构/// </summary>        [Serializable]
privateclassPackageTemplate        {
/// <summary>/// 名称/// </summary>publicstringname;
/// <summary>/// 作者/// </summary>publicstringauthor;
/// <summary>/// 版本/// </summary>publicstringversion;
/// <summary>/// 发布日期/// </summary>publicstringreleasedDate;
/// <summary>/// 简介/// </summary>publicstringdescription;
/// <summary>/// 依赖项/// </summary>publicstring[] dependencies;
        }
    }
}

image.gif

通过GUILayout类中Begin Horizontal和End Horizontal、Begin Vertical和End Vertical方法定义窗口的大概布局:

privatevoidOnGUI()
{
//水平布局 顶部GUILayout.BeginHorizontal("Toolbar");
    {
//在此处添加一个搜索栏    }
GUILayout.EndHorizontal();
//水平布局GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
    {
//垂直布局 设置左侧列表宽度GUILayout.BeginVertical(GUILayout.Width(200f));
        {
//在此处列举所有资源包名称 + 版本信息        }
GUILayout.EndVertical();
//分割线GUILayout.Box(string.Empty, "EyeDropperVerticalLine", GUILayout.ExpandHeight(true), GUILayout.Width(1f));
//垂直布局GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
        {
//在此处展示当前选中的资源包的详细信息        }
GUILayout.EndVertical();
    }
GUILayout.EndHorizontal();
}

image.gif

image.gif

搜索栏使用TextField添加一个文本输入框,GUIStyle使用Unity中内置的SearchTextField,内置GUIStyle的查看方法在往期的博客中有介绍,链接:五、Unity编辑器开发之GUIStyle

usingSystem;
usingSystem.IO;
usingUnityEngine;
usingUnityEditor;
usingSystem.Collections;
usingSystem.Collections.Generic;
namespaceSK.Framework{
publicclassPackageManagerInternal : EditorWindow    {
        [MenuItem("Window/Package Manager Internal", priority=1500)]
privatestaticvoidOpen()
        {
varwindow=GetWindow<PackageManagerInternal>("Package Manager Internal");
window.minSize=newVector2(600f, 400f);
window.Show();
        }
/// <summary>/// 资源包数据结构/// </summary>        [Serializable]
privateclassPackageTemplate        {
/// <summary>/// 名称/// </summary>publicstringname;
/// <summary>/// 作者/// </summary>publicstringauthor;
/// <summary>/// 版本/// </summary>publicstringversion;
/// <summary>/// 发布日期/// </summary>publicstringreleasedDate;
/// <summary>/// 简介/// </summary>publicstringdescription;
/// <summary>/// 依赖项/// </summary>publicstring[] dependencies;
        }
//搜索内容privatestringsearchContent;
privatevoidOnGUI()
        {
//水平布局GUILayout.BeginHorizontal("Toolbar");
            {
//搜索OnSearchGUI();
            }
GUILayout.EndHorizontal();
//水平布局GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
            {
//垂直布局 设置左侧列表宽度GUILayout.BeginVertical(GUILayout.Width(200f));
                {
                }
GUILayout.EndVertical();
//分割线GUILayout.Box(string.Empty, "EyeDropperVerticalLine", GUILayout.ExpandHeight(true), GUILayout.Width(1f));
//垂直布局GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
                {
                }
GUILayout.EndVertical();
            }
GUILayout.EndHorizontal();
        }
privatevoidOnSearchGUI()
        {
varnewSearchContent=GUILayout.TextField(searchContent, "SearchTextField");
if (newSearchContent!=searchContent)
            {
searchContent=newSearchContent;
Repaint();
            }
if (Event.current.type==EventType.MouseDown&&!GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
            {
GUI.FocusControl(null);
            }
        }       
    }
}

image.gif

在左侧列举所有资源包信息,资源包可能会有不同的版本,我们可以将名称相同而版本不同的资源包放到一个列表中,因此使用一个二维列表。因为数量不确定,可能会超出窗口高度大小,因此使用滚动视图来列举资源包信息,滚动视图通过BeginScrollView和EndScrollView方法实现:

//资源包信息列表privateList<List<PackageTemplate>>packages;
//列表滚动视图privateVector2listScroll;
privatevoidOnListGUI()
{
//滚动视图listScroll=GUILayout.BeginScrollView(listScroll);
    {
for (inti=0; i<packages.Count; i++)
        {
List<PackageTemplate>list=packages[i];
        }
    }
GUILayout.EndScrollView();
}

image.gif

遍历资源包列表,通过资源包的名称是否包含搜索栏里检索的内容来判断是否列举该项资源包信息。还需要增加一个折叠栏,折叠栏为打开状态时列举不同的版本,否则只列举第一个版本,使用一个字典来存储折叠状态信息,Key值为资源包名称(string),Value值为折叠栏状态(bool),点击时记录当前选中的资源包

//折叠状态privateDictionary<string, bool>foldoutDic;
//当前选中的资源包信息privatePackageTemplatecurrentSelected;
privatevoidOnListGUI()
{
//滚动视图listScroll=GUILayout.BeginScrollView(listScroll);
    {
GUIStyleversionStyle=newGUIStyle(GUI.skin.label) { fontStyle=FontStyle.Italic };
for (inti=0; i<packages.Count; i++)
        {
List<PackageTemplate>list=packages[i];
PackageTemplatefirst=list[0];
//该资源包不符合检索内容 continueif (!string.IsNullOrEmpty(searchContent) &&!first.name.ToLower().Contains(searchContent.ToLower())) continue;
//折叠栏为打开状态if (foldoutDic[first.name])
            {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
//列举所有版本信息 使用不同GUIStyle区分当前是否为选中项for (intn=0; n<list.Count; n++)
                {
GUILayout.BeginHorizontal(currentSelected==list[n] ?"SelectionRect" : "IN Title");
GUILayout.FlexibleSpace();
GUILayout.Label(list[n].version, versionStyle);
GUILayout.Space(30f);
GUILayout.EndHorizontal();
if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                    {
currentSelected=list[n];
Event.current.Use();
                    }
                }
            }
//折叠栏为关闭状态else            {
GUILayout.BeginHorizontal(currentSelected==first?"SelectionRect" : "Toolbar");
                {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
GUILayout.FlexibleSpace();
GUILayout.Label(first.version, versionStyle);
                }
GUILayout.EndHorizontal();
//鼠标点击选中if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                {
currentSelected=first;
Event.current.Use();
                }
            }
        }
    }
GUILayout.EndScrollView();
}

image.gif

在列表的最下方展示最后刷新的时间,并添加一个刷新按钮,点击按钮时,从服务器下载资源包Json数据,记录最后刷新时间,清空当前的资源包信息列表并根据下载的信息重新赋值:

//最后更新日期privatestringlastUpdateDate;
privatevoidOnListGUI()
{
//滚动视图listScroll=GUILayout.BeginScrollView(listScroll);
    {
GUIStyleversionStyle=newGUIStyle(GUI.skin.label) { fontStyle=FontStyle.Italic };
for (inti=0; i<packages.Count; i++)
        {
List<PackageTemplate>list=packages[i];
PackageTemplatefirst=list[0];
if (!string.IsNullOrEmpty(searchContent) &&!first.name.ToLower().Contains(searchContent.ToLower())) continue;
if (foldoutDic[first.name])
            {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
for (intn=0; n<list.Count; n++)
                {
GUILayout.BeginHorizontal(currentSelected==list[n] ?"SelectionRect" : "IN Title");
GUILayout.FlexibleSpace();
GUILayout.Label(list[n].version, versionStyle);
GUILayout.Space(30f);
GUILayout.EndHorizontal();
if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                    {
currentSelected=list[n];
Event.current.Use();
                    }
                }
            }
else            {
GUILayout.BeginHorizontal(currentSelected==first?"SelectionRect" : "Toolbar");
                {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
GUILayout.FlexibleSpace();
GUILayout.Label(first.version, versionStyle);
                }
GUILayout.EndHorizontal();
//鼠标点击选中if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                {
currentSelected=first;
Event.current.Use();
                }
            }
        }
    }
GUILayout.EndScrollView();
//分割线GUILayout.Box(string.Empty, "EyeDropperHorizontalLine", GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//水平布局 设置高度GUILayout.BeginHorizontal(GUILayout.Height(23f));
    {
//最后更新日期GUILayout.Label(lastUpdateDate);
//刷新按钮if (GUILayout.Button(EditorGUIUtility.IconContent("Refresh"), GUILayout.Width(30f)))
        {
//清空当前的资源包信息列表packages.Clear();
//清空折叠栏信息foldoutDic.Clear();
//当前选中的资源包设为空currentSelected=null;
//发起网络请求EditorCoroutineRunner.StartEditorCoroutine(GetPackagesInfo());
        }
    }
GUILayout.EndHorizontal();
}
//获取资源包信息privateIEnumeratorGetPackagesInfo()
{
stringurl="http://192.168.3.27:8888/packages.txt";
WWWwww=newWWW(url);
yieldreturnwww;
if (www.error==null)
    {
//使用LitJson反序列化List<PackageTemplate>list=JsonMapper.ToObject<List<PackageTemplate>>(www.text);
//遍历列表for (inti=0; i<list.Count; i++)
        {
varpackage=list[i];
//查找列表中是否已经存在该资源包其他版本intindex=packages.FindIndex(m=>m!=null&&m.Count>0&&m[0].name==package.name);
if (index==-1)
            {
varnewList=newList<PackageTemplate> { package };
packages.Add(newList);
foldoutDic.Add(package.name, false);
            }
else            {
packages[index].Add(package);
            }
        }
//更新最后刷新日期lastUpdateDate=DateTime.Now.ToString();
    }
else    {
Debug.LogError(www.error);
    }
}

image.gif

EditorCoroutineRunner类是一个在Unity Editor编辑器环境下运行携程的工具类,代码如下:

publicstaticclassEditorCoroutineRunner{
privateclassEditorCoroutine : IEnumerator    {
privateStack<IEnumerator>executionStack;
publicEditorCoroutine(IEnumeratoriterator)
        {
executionStack=newStack<IEnumerator>();
executionStack.Push(iterator);
        }
publicboolMoveNext()
        {
IEnumeratori=executionStack.Peek();
if (i.MoveNext())
            {
objectresult=i.Current;
if (result!=null&&resultisIEnumerator)
                {
executionStack.Push((IEnumerator)result);
                }
returntrue;
            }
else            {
if (executionStack.Count>1)
                {
executionStack.Pop();
returntrue;
                }
            }
returnfalse;
        }
publicvoidReset()
        {
thrownewNotSupportedException("This Operation Is Not Supported.");
        }
publicobjectCurrent        {
get { returnexecutionStack.Peek().Current; }
        }
publicboolFind(IEnumeratoriterator)
        {
returnexecutionStack.Contains(iterator);
        }
    }
privatestaticList<EditorCoroutine>editorCoroutineList;
privatestaticList<IEnumerator>buffer;
publicstaticIEnumeratorStartEditorCoroutine(IEnumeratoriterator)
    {
if (editorCoroutineList==null)
        {
editorCoroutineList=newList<EditorCoroutine>();
        }
if (buffer==null)
        {
buffer=newList<IEnumerator>();
        }
if (editorCoroutineList.Count==0)
        {
EditorApplication.update+=Update;
        }
buffer.Add(iterator);
returniterator;
    }
privatestaticboolFind(IEnumeratoriterator)
    {
foreach (EditorCoroutineeditorCoroutineineditorCoroutineList)
        {
if (editorCoroutine.Find(iterator))
            {
returntrue;
            }
        }
returnfalse;
    }
privatestaticvoidUpdate()
    {
editorCoroutineList.RemoveAll(coroutine=> { returncoroutine.MoveNext() ==false; });
if (buffer.Count>0)
        {
foreach (IEnumeratoriteratorinbuffer)
            {
if (!Find(iterator))
                {
editorCoroutineList.Add(newEditorCoroutine(iterator));
                }
            }
buffer.Clear();
        }
if (editorCoroutineList.Count==0)
        {
EditorApplication.update-=Update;
        }
    }
}

image.gif

列举资源包列表信息后,在右侧展示当前选中项的详细信息,并在底部添加一个Import的Button按钮,用于下载并导入该资源包:

privatevoidOnDetailGUI()
{
if (currentSelected!=null)
    {
//名称GUILayout.Label(currentSelected.name, newGUIStyle(GUI.skin.label) { fontSize=25, fontStyle=FontStyle.Bold });
EditorGUILayout.Space();
//作者GUILayout.Label(currentSelected.author, newGUIStyle(GUI.skin.label) { fontSize=12 });
EditorGUILayout.Space();
//版本 + 发布日期GUILayout.Label($"Version {currentSelected.version} - {currentSelected.releasedDate}", newGUIStyle(GUI.skin.label) { fontSize=14, fontStyle=FontStyle.Bold });
EditorGUILayout.Space();
//分割线GUILayout.Box(string.Empty, GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//简介GUILayout.Label(currentSelected.description);
    }
GUILayout.FlexibleSpace();
//分割线GUILayout.Box(string.Empty, "EyeDropperHorizontalLine", GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//水平布局 设置高度GUILayout.BeginHorizontal(GUILayout.Height(21f));
    {
GUILayout.FlexibleSpace();
//下载并导入if (GUILayout.Button("Import", GUILayout.Width(50f)))
        {
if (currentSelected!=null)
            {
EditorCoroutineRunner.StartEditorCoroutine(DownloadPackage(currentSelected));
            }
        }
    }
GUILayout.EndHorizontal();
}
//下载并导入资源包privateIEnumeratorDownloadPackage(PackageTemplatepackage)
{
}

image.gif

发起下载资源包的网络请求后,根据接收到的bytes字节数据,将.unitypackage文件写入本地,然后调用AssetDatabase类中的ImportPackage方法,该方法可以将.unitypackage资源包导入Unity中,导入完成后,再将下载的文件删除:

//下载并导入资源包privateIEnumeratorDownloadPackage(PackageTemplatepackage)
{
stringurl=$"http://192.168.3.27:8888/packages/{package.name}/{package.version}.unitypackage";
WWWwww=newWWW(url);
yieldreturnwww;
if (www.error==null)
    {
byte[] bytes=www.bytes;
stringpath=$"{Application.dataPath}/{package.name}-{package.version}.unitypackage";
//写入本地using (FileStreamfs=newFileStream(path, FileMode.Create))
        {
fs.Write(bytes, 0, bytes.Length);
        }
//导入AssetDatabase.ImportPackage(path, false);
//删除File.Delete(path);
    }
else    {
Debug.LogError(www.error);
    }
}

image.gif

最终完整代码:

usingSystem;
usingSystem.IO;
usingUnityEngine;
usingUnityEditor;
usingSystem.Collections;
usingSystem.Collections.Generic;
namespaceSK.Framework{
publicclassPackageManagerInternal : EditorWindow    {
        [MenuItem("Window/Package Manager Internal", priority=1500)]
privatestaticvoidOpen()
        {
varwindow=GetWindow<PackageManagerInternal>("Package Manager Internal");
window.minSize=newVector2(600f, 400f);
window.Show();
        }
/// <summary>/// 资源包数据结构/// </summary>        [Serializable]
privateclassPackageTemplate        {
/// <summary>/// 名称/// </summary>publicstringname;
/// <summary>/// 作者/// </summary>publicstringauthor;
/// <summary>/// 版本/// </summary>publicstringversion;
/// <summary>/// 发布日期/// </summary>publicstringreleasedDate;
/// <summary>/// 简介/// </summary>publicstringdescription;
/// <summary>/// 依赖项/// </summary>publicstring[] dependencies;
        }
//资源包信息列表privateList<List<PackageTemplate>>packages;
//折叠状态privateDictionary<string, bool>foldoutDic;
//列表滚动视图privateVector2listScroll;
//最后更新日期privatestringlastUpdateDate;
//当前选中的资源包信息privatePackageTemplatecurrentSelected;
//搜索内容privatestringsearchContent;
privatevoidOnEnable()
        {
packages=newList<List<PackageTemplate>>();
foldoutDic=newDictionary<string, bool>();
        }
privatevoidOnGUI()
        {
//水平布局GUILayout.BeginHorizontal("Toolbar");
            {
//搜索OnSearchGUI();
            }
GUILayout.EndHorizontal();
//水平布局GUILayout.BeginHorizontal(GUILayout.ExpandHeight(true));
            {
//垂直布局 设置左侧列表宽度GUILayout.BeginVertical(GUILayout.Width(200f));
                {
//绘制列表OnListGUI();
                }
GUILayout.EndVertical();
//分割线GUILayout.Box(string.Empty, "EyeDropperVerticalLine", GUILayout.ExpandHeight(true), GUILayout.Width(1f));
//垂直布局GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
                {
//绘制详情OnDetailGUI();
                }
GUILayout.EndVertical();
            }
GUILayout.EndHorizontal();
        }
privatevoidOnSearchGUI()
        {
varnewSearchContent=GUILayout.TextField(searchContent, "SearchTextField");
if (newSearchContent!=searchContent)
            {
searchContent=newSearchContent;
currentSelected=null;
Repaint();
            }
if (Event.current.type==EventType.MouseDown&&!GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
            {
GUI.FocusControl(null);
            }
        }
privatevoidOnListGUI()
        {
//滚动视图listScroll=GUILayout.BeginScrollView(listScroll);
            {
GUIStyleversionStyle=newGUIStyle(GUI.skin.label) { fontStyle=FontStyle.Italic };
for (inti=0; i<packages.Count; i++)
                {
List<PackageTemplate>list=packages[i];
PackageTemplatefirst=list[0];
if (!string.IsNullOrEmpty(searchContent) &&!first.name.ToLower().Contains(searchContent.ToLower())) continue;
if (foldoutDic[first.name])
                    {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
for (intn=0; n<list.Count; n++)
                        {
GUILayout.BeginHorizontal(currentSelected==list[n] ?"SelectionRect" : "IN Title");
GUILayout.FlexibleSpace();
GUILayout.Label(list[n].version, versionStyle);
GUILayout.Space(30f);
GUILayout.EndHorizontal();
if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                            {
currentSelected=list[n];
Event.current.Use();
                            }
                        }
                    }
else                    {
GUILayout.BeginHorizontal(currentSelected==first?"SelectionRect" : "Toolbar");
                        {
foldoutDic[first.name] =EditorGUILayout.Foldout(foldoutDic[first.name], first.name);
GUILayout.FlexibleSpace();
GUILayout.Label(first.version, versionStyle);
                        }
GUILayout.EndHorizontal();
//鼠标点击选中if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) &&Event.current.type==EventType.MouseDown)
                        {
currentSelected=first;
Event.current.Use();
                        }
                    }
                }
            }
GUILayout.EndScrollView();
//分割线GUILayout.Box(string.Empty, "EyeDropperHorizontalLine", GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//水平布局 设置高度GUILayout.BeginHorizontal(GUILayout.Height(23f));
            {
//最后更新日期GUILayout.Label(lastUpdateDate);
//刷新按钮if (GUILayout.Button(EditorGUIUtility.IconContent("Refresh"), GUILayout.Width(30f)))
                {
//清空当前的资源包信息列表packages.Clear();
//清空折叠栏信息foldoutDic.Clear();
//当前选中的资源包设为空currentSelected=null;
//发起网络请求EditorCoroutineRunner.StartEditorCoroutine(GetPackagesInfo());
                }
            }
GUILayout.EndHorizontal();
        }
privatevoidOnDetailGUI()
        {
if (currentSelected!=null)
            {
//名称GUILayout.Label(currentSelected.name, newGUIStyle(GUI.skin.label) { fontSize=25, fontStyle=FontStyle.Bold });
EditorGUILayout.Space();
//作者GUILayout.Label(currentSelected.author, newGUIStyle(GUI.skin.label) { fontSize=12 });
EditorGUILayout.Space();
//版本 + 发布日期GUILayout.Label($"Version {currentSelected.version} - {currentSelected.releasedDate}", newGUIStyle(GUI.skin.label) { fontSize=14, fontStyle=FontStyle.Bold });
EditorGUILayout.Space();
//分割线GUILayout.Box(string.Empty, GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//简介GUILayout.Label(currentSelected.description);
            }
GUILayout.FlexibleSpace();
//分割线GUILayout.Box(string.Empty, "EyeDropperHorizontalLine", GUILayout.ExpandWidth(true), GUILayout.Height(1f));
//水平布局 设置高度GUILayout.BeginHorizontal(GUILayout.Height(21f));
            {
GUILayout.FlexibleSpace();
//下载并导入if (GUILayout.Button("Import", GUILayout.Width(50f)))
                {
if (currentSelected!=null)
                    {
EditorCoroutineRunner.StartEditorCoroutine(DownloadPackage(currentSelected));
                    }
                }
            }
GUILayout.EndHorizontal();
        }
//获取资源包信息privateIEnumeratorGetPackagesInfo()
        {
stringurl="http://192.168.3.27:8888/packages.txt";
WWWwww=newWWW(url);
yieldreturnwww;
if (www.error==null)
            {
//反序列化List<PackageTemplate>list=JsonMapper.ToObject<List<PackageTemplate>>(www.text);
//遍历列表for (inti=0; i<list.Count; i++)
                {
varpackage=list[i];
//查找列表中是否已经存在该资源包其他版本intindex=packages.FindIndex(m=>m!=null&&m.Count>0&&m[0].name==package.name);
if (index==-1)
                    {
varnewList=newList<PackageTemplate> { package };
packages.Add(newList);
foldoutDic.Add(package.name, false);
                    }
else                    {
packages[index].Add(package);
                    }
                }
//更新最后刷新日期lastUpdateDate=DateTime.Now.ToString();
            }
else            {
Debug.LogError(www.error);
            }
        }
//下载并导入资源包privateIEnumeratorDownloadPackage(PackageTemplatepackage)
        {
stringurl=$"http://192.168.3.27:8888/packages/{package.name}/{package.version}.unitypackage";
WWWwww=newWWW(url);
yieldreturnwww;
if (www.error==null)
            {
byte[] bytes=www.bytes;
stringpath=$"{Application.dataPath}/{package.name}-{package.version}.unitypackage";
//写入本地using (FileStreamfs=newFileStream(path, FileMode.Create))
                {
fs.Write(bytes, 0, bytes.Length);
                }
//导入AssetDatabase.ImportPackage(path, false);
//删除File.Delete(path);
            }
else            {
Debug.LogError(www.error);
            }
        }
    }
publicstaticclassEditorCoroutineRunner    {
privateclassEditorCoroutine : IEnumerator        {
privateStack<IEnumerator>executionStack;
publicEditorCoroutine(IEnumeratoriterator)
            {
executionStack=newStack<IEnumerator>();
executionStack.Push(iterator);
            }
publicboolMoveNext()
            {
IEnumeratori=executionStack.Peek();
if (i.MoveNext())
                {
objectresult=i.Current;
if (result!=null&&resultisIEnumerator)
                    {
executionStack.Push((IEnumerator)result);
                    }
returntrue;
                }
else                {
if (executionStack.Count>1)
                    {
executionStack.Pop();
returntrue;
                    }
                }
returnfalse;
            }
publicvoidReset()
            {
thrownewNotSupportedException("This Operation Is Not Supported.");
            }
publicobjectCurrent            {
get { returnexecutionStack.Peek().Current; }
            }
publicboolFind(IEnumeratoriterator)
            {
returnexecutionStack.Contains(iterator);
            }
        }
privatestaticList<EditorCoroutine>editorCoroutineList;
privatestaticList<IEnumerator>buffer;
publicstaticIEnumeratorStartEditorCoroutine(IEnumeratoriterator)
        {
if (editorCoroutineList==null)
            {
editorCoroutineList=newList<EditorCoroutine>();
            }
if (buffer==null)
            {
buffer=newList<IEnumerator>();
            }
if (editorCoroutineList.Count==0)
            {
EditorApplication.update+=Update;
            }
buffer.Add(iterator);
returniterator;
        }
privatestaticboolFind(IEnumeratoriterator)
        {
foreach (EditorCoroutineeditorCoroutineineditorCoroutineList)
            {
if (editorCoroutine.Find(iterator))
                {
returntrue;
                }
            }
returnfalse;
        }
privatestaticvoidUpdate()
        {
editorCoroutineList.RemoveAll(coroutine=> { returncoroutine.MoveNext() ==false; });
if (buffer.Count>0)
            {
foreach (IEnumeratoriteratorinbuffer)
                {
if (!Find(iterator))
                    {
editorCoroutineList.Add(newEditorCoroutine(iterator));
                    }
                }
buffer.Clear();
            }
if (editorCoroutineList.Count==0)
            {
EditorApplication.update-=Update;
            }
        }
    }
}

image.gif

image.gif

还可以拓展的东西很多,比如资源包的依赖项、比如资源包的Example示例等等。

目录
相关文章
|
13小时前
|
人工智能 定位技术 图形学
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
6 1
|
13小时前
|
图形学
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(下)
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统
6 0
|
19小时前
|
图形学
【unity实战】实现实体子弹射击
【unity实战】实现实体子弹射击
5 0
|
18小时前
|
图形学
【unity实战】实现蓄力丢手榴弹、烟雾弹、燃烧弹的效果
【unity实战】实现蓄力丢手榴弹、烟雾弹、燃烧弹的效果
2 0
|
18小时前
|
图形学
【unity实战】FPS实现拾取和丢弃枪
【unity实战】FPS实现拾取和丢弃枪
5 0
|
21小时前
|
自然语言处理 图形学
【unity实战】一个通用的FPS枪支不同武器射击控制脚本
【unity实战】一个通用的FPS枪支不同武器射击控制脚本
4 0
|
21小时前
|
定位技术 图形学 开发者
【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)
【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)
3 0
|
21小时前
|
定位技术 图形学
【unity实战】实现一个放置3d物品建造装修系统(附项目源码)
【unity实战】实现一个放置3d物品建造装修系统(附项目源码)
4 0
|
21小时前
|
定位技术 图形学
【Unity实战】零代码实现物理2d绳子和绳桥效果——Hinge Joint 2D的使用
【Unity实战】零代码实现物理2d绳子和绳桥效果——Hinge Joint 2D的使用
4 0
|
21小时前
|
图形学
【unity实战】基于权重的随机事件(附项目源码)
【unity实战】基于权重的随机事件(附项目源码)
5 0