当前位置: 首页 > news >正文

Unity图集切割实战:基于Texture2D.SetPixel的离线资源处理工具

1. 项目概述:为什么我们需要手动切割图集?

在Unity项目开发中,尤其是UI和2D游戏领域,Sprite Atlas(精灵图集)是优化性能的标配。它把大量零散的小图打包成一张大图,能显著减少Draw Call,提升渲染效率。官方提供的SpriteAtlas资源用起来很方便,打包、引用、运行时加载一气呵成。但最近我在一个老项目迁移和资源处理工具链开发中,遇到了一个棘手的需求:需要将已经打包好的Sprite Atlas,按原始小图的边界重新切割成独立的Texture2D或Sprite资源

你可能会问,Unity不是有现成的SpriteAtlas.GetSprite方法吗?没错,在运行时,如果你知道精灵的名字,可以动态获取。但我的场景是离线处理:我需要写一个编辑器工具,将图集资源批量还原,用于资源审计、第三方渠道分包、或者为不支持Sprite Atlas的老版本引擎或特定平台准备资源。这时候,直接访问图集纹理并手动切割就成了唯一的选择。

核心思路很直接:获取到图集对应的Texture2D对象,然后根据图集中每个精灵(Sprite)的纹理坐标(UV)和像素尺寸信息,从这张大纹理上把对应的像素块“抠”出来,生成一张新的小纹理。Texture2D.SetPixel方法正是实现这个“抠图”操作的基础API。听起来简单,但实操中从UV计算像素坐标、处理边缘、管理内存、确保颜色准确,每一步都有坑。这篇文章,我就结合一个完整的编辑器工具实现,带你走一遍流程,并附上我踩过的那些坑和解决方案。

2. 核心原理与准备工作

2.1 Sprite Atlas与纹理坐标系统

要切割,首先得知道切哪里。Unity的Sprite Atlas在打包后,会生成一个.spriteatlas文件和一个纹理文件(如Atlas.png)。这个纹理文件就是我们要操作的Texture2D。图集中的每个原始精灵,都对应这个大纹理上的一个矩形区域。

关键信息存储在哪里?在Sprite对象里。即使精灵被打包进图集,其Sprite.rect属性仍然记录着它在原始纹理(即图集纹理)上的像素位置和大小。更重要的是Sprite.uv属性,它返回一个Vector2数组(通常是4个顶点),表示这个精灵的四个角在图集纹理上的归一化纹理坐标

  • 像素坐标 (rect): 例如(x: 128, y: 256, width: 64, height: 64),表示这个精灵位于图集纹理的第128列、第256行(以左下角为原点),宽高各64像素。
  • 纹理坐标 (uv): 范围在[0, 1]之间。例如左下角UV可能是(0.25, 0.5),右上角是(0.375, 0.625)。它们是通过像素坐标除以纹理总尺寸得到的。

我们的切割算法,本质就是利用rect信息在图集纹理上定位一个矩形区域,然后将这个区域内的所有像素读取出来。uv可以用来验证计算是否正确,尤其是在处理纹理边缘或旋转/裁剪过的精灵时。

2.2 Texture2D.SetPixel 与 GetPixel 的工作机制

Texture2D.SetPixel(x, y, color)Texture2D.GetPixel(x, y)是处理纹理像素最直观的方法。

  • GetPixel(int x, int y): 读取纹理在整数坐标(x, y)处的像素颜色。坐标原点(0,0)在纹理的左下角。这一点非常重要,和常见的图像处理库(原点在左上角)不同,是第一个容易混淆的点。
  • SetPixel(int x, int y, Color color): 将指定颜色设置到纹理的(x, y)坐标处。

为什么选择它们?因为它们概念简单,兼容性好,在编辑器脚本中对于一次性处理任务足够直观。当然,它们效率不高,因为每次调用都涉及托管到原生代码的转换。对于超大图集或批量处理,应该使用GetPixels()/SetPixels()这类批量方法。但为了清晰地展示原理和步骤,我们从SetPixel/GetPixel开始,理解了之后,优化到批量方法就水到渠成了。

注意:在编辑器脚本中操作纹理,必须确保纹理的Read/Write Enabled属性在导入设置中已勾选,否则GetPixel会失败。这是第一个大坑。

2.3 工具环境与目标设定

我们将创建一个Editor Window工具。目标功能如下:

  1. 允许用户拖拽或选择一个Sprite Atlas资源。
  2. 解析该图集,列出其中包含的所有精灵及其信息(名称、位置、尺寸)。
  3. 允许用户选择输出目录。
  4. 点击按钮,将图集中的每个精灵切割并保存为独立的PNG图片,并可选地生成对应的Sprite资产。

我们将处理两种常见的精灵类型:

  • 标准矩形精灵:最简单,rect就是精确的像素边界。
  • 九宫格(Sliced)精灵或网格(Grid)精灵:这类精灵的rect可能只定义了有效区域,但uv可能映射到图集的更复杂区域(如果打包时进行了旋转或裁剪)。我们的基础版本先处理标准矩形,后续会讨论复杂情况的处理。

3. 实战步骤:从图集到独立纹理

3.1 第一步:获取图集纹理与精灵列表

首先,我们需要获取到图集对应的纹理和里面所有的精灵。这里不能直接加载.spriteatlas文件,因为它是配置数据。我们需要获取它关联的Texture2D

using UnityEngine; using UnityEditor; using System.IO; using System.Collections.Generic; public class AtlasSplitterWindow : EditorWindow { private SpriteAtlas targetAtlas; private Texture2D atlasTexture; private List<Sprite> spritesInAtlas = new List<Sprite>(); private string outputPath = "Assets/SplitSprites"; void OnGUI() { // 1. 选择图集 EditorGUI.BeginChangeCheck(); targetAtlas = (SpriteAtlas)EditorGUILayout.ObjectField("目标图集", targetAtlas, typeof(SpriteAtlas), false); if (EditorGUI.EndChangeCheck()) { UpdateAtlasData(); } // ... 其他UI和按钮 } void UpdateAtlasData() { spritesInAtlas.Clear(); atlasTexture = null; if (targetAtlas == null) return; // 关键:获取图集打包后产生的纹理 // 方法:获取图集的所有打包资源对象 UnityEngine.Object[] packedAssets = targetAtlas.GetPackables(); // 通常,第一个Texture2D类型的对象就是图集纹理 foreach (var obj in packedAssets) { if (obj is Texture2D tex) { atlasTexture = tex; break; } } if (atlasTexture == null) { Debug.LogError("无法从选中的SpriteAtlas中获取纹理!请确保图集已打包。"); return; } // 获取图集中的所有精灵 // 注意:SpriteAtlas.GetSprites() 在Editor模式下可能无法直接使用或返回空。 // 更可靠的方法是:从打包资源中寻找类型为Sprite的对象。 // 但更通用的方法是:通过图集纹理来获取关联的Sprite。 // 我们可以通过AssetDatabase找到所有引用此纹理的Sprite资源。 string texturePath = AssetDatabase.GetAssetPath(atlasTexture); string[] allAssetPaths = AssetDatabase.GetAllAssetPaths(); foreach (var assetPath in allAssetPaths) { if (assetPath.EndsWith(".png") || assetPath.EndsWith(".jpg") || assetPath.EndsWith(".asset")) { // 加载资源并检查是否是Sprite,且其纹理是否是我们找到的图集纹理 Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); foreach (var asset in assets) { if (asset is Sprite sprite) { // 比较精灵关联的纹理和我们的图集纹理 if (sprite.texture != null && sprite.texture == atlasTexture) { if (!spritesInAtlas.Contains(sprite)) { spritesInAtlas.Add(sprite); } } } } } } Debug.Log($"找到图集纹理: {atlasTexture.name}, 尺寸: {atlasTexture.width}x{atlasTexture.height}"); Debug.Log($"找到精灵数量: {spritesInAtlas.Count}"); } }

这段代码的核心是UpdateAtlasData方法。它首先通过GetPackables()尝试获取图集纹理,然后遍历项目资源,找到所有纹理引用为此图集纹理的Sprite对象。这个方法在编辑器下比较可靠,但效率不是最高。对于已知精灵来源的项目,可以有更优解。

避坑指南1:获取精灵列表的陷阱网上很多教程会直接用Resources.FindObjectsOfTypeAll<Sprite>()然后过滤纹理,这在编辑器场景下会返回场景中和所有预制件里的Sprite组件,数量巨大且包含很多你不想要的。而AssetDatabase.FindAssetsLoadAssetAtPath的组合,虽然准确,但需要你知道精灵资源的确切存储路径。上面提供的遍历方法是一种“广撒网”的可靠方法,适合通用工具,但如果是处理特定目录下的图集,建议优化为搜索特定文件夹。

3.2 第二步:计算精灵的精确像素区域

拿到Sprite对象后,sprite.rect给了我们像素矩形。但这里有个细节:rectRect结构,其x,y是浮点数。纹理像素坐标是整数。我们需要将这个矩形转换为一个整数像素范围的循环。

Rect spriteRect = sprite.rect; // 将Rect的浮点坐标转换为整数像素范围的起始点和结束点 int startX = Mathf.FloorToInt(spriteRect.x); int startY = Mathf.FloorToInt(spriteRect.y); int endX = Mathf.CeilToInt(spriteRect.x + spriteRect.width); int endY = Mathf.CeilToInt(spriteRect.y + spriteRect.height); // 确保坐标在纹理范围内 startX = Mathf.Clamp(startX, 0, atlasTexture.width); startY = Mathf.Clamp(startY, 0, atlasTexture.height); endX = Mathf.Clamp(endX, 0, atlasTexture.width); endY = Mathf.Clamp(endY, 0, atlasTexture.height); int width = endX - startX; int height = endY - startY;

这里使用FloorToIntCeilToInt是为了处理可能的浮点误差,确保整个精灵区域都被包含进来。Clamp是安全措施,防止计算错误导致索引越界。

3.3 第三步:使用GetPixel和SetPixel逐像素复制

这是最核心的循环。我们为每个精灵创建一个新的Texture2D,然后从图集纹理中复制像素。

Texture2D newTexture = new Texture2D(width, height, atlasTexture.format, false); newTexture.name = sprite.name; // 关键的双重循环:遍历新纹理的每一个像素位置 for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { // 计算在图集纹理中的对应坐标 int sourceX = startX + x; int sourceY = startY + y; // 注意:纹理坐标原点在左下角,我们的循环从下往上(y从小变大)是符合的。 // 从图集获取像素颜色 Color pixelColor = atlasTexture.GetPixel(sourceX, sourceY); // 设置到新纹理的对应位置 newTexture.SetPixel(x, y, pixelColor); } } // 必须调用Apply才能使SetPixel的更改生效 newTexture.Apply();

循环的细节:外层x循环遍历宽度,内层y循环遍历高度。对于新纹理上的每个点(x, y),我们在图集纹理上找到对应的点(startX + x, startY + y)。这里y坐标的方向在Texture2D的坐标系中是一致的(左下角原点),所以直接相加即可。如果你用其他图像库(如System.Drawing)可能会遇到Y轴翻转的问题。

避坑指南2:Texture2D的格式与Alpha通道创建新纹理时,我使用了atlasTexture.format来保持格式一致。这通常是正确的,但要注意,如果源图集是压缩格式(如DXT1, DXT5),Texture2D构造函数可能不支持。在编辑器脚本中,为了确保兼容性,我通常使用TextureFormat.RGBA32TextureFormat.ARGB32这种未压缩格式。另外,如果精灵有透明边缘,GetPixel获取的颜色可能包含预乘Alpha或边缘滤波的颜色,直接复制过去可能导致边缘出现杂色。对于要求极高的项目,可能需要更复杂的处理,比如结合sprite.verticessprite.uv进行精确采样。

3.4 第四步:保存纹理为资产并生成Sprite

像素复制完成后,我们需要将Texture2D对象保存到磁盘成为.png.jpg文件,并为其创建Sprite资产。

// 将Texture2D编码为PNG字节 byte[] pngData = newTexture.EncodeToPNG(); // 定义输出路径 string spriteFileName = $"{sprite.name}.png"; string fullOutputPath = Path.Combine(outputPath, spriteFileName); // 确保目录存在 Directory.CreateDirectory(Path.GetDirectoryName(fullOutputPath)); // 写入文件 File.WriteAllBytes(fullOutputPath, pngData); // 重要:刷新AssetDatabase,让Unity识别新文件 AssetDatabase.Refresh(); // 可选:为新纹理文件设置导入设置,并创建Sprite string assetPath = fullOutputPath.Replace(Application.dataPath, "Assets"); TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter; if (importer != null) { importer.textureType = TextureImporterType.Sprite; // 如果你想保持原始精灵的Pivot和MeshType,可以从原Sprite中读取并赋值 // importer.spritePivot = sprite.pivot; // importer.spriteImportMode = SpriteImportMode.Single; // 或 Multiple // 如果需要,还可以设置spriteBorder(九宫格) EditorUtility.SetDirty(importer); importer.SaveAndReimport(); } Debug.Log($"已保存精灵: {assetPath}");

关键操作

  1. EncodeToPNG(): 将内存中的Texture2D转换为PNG格式的字节流。
  2. File.WriteAllBytes: 将字节流写入磁盘。注意路径是系统路径。
  3. AssetDatabase.Refresh():至关重要。写入文件后,必须刷新资源数据库,Unity才会加载这个新文件。
  4. TextureImporter: 通过AssetImporter获取纹理的导入器,将其类型设置为Sprite,这样才能作为精灵使用。你还可以在这里精细配置Pivot(中心点)、Mesh Type等,以匹配原始精灵的设置。

避坑指南3:文件路径与AssetDatabase使用File.WriteAllBytes时,路径是操作系统绝对路径(如C:\Project\Assets\...)。而AssetDatabaseAssetImporter的接口需要的是相对于项目Assets文件夹的路径(如Assets/SplitSprites/icon.png)。Application.dataPath返回的是Assets文件夹的绝对路径。进行转换时务必小心,错误的路径会导致AssetImporter.GetAtPath返回null。一个常见的错误是使用/还是\,在Unity中,使用Path.Combine和相对路径Assets/...是更安全的选择。

4. 性能优化:从SetPixel到GetPixels32

上面的逐像素GetPixel/SetPixel循环在切割一个64x64的精灵时需要4096次函数调用。如果图集有上百个精灵,性能会非常低下。优化方法就是使用批量像素操作方法。

Texture2D.GetPixels32()Texture2D.SetPixels32()一次性读取或设置整个纹理或一个矩形区域的所有像素到一个Color32数组中。Color32Color的整数版本(每个通道0-255),处理速度更快,且避免了浮点运算。

优化后的切割循环:

// 1. 一次性读取图集纹理的全部像素数据 Color32[] allAtlasPixels = atlasTexture.GetPixels32(); // 对于每个精灵... Rect spriteRect = sprite.rect; int startX = (int)spriteRect.x; int startY = (int)spriteRect.y; int width = (int)spriteRect.width; int height = (int)spriteRect.height; // 2. 创建一个空的Color32数组用于存放切割出的像素 Color32[] extractedPixels = new Color32[width * height]; // 3. 计算并复制像素块 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 计算在图集一维数组中的索引 int atlasIndex = (startY + y) * atlasTexture.width + (startX + x); // 计算在提取数组中的索引 int extractIndex = y * width + x; extractedPixels[extractIndex] = allAtlasPixels[atlasIndex]; } } // 4. 创建新纹理并批量设置像素 Texture2D newTex = new Texture2D(width, height, TextureFormat.RGBA32, false); newTex.SetPixels32(extractedPixels); newTex.Apply();

性能对比:假设图集是2048x2048,切割一个64x64的精灵。

  • GetPixel/SetPixel方案:4096次独立调用,每次调用都有开销。
  • GetPixels32/SetPixels32方案:1次读取整个图集(约400万像素),然后进行4096次内存数组拷贝(极快),最后1次SetPixels32调用。

后者速度有数量级的提升。对于编辑器工具,尤其是批量处理,务必使用批量像素接口

避坑指南4:数组索引与行列顺序在一维数组中模拟二维像素布局时,索引计算公式是index = y * width + x。这里width纹理的宽度(每行的像素数)。在嵌套循环中,外层循环y,内层循环x,这种访问顺序是按行访问,对CPU缓存友好,效率更高。如果搞反了顺序(外层x,内层y),就是按列访问,可能会造成大量的缓存未命中,在处理大纹理时性能差异明显。

5. 处理复杂情况与边界问题

5.1 旋转与裁切的精灵

如果精灵在图集打包时被允许旋转(Pack Settings中的Rotation选项),那么sprite.rect定义的矩形可能并不是精灵像素数据的轴对齐边界框。旋转后的精灵,其sprite.uv定义的纹理坐标会映射到图集纹理的一个旋转后的区域。我们上面基于rect的轴对齐矩形切割方法,会切出一个包含精灵的外接矩形,里面会包含大量透明像素(四个角),并且精灵本身是斜的。

如何检测和处理?检查sprite.packedsprite.packingRotation属性。如果sprite.packingRotation不是SpritePackingRotation.None,则说明精灵被旋转了(通常是90度的倍数)。

处理旋转的简易方法(适用于90/180/270度旋转):

  1. 仍然按rect切割出外接矩形纹理。
  2. 根据packingRotation的值,对切割出的纹理进行旋转。
  3. 旋转后,你需要计算精灵实际内容的新边界(可能不再是矩形),这变得非常复杂。

更实用的建议:对于需要处理旋转精灵的通用工具,简单的像素复制方法就不够了。一个更健壮但不完美的方法是:利用sprite.verticessprite.uvvertices是精灵网格在局部空间的顶点,uv是对应的纹理坐标。你可以为精灵创建一个新的网格,并使用uv从图集纹理中进行纹理采样。这涉及到在像素级别进行双线性插值,超出了GetPixel的范畴,需要用到Texture2D.GetPixelBilinear(u, v)或者更底层的图形API。

实操心得:在大多数项目规范中,为了简化资源管理和避免运行时性能损耗,会禁止图集打包旋转。如果你的项目有这个限制,那么就可以安全地忽略旋转情况。在工具中,可以添加一个检查,如果发现旋转的精灵,则输出警告,并跳过或按未旋转处理(结果会是错的,但至少让用户知道)。

5.2 纹理边缘与滤波造成的颜色污染

当精灵紧挨着图集边缘,或者两个精灵在图集中紧密排列时,由于纹理滤波(Filter Mode不是Point)的存在,GetPixel在读取边缘像素时,可能会采样到相邻精灵或透明区域的像素颜色,导致切割出的精灵边缘有一圈淡淡的“颜色污染”。

解决方案:

  1. 使用Point滤波:在切割前,将图集纹理的filterMode临时设置为FilterMode.Point,切割完成后再改回去。这能确保GetPixel读取的是精确的像素,不进行混合。
    FilterMode originalFilter = atlasTexture.filterMode; atlasTexture.filterMode = FilterMode.Point; // ... 执行切割操作 ... atlasTexture.filterMode = originalFilter; // 注意:直接修改导入纹理的filterMode可能不会立即生效或影响原始资产,最好在内存中的纹理副本上操作。
  2. 预留“透明边距”:这是更根本的解决方案。在图集打包设置(SpriteAtlasPadding属性)中增加足够的像素边距(例如2或4像素)。这样精灵之间就有间隙,滤波也不会采样到邻居的颜色。我们切割时,rect定义的区域已经包含了这个边距内的透明像素,所以切割结果是干净的。
  3. 精确的UV边界采样:如果边距为0,又必须用滤波,那么计算采样坐标时,需要向内收缩半个像素。例如,对于一个从uMinuMax的UV范围,实际采样时使用uMin + 0.5/texWidthuMax - 0.5/texWidth。这在使用GetPixelBilinear时尤为重要,但用GetPixel整数坐标采样时无法实现。

5.3 内存管理与大纹理处理

GetPixels32()会返回纹理所有像素的一个副本。一个2048x2048的RGBA32纹理,占用内存约为2048 * 2048 * 4 bytes ≈ 16.7 MB。如果你的图集很大,或者同时处理多个图集,内存压力会剧增。

优化策略:

  1. 分块处理:不要一次性读取整个图集。可以按需读取每个精灵对应的矩形区域,使用Texture2D.GetPixels32(int x, int y, int blockWidth, int blockHeight)。这样每次只读取一小块内存。
  2. 及时销毁:使用new Texture2D创建的新纹理,以及GetPixels32返回的数组,都是托管内存。在处理完一个精灵并保存为文件后,应立即将引用置为null,并可以调用Resources.UnloadAssetDestroyImmediate来释放纹理对象(在编辑器脚本中需谨慎)。
  3. 使用using或封装作用域:将纹理处理代码封装在局部作用域内,让临时变量尽快被垃圾回收。
  4. 进度条与异步:对于非常大的图集,使用EditorUtility.DisplayProgressBar显示进度条,并考虑将处理逻辑放在协程或异步任务中,避免编辑器卡死无响应。
// 示例:分块读取与进度显示 int totalSprites = spritesInAtlas.Count; for (int i = 0; i < totalSprites; i++) { Sprite sprite = spritesInAtlas[i]; // 更新进度条 if (EditorUtility.DisplayCancelableProgressBar("切割图集", $"正在处理 {sprite.name} ({i+1}/{totalSprites})", (float)i / totalSprites)) { // 用户取消了 break; } Rect rect = sprite.rect; // 只读取这个精灵区域的像素 Color32[] blockPixels = atlasTexture.GetPixels32((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); // ... 处理并保存 ... // 及时清理 blockPixels = null; Resources.UnloadUnusedAssets(); } EditorUtility.ClearProgressBar();

6. 完整工具实现与扩展思路

将上述所有步骤整合,一个基础的图集切割工具就完成了。你可以为其添加更多功能:

  • 批量选择与处理:允许用户选择一个文件夹,自动处理其中所有的Sprite Atlas。
  • 自定义命名规则:输出文件名的规则,例如添加前缀、后缀。
  • 多种输出格式:支持PNG、JPG、TGA等。
  • 保留Sprite元数据:不仅切割纹理,还尝试将原始Sprite的Pivot、Border、Pixels Per Unit等信息保存到新的Sprite设置中。这需要更精细地操作TextureImporterSpriteMetaData
  • 生成Prefab或Animation Clip:对于序列帧动画图集,可以按命名规则排序后,自动生成Animation Clip甚至包含Animator的Prefab。
  • 与AssetPostprocessor结合:创建一个后处理器,当检测到图集纹理被修改时,自动执行切割,用于某些自动化流水线。

一个重要的扩展:处理Multiple模式Sprite如果原始精灵是SpriteMode.Multiple(即一张纹理包含多个精灵),被打包进图集后,每个子精灵都是一个独立的Sprite对象。我们的工具已经能处理这种情况,因为spritesInAtlas列表里包含了每一个子精灵。切割后会为每一个子精灵生成独立的纹理文件。如果你希望保持“一张纹理包含多个精灵”的结构,就需要更复杂的逻辑:将所有子精灵切割后,再根据它们的相对位置,拼接到一张新的纹理上,并生成对应的sprite sheet数据(通过TextureImporter.spritesheet)。这相当于一个反向的图集打包过程,实现起来复杂得多,需要仔细计算布局和UV。

7. 避坑指南总结与常见问题排查

Q1: 运行工具后,输出的图片全是空白(透明)或颜色不对。

  • A1:首先检查图集纹理的Read/Write Enabled是否打开。这是最常见的原因。在Project窗口选中图集纹理,在Inspector中查看导入设置。
  • A2:检查像素坐标计算。在循环中打印startX, startY, width, height以及几个样本点的sourceX, sourceY,确认它们在纹理范围内,并且GetPixel读取的颜色不是Color.clear
  • A3:确认你操作的是正确的纹理。通过Debug.Log(atlasTexture.name + ", " + atlasTexture.width + "x" + atlasTexture.height)输出信息核对。

Q2: 切割出来的图片边缘有杂色或模糊。

  • A2:如图集纹理的Filter Mode不是Point,且精灵之间没有Padding。按照上文“纹理边缘与滤波”章节的解决方案处理。最推荐的方法是确保图集打包时有足够的Padding(2-4像素)。

Q3: 处理速度非常慢,编辑器卡死。

  • A3:你很可能在使用GetPixel/SetPixel循环。立即切换到GetPixels32/SetPixels32方案。如果已经用了批量接口还慢,检查是否一次性读取了超大图集。考虑使用分块读取(GetPixels32带区域参数)并添加进度条。

Q4: 有些精灵切割后方向不对(旋转了90度)。

  • A4:该精灵在图集中被旋转打包了。检查sprite.packingRotation。如果项目允许旋转,你需要实现旋转逻辑。如果项目不允许,应该在图集打包设置中禁用旋转,并重新打包。

Q5: 生成的Sprite的Pivot(中心点)不对。

  • A5:我们的基础工具只复制了像素,没有复制Sprite的元数据。在保存后设置TextureImporter时,你可以从原Sprite读取pivotspriteBorder等信息进行赋值。
    importer.spritePivot = sprite.pivot; Vector4 border = sprite.border; importer.spriteBorder = new Vector4(border.x, border.y, border.z, border.w);

Q6: 如何切割Mesh Type为Tightly Packed的精灵?

  • A6:Tightly Packed的精灵其网格不是矩形,而是由许多三角形组成的自定义形状,以节省透明区域。sprite.rect是其包围盒。如果你按矩形切割,会包含很多透明像素。要精确切割,需要遍历sprite.verticessprite.triangles,为每个顶点根据sprite.uv从图集采样颜色,然后在新纹理上对应位置(需要重新计算UV)绘制。这是一个高级主题,涉及网格和纹理的重新映射,通常需要借助Texture2D.ReadPixels配合临时RenderTexture或使用Graphics.DrawMesh来实现,复杂度远超本文范围。对于大多数情况,按矩形切割并保留透明边是可以接受的。

最后,分享一个我个人的小技巧:在编写这类资源处理工具时,总是先在一个备份项目或单独目录中测试。因为文件操作和AssetDatabase的刷新是不可逆的,批量处理一旦出错可能会覆盖大量原始数据。先小范围测试,确认输出无误后再进行全量操作。

http://www.jsqmd.com/news/1328520/

相关文章:

  • 如何快速掌握绝区零一条龙:新手必备的完整使用教程
  • 数据标注有“三集”:训练集、验证集、测试集到底有什么区别?
  • 2026暑假老师效率助手:跳绳阅读练字打卡,作业收集批改全搞定
  • 无断链三维轨迹重构与战场情报溯源
  • 十万个why:为什么做RAG 最怕的,不是检索失败,是明令禁止瞎答,它还硬编答案
  • 屈原:世人只知屈原投江,却不知他的楚辞,是华夏浪漫文学的起点
  • 【信息科学与工程学】【数据中心】计算机科学与自动化——第三百零五篇 数据中心 Scale-Up、Scale-Out、Scale-Across101 芯片接入数据中心129
  • 杭州专业的蜂窝板材厂家怎么选?看工艺与交付更靠谱 - 热点品牌推荐
  • 2026徐州靠谱家装企业盘点,本土老牌徐州大自然装饰公司 - 招财兔数字员工
  • 网站时光机HTTrack:3分钟掌握离线浏览与数据备份终极指南
  • 英雄联盟Akari助手:免费开源的游戏效率工具,提升你的操作水平300%
  • 掌握谈判主动权,从读懂这本经典谈判书籍开始
  • 基于ARM+FPGA平台在精密测控领域的应用:ZYNQ高精度温振压多参融合采集系统
  • SpringBoot+Vue电影推荐系统开发与协同过滤算法实践
  • 杭州毛坯房地暖选择与安装全攻略
  • GEO技术如何操控大模型推荐系统及防御方案
  • 复杂电磁环境下石油通信训练,基于 HWG1 分析电磁环境模拟技术
  • AI宠物产品客观评测:核心参数与使用体验的实测解析 - 招财兔数字员工
  • 山海万灵 HarmonyOS 文化知识设计续篇(10):地图缩放与区域选中态协同方案
  • 线上竞拍实操技巧,理性参拍不冲动消费
  • 【限时解密】AI景深效果工业级落地手册:从单目深度估计到DOF物理仿真,附NASA开源光学引擎适配补丁
  • 医院药品与样本配送机器人安全性评测:哪家品牌更值得信赖?
  • [实践经验] 指导 AI 写文章时,别只改一篇稿子:把口癖和空话沉淀成 skill 再审一遍
  • tsMuxer视频封装技术深度解析:专业级传输流复用解决方案
  • AI-SEO优化策略:提升内容在ChatGPT中的可见性
  • Unity刚体Rigidbody实战:5分钟搞定物体自由落体与碰撞检测(附避坑指南)
  • 3步高效解密网易云音乐NCM文件:ncmdump完整实用指南
  • 唐山教育行业优质 GEO 服务商全业态大盘点,附获客效果与转化增长体系深度解析 - 资讯123
  • 镜像视界浙江普陀时空大数据研究院耿文海
  • 2026最权威的五大AI论文网站实测分析