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示例等等。

目录
相关文章
|
11天前
|
机器学习/深度学习 人工智能 自然语言处理
构建智能化编程环境:AI 与代码编辑器的融合
在人工智能的推动下,未来的代码编辑器将转变为智能化编程环境,具备智能代码补全、自动化错误检测与修复、个性化学习支持及自动化代码审查等功能。本文探讨了其核心功能、技术实现(包括机器学习、自然语言处理、深度学习及知识图谱)及应用场景,如辅助新手开发者、提升高级开发者效率和优化团队协作。随着AI技术进步,智能化编程环境将成为软件开发的重要趋势,变革开发者工作方式,提升效率,降低编程门槛,并推动行业创新。
|
2月前
|
存储 安全 数据安全/隐私保护
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
79 0
|
6天前
|
JavaScript 前端开发 API
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
本文介绍了如何在Vue 3项目中使用v-md-editor组件库来创建markdown编辑器和预览组件。文章提供了安装步骤、如何在main.js中进行全局配置、以及如何在页面中使用VMdEditor和VMdPreview组件的示例代码。此外,还提供了一个完整示例的链接,包括编辑器和预览组件的使用效果和代码。
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
|
2月前
|
JavaScript
基于Vue2.X/Vue3.X对Monaco Editor在线代码编辑器进行封装与使用
这篇文章介绍了如何在Vue 2.X和Vue 3.X项目中封装和使用Monaco Editor在线代码编辑器,包括安装所需依赖、创建封装组件、在父组件中调用以及处理Vue 3中可能遇到的问题。
308 1
基于Vue2.X/Vue3.X对Monaco Editor在线代码编辑器进行封装与使用
|
2月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
39 1
|
2月前
|
算法 vr&ar C#
使用Unity进行虚拟现实开发:深入探索与实践
【8月更文挑战第24天】使用Unity进行虚拟现实开发是一个充满挑战和机遇的过程。通过掌握Unity的VR开发技术,你可以创造出令人惊叹的VR体验,为用户带来前所未有的沉浸感和乐趣。随着技术的不断进步和应用场景的不断拓展,VR开发的未来充满了无限可能。希望本文能为你提供有用的指导和启发!
|
2月前
|
存储 JavaScript 前端开发
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
文章展示了在Vue项目中通过集成Quill富文本编辑器实现公告功能的完整开发过程,包括前端的公告发布、修改、删除操作以及后端的数据存储和处理逻辑。
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
|
1月前
一款非常棒的十六进制编辑器 —— 010 Editor
一款非常棒的十六进制编辑器 —— 010 Editor
|
1月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
48 0
|
1月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
108 0
下一篇
无影云桌面