1023 lines
36 KiB
C#
Raw Permalink Normal View History

2025-11-11 15:19:01 +08:00
using log4net.Util;
2025-11-05 17:34:40 +08:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2025-11-11 15:19:01 +08:00
using System.Linq.Expressions;
2025-11-05 17:34:40 +08:00
using System.Reflection;
using TexturePacker;
2025-11-11 15:19:01 +08:00
using Unity.Collections;
2025-11-05 17:34:40 +08:00
using Unity.Mathematics;
using UnityEditor;
2025-11-11 15:19:01 +08:00
using UnityEditor.Formats.Fbx.Exporter;
2025-11-05 17:34:40 +08:00
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
public sealed class TexturePackerEditor : EditorWindow
{
[SerializeField]
private VisualTreeAsset m_VisualTreeAsset = default;
private TextureMapInfo textureMapInfo;
private Texture2D textureAtlas;
private Texture2DArray textureArray;
private IMGUIContainer imageListContainer;
private List<Texture2D> selectedTexs = new();
private List<Rect> texRects = new();
2025-11-11 15:19:01 +08:00
private static Type gameObjectInspectorType;
private List<Editor> gameObjectInspectorView = new();
private List<UnityEngine.Object> models = new();
private List<Rect> modelRects = new();
private Button btApplyMesh;
2025-11-05 17:34:40 +08:00
private Vector2 cellSize = new Vector2(100, 100);
private const int MaxArraySize = 255;
private int selectIndex = -1;
private int selectIndexForMove = -1;
private Vector2 toolbarOffset = new Vector2(0, 35);
private int atlasPadding = 0;
private static Func<UnityEngine.Object, bool, EditorWindow> fnOpenPropertyEditor;
private int textureSize = 512;
private EditorWindow atlasPropertiesWindow = null;
private EditorWindow arrayPropertiesWindow = null;
private Label imgCntLabel = null;
private Label infoLabel = null;
private GraphicsFormat graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
2025-11-11 15:19:01 +08:00
private bool dockPreviewWindowFail = false;
2025-11-05 17:34:40 +08:00
public enum ETextureSize
{
_128 = 1 << 7,
_256 = 1 << 8,
_512 = 1 << 9,
_1024 = 1 << 10,
}
2025-11-11 15:19:01 +08:00
static readonly object fbxExportSetting = null;
static Delegate exportObjectFunc;
2025-11-05 17:34:40 +08:00
static TexturePackerEditor()
{
var editorCore = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name.Equals("UnityEditor.CoreModule")).FirstOrDefault();
var type = editorCore.GetType("UnityEditor.PropertyEditor");
var methodInfo = type.GetMethod("OpenPropertyEditor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, null, new Type[] { typeof(UnityEngine.Object), typeof(bool) }, null);
fnOpenPropertyEditor = methodInfo.CreateDelegate(typeof(Func<UnityEngine.Object, bool, EditorWindow>)) as Func<UnityEngine.Object, bool, EditorWindow>;
2025-11-11 15:19:01 +08:00
gameObjectInspectorType = editorCore.GetType("UnityEditor.GameObjectInspector");
var fbxAssembly = Assembly.GetAssembly(typeof(ModelExporter));
var exportMethodInfo = typeof(ModelExporter).GetMethod("ExportObject", BindingFlags.NonPublic | BindingFlags.Static);
exportObjectFunc = exportMethodInfo.CreateDelegate(Expression.GetDelegateType(exportMethodInfo.GetParameters().Select(p => p.ParameterType).Concat(new[] { exportMethodInfo.ReturnType }).ToArray()));
Type exportModelSettingsSerialize = fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportModelSettingsSerialize");
fbxExportSetting = Activator.CreateInstance(exportModelSettingsSerialize);
foreach (var item in exportModelSettingsSerialize.GetMethods().Union(exportModelSettingsSerialize.BaseType.GetMethods()))
{
switch (item.Name)
{
case "SetExportFormat":
{
item.Invoke(fbxExportSetting, new object[] { 1 });
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+ExportFormat"), "1") });
break;
}
case "SetAnimatedSkinnedMesh":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
case "SetUseMayaCompatibleNames":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
case "SetModelAnimIncludeOption":
{
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+Include"), "0") });
break;
}
case "SetLODExportType":
{
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+LODExportType"), "0") });
break;
}
case "SetEmbedTextures":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
case "SetPreserveImportSettings":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
case "SetKeepInstances":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
case "SetExportUnredererd":
{
item.Invoke(fbxExportSetting, new object[] { false });
break;
}
default:
break;
}
}
2025-11-05 17:34:40 +08:00
}
[MenuItem("Tools/Performance/TexturePackerEditor")]
public static void OpenTexturePackerEditor()
{
2025-11-11 15:19:01 +08:00
TexturePackerEditor wnd = CreateInstance<TexturePackerEditor>();
wnd.titleContent = new GUIContent("TexturePackerEditor");
wnd.Show();
}
public static void OpenTexturePackerEditor(Texture2D[] textures)
{
TexturePackerEditor wnd = CreateInstance<TexturePackerEditor>();
2025-11-05 17:34:40 +08:00
wnd.titleContent = new GUIContent("TexturePackerEditor");
2025-11-11 15:19:01 +08:00
for (int i = 0; i < textures.Length; i++)
{
wnd.AddTexture(textures[i]);
}
wnd.Show();
2025-11-05 17:34:40 +08:00
}
[UnityEditor.Callbacks.OnOpenAsset(0)]
private static bool OnOpenTextureMapInfo(int instanceID, int line)
{
var textureMapInfo = EditorUtility.InstanceIDToObject(instanceID) as TextureMapInfo;
2025-11-11 15:19:01 +08:00
if (!textureMapInfo)
2025-11-05 17:34:40 +08:00
{
return false;
}
TexturePackerEditor wnd = GetWindow<TexturePackerEditor>();
wnd.titleContent = new GUIContent("TexturePackerEditor");
wnd.InitWithTextureMapInfo(textureMapInfo);
EditorApplication.delayCall += () =>
{
wnd.UpdateResult();
};
return false;
}
private static readonly Color btNormalColor = ColorUtils.ToRGBA(0xFF585858);
private static readonly Color btClikedColor = ColorUtils.ToRGBA(0xFF46607C);
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// Instantiate UXML
VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate();
root.Add(labelFromUXML);
var btArray = root.Q<Button>("btArray");
var btAtlas = root.Q<Button>("btAtlas");
var btSave = root.Q<Button>("btSave");
2025-11-11 15:19:01 +08:00
btApplyMesh = root.Q<Button>("btApplyMesh");
2025-11-05 17:34:40 +08:00
infoLabel = root.Q<Label>("infoLabel");
var slAtlasPadding = root.Q<SliderInt>("slAtlasPadding");
var textureSizeEnum = root.Q<EnumField>("textureSizeEnum");
imgCntLabel = root.Q<Label>("imgCntLabel");
root.Q<Toggle>("tgIsNormal").RegisterValueChangedCallback(b =>
{
textureMapInfo.IsNormalMap = b.newValue;
SetNormalOriginTextureFormat(!textureMapInfo.IsNormalMap);
UpdateResult();
});
textureSizeEnum.RegisterCallback<ChangeEvent<Enum>>((e) =>
{
var newTextureSize = (int)(ETextureSize)e.newValue;
2025-11-11 15:19:01 +08:00
if (textureSize != newTextureSize)
2025-11-05 17:34:40 +08:00
{
textureSize = newTextureSize;
UpdateResult();
}
});
btArray.clicked += () =>
{
btArray.style.backgroundColor = btClikedColor;
btAtlas.style.backgroundColor = btNormalColor;
textureMapInfo.PackType = TextureMapInfo.EPackType.Array;
slAtlasPadding.visible = false;
UpdateResult();
};
btAtlas.clicked += () =>
{
btAtlas.style.backgroundColor = btClikedColor;
btArray.style.backgroundColor = btNormalColor;
textureMapInfo.PackType = TextureMapInfo.EPackType.Atlas;
slAtlasPadding.visible = true;
UpdateResult();
};
btSave.clicked += Save;
btAtlas.style.backgroundColor = btClikedColor;
slAtlasPadding.RegisterCallback<ChangeEvent<int>>((i) =>
{
atlasPadding = i.newValue;
UpdateResult();
});
imageListContainer = root.Q<IMGUIContainer>("imgList");
imageListContainer.onGUIHandler = OnImageListGUI;
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
imageListContainer.RegisterCallback<MouseDownEvent>((e) =>
{
var mousePos = Event.current.mousePosition - toolbarOffset;
2025-11-11 15:19:01 +08:00
int clickIndex = -1;
2025-11-05 17:34:40 +08:00
for (int i = 0; i < texRects.Count; i++)
{
if (texRects[i].Contains(mousePos))
{
selectIndexForMove = i;
break;
}
}
2025-11-11 15:19:01 +08:00
if (Event.current.type == EventType.MouseDown && Event.current.clickCount == 2)
{
if (clickIndex == -1)
{
for (int i = 0; i < modelRects.Count; i++)
{
if (modelRects[i].Contains(mousePos))
{
clickIndex = i;
break;
}
}
if (clickIndex != -1)
{
EditorGUIUtility.PingObject(models[clickIndex]);
}
}
else if (clickIndex != -1)
{
EditorGUIUtility.PingObject(selectedTexs[clickIndex]);
}
}
2025-11-05 17:34:40 +08:00
});
imageListContainer.RegisterCallback<MouseUpEvent>((e) =>
{
2025-11-11 15:19:01 +08:00
var swapIndex = -1;
2025-11-05 17:34:40 +08:00
var mousePos = Event.current.mousePosition - toolbarOffset;
for (int i = 0; i < texRects.Count; i++)
{
if (texRects[i].Contains(mousePos))
{
2025-11-11 15:19:01 +08:00
swapIndex = i;
2025-11-05 17:34:40 +08:00
break;
}
}
2025-11-11 15:19:01 +08:00
if (swapIndex != -1 && selectIndexForMove != -1)
2025-11-05 17:34:40 +08:00
{
2025-11-11 15:19:01 +08:00
if (selectIndexForMove != swapIndex)
2025-11-05 17:34:40 +08:00
{
2025-11-11 15:19:01 +08:00
(selectedTexs[selectIndexForMove], selectedTexs[swapIndex]) = (selectedTexs[swapIndex], selectedTexs[selectIndexForMove]);
(texRects[selectIndexForMove], texRects[swapIndex]) = (texRects[swapIndex], texRects[selectIndexForMove]);
2025-11-05 17:34:40 +08:00
UpdateResult();
}
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
selectIndexForMove = -1;
});
2025-11-11 15:19:01 +08:00
imageListContainer.RegisterCallback<DragUpdatedEvent>((e) =>
{
var mousePos = Event.current.mousePosition - toolbarOffset;
var replaceIdx = -1;
for (int i = 0; i < modelRects.Count; i++)
{
if (modelRects[i].Contains(mousePos))
{
replaceIdx = i;
break;
}
}
if (replaceIdx != -1 && DragAndDrop.objectReferences.Length > 0)
{
if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
{
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
}
}
});
imageListContainer.RegisterCallback<DragExitedEvent>((e) =>
{
var mousePos = Event.current.mousePosition - toolbarOffset;
var replaceIdx = -1;
for (int i = 0; i < modelRects.Count; i++)
{
if (modelRects[i].Contains(mousePos))
{
replaceIdx = i;
break;
}
}
if (replaceIdx != -1 && DragAndDrop.objectReferences.Length > 0)
{
if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
{
ReplaceModel(replaceIdx, DragAndDrop.objectReferences[0]);
}
}
});
btApplyMesh.clicked += () =>
{
ModifyMesh();
};
2025-11-05 17:34:40 +08:00
}
private void SetNormalOriginTextureFormat(bool toNormal)
{
foreach (var item in selectedTexs)
{
var ti = TextureImporter.GetAtPath(AssetDatabase.GetAssetPath(item)) as TextureImporter;
2025-11-11 15:19:01 +08:00
if (toNormal)
2025-11-05 17:34:40 +08:00
{
ti.textureType = TextureImporterType.NormalMap;
}
else if (ti.textureType == TextureImporterType.NormalMap)
{
ti.textureType = TextureImporterType.Default;
ti.sRGBTexture = false;
}
ti.SaveAndReimport();
}
}
private void OnImageListGUI()
{
float windowWidth = position.width;
int dynamicColumns = Mathf.FloorToInt(windowWidth / (cellSize.x + 5));
int count = selectedTexs.Count;
int currentColumn = 0;
int currentRow = 0;
var bkColor = GUI.backgroundColor;
2025-11-11 15:19:01 +08:00
EditorGUILayout.LabelField("Texture:");
2025-11-05 17:34:40 +08:00
for (int i = 0; i < count; i++)
{
if (currentColumn == 0)
{
GUILayout.BeginHorizontal();
}
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(cellSize.x), GUILayout.Height(cellSize.y));
texRects[i] = rect;
if (i == selectIndex || selectIndexForMove != -1 && rect.Contains(Event.current.mousePosition - toolbarOffset))
{
GUI.backgroundColor = Color.blue;
}
else
{
GUI.backgroundColor = bkColor;
}
if (GUI.Button(rect, selectedTexs[i]))
{
if (selectIndex == i)
{
EditorGUIUtility.PingObject(selectedTexs[i]);
}
selectIndex = i;
}
currentColumn++;
if (currentColumn >= dynamicColumns)
{
GUILayout.EndHorizontal();
currentRow++;
currentColumn = 0;
}
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
GUI.backgroundColor = bkColor;
if (selectIndexForMove != -1)
{
GUI.Button(new Rect(Event.current.mousePosition, cellSize), selectedTexs[selectIndexForMove]);
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
if (selectIndex != -1)
{
infoLabel.visible = true;
var tex = selectedTexs[selectIndex];
var tinfo = textureMapInfo.OriginTexture2TextureInfo[tex];
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Array)
{
infoLabel.text = $"{tex.name} array index:{tinfo.ArrayIndex}";
}
else
{
infoLabel.text = $"{tex.name} atlas offset:{tinfo.Offset}";
}
}
else
{
infoLabel.visible = false;
}
if (currentColumn != 0)
{
GUILayout.EndHorizontal();
}
2025-11-11 15:19:01 +08:00
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
EditorGUILayout.LabelField("Model:");
GUILayout.BeginHorizontal();
for (int i = 0; i < gameObjectInspectorView.Count; i++)
{
var pv = gameObjectInspectorView[i];
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(cellSize.x * 2), GUILayout.Height(cellSize.y * 2));
modelRects[i] = rect;
pv.DrawPreview(rect);
}
GUILayout.EndHorizontal();
if (dockPreviewWindowFail)
{
dockPreviewWindowFail = false;
try
{
if (atlasPropertiesWindow != null)
{
this.Dock(atlasPropertiesWindow, Docker.DockPosition.Right);
}
if (arrayPropertiesWindow != null)
{
this.Dock(arrayPropertiesWindow, Docker.DockPosition.Right);
}
}
catch
{
dockPreviewWindowFail = true;
}
}
2025-11-05 17:34:40 +08:00
}
private void InitWithTextureMapInfo(TextureMapInfo textureMapInfo)
{
this.textureMapInfo = textureMapInfo;
VisualElement root = rootVisualElement;
var btArray = root.Q<Button>("btArray");
var btAtlas = root.Q<Button>("btAtlas");
var slAtlasPadding = root.Q<SliderInt>("slAtlasPadding");
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
{
btArray.style.backgroundColor = btNormalColor;
btAtlas.style.backgroundColor = btClikedColor;
slAtlasPadding.visible = true;
}
else
{
btArray.style.backgroundColor = btClikedColor;
btAtlas.style.backgroundColor = btNormalColor;
slAtlasPadding.visible = false;
}
2025-11-11 15:19:01 +08:00
if (textureMapInfo.IsNormalMap)
2025-11-05 17:34:40 +08:00
{
root.Q<Toggle>("tgIsNormal").value = true;
SetNormalOriginTextureFormat(!textureMapInfo.IsNormalMap);
2025-11-11 15:19:01 +08:00
}
2025-11-05 17:34:40 +08:00
texRects = new List<Rect>(new Rect[selectedTexs.Count]);
2025-11-11 15:19:01 +08:00
foreach (var item in textureMapInfo.Textures)
{
AddTexture(item);
}
foreach (var item in textureMapInfo.Fbxs)
{
AddModel(item);
}
2025-11-05 17:34:40 +08:00
}
private void OnGUI()
{
if (textureMapInfo == null)
{
textureMapInfo = ScriptableObject.CreateInstance<TextureMapInfo>();
}
switch (Event.current.type)
{
case EventType.MouseDrag:
break;
case EventType.MouseDown:
selectIndex = -1;
Repaint();
break;
case EventType.KeyDown:
break;
case EventType.KeyUp:
if (Event.current.modifiers == EventModifiers.Control && Event.current.keyCode == KeyCode.S)
{
Save();
}
if (Event.current.keyCode == KeyCode.Delete)
{
if (selectIndex != -1)
{
RmTexture(selectedTexs[selectIndex]);
selectIndex = -1;
}
}
break;
case EventType.DragUpdated:
{
for (var i = 0; i < DragAndDrop.objectReferences.Length; i++)
{
2025-11-11 15:19:01 +08:00
if (DragAndDrop.objectReferences[i] is Texture || AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
2025-11-05 17:34:40 +08:00
{
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
break;
}
}
break;
}
case EventType.DragExited:
{
for (var i = 0; i < DragAndDrop.objectReferences.Length; i++)
{
var tex = DragAndDrop.objectReferences[i] as Texture2D;
if (tex)
{
AddTexture(tex);
}
2025-11-11 15:19:01 +08:00
else if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
{
AddModel(DragAndDrop.objectReferences[i]);
}
2025-11-05 17:34:40 +08:00
}
}
break;
case EventType.ContextClick:
break;
case EventType.MouseEnterWindow:
break;
case EventType.MouseLeaveWindow:
break;
2025-11-11 15:19:01 +08:00
case EventType.Repaint:
{
btApplyMesh.visible = models.Count > 0;
break;
}
2025-11-05 17:34:40 +08:00
default:
break;
}
}
private void UpdateResult(Texture2D result, int cellCnt)
{
result.Apply();
var scale = (float)textureSize / result.width;
for (int i = 0; i < selectedTexs.Count; i++)
{
var tex = selectedTexs[i];
if (tex.width != textureSize)
{
Debug.LogWarning($"{tex.name} 尺寸错误: {tex.width} 正确值: {textureSize}");
}
int cellx = i % cellCnt;
int celly = i / cellCnt;
var mip = 0;
if (tex.width > textureSize)
{
mip = (int)Mathf.Log(2, tex.width / textureSize);
}
{
var temp = new Texture2D(textureSize, textureSize, graphicsFormat, 0, TextureCreationFlags.None);
Graphics.ConvertTexture(tex, temp);
var offsetx = cellx * (textureSize + atlasPadding);
var offsety = celly * (textureSize + atlasPadding);
Graphics.CopyTexture(temp, 0, 0, 0, 0, textureSize, textureSize, result, 0, 0, offsetx, offsety);
GameObject.DestroyImmediate(temp);
textureMapInfo.AtlasScale = scale;
textureMapInfo.OriginTexture2TextureInfo[tex] = new TextureMapInfo.TextureInfo()
{
OriginTexture = tex,
Offset = new float2((float)offsetx / result.width, (float)offsety / result.height),
};
}
}
}
private void UpdateResult(Texture2DArray result)
{
for (int i = 0; i < selectedTexs.Count; i++)
{
var tex = selectedTexs[i];
if (tex.width != textureSize)
{
Debug.LogWarning($"{tex.name} 尺寸错误: {tex.width} 正确值: {textureSize}");
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
Graphics.ConvertTexture(tex, 0, result, i);
textureMapInfo.OriginTexture2TextureInfo[tex] = new TextureMapInfo.TextureInfo()
{
OriginTexture = tex,
ArrayIndex = i,
};
}
}
private void UpdateResult()
{
2025-11-11 15:19:01 +08:00
if (selectedTexs.Count < 2)
2025-11-05 17:34:40 +08:00
{
return;
}
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
{
arrayPropertiesWindow?.Close();
arrayPropertiesWindow = null;
var cellCnt = Mathf.CeilToInt(Mathf.Sqrt(selectedTexs.Count));
var atlasPaddedSize = cellCnt * textureSize + (cellCnt - 1) * atlasPadding;
if (textureAtlas == null || textureAtlas.width != atlasPaddedSize)
{
if (textureAtlas != null)
{
GameObject.DestroyImmediate(textureAtlas, false);
}
textureAtlas = new Texture2D(atlasPaddedSize, atlasPaddedSize,
graphicsFormat, (int)Mathf.Log(2, atlasPaddedSize) + 1, TextureCreationFlags.None)
{
wrapMode = TextureWrapMode.Clamp,
};
atlasPropertiesWindow?.Close();
atlasPropertiesWindow = null;
}
UpdateResult(textureAtlas, cellCnt);
if (!atlasPropertiesWindow)
{
atlasPropertiesWindow = fnOpenPropertyEditor(textureAtlas, true);
try
{
this.Dock(atlasPropertiesWindow, Docker.DockPosition.Right);
}
catch
{
2025-11-11 15:19:01 +08:00
dockPreviewWindowFail = true;
2025-11-05 17:34:40 +08:00
}
}
atlasPropertiesWindow.Repaint();
}
else
{
atlasPropertiesWindow?.Close();
atlasPropertiesWindow = null;
if (textureArray == null || textureArray.depth != selectedTexs.Count || textureArray.width != textureSize)
{
if (textureArray != null)
{
GameObject.DestroyImmediate(textureArray, false);
}
textureArray = new Texture2DArray(textureSize,
textureSize, selectedTexs.Count, graphicsFormat, TextureCreationFlags.None, 12)
{
wrapMode = TextureWrapMode.Clamp,
};
arrayPropertiesWindow?.Close();
arrayPropertiesWindow = null;
}
UpdateResult(textureArray);
if (!arrayPropertiesWindow)
{
arrayPropertiesWindow = fnOpenPropertyEditor(textureArray, true);
2025-11-11 15:19:01 +08:00
try
{
this.Dock(arrayPropertiesWindow, Docker.DockPosition.Right);
}
catch
{
dockPreviewWindowFail = true;
}
2025-11-05 17:34:40 +08:00
}
arrayPropertiesWindow.Repaint();
}
}
private void AddTexture(Texture2D texture2D)
{
2025-11-11 15:19:01 +08:00
if (TextureMapIndex.Instance.ResultGuid2TextureMap.TryGetValue(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(texture2D)), out var mapInfo))
2025-11-05 17:34:40 +08:00
{
selectedTexs.Clear();
textureMapInfo = mapInfo;
InitWithTextureMapInfo(textureMapInfo);
}
if (texture2D.width % textureSize != 0)
{
EditorUtility.DisplayDialog("提示", $"贴图 {texture2D.name} 尺寸错误", "ok");
return;
}
2025-11-11 15:19:01 +08:00
if (selectedTexs.Count == MaxArraySize)
2025-11-05 17:34:40 +08:00
{
EditorUtility.DisplayDialog("提示", $"贴图数量最多 {MaxArraySize}", "ok");
return;
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
if (!selectedTexs.Contains(texture2D))
{
selectedTexs.Add(texture2D);
texRects.Add(new Rect());
}
else
{
ToastManager.ShowNotification(new Toast.ToastArgs()
{
Title = "Info",
Message = "当前图片已存在",
2025-11-11 15:19:01 +08:00
LifeTime = 2f,
2025-11-05 17:34:40 +08:00
Severity = Toast.ToastSeverity.Info,
ToastPosition = Toast.ToastPosition.TopCenter
});
return;
}
imgCntLabel.text = selectedTexs.Count.ToString();
UpdateResult();
}
private void RmTexture(Texture2D texture2D)
{
var idx = selectedTexs.IndexOf(texture2D);
if (idx != -1)
{
texRects.RemoveAt(idx);
selectedTexs.RemoveAt(idx);
2025-11-11 15:19:01 +08:00
if (models.Count > idx)
{
models.RemoveAt(idx);
modelRects.RemoveAt(idx);
gameObjectInspectorView.RemoveAt(idx);
}
2025-11-05 17:34:40 +08:00
}
imgCntLabel.text = selectedTexs.Count.ToString();
UpdateResult();
}
2025-11-11 15:19:01 +08:00
private void AddModel(UnityEngine.Object gameObject)
{
if (!models.Contains(gameObject) && models.Count < selectedTexs.Count)
{
models.Add(gameObject);
modelRects.Add(new());
gameObjectInspectorView.Add(Editor.CreateEditor(gameObject, gameObjectInspectorType));
}
}
private void ReplaceModel(int replaceIdx, UnityEngine.Object gameObject)
{
if (gameObject != models[replaceIdx])
{
models[replaceIdx] = gameObject;
gameObjectInspectorView[replaceIdx] = Editor.CreateEditor(gameObject, gameObjectInspectorType);
}
}
2025-11-05 17:34:40 +08:00
private void UpdateMapinfoBeforeSave()
{
textureMapInfo.Textures = selectedTexs;
2025-11-11 15:19:01 +08:00
textureMapInfo.Fbxs = models;
2025-11-05 17:34:40 +08:00
var hasAlpha = GraphicsFormatUtility.HasAlphaChannel(selectedTexs[0].graphicsFormat);
for (int i = 1; i < selectedTexs.Count; i++)
{
var tex = selectedTexs[i];
if (hasAlpha != GraphicsFormatUtility.HasAlphaChannel(tex.graphicsFormat))
{
2025-11-11 15:19:01 +08:00
if (!EditorUtility.DisplayDialog("警告", $"源贴图 alpha 不一致 :{tex.name}", "继续", "取消"))
2025-11-05 17:34:40 +08:00
{
return;
}
}
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
var rmLs = textureMapInfo.OriginTexture2TextureInfo.Keys.Where(t => !selectedTexs.Contains(t)).ToList();
foreach (var rm in rmLs)
{
textureMapInfo.OriginTexture2TextureInfo.Remove(rm);
}
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
{
GameObject.DestroyImmediate(textureArray);
textureArray = null;
textureMapInfo.TextureArray = null;
if (string.IsNullOrEmpty(textureMapInfo.TextureAtlasPath))
{
var savePath = EditorUtility.SaveFilePanelInProject("保存", "", "png", "");
if (string.IsNullOrEmpty(savePath))
{
return;
}
textureMapInfo.TextureAtlasPath = savePath;
}
AsyncGPUReadback.Request(textureAtlas, 0, (req) =>
{
if (!req.hasError)
{
{
using var dataArray = ImageConversion.EncodeNativeArrayToPNG(req.GetData<byte>(), textureAtlas.graphicsFormat, (uint)textureAtlas.width, (uint)textureAtlas.height);
using var fr = File.OpenWrite(textureMapInfo.TextureAtlasPath);
fr.Write(dataArray);
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
AssetDatabase.ImportAsset(textureMapInfo.TextureAtlasPath);
2025-11-11 15:19:01 +08:00
if (TextureImporter.GetAtPath(textureMapInfo.TextureAtlasPath) is TextureImporter ti)
2025-11-05 17:34:40 +08:00
{
ti.SetTextureSettings(new TextureImporterSettings()
{
textureShape = TextureImporterShape.Texture2D,
flipbookColumns = 1,
flipbookRows = 1,
sRGBTexture = false,
readable = false,
mipmapEnabled = true,
wrapMode = TextureWrapMode.Clamp,
});
ti.SaveAndReimport();
}
EditorApplication.delayCall += () =>
{
textureMapInfo.TextureAtlas = AssetDatabase.LoadAssetAtPath<Texture2D>(textureMapInfo.TextureAtlasPath);
var guid = AssetDatabase.AssetPathToGUID(textureMapInfo.TextureAtlasPath);
TextureMapIndex.Instance.ResultGuid2TextureMap[guid] = textureMapInfo;
EditorGUIUtility.PingObject(textureMapInfo.TextureAtlas);
};
}
});
}
else
{
GameObject.DestroyImmediate(textureAtlas);
textureAtlas = null;
textureMapInfo.TextureAtlas = null;
if (string.IsNullOrEmpty(textureMapInfo.TextureArrayPath))
{
var savePath = EditorUtility.SaveFilePanelInProject("保存", "", "png", "");
if (string.IsNullOrEmpty(savePath))
{
return;
}
textureMapInfo.TextureArrayPath = savePath;
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
AsyncGPUReadback.Request(textureArray, 0, 0, textureArray.width, 0, textureArray.height, 0, textureArray.depth, (req) =>
{
if (!req.hasError)
{
{
using var fr = File.Open(textureMapInfo.TextureArrayPath, FileMode.OpenOrCreate);
using MemoryStream memoryStream = new MemoryStream();
2025-11-11 15:19:01 +08:00
for (int i = textureArray.depth - 1; i >= 0; --i)
2025-11-05 17:34:40 +08:00
{
var array = req.GetData<byte>(i);
memoryStream.Write(array);
}
memoryStream.Seek(0, SeekOrigin.Begin);
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
var dataArray = ImageConversion.EncodeArrayToPNG(memoryStream.GetBuffer(), textureArray.graphicsFormat, (uint)textureArray.width, (uint)textureArray.height * (uint)textureArray.depth);
fr.Write(dataArray);
}
2025-11-11 15:19:01 +08:00
2025-11-05 17:34:40 +08:00
AssetDatabase.ImportAsset(textureMapInfo.TextureArrayPath);
if (TextureImporter.GetAtPath(textureMapInfo.TextureArrayPath) is TextureImporter ti)
{
ti.SetTextureSettings(new TextureImporterSettings()
{
textureShape = TextureImporterShape.Texture2DArray,
flipbookColumns = 1,
flipbookRows = textureArray.depth,
sRGBTexture = false,
readable = false,
mipmapEnabled = true,
wrapMode = TextureWrapMode.Clamp,
});
ti.SaveAndReimport();
}
textureMapInfo.TextureArray = AssetDatabase.LoadAssetAtPath<Texture2DArray>(textureMapInfo.TextureArrayPath);
var guid = AssetDatabase.AssetPathToGUID(textureMapInfo.TextureArrayPath);
TextureMapIndex.Instance.ResultGuid2TextureMap[guid] = textureMapInfo;
EditorGUIUtility.PingObject(textureMapInfo.TextureArray);
}
});
}
AssetDatabase.SaveAssetIfDirty(TextureMapIndex.Instance);
}
private void Save()
{
if (AssetDatabase.IsMainAsset(textureMapInfo))
{
UpdateMapinfoBeforeSave();
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
}
else
{
var path = EditorUtility.SaveFilePanelInProject("保存映射关系", DateTime.Now.ToString("yy_MM_dd_hhmmss"), "asset", "保存源贴图到 atlas 对应关系", TextureMapInfo.TextureMapDir);
2025-11-11 15:19:01 +08:00
if (string.IsNullOrEmpty(path))
2025-11-05 17:34:40 +08:00
{
return;
}
AssetDatabase.CreateAsset(textureMapInfo, path);
UpdateMapinfoBeforeSave();
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
}
base.SaveChanges();
AssetDatabase.Refresh();
}
2025-11-11 15:19:01 +08:00
private void ModifyMesh()
{
for (int i = 0; i < models.Count; i++)
{
var model = models[i] as GameObject;
var mfs = model.GetComponentsInChildren<MeshFilter>();
var tex = selectedTexs[i];
var info = textureMapInfo.OriginTexture2TextureInfo[tex];
for (int j = 0; j < mfs.Length; j++)
{
var mf = mfs[j];
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
{
NativeArray<float2> uv2 = new (mf.sharedMesh.vertexCount, Allocator.Temp);
for (int k = 0; k < mf.sharedMesh.uv.Length; k++)
{
var uv = mf.sharedMesh.uv[k];
uv2[k] = uv * textureMapInfo.AtlasScale + (Vector2)info.Offset;
}
mf.sharedMesh.SetUVs(0, uv2);
uv2.Dispose();
}
else
{
using NativeArray<int2> uv = new (mf.sharedMesh.vertexCount, Allocator.Temp);
uv.AsSpan().Fill(new int2(info.ArrayIndex));
mf.sharedMesh.SetUVs(3, uv);
}
}
var path = AssetDatabase.GetAssetPath(model);
exportObjectFunc.DynamicInvoke(path, model, fbxExportSetting);
}
AssetDatabase.Refresh();
}
2025-11-05 17:34:40 +08:00
private void OnDestroy()
{
SetNormalOriginTextureFormat(textureMapInfo.IsNormalMap);
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
}
}