在游戏开发或多媒体应用制作过程中,美术资源处理往往是最耗时的环节之一。最近接手的一个手游项目需要处理上千张UI贴图,每张都需要统一调整为特定像素规格。如果手动一张张处理,不仅效率低下还容易出错。于是研究了一套Unity批量处理图片像素的自动化方案,在这里把完整实现过程和踩坑经验分享给大家。
这个方案特别适合以下场景:
Unity本身提供了Texture2D类来处理图片数据,配合Editor脚本可以实现自动化处理。相比使用外部工具(如Photoshop脚本),直接在Unity中处理的优势在于:
核心用到的API包括:
完整的批量处理流程分为四个阶段:
资源收集阶段
像素处理阶段
输出保存阶段
后处理阶段
首先创建一个Editor文件夹下的C#脚本,继承自EditorWindow:
csharp复制using UnityEditor;
using UnityEngine;
using System.IO;
public class BatchTextureProcessor : EditorWindow
{
[MenuItem("Tools/Batch Process Textures")]
static void Init()
{
var window = GetWindow<BatchTextureProcessor>();
window.titleContent = new GUIContent("Texture Processor");
window.Show();
}
// 后续代码将在这里添加
}
关键的重采样函数实现如下:
csharp复制Texture2D ResizeTexture(Texture2D source, int targetWidth, int targetHeight)
{
// 创建目标纹理
Texture2D result = new Texture2D(targetWidth, targetHeight, source.format, false);
// 计算缩放比例
float scaleX = (float)source.width / targetWidth;
float scaleY = (float)source.height / targetHeight;
// 逐像素处理
for(int y = 0; y < result.height; y++)
{
for(int x = 0; x < result.width; x++)
{
// 双线性采样
Color pixel = source.GetPixelBilinear(
(float)x / result.width * scaleX,
(float)y / result.height * scaleY);
result.SetPixel(x, y, pixel);
}
}
result.Apply();
return result;
}
添加目录遍历和处理逻辑:
csharp复制void ProcessDirectory(string inputPath, string outputPath, int width, int height)
{
// 确保输出目录存在
if(!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
// 获取所有图片文件
string[] files = Directory.GetFiles(inputPath, "*.*", SearchOption.AllDirectories)
.Where(f => f.EndsWith(".png") || f.EndsWith(".jpg") || f.EndsWith(".tga")).ToArray();
// 进度显示
EditorUtility.DisplayProgressBar("Processing", "Starting...", 0);
for(int i = 0; i < files.Length; i++)
{
string relativePath = files[i].Substring(inputPath.Length);
string outputFile = Path.Combine(outputPath, relativePath);
// 更新进度
EditorUtility.DisplayProgressBar(
"Processing Textures",
$"Processing {Path.GetFileName(files[i])}",
(float)i / files.Length);
try
{
// 加载原始文件
byte[] bytes = File.ReadAllBytes(files[i]);
Texture2D srcTex = new Texture2D(2, 2);
srcTex.LoadImage(bytes);
// 调整尺寸
Texture2D dstTex = ResizeTexture(srcTex, width, height);
// 保存处理结果
byte[] pngData = dstTex.EncodeToPNG();
Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
File.WriteAllBytes(outputFile, pngData);
}
catch(System.Exception e)
{
Debug.LogError($"Failed to process {files[i]}: {e.Message}");
}
}
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
为了让工具更易用,添加一个简单的GUI:
csharp复制Vector2Int targetSize = new Vector2Int(512, 512);
string inputFolder = "Assets/Textures";
string outputFolder = "Assets/Textures_Processed";
void OnGUI()
{
GUILayout.Label("Batch Texture Processor", EditorStyles.boldLabel);
EditorGUILayout.Space();
targetSize = EditorGUILayout.Vector2IntField("Target Size", targetSize);
EditorGUILayout.Space();
GUILayout.Label("Input Folder:");
inputFolder = EditorGUILayout.TextField(inputFolder);
EditorGUILayout.Space();
GUILayout.Label("Output Folder:");
outputFolder = EditorGUILayout.TextField(outputFolder);
EditorGUILayout.Space();
if(GUILayout.Button("Process Textures"))
{
if(Directory.Exists(inputFolder))
{
ProcessDirectory(inputFolder, outputFolder, targetSize.x, targetSize.y);
EditorUtility.DisplayDialog("Complete",
$"Processed all textures in {inputFolder}", "OK");
}
else
{
EditorUtility.DisplayDialog("Error",
"Input directory does not exist", "OK");
}
}
}
修改ResizeTexture函数,添加保持宽高比的逻辑:
csharp复制Texture2D ResizeTexture(Texture2D source, int targetWidth, int targetHeight, bool keepAspect)
{
if(keepAspect)
{
float aspect = (float)source.width / source.height;
if(targetWidth / aspect <= targetHeight)
{
targetHeight = Mathf.RoundToInt(targetWidth / aspect);
}
else
{
targetWidth = Mathf.RoundToInt(targetHeight * aspect);
}
}
// ...原有处理逻辑
}
添加按规则自动重命名的支持:
csharp复制string GenerateOutputPath(string inputPath, string outputRoot, string prefix, string suffix)
{
string dir = Path.GetDirectoryName(inputPath);
string filename = Path.GetFileNameWithoutExtension(inputPath);
string ext = Path.GetExtension(inputPath);
string relativeDir = dir.Substring(inputFolder.Length);
string newFilename = $"{prefix}{filename}{suffix}{ext}";
return Path.Combine(outputRoot, relativeDir, newFilename);
}
处理大量图片时需要注意:
内存管理:
多线程处理:
csharp复制ThreadPool.QueueUserWorkItem(state => {
// 处理逻辑
EditorApplication.delayCall += () => {
// 回到主线程更新UI
};
});
进度反馈优化:
问题1:处理后的图片颜色异常
问题2:内存不足崩溃
问题3:边缘像素模糊
最近一个卡牌游戏项目需要:
使用这个工具后: