Unity 之 图集属性详解和代码示例 -- 拓展一键自动打包图集工具

简介: 图集只是当所有给低昂的纹理需要相同的着色器时采用的一种方法,如果一些纹理需要通过着色器应用独立的图形效果,它们就必须分离到自己的材质中,并在单独的组中打图集。

Unity图集.png

一,图集的相关概念

1.1 图集的定义

  图集是将许多较小的,独立的纹理合并到一个较大的纹理文件中,从而最小化材质的数量,因此最小化所需使用的Draw Call数量。这是利用动态批处理的有效方法。

  每个独特的材质都需要额外的Draw Call,但是每种材质只支持单一的主纹理。当然,它们也可以支持多个二级纹理,比如法线纹理和发射纹理。然而,将多个主纹理合并到一个大的纹理文件中,渲染这个纹理的对象时,可以最小化所使用的Draw Call数量。

⚠️ :图集只是当所有给低昂的纹理需要相同的着色器时采用的一种方法,如果一些纹理需要通过着色器应用独立的图形效果,它们就必须分离到自己的材质中,并在单独的组中打图集。


1.2 图集的意义

  1. 减少DrawCall:多张图片需要多次DrawCall,合并成一张大图只需要调用一次DrawCall
  2. 性能优化:图集将图片打包为2的幂次方的素材大小,GPU处理起来会快很多,方便unity渲染合批,降低渲染消耗,小图不可能每张都是2的n次方的大小,所以会提升效率。
  3. 减小包体大小:多张图片的打包占用会大于一张2的n次方的大图的大小。
  • 举个例子:

游戏大厅需要这么多张图片:

如果我么单张使用,这就存在上面说的三个问题了:多个DrawCall,效率降低,包体变大

为了解决这三个问题,图集的存在就变得有意义了。那么可能就有同学要问了,图片是美术给的,那么图集也由美术打包,这样可行吗?

答案是:可行,但有一定的限制。

这是美术将所有用到的图片放在一起打包的图集:

  我们在使用的时候需要自己拆分,这样使用起来就不是很方便了。最关键的是以后大厅随便添加或者修改一张图都需要美术给我们一整张图集,这样明显是不合理的。 所以只有当场景资源固定了,以后不会在有任何改动的情况下,是可以由美术出图集的。

  但是,稍微有点经验的程序员都不会使用上述这种情况。因为项目本身不被修改那几乎是不可能的,有的时候不但要改,而且还要大改;所以将打包图集放在程序上来出来也是更方便,更合理的,下面我们就来一起学习一下Unity的图集吧。


二,图集的属性介绍

2.1 属性面板

属性 功能
Type 将“Sprite Atlas”类型设置为MasterVariantMaster是默认类型设置,Variant设置一般用于高清/低清资源切换。下面会有一个示例帮助理解。
Include in build 选中此框可将Sprite Atlas资源包括在当前构建中。
Allow Rotation 选中此框可以在Unity将精灵打包到精灵地图集时允许精灵旋转。开启后图集会根据计算将一些精灵旋转排列,这样使得图集更小更紧密。被旋转的精灵在场景被使用时也会对应旋转,这样处理起来就会很麻烦,所以一般情况会关闭此选项。
Tight Packing 选中此框可根据精灵轮廓而不是默认矩形轮廓来打包精灵。开启后,精灵在图集中密集排列,甚至有时会出现一张图上有另外一张图的边角的情况,所以一般使用时会关闭这个属性。
Read/Write Enabled 选中此框可以从脚本函数(例如Texture2D.SetPixels代码访问时需要开启)访问纹理数据。开启此属性,Unity将创建纹理数据的副本。所以会增大内存占用。此属性仅对未压缩或DXT压缩的纹理有效,所以需要开启此属性的图片也不能修改压缩格式。
Generate Mip Maps 选择此选项可启用Mip-Map生成。Mip贴图是Texture的较小版本,当Texture在屏幕上很小时会使用。
Filter Mode 选择如何过滤纹理;选择Unity在变换期间拉伸时过滤压缩纹理的方式。此设置覆盖Atlas中任何打包精灵的过滤模式设置。

  • 手动添加图集

Objects For Packing:将此列表中的所有项目打包到当前选定的Sprite图集中,点击+加号,则可选择一张图片放入图集中,所有图集选择完毕后,点击Pack Preview结果如下图所示:

  • Variant - 高清/低清资源切换

