Here's a rough sketch I have not yet tested, based on our comments above. I've omitted a bunch of error checking and convenience items so it still needs work to refine into a complete solution.
First, we take each grouping of sprites that are commonly used together and make a small directory asset just for them. (You could also auto-generate these assets based on packing tags of your sprites)
When we load this directory at runtime, we'll load only the atlasses it references, not every sprite atlas in the game, which can speed up loading. To keep peak memory use down, you can also unload directories you're no longer using, but I haven't shown that here.
[CreateAssetMenu]
public class SpriteDirectory : ScriptableObject {
// Populate this in the inspector to map your sprite IDs
// to their corresponding packed Sprite assets.
public KeyValuePair<SpriteID, Sprite> [] idMaping;
Dictionary<SpriteID, Sprite> _index;
void Awake () {
// Unpack our id mappings into a dictionary for O(1) lookups.
_index = new Dictionary<SpriteID, Sprite>(idMaping.Length);
foreach (var entry in idMaping)
_index.Add(entry.Key, entry.Value);
}
public Sprite GetSprite(SpriteID id) {
return _index[id];
}
}
Next, we need a way to find the right directory for the sprite we want to load, given only its ID. (If your IDs for each group are integers in non-overlapping ranges, or if your sprite-consuming logic has knowledge of what sprite set it's working with, this can be much simpler, but here I'm assuming the most general case of random sprite access)
Here I've got a master directory that doesn't have direct links to the individual sprite directories (so it doesn't pull them all into the scene's dependency list and force all their atlases to be loaded before the game starts), instead, it just knows where to find them, and handles loading them asynchronously when needed:
public class SpriteMasterDirectory : MonoBehaviour {
[System.Serializable]
class DirectorySlot {
public string path;
public SpriteDirectory asset;
public bool pending;
}
Dictionary<SpriteID, DirectorySlot> _index;
[SerializeField]
[HideInInspector]
List<KeyValuePair<SpriteID, DirectorySlot>> _flattenedDictionary;
#if UNITY_EDITOR
// Use this in the editor to link the master directory to the
// individual directories it needs to pull from. The list will
// be stripped from the built game so it doesn't load *everything*.
public SpriteDirectory[] directories;
void Reset() { OnValidate(); }
void OnValidate() {
// When the asset is deserialized or changed in the editor,
// update its mapping of sprite IDs to their corresponding directories.
_flattenedDictionary.Clear();
foreach (var directory in directories) {
var slot = new DirectorySlot();
slot.path = UnityEditor.AssetDatabase.GetAssetPath(directory);
slot.path = slot.path.Substring(
slot.path.LastIndexOf("Resources/") + 10);
foreach (var entry in directory.idMaping)
_flattenedDictionary.Add(
new KeyValuePair<SpriteID, DirectorySlot>
(entry.Key, slot));
}
}
#endif
private void Awake() {
// Unpack our flattened dictionary into one we can search in O(1).
_index = new Dictionary<SpriteID, DirectorySlot>
(_flattenedDictionary.Count);
foreach (var entry in _flattenedDictionary)
_index.Add(entry.Key, entry.Value);
// Don't need this anymore.
_flattenedDictionary = null;
}
public class SpriteRequest {
public Sprite sprite;
public bool isReady;
}
// Call this when you want to access a sprite that might not be loaded yet.
public bool LoadSprite(SpriteID id, out SpriteRequest request) {
request = new SpriteRequest();
var slot = _index[id];
if (slot.asset != null) {
request.sprite = slot.asset.GetSprite(id);
request.isReady = true;
} else {
// Currently we'll start multiple coroutines & load requests
// if the same directory is requested multiple times before
// it's loaded in. Hoping Resources.LoadAsync is smart enough
// to only load it once, but more sophisticated queuing here
// is another potential solution.
StartCoroutine(LoadFromPendingDirectory(slot, id, request));
}
return request.isReady;
}
// Asynchronously load a sprite directory (and all its sprite atlasses).
IEnumerator LoadFromPendingDirectory(
DirectorySlot slot,
SpriteID id,
SpriteRequest request) {
var loading = Resources.LoadAsync(slot.path);
yield return loading;
slot.asset = (SpriteDirectory)loading.asset;
request.sprite = slot.asset.GetSprite(id);
request.isReady = true;
}
}