【NGUI】Atlas的动态创建方法

1、参见SZUIAtlasMakerRuntimeTest设置相应的值以上值需要提前设置好

2、没有检查是否atlas能够正确创建,自己可以改,加入返回值

3、代码都是在NGUI里面拷贝出来的,只是进行改动,没有新代码

4、适用与那种从网上下图片,之后还不想用UITexture的人,但是还是建议用UITexture如果drawcall不是问题的话

5、自己以后更新按我的方式改改就可以

6、动态创建速度较慢,建议在游戏启动的时候运行

7、游戏时可以将创建的atlas保存到可写目录,避免每次都新创建

SZUIAtlasMakerRuntimeTest.cs


<font face="Arial">using UnityEngine;
using System.Collections;
 
public class SZUIAtlasMakerRuntimeTest : MonoBehaviour {
 
    public Texture2D[] texs;
    public UISprite sprite;
    private UIAtlas atlas;
 
    void Start () {
 
        SZUIAtlasMakerRuntime.atlasTrimming = true;
        SZUIAtlasMakerRuntime.atlasPMA = atlas != null ? atlas.premultipliedAlpha : false;
        SZUIAtlasMakerRuntime.unityPacking = false;
        SZUIAtlasMakerRuntime.atlasPadding = 1;
        SZUIAtlasMakerRuntime.allow4096 = true;
        SZUIAtlasMakerRuntime.UITexturePacker.forceSquareAtlas = true;
 
        if (atlas == null)
        {
            atlas = this.gameObject.AddComponent<UIAtlas>();
        }
        string lastName = string.Empty;
        foreach (var tex in texs)
        {
            SZUIAtlasMakerRuntime.AddOrUpdate(atlas, tex);
            lastName = tex.name;
        }
        sprite.atlas = atlas;
        sprite.spriteName = lastName;
    }
 
}
</font>


SZUIAtlasMakerRuntime.cs