创建新的Sprite Atlas,然后设置Type为Variant类型,并设置关联的Master Atlas,修改Scale即可改变分辨率,可以看到当Scale设置为0.5时,图片以及变的模糊了:


2.2 格式处理

Platform-specific overrides panel :为Sprite Atlas的每个目标平台设置分辨率、文件大小以及相关内存大小要求、像素尺寸和纹理质量。该面板允许您覆盖精灵图集包含的各个纹理上的这些设置。

后面那些带有自己平台的标识的,都可以通过勾选Override属性,来设置对应平台的图集属性。
比如:在移动的平台(Android,IOS)需要控制包体,那么可以将图集格式设置为ASCT6x6;而在PC端图片的清晰度比包体大小跟重要,则图集可是就可以设置为RGBA32。


2.3 代码操作

// 1,创建图集
SpriteAtlas spriteAtlas = new SpriteAtlas();
AssetDatabase.CreateAsset(spriteAtlas, "Assets/CzhenyaTest.spriteatlas");

// 2,添加图片到Sprite Atlas图集

SpriteAtlas spriteAtlas = new SpriteAtlas();
// 获取图集下图片
List<Object> packables = new List<Object>(spriteAtlas.GetPackables());

// 每个图集的所有图片路径
private static List<string> textureFullName = new List<string>();
foreach (string item in textureFullName)
{
   // 加载指定目录下的图片
   Object spriteObj = AssetDatabase.LoadAssetAtPath(item, typeof(Object));
   if (!packables.Contains(spriteObj))
   {
       // 添加到图集中
      spriteAtlas.Add(new Object[] {spriteObj});
   }
}

// 3,图集基础设置

SpriteAtlas spriteAtlas = new SpriteAtlas();

SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings()
{
    blockOffset = 1,
    enableRotation = false,
    enableTightPacking = false,
    padding = 8,
};

spriteAtlas.SetPackingSettings(packSetting);      

// 4,图集纹理设置
  
SpriteAtlas spriteAtlas = new SpriteAtlas();

SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings()
{
    readable = false,
    generateMipMaps = false,
    sRGB = true,
    filterMode = FilterMode.Bilinear,
};

spriteAtlas.SetTextureSettings(textureSettings);

// 5,分平台设置纹理

SpriteAtlas spriteAtlas = new SpriteAtlas();

TextureImporterPlatformSettings platformSetting = atlas.GetPlatformSettings("Android");
platformSetting.overridden = true;
platformSetting.maxTextureSize = 2048;
platformSetting.textureCompression = TextureImporterCompression.Compressed;
platformSetting.format = TextureImporterFormat.ASTC_6x6;

spriteAtlas.SetPlatformSettings(platformSetting);

三,拓展 -- 打包图集工具

3.1 图片存放策略

创建在Editor下面一个所有图片的根目录,然后在根据游戏功能或者面板公用等条件,将同一组图片归类到一个文件夹下。比如:Editor/Textures/Login,Editor/Textures/Lobby

这是我下面Demo中使用的图片路径:Assets/CreateAtlas/Editor/Res/SpriteAtlas

PS:按照设计以后新增的图片都会按照功能再分文件夹放到SpriteAtlas文件夹下。


3.2 图集打包逻辑

  • 创建图集 --> 图集基础设置 --> 图集纹理设置 --> 按需设置分平台图集格式
  • 遍历文件夹 --> 根据格式过滤图片 --> 判断是否存在图集 --> 不存在则加入

3.3 图集打包代码

按照3.2的逻辑编写代码,注释写的已很详细:

注意代码放到Editor文件夹下。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.U2D;
using UnityEngine;
using UnityEngine.U2D;
using Object = UnityEngine.Object;

public class CreateAtlas : MonoBehaviour
{
    /// <summary>
    /// 图片根目录 -- 需要打包图集的文件夹父级
    /// 适用目录结构:根部文件夹
    ///                -> 图片文件夹1
    ///                -> 图片文件夹2
    ///                ... 
    /// </summary>
    private static string pathRoot = Application.dataPath + "/CreateAtlas/Editor/Res/SpriteAtlas/";
    
    /// <summary>
    /// 图集存储路径
    /// </summary>Assets/CreateAtlas/Editor/Res/Textures
    private static string atlasStoragePath = "Assets/CreateAtlas/Editor/Res/Textures/";
    
