导出 PSB 文件中通过 PSD Importer 生成的所有子 Sprite 为独立的 PNG 图片

ko-fi

这是一个 Unity 编辑器脚本,用于导出 PSD/PSB 文件中通过 PSD Importer 生成的所有子 Sprite 为独立的 PNG 图片。

菜单入口

Tools/PSD/导出选中PSB的所有子Sprite

功能流程

  1. 获取当前选中的 .psb 文件
  2. 加载该 PSD 文件包含的所有 Asset(通过 AssetDatabase.LoadAllAssetsAtPath
  3. 筛选出所有 Sprite 类型的资源
  4. 在 PSD 同目录下创建 <文件名>_SubSprites 文件夹
  5. 将每个 Sprite 裁剪为独立 PNG 导出

原始使用场景

Unity 的 PSD Importer 可以将 PSD 文件作为多图层 Sprite 图集导入,每个图层自动生成为一个子 Sprite。但 Unity 没有提供反向导出功能,这个脚本就是用来解决这个痛点——将已导入的 Sprite 重新导出为独立的 PNG 图片。

典型用途

  • 导出 PSD 分层后的 Sprite 供其他工具/项目使用
  • 备份或检查 PSD Importer 实际生成的 Sprite 内容
  • 将图集拆分为单独的资源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

public class ExportPsbSubSprites
{
[MenuItem("Tools/PSD/导出选中PSB的所有子Sprite")]
public static void ExportSelectedPsbSprites()
{
Object selected = Selection.activeObject;
if (selected == null)
{
Debug.LogError("请先选中 .psb 文件");
return;
}

string path = AssetDatabase.GetAssetPath(selected);
var assets = AssetDatabase.LoadAllAssetsAtPath(path);

string outputDir = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path) + "_SubSprites");
Directory.CreateDirectory(outputDir);

foreach (var a in assets)
{
if (a is Sprite sprite)
{
ExportSprite(sprite, SafeCombine(outputDir, sprite.name + ".png"));
}
}

AssetDatabase.Refresh();
Debug.Log("导出完成: " + outputDir);
}

public static string SafeCombine(string directory, string fileName)
{
if (string.IsNullOrWhiteSpace(directory))
throw new ArgumentException("directory 不能为空", nameof(directory));

if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException("fileName 不能为空", nameof(fileName));

// 清理文件名中的非法字符
var invalidChars = Path.GetInvalidFileNameChars();
var safeFileName = new string(fileName
.Where(c => !invalidChars.Contains(c))
.ToArray());

// 防止清理后为空
if (string.IsNullOrWhiteSpace(safeFileName))
throw new ArgumentException("fileName 清理后为空,原始文件名可能全是非法字符");

return Path.Combine(directory, safeFileName);
}

static void ExportSprite(Sprite sprite, string outputPath)
{
Texture2D tex = sprite.texture;
Rect rect = sprite.rect;

string texPath = AssetDatabase.GetAssetPath(tex);
var importer = AssetImporter.GetAtPath(texPath) as TextureImporter;
bool reset = false;

if (importer != null && !importer.isReadable)
{
importer.isReadable = true;
importer.SaveAndReimport();
reset = true;
}

Texture2D outTex = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGBA32, false);
outTex.SetPixels(tex.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height));
outTex.Apply();

File.WriteAllBytes(outputPath, outTex.EncodeToPNG());
Object.DestroyImmediate(outTex);

if (reset && importer != null)
{
importer.isReadable = false;
importer.SaveAndReimport();
}
}
}