需求
Unity3d编辑器脚本时而不时会有数据持久化需求,换句话说就是写入数据、保存配置的需求,怎么保存数据先不谈,但我们都知道,想要自己写的工具不污染整个工程的结构,往往我们需要将配置文件(生成的新文件)跟工具脚本整一起,或上一个目录,或同级目录,或同级下的子目录。
于是,“在指定脚本的周边目录下动态创建文件夹”的需求就这样应运而生啦!
代码
简单的描述完这个需求的使用场景,下面我们直接上代码:
using System.IO;
using System.Linq;
#if UNITY_EDITOR
using ADB = UnityEditor.AssetDatabase;
#endif
public static class FolderMaker
{
/// <summary>
/// 编辑器下使用,给定一个类的对象,在这个类的同级目录下创建文件夹并返回路径
/// </summary>
/// <typeparam name="T">类</typeparam>
/// <param name="script">对象</param>
/// <param name="subPath">指定要创建的文件夹的名称</param>
/// <returns>文件夹的相对路径,相对于Assets文件夹</returns>
public static string Creat<T>(T script, string subPath) where T : UnityEngine.Object //class
{
string newPath = "";
#if UNITY_EDITOR
string path = ADB.FindAssets("t:Script")
.Where(v => Path.GetFileNameWithoutExtension(ADB.GUIDToAssetPath(v)) == script.GetType().Name)
.Select(id => ADB.GUIDToAssetPath(id))
.FirstOrDefault()
.ToString();
//newPath = path.Remove(path.LastIndexOf("/") + 1, Path.GetFileName(path).Length) + subPath;
newPath =Path.Combine( Path.GetDirectoryName(path) ,subPath);
if (!ADB.IsValidFolder(newPath))
{
newPath = ADB.GUIDToAssetPath(ADB.CreateFolder(path, subPath));
}
#endif
return newPath;
}
}
/// <summary>
/// 编辑器下使用,给定一个类型,在这个类的周边目录下创建文件夹并返回路径
/// </summary>
/// <param name="script">对象</param>
/// <param name="subPath">指定要创建的文件夹的名称</param>
/// <returns>文件夹的相对路径,相对于Assets文件夹</returns>
public static string Creat(Type script, string subPath)
{
#if UNITY_EDITOR
string path = ADB.FindAssets("t:Script")
.Where(v => Path.GetFileNameWithoutExtension(ADB.GUIDToAssetPath(v)) == script.Name)
.Select(id => ADB.GUIDToAssetPath(id))
.FirstOrDefault()
.ToString();
path = Path.GetDirectoryName(path); //去除文件名
path = Path.GetFullPath(path + "/" + subPath); //整合到完整路径,使用"/../" 回退到上一目录
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
#endif
return path;
}
Tips:
-
using的使用:AssetDatabase 这个类用的简直不要太频繁,使用
using ADB = UnityEditor.AssetDatabase;
给他取个别名“ADB”。 - 预编译指令的使用:预编译指令是必不可少的,如果这个脚本处于Editor文件夹下,继承MonoBehavior的类将无法访问。挪出来就挺不错,但此时如不使用预编译指令,打包便会报错~
- 泛型的使用:泛型的使用使得逻辑复用,同时也能类型安全,where T:UnityEngine.Object 限制了使用环境是Unity,如果限制仅仅为 class ,嗯,随便一个字符串就能打发第一个参数,当然运行也就得不到我们想要的结果,报错呢,也肯定是要报的了。
- 实现思路,就是轮番使用UnityEditor下的API,查找到这个脚本,拿到脚本所在的目录拼接并创建新的目录。
2019年1月11日 补充:
- 可以看到笔者在上面提供的代码块中又重载了这个方法,通过这个重载,现在不提供实例也能获取路径啦。
- 另外,IO操作大幅回归System.IO的API,主要是为了解决2个问题:
- 得到上一个文件夹路径 ,使用
File.GetFullPath(path)
,只要在传的参数中拼接 “../” 就能得到上一级目录,拼接多个就能回退多步。 - 递归创建文件夹,只需给定一个路径就好。
- 得到上一个文件夹路径 ,使用
使用方法:↓
//返回脚本所在的目录的上两级,然后创建2个文件夹
var path =FolderMaker.Creat(typeof(TagEnumGenarator),@"/../../aa/Bb") + "/EnumTag.cs";
2019年1月18日 补充
/// <summary>
/// 编辑器下使用,给定一个类型,在这个类的周边目录下创建文件夹并返回路径
/// </summary>
/// <param name="script">对象</param>
/// <param name="subPath">指定要创建的文件夹的名称</param>
/// <returns>文件夹的相对路径,相对于Assets文件夹</returns>
public static string Creat(MonoBehaviour script, string subPath)
{
#if UNITY_EDITOR
MonoScript m_Script = MonoScript.FromMonoBehaviour(script); //更新 使用UnityEditor API
string path = AssetDatabase.GetAssetPath(m_Script);
path = Path.GetDirectoryName(path); //去除文件名
path = Path.GetFullPath(path + "/" + subPath); //整合到完整路径,使用"/../" 回退到上一目录
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
#endif
return path;
}
很多时候,Unity 要的是相对于Assets 文件夹的相对路径,加一句就行了,顺便改下方法名,感觉这个方法名与功能才是真的对口
/// <summary>
/// 编辑器下使用,给定一个类型,在这个类的周边目录下创建文件夹并返回相对路径
/// </summary>
/// <param name="script">ScriptableObject 对象</param>
/// <param name="subPath">指定要创建的文件夹的名称</param>
/// <returns>文件夹的相对路径,相对于Assets文件夹</returns>
public static string AllocateLocalPath(ScriptableObject script, string subPath)
{
#if UNITY_EDITOR
MonoScript m_Script = MonoScript.FromScriptableObject(script); //更新 使用UnityEditor API
string path = AssetDatabase.GetAssetPath(m_Script);
path = Path.GetDirectoryName(path); //去除文件名
path = Path.GetFullPath(path + "/" + subPath); //整合到完整路径,使用"/../" 回退到上一目录
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
path = path.Substring(path.IndexOf("Assets"));
#endif
return path;
}
Tips:
- 在Unity编辑器,脚本文件也是一个资源,并且是一个文本资源,由MonoScript表示,通过
UnityEditor.MonoScript.FromMonoBehaviour(MonoBehaviour script)
拿到。 - Unity编辑器模式下,有一套资源管理体系,
UnityEditor.AssetDatabase.GetAssetPath(Object assetObject)
就可以拿到脚本资源的路径。 - 从对上述提及的方法签名的描述可知,传入 MonoScript 实例就能得到脚本路径啦。
使用
使用没什么要说的,调用一下这个方法就好了,譬如:
using UnityEngine;
public class Car : MonoBehaviour
{
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Debug.Log(FolderMaker.Creat(this,"HAHA"+Random.Range(1,150)));
}
}
}
但是需要了解的是,尽管这个返回的路径是相对值,但很够用呀,如果想要完整路径的,加上这句就好了:
2024年7月3日 更新
通过以下方式也能获得脚本路径,但是需要区分这个路径是系统路径还是编辑器规则下的路径
var stack = new StackTrace(true);
var path = stack.GetFrame(0).GetFileName();
比如,Packages 中的路径就很奇怪,物理路径跟编辑器内路径是不一致的,下面截图表示:
系统路径 | Unity路径 |
---|---|
在系统的文件系统中 路径是:E:\Unity\Test\TestBuildLinux\Library\PackageCache\com.unity.editorcoroutines@1.0.0/README.md
然而在 Unity 中,他的路径是:Packages/com.unity.editorcoroutines/README.md
动画
- 在上面的动画中,每一次点击鼠标左键便会创建一个文件夹,文件夹名称以“HAHA”开头加上一串随机数。
- 另外,笔者在Unity播放状态拖拽并移动了Car这个脚本的位置,但事实上不建议大家这么做。
- 其实,这个解决方案只是为了实现编辑器脚本配置文件自动跟随而作,动图演示的貌似用处不大。
总结
这是一个简单的笔记,希望遇到有需要的人~