修改临时
This commit is contained in:
parent
4317656c64
commit
da3a43a2a6
8
Editor/TexturePacker.meta
Normal file
8
Editor/TexturePacker.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 958ac3975f1661b4fbd42bbae89ec8ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
334
Editor/TexturePacker/UnityTexturePackEditor.cs
Normal file
334
Editor/TexturePacker/UnityTexturePackEditor.cs
Normal file
@ -0,0 +1,334 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class UnityTexturePackerEditor : EditorWindow
|
||||
{
|
||||
private List<Texture2D> textures = new List<Texture2D>();
|
||||
private Vector2 scrollPos;
|
||||
private string outputFolder = "Assets/TexturePackerOutput";
|
||||
private string atlasFileName = "atlas.png";
|
||||
private string jsonFileName = "atlas.json";
|
||||
private int maxAtlasSize = 4096;
|
||||
private int padding = 2;
|
||||
private bool overwriteExisting = true;
|
||||
private string lastJson = "";
|
||||
private string lastAtlasPath = "";
|
||||
|
||||
[MenuItem("Tools/Extension/Texture Packer")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var w = GetWindow<UnityTexturePackerEditor>("Texture Packer");
|
||||
w.minSize = new Vector2(450, 300);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Texture Packer (for TMP-compatible atlases)", EditorStyles.boldLabel);
|
||||
DrawDragDropArea();
|
||||
GUILayout.Space(6);
|
||||
GUILayout.Label($"Textures ({textures.Count})", EditorStyles.label);
|
||||
scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Height(160));
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
{
|
||||
GUILayout.BeginHorizontal("box");
|
||||
Texture2D tex = textures[i];
|
||||
Texture preview = AssetPreview.GetAssetPreview(tex) ?? tex;
|
||||
GUILayout.Label(preview, GUILayout.Width(64), GUILayout.Height(64));
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Label(tex.name, EditorStyles.boldLabel);
|
||||
string path = AssetDatabase.GetAssetPath(tex);
|
||||
GUILayout.Label($"{tex.width}x{tex.height} — {path}");
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("▲", GUILayout.Width(24))) { if (i > 0) { var t = textures[i - 1]; textures[i - 1] = textures[i]; textures[i] = t; } }
|
||||
if (GUILayout.Button("▼", GUILayout.Width(24))) { if (i < textures.Count - 1) { var t = textures[i + 1]; textures[i + 1] = textures[i]; textures[i] = t; } }
|
||||
if (GUILayout.Button("Remove", GUILayout.Width(64))) { textures.RemoveAt(i); }
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.Space(8);
|
||||
GUILayout.Label("Output Settings", EditorStyles.boldLabel);
|
||||
outputFolder = EditorGUILayout.TextField("Output Folder", outputFolder);
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Choose..."))
|
||||
{
|
||||
string folder = EditorUtility.OpenFolderPanel("Select output folder", "", "");
|
||||
if (!string.IsNullOrEmpty(folder))
|
||||
{
|
||||
if (folder.StartsWith(Application.dataPath))
|
||||
{
|
||||
outputFolder = "Assets" + folder.Substring(Application.dataPath.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Invalid Folder", "Please select a folder inside this Unity project (under Assets).", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
atlasFileName = EditorGUILayout.TextField("Atlas Filename", atlasFileName);
|
||||
jsonFileName = EditorGUILayout.TextField("JSON Filename", jsonFileName);
|
||||
maxAtlasSize = EditorGUILayout.IntPopup("Max Atlas Size", maxAtlasSize, new string[] { "1024", "2048", "4096" }, new int[] { 1024, 2048, 4096 });
|
||||
padding = EditorGUILayout.IntField("Padding (px)", padding);
|
||||
overwriteExisting = EditorGUILayout.Toggle("Overwrite Existing", overwriteExisting);
|
||||
GUILayout.Space(6);
|
||||
if (GUILayout.Button("Build Atlas", GUILayout.Height(30)))
|
||||
{
|
||||
if (textures.Count == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("No textures", "Please drag textures into the list before building.", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildAtlas();
|
||||
}
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Last result", EditorStyles.boldLabel);
|
||||
EditorGUILayout.SelectableLabel(lastAtlasPath, GUILayout.Height(16));
|
||||
EditorGUILayout.TextArea(lastJson, GUILayout.Height(120));
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.Label("Notes: Output atlas is created as an uncompressed PNG and imported as multiple sprites so TextMeshPro can use it.", EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
|
||||
private void DrawDragDropArea()
|
||||
{
|
||||
var evt = Event.current;
|
||||
Rect dropArea = GUILayoutUtility.GetRect(0.0f, 80.0f, GUILayout.ExpandWidth(true));
|
||||
GUI.Box(dropArea, "Drag textures here (Project or Explorer)", EditorStyles.helpBox);
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
if (!dropArea.Contains(evt.mousePosition)) return;
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
if (evt.type == EventType.DragPerform)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
foreach (var obj in DragAndDrop.objectReferences)
|
||||
{
|
||||
if (obj is Texture2D t)
|
||||
{
|
||||
AddTexture(t);
|
||||
}
|
||||
else if (obj is DefaultAsset)
|
||||
{
|
||||
string path = AssetDatabase.GetAssetPath(obj);
|
||||
string[] guids = AssetDatabase.FindAssets("t:Texture2D", new[] { path });
|
||||
foreach (var g in guids)
|
||||
{
|
||||
string p = AssetDatabase.GUIDToAssetPath(g);
|
||||
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
|
||||
AddTexture(tex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string p = AssetDatabase.GetAssetPath(obj);
|
||||
if (!string.IsNullOrEmpty(p))
|
||||
{
|
||||
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
|
||||
if (tex != null) AddTexture(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event.current.Use();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTexture(Texture2D tex)
|
||||
{
|
||||
if (tex == null) return;
|
||||
if (!textures.Contains(tex)) textures.Add(tex);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class AtlasEntry
|
||||
{
|
||||
public string name;
|
||||
public string sourcePath;
|
||||
public int x;
|
||||
public int y;
|
||||
public int w;
|
||||
public int h;
|
||||
public int sourceW;
|
||||
public int sourceH;
|
||||
}
|
||||
|
||||
private class ImporterBackup
|
||||
{
|
||||
public string path;
|
||||
public bool isReadable;
|
||||
public TextureImporterType type;
|
||||
public TextureImporterCompression compression;
|
||||
public int maxSize;
|
||||
}
|
||||
|
||||
private void BuildAtlas()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder(outputFolder))
|
||||
{
|
||||
if (!outputFolder.StartsWith("Assets"))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Invalid output folder", "Output folder must be inside the project's Assets folder.", "OK");
|
||||
return;
|
||||
}
|
||||
string[] parts = outputFolder.Split('/');
|
||||
string parent = "Assets";
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
string sub = string.Join("/", parts, 0, i + 1);
|
||||
if (!AssetDatabase.IsValidFolder(sub))
|
||||
{
|
||||
AssetDatabase.CreateFolder(parent, parts[i]);
|
||||
}
|
||||
parent = sub;
|
||||
}
|
||||
}
|
||||
var backups = new List<ImporterBackup>();
|
||||
var readableTextures = new List<Texture2D>();
|
||||
foreach (var t in textures)
|
||||
{
|
||||
string p = AssetDatabase.GetAssetPath(t);
|
||||
var ti = TextureImporter.GetAtPath(p) as TextureImporter;
|
||||
if (ti == null) continue;
|
||||
var b = new ImporterBackup();
|
||||
b.path = p;
|
||||
b.isReadable = ti.isReadable;
|
||||
b.type = ti.textureType;
|
||||
b.compression = ti.textureCompression;
|
||||
b.maxSize = ti.maxTextureSize;
|
||||
backups.Add(b);
|
||||
ti.isReadable = true;
|
||||
ti.textureType = TextureImporterType.Default;
|
||||
ti.textureCompression = TextureImporterCompression.Uncompressed;
|
||||
ti.maxTextureSize = Math.Max(t.width, t.height);
|
||||
EditorUtility.SetDirty(ti);
|
||||
ti.SaveAndReimport();
|
||||
var loaded = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
|
||||
if (loaded != null) readableTextures.Add(loaded);
|
||||
}
|
||||
try
|
||||
{
|
||||
Texture2D atlas = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
||||
Rect[] rects = atlas.PackTextures(readableTextures.ToArray(), padding, maxAtlasSize);
|
||||
if (rects == null || rects.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Pack failed", "Could not pack textures. Try increasing max atlas size or reduce number of textures.", "OK");
|
||||
RestoreImporters(backups);
|
||||
return;
|
||||
}
|
||||
int usedW = atlas.width;
|
||||
int usedH = atlas.height;
|
||||
byte[] png = atlas.EncodeToPNG();
|
||||
string atlasPath = Path.Combine(outputFolder, atlasFileName).Replace("\\", "/");
|
||||
if (File.Exists(atlasPath) && !overwriteExisting)
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("File exists", $"{atlasPath} already exists. Overwrite?", "Yes", "No"))
|
||||
{
|
||||
RestoreImporters(backups);
|
||||
return;
|
||||
}
|
||||
}
|
||||
File.WriteAllBytes(atlasPath, png);
|
||||
AssetDatabase.ImportAsset(atlasPath);
|
||||
var atlasImporter = TextureImporter.GetAtPath(atlasPath) as TextureImporter;
|
||||
atlasImporter.textureType = TextureImporterType.Sprite;
|
||||
atlasImporter.spriteImportMode = SpriteImportMode.Multiple;
|
||||
atlasImporter.spritePixelsPerUnit = 100;
|
||||
List<SpriteMetaData> metas = new List<SpriteMetaData>();
|
||||
List<AtlasEntry> mapping = new List<AtlasEntry>();
|
||||
for (int i = 0; i < readableTextures.Count; i++)
|
||||
{
|
||||
var src = readableTextures[i];
|
||||
Rect r = rects[i];
|
||||
int px = Mathf.RoundToInt(r.x * atlas.width);
|
||||
int py = Mathf.RoundToInt(r.y * atlas.height);
|
||||
int pw = Mathf.RoundToInt(r.width * atlas.width);
|
||||
int ph = Mathf.RoundToInt(r.height * atlas.height);
|
||||
SpriteMetaData md = new SpriteMetaData();
|
||||
md.name = src.name;
|
||||
md.pivot = new Vector2(0.5f, 0.5f);
|
||||
md.rect = new Rect(px, py, pw, ph);
|
||||
md.alignment = (int)SpriteAlignment.Center;
|
||||
metas.Add(md);
|
||||
AtlasEntry e = new AtlasEntry();
|
||||
e.name = src.name;
|
||||
e.sourcePath = AssetDatabase.GetAssetPath(src);
|
||||
e.x = px; e.y = py; e.w = pw; e.h = ph;
|
||||
e.sourceW = src.width; e.sourceH = src.height;
|
||||
mapping.Add(e);
|
||||
}
|
||||
atlasImporter.spritesheet = metas.ToArray();
|
||||
EditorUtility.SetDirty(atlasImporter);
|
||||
atlasImporter.SaveAndReimport();
|
||||
string jsonPath = Path.Combine(outputFolder, jsonFileName).Replace("\\", "/");
|
||||
WriteTexturePackerJson(jsonPath, mapping, atlas.width, atlas.height, atlasFileName);
|
||||
AssetDatabase.ImportAsset(jsonPath);
|
||||
lastAtlasPath = atlasPath;
|
||||
lastJson = File.ReadAllText(jsonPath);
|
||||
EditorUtility.DisplayDialog("Success", $"Atlas created: {atlasPath}\nJSON: {jsonPath}", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("Error while building atlas: " + ex.Message + "\n" + ex.StackTrace);
|
||||
EditorUtility.DisplayDialog("Error", ex.Message, "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestoreImporters(backups);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteTexturePackerJson(string jsonPath, List<AtlasEntry> entries, int atlasW, int atlasH, string atlasFileName)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('{');
|
||||
sb.Append("\"frames\":[");
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
var e = entries[i];
|
||||
int jsonY = atlasH - (e.y + e.h);
|
||||
string filename = e.name + ".png";
|
||||
sb.Append('{');
|
||||
sb.AppendFormat("\"filename\":\"{0}\",", filename);
|
||||
sb.AppendFormat("\"frame\":{{\"x\":{0},\"y\":{1},\"w\":{2},\"h\":{3}}},", e.x, jsonY, e.w, e.h);
|
||||
sb.Append("\"rotated\":false,");
|
||||
sb.Append("\"trimmed\":false,");
|
||||
sb.AppendFormat("\"spriteSourceSize\":{{\"x\":0,\"y\":0,\"w\":{0},\"h\":{1}}},", e.sourceW, e.sourceH);
|
||||
sb.AppendFormat("\"sourceSize\":{{\"w\":{0},\"h\":{1}}},", e.sourceW, e.sourceH);
|
||||
sb.Append("\"pivot\":{\"x\":0.5,\"y\":0.5}");
|
||||
sb.Append('}');
|
||||
if (i < entries.Count - 1) sb.Append(',');
|
||||
}
|
||||
sb.Append(']');
|
||||
sb.Append(',');
|
||||
sb.Append("\"meta\":{");
|
||||
sb.AppendFormat("\"app\":\"Unity TexturePacker Export\",\"version\":\"1.0\",\"image\":\"{0}\",\"format\":\"RGBA8888\",\"size\":{{\"w\":{1},\"h\":{2}}},\"scale\":\"1\"", atlasFileName, atlasW, atlasH);
|
||||
sb.Append('}');
|
||||
sb.Append('}');
|
||||
File.WriteAllText(jsonPath, sb.ToString());
|
||||
}
|
||||
|
||||
private void RestoreImporters(List<ImporterBackup> backups)
|
||||
{
|
||||
foreach (var b in backups)
|
||||
{
|
||||
var ti = TextureImporter.GetAtPath(b.path) as TextureImporter;
|
||||
if (ti == null) continue;
|
||||
ti.isReadable = b.isReadable;
|
||||
ti.textureType = b.type;
|
||||
ti.textureCompression = b.compression;
|
||||
ti.maxTextureSize = b.maxSize;
|
||||
EditorUtility.SetDirty(ti);
|
||||
ti.SaveAndReimport();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/TexturePacker/UnityTexturePackEditor.cs.meta
Normal file
11
Editor/TexturePacker/UnityTexturePackEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c64fbafe8359fb242adff677b54b4697
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue
Block a user