I’ve ran into many threads talking about unity Atlas and changing sprites in code. Some came up with solutions that load all sprites and index them, or used animations to swap between sprites in a sheet.
I needed an atlas solution that allows me to swap easily between sprite but also load/unload the atlas at runtime, as it would sometime not be needed and take memory for no reason.
Here’s the code I came solution I came up with:
-
Create an empty GameObject, it will be used to prefab our atlas.
-
Create an Atlas.cs code file, and drop the following code in it:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class Atlas : MonoBehaviour
{
[System.Serializable]
public class SpritePair
{
public string m_spriteName;
public Sprite m_sprite;
}
protected static Atlas s_atlas;
private List<Texture> m_assets = new List<Texture>();
private List<Image> m_atlasReferences;
public List<SpritePair> m_spritesList;
private Dictionary<string, Sprite> m_atlasSprites;
void Awake()
{
//init static reference and list
s_atlas = this;
m_atlasReferences = new List<Image>();
//convert list to dictionary, for faster lookup
m_atlasSprites = new Dictionary<string,Sprite>();
foreach (SpritePair pair in m_spritesList)
{
m_atlasSprites.Add(pair.m_spriteName, pair.m_sprite);
}
}
Sprite GetSprite(string zSpriteName)
{
Sprite sprite = null;
//fetch sprite in dictionary
if (m_atlasSprites.ContainsKey(zSpriteName))
sprite = m_atlasSprites[zSpriteName];
return sprite;
}
public static void AssignSprite(Image zImage, string zSpriteName)
{
s_atlas.SetSprite(zImage, zSpriteName);
}
protected void SetSprite(Image zImage, string zSpriteName)
{
if (!m_atlasReferences.Contains(zImage))
{
//add reference
m_atlasReferences.Add(zImage);
}
//assign sprite to image
Sprite sprite = s_atlas.GetSprite(zSpriteName);
zImage.sprite = sprite;
//keep reference of the atlas texture loaded with this sprite
if (!m_assets.Contains(zImage.mainTexture))
m_assets.Add(zImage.mainTexture);
}
public static void RemoveSprite(Image zImage)
{
s_atlas.DeleteSprite(zImage);
}
protected void DeleteSprite(Image zImage)
{
//clean-up sprite and atlas reference
if (m_atlasReferences.Contains(zImage))
{
m_atlasReferences.Remove(zImage);
zImage.sprite = null;
}
}
void CleanUp()
{
//clear images refering the atlas
foreach (Image img in m_atlasReferences)
img.sprite = null;
m_atlasReferences.Clear();
//clean-up list and dictionary
m_spritesList.Clear();
m_atlasSprites.Clear();
//finally unload atlas textures
for (int i = 0; i < m_assets.Count; i++)
{
Resources.UnloadAsset(m_assets[i]);
m_assets[i] = null;
}
m_assets.Clear();
}
public static void DestroyAtlas()
{
GameObject obj = s_atlas.gameObject;
s_atlas.CleanUp();
s_atlas = null;
Destroy(obj);
}
}
-
Drop the script on the empty object we created in 1
-
Populate the object by creating SpriteName and Sprite pairs (in editor)
-
Prefab it by dragging the object into your Resources folder.
So that’s our atlas created.
How to use:
-
You first need to do a
Resources.Load<GameObject>("Path/YourAtlasName");
so it is added to your scene. -
Then, you need an image in your scene, and assign it with the static function, like so:
public Image someImage;
...
Atlas.AssignSprite(someImage, "spriteName");
-
When you are done with a sprite, you can clean it up by doing:
Atlas.RemoveSprite(someImage);
-
Finally, when you are done with it, clean-up the atlas and all the sprite being referenced from it
Atlas.DestroyAtlas();
Careful:
Destroying the atlas, removes all the sprite referenced from images. It also delete the atlas texture from memory. So I suggest that you be careful that all sprites in that atlas are using the same Packing Tag.
If you want to use more than one atlas, I suggest creating sub-classes to the Atlas, and creating a prefab with the script on it for each.
I hope it helps some people.