    /// <summary>
    /// 每个需要打图集的文件夹名 -- 即图集名
    /// </summary>
    private static string spritefilePathName;

    [MenuItem("Tools/打包图集")]
    public static void CreateAllSpriteAtlas()
    {
        Debug.Log("打包图集开始执行");

        DirectoryInfo info = new DirectoryInfo(pathRoot);
        int index = 0;
        // 遍历根目录
        foreach (DirectoryInfo item in info.GetDirectories())
        {
            spritefilePathName = item.Name;
             
            SpriteAtlas spriteAtlas = AssetDatabase.LoadAssetAtPath(atlasStoragePath + "/" +  spritefilePathName + ".spriteatlas", typeof(Object)) as SpriteAtlas;
   
            // 不存在则创建后更新图集
            if (spriteAtlas == null)
            {
                spriteAtlas = CreateSpriteAtlas(spritefilePathName);
            }

            string spriteFilePath = pathRoot + "/" + spritefilePathName;
            UpdateAtlas(spriteAtlas, spriteFilePath);
            
            // 打包进度
            EditorUtility.DisplayProgressBar("打包图集中...", "正在处理:" + item, index / info.GetDirectories().Length);
            index++;
        }

        EditorUtility.ClearProgressBar();
        AssetDatabase.Refresh();
        
        Debug.Log("打包图集执行结束");
    }
     
    /// <summary>
    /// 创建图集
    /// </summary>
    /// <param name="atlasName">图集名字</param>
    private static SpriteAtlas CreateSpriteAtlas(string atlasName)
    {
        SpriteAtlas atlas = new SpriteAtlas();

        #region 图集基础设置
        
        SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings()
        {
            blockOffset = 1,
            enableRotation = false,
            enableTightPacking = false,
            padding = 8,
        };
        atlas.SetPackingSettings(packSetting);
        
        #endregion
        
        #region 图集纹理设置
        
        SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings()
        {
            readable = false,
            generateMipMaps = false,
            sRGB = true,
            filterMode = FilterMode.Bilinear,
        };
        atlas.SetTextureSettings(textureSettings);
        
        #endregion
        
        #region 分平台设置图集格式
        
        TextureImporterPlatformSettings platformSetting = atlas.GetPlatformSettings(GetPlatformName(BuildTarget.iOS));
        platformSetting.overridden = true;
        platformSetting.maxTextureSize = 2048;
        platformSetting.textureCompression = TextureImporterCompression.Compressed;
        platformSetting.format = TextureImporterFormat.PVRTC_RGB4;
        atlas.SetPlatformSettings(platformSetting);
        
        // 需要多端同步,就在写一份
        platformSetting = atlas.GetPlatformSettings(GetPlatformName(BuildTarget.Android));
        platformSetting.overridden = true;
        platformSetting.maxTextureSize = 2048;
        platformSetting.textureCompression = TextureImporterCompression.Compressed;
        platformSetting.format = TextureImporterFormat.ASTC_6x6;
        atlas.SetPlatformSettings(platformSetting); 
        
        #endregion

        string atlasPath = atlasStoragePath + "/" + atlasName + ".spriteatlas";
        AssetDatabase.CreateAsset(atlas, atlasPath);
        AssetDatabase.SaveAssets();

        return atlas;
    }

    /// <summary>
    /// 每个图集的所有图片路径  --  记得用之前清空
    /// </summary>
    private static List<string> textureFullName = new List<string>();

    /// <summary>
    /// 更新图集内容
    /// </summary>
    /// <param name="atlas">图集</param>
    static void UpdateAtlas(SpriteAtlas atlas, string spriteFilePath)
    {
        textureFullName.Clear();
        FileName(spriteFilePath);

        // 获取图集下图片
        List<Object> packables = new List<Object>(atlas.GetPackables());

        foreach (string item in textureFullName)
        {
            // 加载指定目录
            Object spriteObj = AssetDatabase.LoadAssetAtPath(item, typeof(Object));
            Debug.Log("存png和jpg后缀的图片: " +item + " , " + !packables.Contains(spriteObj));
            if (!packables.Contains(spriteObj))
            {
                atlas.Add(new Object[] {spriteObj});
            }
        }
    }

