项目需要,1.需提供给美术合图集工具,2.图集更新功能,使得新图集内的子图不必重新拖拽定位
解决思路:
1.unity调用外部TexturePacker命令行工具执行合图,unity根据TP生成的数据对图集切割出多个sprite
2.图集内的子图拖拽定位时,资源内产生对应的GUID及FileID。因此新图集更新对应的GUID与FileID即可,但是API没找到相应的接口,因此从资源本身下手通过txt方式打开,强制替换相应的ID
参考文档:
https://blog.csdn.net/pdw_jsp/article/details/83623150?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159900871219725264608797%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=159900871219725264608797&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_blog_v1-4-83623150.pc_v2_rank_blog_v1&utm_term=TexturePacker+%E4%B8%80%E9%94%AE%E6%89%93%E5%8C%85&spm=1018.2118.3001.4187
https://www.xuanyusong.com/archives/4207
下面三个文件,放置于工程Assets\Editor即可
using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEditor;
using System.Text;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Collections;
using LuaFramework;
public class TexturePackerBuild : Editor
{
private const string BuildDir = "Assets/TexturePacker/打包该图集";
///
/// TexturePacker.exe ###https://www.codeandweb.com/texturepacker/documentation/texture-settings
///
private const string Paramet = " --sheet {0}.png --data {1}.xml --format sparrow --trim-mode CropKeepPos --pack-mode Best --algorithm MaxRects --max-size 4096 --size-constraints POT --disable-rotation {2}";
///
/// 输出目录
///
private const string OutPutDirRoot = "Assets/TPSpriteBuild/";
private static string tpDir = "";
private static string argument = "";
[MenuItem(BuildDir, false)]
public static void BuildOneTP()
{
if (Directory.Exists(OutPutDirRoot) == false)
{
Directory.CreateDirectory(OutPutDirRoot);
}
var assetPaths = Selection.assetGUIDs.Select(AssetDatabase.GUIDToAssetPath).ToList();
List filePathList = new List();
foreach (var assetPath in assetPaths)
{
if (AssetDatabase.IsValidFolder(assetPath))
{
string[] filesPath = Directory.GetFiles(assetPath);
foreach (var filePath in filesPath)
{
if (filePathList.Contains(filePath) == false)
{
filePathList.Add(filePath);
}
else
{
UnityEngine.Debug.LogErrorFormat("检测到相同文件 {0}", assetPath);
}
}
}
else
{
filePathList.Add(assetPath);
}
}
Dictionary fileDic = new Dictionary();
foreach (var path in filePathList)
{
var dir = Path.GetFileName(Path.GetDirectoryName(path));
var fileName = Path.GetFileName(path);
UnityEngine.Debug.LogFormat("_TP dirName {0} _TP fileName {1} _TP fullPath {2}", dir, fileName, path);
if (fileDic.ContainsKey(dir) == false)
{
FileData fileData = new FileData();
fileData.filesList = new List();
fileDic[dir] = fileData;
fileDic[dir].dirName = dir;
}
fileDic[dir].filesList.Add(path);
}
foreach (var data in fileDic)
{
TexturePackerBuild tpBuild = new TexturePackerBuild();
tpBuild.Build(data.Value);
}
}
public void Build(FileData data)
{
StringBuilder sb = new StringBuilder("");
GetImageName(data.filesList, ref sb);
string sheetName = OutPutDirRoot + data.dirName;
argument = string.Format(Paramet, sheetName, sheetName, sb.ToString());
tpDir = PlayerPrefs.GetString("_TPInstallDir", "");
RunExternalApp(tpDir, argument);
}
private StringBuilder GetImageName(List fileName, ref StringBuilder sb)
{
foreach (var file in fileName)
{
string extenstion = Path.GetExtension(file);
if (extenstion == ".png")
{
sb.Append(file);
sb.Append(" ");
}
}
return sb;
}
private void RunExternalApp(string command, string argument)
{
UnityEngine.Debug.Log("TPSprite Recover");
ClearOtherFiles();
ProcessStartInfo start = new ProcessStartInfo(command);
start.Arguments = argument;
start.CreateNoWindow = false;
start.ErrorDialog = true;
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true;
start.RedirectStandardInput = true;
start.StandardOutputEncoding = System.Text.UTF8Encoding.UTF8;
start.StandardErrorEncoding = System.Text.UTF8Encoding.UTF8;
try
{
Process p = Process.Start(start);
p.WaitForExit();
p.Close();
AssetDatabase.Refresh();
bool isNullFile; // 判断输出目录下文件是否为空
string outPngPath;
BuildTexturePacker(out isNullFile,out outPngPath);
if (isNullFile == false)
{
tpDir = command;
PlayerPrefs.SetString("_TPInstallDir", command);
SelectAtlasUpdataWindow(outPngPath);
}
else
{
// 输出文件为空,猜测exe使用不正确,重新导入exe
OnError("输出目录下未检测到图片,请重新导入");
}
}
catch (Exception ex)
{
OnError(ex.Message);
}
}
///
/// 清理输出目录下原始图
///
private void ClearOtherFiles()
{
string[] fileName = Directory.GetFiles(OutPutDirRoot);
if (fileName != null && fileName.Length > 0)
{
for (int i = 0; i < fileName.Length; i++)
{
string extenstion = Path.GetExtension(fileName[i]);
if (extenstion == ".png" || extenstion == ".xml")
{
File.Delete(fileName[i]);
}
}
}
}
///
/// 创建Texture
///
///
///
public void BuildTexturePacker(out bool isNull, out string outPngPath)
{
isNull = true;
outPngPath = "";
string[] imagePath = Directory.GetFiles(OutPutDirRoot);
foreach (string path in imagePath)
{
if (Path.GetExtension(path) == ".png" || Path.GetExtension(path) == ".PNG")
{
Texture2D texture = AssetDatabase.LoadAssetAtPath(path);
string rootPath = OutPutDirRoot + texture.name;
string pngPath = rootPath + "/" + texture.name + "_" + DateTime.Now.ToString("hh_mm_ss") + ".png";
outPngPath = pngPath;
isNull = false;
if (Directory.Exists(rootPath) == false)
{
Directory.CreateDirectory(rootPath);
}
File.Copy(OutPutDirRoot + texture.name + ".png", pngPath);
AssetDatabase.Refresh();
FileStream fs = new FileStream(OutPutDirRoot + texture.name + ".xml", FileMode.Open);
StreamReader sr = new StreamReader(fs);
string jText = sr.ReadToEnd();
fs.Close();
sr.Close();
WriteMeta(jText, pngPath);
}
}
ClearOtherFiles();
AssetDatabase.Refresh();
}
void SelectAtlasUpdataWindow(string pngPath)
{
TPSelectFileWindow win = new TPSelectFileWindow();
win.Popup(pngPath, UpdataAssetRes);
}
void UpdataAssetRes(string opPath, Texture pendingTex)
{
var srcObj = AssetDatabase.LoadAssetAtPath(opPath);
TPFindReplaceRes tpFindRep = new TPFindReplaceRes();
tpFindRep.OnTPReplaceAssetData(pendingTex, srcObj);
}
//写信息到SpritesSheet里
void WriteMeta(string jText, string path)
{
UnityEngine.Debug.Log("WriteMeta " + path);
XmlDocument xml = new XmlDocument();
xml.LoadXml(jText);
XmlNodeList elemList = xml.GetElementsByTagName("SubTexture");
Texture2D texture = AssetDatabase.LoadAssetAtPath(path);
string impPath = AssetDatabase.GetAssetPath(texture);
TextureImporter asetImp = TextureImporter.GetAtPath(impPath) as TextureImporter;
SpriteMetaData[] metaData = new SpriteMetaData[elemList.Count];
for (int i = 0, size = elemList.Count; i < size; i++)
{
XmlElement node = (XmlElement)elemList.Item(i);
Rect rect = new Rect();
rect.x = int.Parse(node.GetAttribute("x"));
rect.y = texture.height - int.Parse(node.GetAttribute("y")) - int.Parse(node.GetAttribute("height"));
rect.width = int.Parse(node.GetAttribute("width"));
rect.height = int.Parse(node.GetAttribute("height"));
metaData[i].rect = rect;
metaData[i].pivot = new Vector2(0.5f, 0.5f);
metaData[i].name = node.GetAttribute("name");
}
asetImp.spritesheet = metaData;
asetImp.textureType = TextureImporterType.Sprite;
asetImp.spriteImportMode = SpriteImportMode.Multiple;
asetImp.mipmapEnabled = false;
asetImp.SaveAndReimport();
}
private string OpenTPEXE()
{
string path = EditorUtility.OpenFilePanel("请定位TexturePacker.exe程序,用于后续操作", "", "exe");
return path;
}
private void OnError(string err)
{
UnityEngine.Debug.LogError("TPBuild Err:" + err);
EditorUtility.DisplayDialog("请重新定位TexturePacker.exe程序!", "Err:" + err, "确定");
var path = OpenTPEXE();
if (path.Length != 0)
{
RunExternalApp(path, argument);
}
}
public class FileData
{
public string dirName;
public List filesList;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
///
/// 编辑器主界面
///
public class TPSelectFileWindow : EditorWindow {
private Action _callBack;
private string _opPath;
private Texture _selectTex;
public void Popup(string opPath, Action cb)
{
_callBack = cb;
_opPath = opPath;
var isSeek = EditorUtility.DisplayDialog("等待执行...", "是否更新原有的图集, 目前仅支持更新单个图集", "确定", "取消");
if (isSeek == true)
{
TPSelectFileWindow window = EditorWindow.GetWindow(typeof(TPSelectFileWindow), true, "等待执行...") as TPSelectFileWindow;
window.minSize = new Vector2(450, 300);
window.Show();
}
else
{
if (_callBack != null)
{
_callBack.Invoke(_opPath, _selectTex);
}
_callBack = null;
}
}
private void OnGUI()
{
ShowEditorGUI();
}
private void ShowEditorGUI()
{
_selectTex = EditorGUILayout.ObjectField("添加原绑定的Texture", _selectTex, typeof(Texture), true) as Texture;
if (_selectTex != null && string.IsNullOrEmpty(_selectTex.name) == false)
{
EditorGUILayout.TextField(AssetDatabase.GetAssetOrScenePath(_selectTex));
}
if (GUILayout.Button("关闭窗口并继续执行"))
{
//关闭窗口
this.Close();
}
}
private void OnDestroy()
{
if (_callBack != null)
{
_callBack.Invoke(_opPath, _selectTex);
}
_callBack = null;
_selectTex = null;
}
}
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
public class TPFindReplaceRes {
Dictionary assetDic;
string srcGUID;
string tarGUID;
///
/// 更新Asset数据
///
/// 检索已定位过的OBJ,操作后可废弃
/// 等待更新数据干净的OBJ
public void OnTPReplaceAssetData(Object srcObj, Object tarObj) {
AssetDatabase.Refresh();
EditorSettings.serializationMode = SerializationMode.ForceText;
string srcPath = AssetDatabase.GetAssetPath(srcObj);
string tarPath = AssetDatabase.GetAssetPath(tarObj);
if (string.IsNullOrEmpty(tarPath) || string.IsNullOrEmpty(srcPath))
{
Debug.Log("srcObj = null || tarObj = null");
return;
}
Debug.LogFormat("srcPath:{0} , tarPath:{1}", srcPath, tarPath);
srcGUID = AssetDatabase.AssetPathToGUID(srcPath);
tarGUID = AssetDatabase.AssetPathToGUID(tarPath);
OnCreateMetaData(srcPath, tarPath);
var withoutExtensions = new List() { ".prefab", ".unity", ".mat", ".asset" };
string[] files = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories).Where(s => withoutExtensions.Contains(Path.GetExtension(s).ToLower())).ToArray();
int startIndex = 0;
EditorApplication.update = delegate () {
string file = files[startIndex];
bool isCancel = EditorUtility.DisplayCancelableProgressBar("匹配资源中", file, (float)startIndex / (float)files.Length);
if (Regex.IsMatch(File.ReadAllText(file), srcGUID))
{
OnUpdateData(file);
var obj = AssetDatabase.LoadAssetAtPath(GetRelativeAssetsPath(file));
Debug.Log("替换的对象 " + file, obj);
}
startIndex++;
if (isCancel || startIndex >= files.Length)
{
EditorUtility.ClearProgressBar();
EditorApplication.update = null;
startIndex = 0;
Debug.Log("更新数据结束");
}
};
}
void OnCreateMetaData(string srcPath, string tarPath)
{
string srcMeta = AssetDatabase.GetTextMetaFilePathFromAssetPath(srcPath);
string tarMeta = AssetDatabase.GetTextMetaFilePathFromAssetPath(tarPath);
string[] srcFile = File.ReadAllLines(srcMeta);
string[] tarFile = File.ReadAllLines(tarMeta);
List smList = new List();
List tempList = new List();
TextureImporter srcImp = TextureImporter.GetAtPath(srcPath) as TextureImporter;
TextureImporter tarImp = TextureImporter.GetAtPath(tarPath) as TextureImporter;
foreach (var spMD in tarImp.spritesheet)
{
tempList.Add(spMD.name);
}
foreach (var spMD in srcImp.spritesheet)
{
if (tempList.Contains(spMD.name))
{
smList.Add(spMD.name);
//Debug.Log("匹配SpriteSheetMeta " + spMeta.name);
}
}
assetDic = new Dictionary();
foreach (var key in smList)
{
AssetData data = new AssetData();
data.name = key;
assetDic.Add(key, data);
foreach (var srcStr in srcFile)
{
if (srcStr.IndexOf(key) >= 0)
{
var str = srcStr.Trim();
var spMetaId = str.Substring(0, str.IndexOf(":"));
//Debug.LogFormat("src ==> {0} {1}", key, spMetaId);
if (string.IsNullOrEmpty(assetDic[key].src))
{
assetDic[key].src = spMetaId;
}
}
}
foreach (var tarStr in tarFile)
{
if (tarStr.IndexOf(key) >= 0)
{
var str = tarStr.Trim();
var spMetaId = str.Substring(0, str.IndexOf(":"));
//Debug.LogFormat("tar ==> {0} {1}", key, spMetaId);
if (string.IsNullOrEmpty(assetDic[key].tar))
{
assetDic[key].tar = spMetaId;
}
}
}
if (string.IsNullOrEmpty(assetDic[key].src) || string.IsNullOrEmpty(assetDic[key].tar))
{
assetDic.Remove(key);
}
}
//foreach (var meta in assetDic)
//{
// var data = meta.Value;
// Debug.LogFormat("meta ==> name:{0} src:{1} tar:{2}", data.name, data.src, data.tar);
//}
}
void OnUpdateData(string file)
{
var allInfo = File.ReadAllLines(file);
for (int i = 0; i < allInfo.Length; i++)
{
var str = allInfo[i];
if (str.IndexOf(srcGUID) >= 0)
{
if (str.IndexOf("guid") >= 0)
{
// 替换旧的GUID
str = str.Replace(srcGUID, tarGUID);
// 替换旧的fileID
if (str.IndexOf("fileID") >= 0)
{
foreach (var meta in assetDic)
{
if (str.IndexOf(meta.Value.src) >= 0)
{
str = str.Replace(meta.Value.src, meta.Value.tar);
}
}
}
}
allInfo[i] = str;
}
}
File.WriteAllLines(file, allInfo);
}
static string GetRelativeAssetsPath(string path) {
return "Assets" + Path.GetFullPath(path).Replace(Path.GetFullPath(Application.dataPath), "").Replace('\\', '/');
}
public class AssetData
{
public string name;
public string src;
public string tar;
}
}