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

目录
相关文章
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
构建智能化编程环境:AI 与代码编辑器的融合
在人工智能的推动下,未来的代码编辑器将转变为智能化编程环境,具备智能代码补全、自动化错误检测与修复、个性化学习支持及自动化代码审查等功能。本文探讨了其核心功能、技术实现(包括机器学习、自然语言处理、深度学习及知识图谱)及应用场景,如辅助新手开发者、提升高级开发者效率和优化团队协作。随着AI技术进步,智能化编程环境将成为软件开发的重要趋势,变革开发者工作方式,提升效率,降低编程门槛,并推动行业创新。
|
4天前
|
前端开发
业余时间开发了个海报编辑器
为了满足撰写博客或录制教程视频时对高质量海报的需求,我利用业余时间开发了一款海报编辑器。第一版功能简单,支持固定尺寸、黑底白字的标题。后来经过优化,增加了背景图、模糊效果、文字样式调整等功能,使海报更具吸引力。目前该编辑器已上线,欢迎大家试用并反馈。[访问海报编辑器](https://tool.share888.top/#/poster)
42 6
业余时间开发了个海报编辑器
|
3月前
|
存储 安全 数据安全/隐私保护
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
143 0
|
2月前
|
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 )的使用
|
1月前
|
运维 Java Linux
【运维基础知识】掌握VI编辑器:提升你的Java开发效率
本文详细介绍了VI编辑器的常用命令,包括模式切换、文本编辑、搜索替换及退出操作,帮助Java开发者提高在Linux环境下的编码效率。掌握这些命令,将使你在开发过程中更加得心应手。
32 2
|
3月前
|
JavaScript
基于Vue2.X/Vue3.X对Monaco Editor在线代码编辑器进行封装与使用
这篇文章介绍了如何在Vue 2.X和Vue 3.X项目中封装和使用Monaco Editor在线代码编辑器,包括安装所需依赖、创建封装组件、在父组件中调用以及处理Vue 3中可能遇到的问题。
603 1
基于Vue2.X/Vue3.X对Monaco Editor在线代码编辑器进行封装与使用
|
3月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
172 6
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
180 3
|
3月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
150 3
|
3月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
351 1