    /// <summary>
    /// 递归文件夹下的图
    /// </summary>
    /// <param name="folderPath"></param>
    static void FileName(string folderPath)
    {
        DirectoryInfo info = new DirectoryInfo(folderPath);
        foreach (DirectoryInfo item in info.GetDirectories())
        {
            FileName(item.FullName);
        }
        foreach (FileInfo item in info.GetFiles())
        { 
            // 存png和jpg后缀的图片
            if (item.FullName.EndsWith(".png", StringComparison.Ordinal)
                || item.FullName.EndsWith(".jpg", StringComparison.Ordinal))
            {
                textureFullName.Add("Assets" + item.FullName.Replace(Application.dataPath, ""));
            }
        }
    }
 
    /// <summary>
    /// 不同平台枚举对应的值
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    static string GetPlatformName(BuildTarget target)
    {
        string platformName = "";
        switch (target)
        {
            case BuildTarget.Android:
                platformName = "Android";
                break;
            case BuildTarget.iOS:
                platformName = "iPhone";
                break;
            case BuildTarget.PS4:
                platformName = "PS4";
                break;
            case BuildTarget.XboxOne:
                platformName = "XboxOne";
                break;
            case BuildTarget.NoTarget:
                platformName = "DefaultTexturePlatform";
                break;
            default:
                platformName = "Standalone";
                break;
        }
        return platformName;
    }    
}

3.4 图集打包示例

1.将3.3代码添加到工程之后,当代码编译完成后,会在上方工具栏中多出一个:Tools/打包图集 菜单,如下图:

2.点击打包图集后,就会把文件夹SpriteAtlas下面的图按照文件夹打包成图集。逻辑执行完成后就会在
Textures文件夹下生成新的图集:

3.代码打包后的图集效果:


至此,关于Unity图集SpriteAtlas的相关介绍和一键打包工具就全部介绍完了。

相关文章
|
2月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
39 1
|
2月前
|
图形学
小功能⭐️Unity UnityEvent实现代码的选择
小功能⭐️Unity UnityEvent实现代码的选择
|
2月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
66 0
|
2月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
43 0
|
2月前
|
图形学 开发者
【Unity光照艺术手册】掌握这些技巧,让你的游戏场景瞬间提升档次:从基础光源到全局光照,打造24小时不间断的视觉盛宴——如何运用代码与烘焙创造逼真光影效果全解析
【8月更文挑战第31天】在Unity中,合理的光照与阴影设置对于打造逼真环境至关重要。本文介绍Unity支持的多种光源类型,如定向光、点光源、聚光灯等,并通过具体示例展示如何使用着色器和脚本控制光照强度,模拟不同时间段的光照变化。此外,还介绍了动态和静态阴影、全局光照及光照探针等高级功能,帮助开发者创造丰富多样的光影效果,提升游戏沉浸感。
39 0
|
2月前
|
开发者 图形学 UED
深度解析Unity游戏开发中的性能瓶颈与优化方案:从资源管理到代码执行,全方位提升你的游戏流畅度,让玩家体验飞跃性的顺滑——不止是技巧,更是艺术的追求
【8月更文挑战第31天】《Unity性能优化实战:让你的游戏流畅如飞》详细介绍了Unity游戏性能优化的关键技巧,涵盖资源管理、代码优化、场景管理和内存管理等方面。通过具体示例,如纹理打包、异步加载、协程使用及LOD技术,帮助开发者打造高效流畅的游戏体验。文中提供了实用代码片段,助力减少内存消耗、提升渲染效率,确保游戏运行丝滑顺畅。性能优化是一个持续过程,需不断测试调整以达最佳效果。
62 0
|
2月前
|
图形学 数据安全/隐私保护 iOS开发
Unity与IOS⭐Xcode打包,上架TestFlight的完整教程
Unity与IOS⭐Xcode打包,上架TestFlight的完整教程
|
2月前
|
图形学
小功能⭐️Unity改变代码执行顺序
小功能⭐️Unity改变代码执行顺序
|
4月前
|
图形学
【unity小技巧】unity通过代码进行更改后处理效果
【unity小技巧】unity通过代码进行更改后处理效果
47 0
|
4月前
|
编解码 算法 图形学
【unity小技巧】减少Unity中的构建打包大小
【unity小技巧】减少Unity中的构建打包大小
90 0