<font face="Arial">using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class SZUIAtlasMakerRuntime {
 
    public static bool atlasTrimming = true;
    public static bool atlasPMA = false;
    public static bool unityPacking = false;
    public static int atlasPadding = 1;
    public static bool allow4096 = true;
 
    public class SpriteEntry : UISpriteData
    {
        // Sprite texture -- original texture or a temporary texture
        public Texture2D tex;
        
        // Whether the texture is temporary and should be deleted
        public bool temporaryTexture = false;
    }
 
    /// <summary>
    /// Used to sort the sprites by pixels used
    /// </summary>
    
    static int Compare (SpriteEntry a, SpriteEntry b)
    {
        // A is null b is not b is greater so put it at the front of the list
        if (a == null && b != null) return 1;
        
        // A is not null b is null a is greater so put it at the front of the list
        if (a != null && b == null) return -1;
        
        // Get the total pixels used for each sprite
        int aPixels = a.width * a.height;
        int bPixels = b.width * b.height;
        
        if (aPixels > bPixels) return -1;
        else if (aPixels < bPixels) return 1;
        return 0;
    }
 
    /// <summary>
    /// Pack all of the specified sprites into a single texture, updating the outer and inner rects of the sprites as needed.
    /// </summary>
    
    static bool PackTextures (Texture2D tex, List<SpriteEntry> sprites)
    {
        Texture2D[] textures = new Texture2D[sprites.Count];
        Rect[] rects;
        
        #if UNITY_3_5 || UNITY_4_0
        int maxSize = 4096;
        #else
        int maxSize = SystemInfo.maxTextureSize;
        #endif
        
        #if UNITY_ANDROID || UNITY_IPHONE
        maxSize = Mathf.Min(maxSize, allow4096 ? 4096 : 2048);
        #endif
        if (unityPacking)
        {
            for (int i = 0; i < sprites.Count; ++i) textures[i] = sprites[i].tex;
            rects = tex.PackTextures(textures, atlasPadding, maxSize);
        }
        else
        {
            sprites.Sort(Compare);
            for (int i = 0; i < sprites.Count; ++i) textures[i] = sprites[i].tex;
            rects = UITexturePacker.PackTextures(tex, textures, 4, 4, atlasPadding, maxSize);
        }
        
        for (int i = 0; i < sprites.Count; ++i)
        {
            Rect rect = NGUIMath.ConvertToPixels(rects[i], tex.width, tex.height, true);
            
            // Make sure that we don't shrink the textures
            if (Mathf.RoundToInt(rect.width) != textures[i].width) return false;
            
            SpriteEntry se = sprites[i];
            se.x = Mathf.RoundToInt(rect.x);
            se.y = Mathf.RoundToInt(rect.y);
            se.width = Mathf.RoundToInt(rect.width);
            se.height = Mathf.RoundToInt(rect.height);
        }
        return true;
    }
 
    static public void AddOrUpdate (UIAtlas atlas, Texture2D tex)
    {
        if (atlas != null && tex != null)
        {
            List<Texture> textures = new List<Texture>();
            textures.Add(tex);
            List<SpriteEntry> sprites = CreateSprites(textures);
            ExtractSprites(atlas, sprites);
            UpdateAtlas(atlas, sprites);
        }
    }
    
    /// <summary>
    /// Update the sprite atlas, keeping only the sprites that are on the specified list.
    /// </summary>
    
    static public void UpdateAtlas (UIAtlas atlas, List<SpriteEntry> sprites)
    {
        if (sprites.Count > 0)
        {
            // Combine all sprites into a single texture and save it
            if (UpdateTexture(atlas, sprites))
            {
                // Replace the sprites within the atlas
                ReplaceSprites(atlas, sprites);
            }
            
            // Release the temporary textures
            ReleaseSprites(sprites);
            return;
        }
        else
        {
            atlas.spriteList.Clear();
            NGUITools.Destroy(atlas.spriteMaterial.mainTexture);
            atlas.spriteMaterial.mainTexture = null;
        }
        
        atlas.MarkAsChanged();
    }
 
    /// <summary>
    /// Add a new sprite to the atlas, given the texture it's coming from and the packed rect within the atlas.
    /// </summary>
    
    static public UISpriteData AddSprite (List<UISpriteData> sprites, SpriteEntry se)
    {
        // See if this sprite already exists
        foreach (UISpriteData sp in sprites)
        {
            if (sp.name == se.name)
            {
                sp.CopyFrom(se);
                return sp;
            }
        }
        
        UISpriteData sprite = new UISpriteData();
        sprite.CopyFrom(se);
        sprites.Add(sprite);
        return sprite;
    }
 
    /// <summary>
    /// Create a list of sprites using the specified list of textures.
    /// </summary>
    ///
    static public List<SpriteEntry> CreateSprites (List<Texture> textures)
    {
        List<SpriteEntry> list = new List<SpriteEntry>();
        
        foreach (Texture tex in textures)
        {
            Texture2D oldTex = tex as Texture2D;
            
            // If we aren't doing trimming, just use the texture as-is
            if (!atlasTrimming && !atlasPMA)
            {
                SpriteEntry sprite = new SpriteEntry();
                sprite.SetRect(0, 0, oldTex.width, oldTex.height);
                sprite.tex = oldTex;
                sprite.name = oldTex.name;
                sprite.temporaryTexture = false;
                list.Add(sprite);
                continue;
            }
            
            // If we want to trim transparent pixels, there is more work to be done
            Color32[] pixels = oldTex.GetPixels32();
            
            int xmin = oldTex.width;
            int xmax = 0;
            int ymin = oldTex.height;
            int ymax = 0;
            int oldWidth = oldTex.width;
            int oldHeight = oldTex.height;
            
            // Find solid pixels
            if (atlasTrimming)
            {
                for (int y = 0, yw = oldHeight; y < yw; ++y)
                {
                    for (int x = 0, xw = oldWidth; x < xw; ++x)
                    {
                        Color32 c = pixels[y * xw + x];
                        
                        if (c.a != 0)
                        {
                            if (y < ymin) ymin = y;
                            if (y > ymax) ymax = y;
                            if (x < xmin) xmin = x;
                            if (x > xmax) xmax = x;
                        }
                    }
                }
            }
            else
            {
                xmin = 0;
                xmax = oldWidth - 1;
                ymin = 0;
                ymax = oldHeight - 1;
            }
            
            int newWidth  = (xmax - xmin) + 1;
            int newHeight = (ymax - ymin) + 1;
            
            if (newWidth > 0 && newHeight > 0)
            {
                SpriteEntry sprite = new SpriteEntry();
                sprite.x = 0;
                sprite.y = 0;
                sprite.width = oldTex.width;
                sprite.height = oldTex.height;
                
                // If the dimensions match, then nothing was actually trimmed
                if (!atlasPMA && (newWidth == oldWidth && newHeight == oldHeight))
                {
                    sprite.tex = oldTex;
                    sprite.name = oldTex.name;
                    sprite.temporaryTexture = false;
                }
                else
                {
                    // Copy the non-trimmed texture data into a temporary buffer
                    Color32[] newPixels = new Color32[newWidth * newHeight];
                    
                    for (int y = 0; y < newHeight; ++y)
                    {
                        for (int x = 0; x < newWidth; ++x)
                        {
                            int newIndex = y * newWidth + x;
                            int oldIndex = (ymin + y) * oldWidth + (xmin + x);
                            if (atlasPMA) newPixels[newIndex] = NGUITools.ApplyPMA(pixels[oldIndex]);
                            else newPixels[newIndex] = pixels[oldIndex];
                        }
                    }
                    
                    // Create a new texture
                    sprite.temporaryTexture = true;
                    sprite.name = oldTex.name;
                    sprite.tex = new Texture2D(newWidth, newHeight);
                    sprite.tex.SetPixels32(newPixels);
                    sprite.tex.Apply();
                    
                    // Remember the padding offset
                    sprite.SetPadding(xmin, ymin, oldWidth - newWidth - xmin, oldHeight - newHeight - ymin);
                }
                list.Add(sprite);
            }
        }
        return list;
    }
 
    /// <summary>
    /// Release all temporary textures created for the sprites.
    /// </summary>
    
    static public void ReleaseSprites (List<SpriteEntry> sprites)
    {
        foreach (SpriteEntry se in sprites)
        {
            if (se.temporaryTexture)
            {
                NGUITools.Destroy(se.tex);
                se.tex = null;
            }
        }
        Resources.UnloadUnusedAssets();
    }
    
    /// <summary>
    /// Replace the sprites within the atlas.
    /// </summary>
    
    static public void ReplaceSprites (UIAtlas atlas, List<SpriteEntry> sprites)
    {
        // Get the list of sprites we'll be updating
        List<UISpriteData> spriteList = atlas.spriteList;
        List<UISpriteData> kept = new List<UISpriteData>();
        
        // Run through all the textures we added and add them as sprites to the atlas
        for (int i = 0; i < sprites.Count; ++i)
        {
            SpriteEntry se = sprites[i];
            UISpriteData sprite = AddSprite(spriteList, se);
            kept.Add(sprite);
        }
        
        // Remove unused sprites
        for (int i = spriteList.Count; i > 0; )
        {
            UISpriteData sp = spriteList[--i];
            if (!kept.Contains(sp)) spriteList.RemoveAt(i);
        }
        
        // Sort the sprites so that they are alphabetical within the atlas
        atlas.SortAlphabetically();
        atlas.MarkAsChanged();
    }
 
    /// <summary>
    /// Extract the specified sprite from the atlas.
    /// </summary>
    ///
    static public SpriteEntry ExtractSprite (UIAtlas atlas, string spriteName)
    {
        if (atlas.texture == null) return null;
        UISpriteData sd = atlas.GetSprite(spriteName);
        if (sd == null) return null;
        
        Texture2D tex = atlas.texture as Texture2D;
        SpriteEntry se = ExtractSprite(sd, tex);
        return se;
    }
    
    /// <summary>
    /// Extract the specified sprite from the atlas texture.
    /// </summary>
    
    static SpriteEntry ExtractSprite (UISpriteData es, Texture2D tex)
    {
        return (tex != null) ? ExtractSprite(es, tex.GetPixels32(), tex.width, tex.height) : null;
    }
    
    /// <summary>
    /// Extract the specified sprite from the atlas texture.
    /// </summary>
    
    static SpriteEntry ExtractSprite (UISpriteData es, Color32[] oldPixels, int oldWidth, int oldHeight)
    {
        int xmin = Mathf.Clamp(es.x, 0, oldWidth);
        int ymin = Mathf.Clamp(es.y, 0, oldHeight);
        int xmax = Mathf.Min(xmin + es.width, oldWidth - 1);
        int ymax = Mathf.Min(ymin + es.height, oldHeight - 1);
        int newWidth = Mathf.Clamp(es.width, 0, oldWidth);
        int newHeight = Mathf.Clamp(es.height, 0, oldHeight);
        
        if (newWidth == 0 || newHeight == 0) return null;
        
        Color32[] newPixels = new Color32[newWidth * newHeight];
        
        for (int y = 0; y < newHeight; ++y)
        {
            int cy = ymin + y;
            if (cy > ymax) cy = ymax;
            
            for (int x = 0; x < newWidth; ++x)
            {
                int cx = xmin + x;
                if (cx > xmax) cx = xmax;
                
                int newIndex = (newHeight - 1 - y) * newWidth + x;
                int oldIndex = (oldHeight - 1 - cy) * oldWidth + cx;
                
                newPixels[newIndex] = oldPixels[oldIndex];
            }
        }
        
        // Create a new sprite
        SpriteEntry sprite = new SpriteEntry();
        sprite.CopyFrom(es);
        sprite.SetRect(0, 0, newWidth, newHeight);
        sprite.temporaryTexture = true;
        sprite.tex = new Texture2D(newWidth, newHeight);
        sprite.tex.SetPixels32(newPixels);
        sprite.tex.Apply();
        return sprite;
    }
    
    /// <summary>
    /// Extract sprites from the atlas, adding them to the list.
    /// </summary>
    
    static public void ExtractSprites (UIAtlas atlas, List<SpriteEntry> finalSprites)
    {
        Texture2D tex = atlas.texture as Texture2D;
        
        if (tex != null)
        {
            Color32[] pixels = null;
            int width = tex.width;
            int height = tex.height;
            List<UISpriteData> sprites = atlas.spriteList;
            float count = sprites.Count;
            int index = 0;
            
            foreach (UISpriteData es in sprites)
            {              
                bool found = false;
                
                foreach (SpriteEntry fs in finalSprites)
                {
                    if (es.name == fs.name)
                    {
                        fs.CopyBorderFrom(es);
                        found = true;
                        break;
                    }
                }
                
                if (!found)
                {
                    if (pixels == null) pixels = tex.GetPixels32();
                    SpriteEntry sprite = ExtractSprite(es, pixels, width, height);
                    if (sprite != null) finalSprites.Add(sprite);
                }
            }
        }
    }
 
    static public bool UpdateTexture (UIAtlas atlas, List<SpriteEntry> sprites)
    {
        // Get the texture for the atlas
        Texture2D tex = atlas.texture as Texture2D;
    
        bool newTexture = tex == null;
        
        if (newTexture)
        {
            // Create a new texture for the atlas
            tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
        }
        
        // Pack the sprites into this texture
        if (PackTextures(tex, sprites) && tex != null)
        {
            // Update the atlas texture
            if (newTexture)
            {
                if (atlas.spriteMaterial == null)
                {
                    Shader shader = Shader.Find(atlasPMA ? "Unlit/Premultiplied Colored" : "Unlit/Transparent Colored");
                    atlas.spriteMaterial = new Material(shader);
                }
                atlas.spriteMaterial.mainTexture = tex;
                ReleaseSprites(sprites);
            }
            return true;
        }
        else
        {
            return false;
        }
    }
 
    // save as ngui's
    public class UITexturePacker
    {
        // sz modify
        public static bool forceSquareAtlas = true;
        public int binWidth = 0;
        public int binHeight = 0;
        public bool allowRotations;
        
        public List<Rect> usedRectangles = new List<Rect>();
        public List<Rect> freeRectangles = new List<Rect>();
        
        public enum FreeRectChoiceHeuristic
        {
            RectBestShortSideFit, //< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
            RectBestLongSideFit, //< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
            RectBestAreaFit, //< -BAF: Positions the rectangle into the smallest free rect into which it fits.
            RectBottomLeftRule, //< -BL: Does the Tetris placement.
            RectContactPointRule //< -CP: Choosest the placement where the rectangle touches other rects as much as possible.
        };
        
        public UITexturePacker (int width, int height, bool rotations)
        {
            Init(width, height, rotations);
        }
        
        public void Init (int width, int height, bool rotations)
        {
            binWidth = width;
            binHeight = height;
            allowRotations = rotations;
            
            Rect n = new Rect();
            n.x = 0;
            n.y = 0;
            n.width = width;
            n.height = height;
            
            usedRectangles.Clear();
            
            freeRectangles.Clear();
            freeRectangles.Add(n);
        }
        
        private struct Storage
        {
            public Rect rect;
            public bool paddingX;
            public bool paddingY;
        }
        
        public static Rect[] PackTextures (Texture2D texture, Texture2D[] textures, int width, int height, int padding, int maxSize)
        {
            if (width > maxSize && height > maxSize) return null;
            if (width > maxSize || height > maxSize) { int temp = width; width = height; height = temp; }
            
            // Force square by sizing up
            // sz modify
            //if (NGUISettings.forceSquareAtlas)
            if (forceSquareAtlas)
            {
                if (width > height)
                    height = width;
                else if (height > width)
                    width = height;
            }
            UITexturePacker bp = new UITexturePacker(width, height, false);
            Storage[] storage = new Storage[textures.Length];
            
            for (int i = 0; i < textures.Length; i++)
            {
                Texture2D tex = textures[i];
                if (!tex) continue;
                
                Rect rect = new Rect();
                
                int xPadding = 1;
                int yPadding = 1;
                
                for (xPadding = 1; xPadding >= 0; --xPadding)
                {
                    for (yPadding = 1; yPadding >= 0; --yPadding)
                    {
                        rect = bp.Insert(tex.width + (xPadding * padding), tex.height + (yPadding * padding),
                                         UITexturePacker.FreeRectChoiceHeuristic.RectBestAreaFit);
                        if (rect.width != 0 && rect.height != 0) break;
                        
                        // After having no padding if it still doesn't fit -- increase texture size.
                        else if (xPadding == 0 && yPadding == 0)
                        {
                            return PackTextures(texture, textures, width * (width <= height ? 2 : 1),
                                                height * (height < width ? 2 : 1), padding, maxSize);
                        }
                    }
                    if (rect.width != 0 && rect.height != 0) break;
                }
                
                storage[i] = new Storage();
                storage[i].rect = rect;
                storage[i].paddingX = (xPadding != 0);
                storage[i].paddingY = (yPadding != 0);
            }
            
            texture.Resize(width, height);
            texture.SetPixels(new Color[width * height]);
            
            // The returned rects
            Rect[] rects = new Rect[textures.Length];
            
            for (int i = 0; i < textures.Length; i++)
            {
                Texture2D tex = textures[i];
                if (!tex) continue;
                
                Rect rect = storage[i].rect;
                int xPadding = (storage[i].paddingX ? padding : 0);
                int yPadding = (storage[i].paddingY ? padding : 0);
                Color[] colors = tex.GetPixels();
                
                // Would be used to rotate the texture if need be.
                if (rect.width != tex.width + xPadding)
                {
                    Color[] newColors = tex.GetPixels();
                    
                    for (int x = 0; x < rect.width; x++)
                    {
                        for (int y = 0; y < rect.height; y++)
                        {
                            int prevIndex = ((int)rect.height - (y + 1)) + x * (int)tex.width;
                            newColors[x + y * (int)rect.width] = colors[prevIndex];
                        }
                    }
                    
                    colors = newColors;
                }
                
                texture.SetPixels((int)rect.x, (int)rect.y, (int)rect.width - xPadding, (int)rect.height - yPadding, colors);
                rect.x /= width;
                rect.y /= height;
                rect.width = (rect.width - xPadding) / width;
                rect.height = (rect.height - yPadding) / height;
                rects[i] = rect;
            }
            texture.Apply();
            return rects;
        }
        
        public Rect Insert (int width, int height, FreeRectChoiceHeuristic method)
        {
            Rect newNode = new Rect();
            int score1 = 0; // Unused in this function. We don't need to know the score after finding the position.
            int score2 = 0;
            switch (method)
            {
            case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, ref score1, ref score2); break;
            case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, ref score1, ref score2); break;
            case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, ref score1); break;
            case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, ref score1); break;
            case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, ref score1, ref score2); break;
            }
            
            if (newNode.height == 0)
                return newNode;
            
            int numRectanglesToProcess = freeRectangles.Count;
            for (int i = 0; i < numRectanglesToProcess; ++i)
            {
                if (SplitFreeNode(freeRectangles[i], ref newNode))
                {
                    freeRectangles.RemoveAt(i);
                    --i;
                    --numRectanglesToProcess;
                }
            }
            
            PruneFreeList();
            
            usedRectangles.Add(newNode);
            return newNode;
        }
        
        public void Insert (List<Rect> rects, List<Rect> dst, FreeRectChoiceHeuristic method)
        {
            dst.Clear();
            
            while (rects.Count > 0)
            {
                int bestScore1 = int.MaxValue;
                int bestScore2 = int.MaxValue;
                int bestRectIndex = -1;
                Rect bestNode = new Rect();
                
                for (int i = 0; i < rects.Count; ++i)
  &