目前网络上常用的Unity AssetBundle解包工具主要是disunity、AssetsBundleExtractor和UnityStudio三种,但是disunity不支持较新版的包,也不能导出PNG格式,后两者功能强大、更新及时,然而(据我所知)不支持批量自动化导出。于是我直接使用Unity本身的组件写了一个GUI工具,能够将指定目录下的所有”.unity3d”文件中的所有Texture2D资源批量导出成PNG格式,并支持根据不同来源的包来定制导出方式。
第一步,我们需要一个UI,简单起见就放一个Dropdown和一个Button:
然后,我们写一个UnityExtractor类,用来处理按钮Click事件。首先实现核心的ExtractAsset方法,为了支持各种定制化要求,这个方法需要支持很多参数:
string folderPath, // PNG输出目录
string filePath, // ".unity3d"文件路径
int pngX, int pngY, int pngWidth, int pngHeight, // 如果PNG需要裁剪,指定目标区域的坐标和尺寸。尺寸传入0则不裁剪。pngY一般传0或-1,具体算法见实现。
bool needScale, int scaledWidth, int scaledHeight, // 如果PNG需要缩放,指定最终尺寸。
Func<string, bool> assetNameChecker, // 根据name判定是否需要导出这个asset
Func<string, string, string> pngFileNameMaker // 支持对导出的PNG文件重命名)
{
// ...
}
ExtractAsset函数要做的第一件事就是打开AssetBundle文件并遍历所有包含的asset,判断是否需要导出:
UnityEngine.Object[] assetList = assetBundle.LoadAllAssets();
foreach (var asset in assetList)
{
string assetName = asset.name;
if (asset is Texture2D && (assetNameChecker == null || assetNameChecker(assetName)))
{
Debug.Log("Processing " + asset.GetType() + " asset: " + assetName);
// ...
}
}
如果需要导出,那么我们将这个Texture2D渲染到RenderTexture中[1],然后用一个新的Texture2D从RenderTexture中读出像素值并应用裁剪,接着应用缩放,最后保存为PNG格式文件:
int width = pngWidth == 0 ? t2d.width : pngWidth;
int height = pngHeight == 0 ? t2d.height : pngHeight;
t2d.filterMode = FilterMode.Point;
RenderTexture rt = RenderTexture.GetTemporary(t2d.width, t2d.height);
rt.filterMode = FilterMode.Point;
RenderTexture.active = rt;
Graphics.Blit(t2d, rt);
Texture2D newT2d = new Texture2D(width, height);
newT2d.ReadPixels(new Rect(pngX, pngY == -1 ? t2d.height - height : pngY, width, height), 0, 0);
newT2d.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
if (needScale)
{
newT2d = ScaleTexture(newT2d, scaledWidth, scaledHeight);
}
byte[] pngData = newT2d.EncodeToPNG();
string assetFileName = Path.GetFileName(assetName);
string outputFileName = pngFileNameMaker == null ? assetFileName : pngFileNameMaker(Path.GetFileNameWithoutExtension(filePath), assetFileName);
File.WriteAllBytes(folderPath + "/output/" + outputFileName, pngData);
ScaleTexture函数实现如下:
{
Texture2D result = new Texture2D(targetWidth, targetHeight, source.format, true);
Color[] rpixels = result.GetPixels(0);
float incX = ((float)1 / source.width) * ((float)source.width / targetWidth);
float incY = ((float)1 / source.height) * ((float)source.height / targetHeight);
for (int px = 0; px < rpixels.Length; px++)
{
rpixels[px] = source.GetPixelBilinear(incX * ((float)px % targetWidth), incY * Mathf.Floor(px / targetWidth));
}
result.SetPixels(rpixels, 0);
result.Apply();
return result;
}
最后在ExtractAsset函数结尾别忘了:
下面是Run函数的基本实现:
string selectedName = dropdown.GetComponentInChildren<Text>().text;
int pngX = 0;
int pngY = 0;
int pngWidth = 0;
int pngHeight = 0;
bool needScale = false;
int scaledWidth = 0;
int scaledHeight = 0;
Func<string, bool> assetNameChecker = null;
Func<string, string, string> pngFileNameMaker = (namePrefix, assetFileName) => namePrefix + ".png";
switch (selectedName)
{
case "Name1":
{
pngWidth = 1334;
pngHeight = 750;
pngX = 0;
pngY = -1;
assetNameChecker = (assetName) => Regex.IsMatch(assetName, "^\\d+$");
pngFileNameMaker = (namePrefix, assetFileName) => namePrefix + "_" + assetFileName + ".png";
break;
}
case "Name2":
{
needScale = true;
scaledWidth = 1024;
scaledHeight = 576;
break;
}
}
string folderRoot = EditorUtility.OpenFolderPanel("Choose folder", @"D:\Temp", "");
if (folderRoot.Length != 0)
{
string[] folderList = Directory.GetDirectories(folderRoot);
if (folderList.Length > 0)
{
foreach (string folderPath in folderList)
{
if (!folderPath.EndsWith("output"))
{
Directory.CreateDirectory(folderPath + "/output");
string[] fileList = Directory.GetFiles(folderPath);
foreach (string filePath in fileList)
{
if (filePath.EndsWith(".unity3d"))
{
ExtractAsset(folderPath, filePath, pngX, pngY, pngWidth, pngHeight, needScale, scaledWidth, scaledHeight, assetNameChecker, pngFileNameMaker);
}
}
}
}
}
}
Debug.Log("ExtractAsset() done!");
使用的时候,打开这个Unity工程,直接运行然后点击Start按钮选择目录即可。以上的代码为了说明原理做了简化,我实际中用的代码还有些其他细节处理,并支持TXT格式导出,原理类似就不赘述了。
需要注意的是,这个工程无法Build,因为使用了UnityEditor命名空间下的EditorUtility来打开目录,如果想要Build成EXE文件来支持命令行操作等更深自动化操作的话,需要用其他方式实现打开文件操作,因为我这个人很懒,就不继续搞了(逃
参考资料:
[1] How Can I Get Pixels From Unreadable Textures?
» 转载请注明来源及链接:未来代码研究所
卧槽 AssetBundle 官方有库直接搞的啊(
我还直接去逆向结构了 orz
搞结构当然更好,我就是比较懒>_<