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月前
|
缓存 API 开发工具
有关Unity使用Rider编辑器无法弹出代码提示的有效解决方法
【11月更文挑战第13天】在 Unity 中使用 Rider 编辑器时,若遇到代码提示无法弹出的问题,可以通过检查 Rider 设置(如自动补全选项、Unity 插件安装、索引设置)、Unity 项目设置(如解决方案正确关联、脚本导入设置)以及环境和依赖关系(如 .NET SDK 版本兼容性、Unity 和 Rider 版本兼容性)等方面进行排查和解决。
233 5
|
4月前
|
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 )的使用
|
5月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
223 3
|
5月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
491 1
|
4月前
一款非常棒的十六进制编辑器 —— 010 Editor
一款非常棒的十六进制编辑器 —— 010 Editor
|
4月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
406 0
|
5月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
173 0
|
5月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
147 0
|
5月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
260 6
|
5月前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
378 5