需求
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這個腳本的位置,但事實上不建議大家這么做。
- 其實,這個解決方案只是為了實現編輯器腳本配置文件自動跟隨而作,動圖演示的貌似用處不大。
總結
這是一個簡單的筆記,希望遇到有需要的人~