As promised the scripts I’m using. They aren’t prefect but it seems to work. Biggest point to remember is that this should always be run before you execute a build to make sure there are no lingering inspector references to sprites.
Also I’m not doing anything about animations or prefabs that aren’t present in your scene:
SpriteTools.cs:
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public class SpriteTools : Editor
{
public const string SUFFIX_SD = "";
public const string SUFFIX_HD = "@2x";
/// <summary>
/// Can be used as part of a build tool chain to make sure all sprites are mem friendly string references
/// to textures in the Resources folder.
/// </summary>
[MenuItem("Sprites/Preprocess All Scenes")]
public static void PreprocessAllScenes()
{
if (!EditorApplication.SaveCurrentSceneIfUserWantsTo())
{
return;
}
var currentScene = EditorApplication.currentScene;
foreach (var scene in EditorBuildSettings.scenes)
{
EditorApplication.OpenScene(scene.path);
PreprocessSprites();
}
EditorApplication.OpenScene(currentScene);
}
/// <summary>
/// This removes all scene references and make sure they are being loaded from Resources.
/// </summary>
[MenuItem("Sprites/Preprocess Current Scene")]
public static void PreprocessSprites()
{
if (!EditorApplication.SaveCurrentSceneIfUserWantsTo())
{
return;
}
//Resources.FindObjectsOfTypeAll is the same as FindObjectsOfType, except if finds disabled objects as well
var spriteRenderers = Resources.FindObjectsOfTypeAll<SpriteRenderer>();
for (int i = 0; i < spriteRenderers.Length; i++)
{
var renderer = spriteRenderers[i];
if(MapSprite(renderer.sprite, renderer))
{
//A valid mapping has been created, safe to remove scene reference
renderer.sprite = null;
}
}
var imageRenderers = Resources.FindObjectsOfTypeAll<Image>();
for (int i = 0; i < imageRenderers.Length; i++)
{
var image = imageRenderers[i];
if(MapSprite(image.sprite, image))
{
//A valid mapping has been created, safe to remove scene reference
image.sprite = null;
}
}
EditorApplication.SaveScene();
}
private static bool MapSprite(Sprite sprite, Component rendererComponent)
{
if(sprite == null) return false;
var path = AssetDatabase.GetAssetPath(sprite.texture);
if(path.IndexOf("Resources/unity_builtin_extra") > -1)
{
//Ignore default unity assets
return false;
}
if(path.IndexOf("Assets/Resources") < 0)
{
Debug.LogError(string.Format("The sprite {0} doesn't exist in the resources folder and will be ignored. Scene: {1} Current path: {2}",
sprite.name,
EditorApplication.currentScene,
path)
);
return false;
}
var resolver = rendererComponent.GetComponent<SpriteResolver>();
if (resolver == null)
{
resolver = rendererComponent.gameObject.AddComponent<SpriteResolver>();
resolver.Preinitialize();
}
BuildDefinitions(resolver, sprite);
return true;
}
private static void BuildDefinitions(SpriteResolver resolver, Sprite sprite)
{
resolver.spriteName = StripName(sprite.name);
resolver.assetPath = StripName(StripPath(AssetDatabase.GetAssetPath(sprite.texture)));
BuildDefinitonAtScale(resolver, 1, SUFFIX_SD);
BuildDefinitonAtScale(resolver, 2, SUFFIX_HD);
}
private static void BuildDefinitonAtScale(SpriteResolver resolver, float scale, string suffix)
{
var resolution = resolver.GetResolution(scale, false);
if(resolution == null)
{
resolution = new SpriteResolution() {suffix = suffix, scale = scale};
resolver.resolutions.Add(resolution);
}
if (!CheckResourceExists(resolver.spriteName, suffix, resolver.assetPath))
{
resolver.resolutions.Remove(resolution);
Debug.LogError(string.Format("{0} has a missing resolution at scale {1}", resolver.spriteName, scale));
}
}
private static bool CheckResourceExists(string spriteName, string suffix, string assetPath)
{
//Sadly it seems to only way to verify the existence of sprites is to load them.
//Though this only happens in the editor, so should have no gameplay memory consequences
var sprites = Resources.LoadAll<Sprite>(assetPath + suffix);
if (sprites == null || sprites.Length < 1) return false;
for (int i = 0; i < sprites.Length; i++)
{
if (spriteName + suffix == sprites[i].name) return true;
}
return false;
}
[MenuItem("Sprites/Test SD")]
private static void TestSD()
{
var spriteResolvers = FindObjectsOfType<SpriteResolver>();
foreach (var sr in spriteResolvers)
{
sr.ForceSD();
}
}
[MenuItem("Sprites/Test HD")]
private static void TestHD()
{
var spriteResolvers = FindObjectsOfType<SpriteResolver>();
foreach (var sr in spriteResolvers)
{
sr.ForceHD();
}
}
#region Utils
private static bool IsHD(string name)
{
if(name.IndexOf(SUFFIX_HD) > -1)
{
return true;
}
return false;
}
private static string StripName(string name)
{
var stripped = name;
if(SUFFIX_SD.Length> 0)
{
stripped = stripped.Replace(SUFFIX_SD, string.Empty);
}
if(SUFFIX_HD.Length > 0)
{
stripped = stripped.Replace(SUFFIX_HD, string.Empty);
}
return stripped;
}
private static string StripPath(string path)
{
var stripped = path.Replace("Assets/Resources/", string.Empty);
var ind = stripped.LastIndexOf(".");
stripped = stripped.Substring(0, ind);
return stripped;
}
private static string GetExtension(string filepath)
{
var result = filepath.Split(".".ToCharArray());
return "." + result[result.Length - 1];
}
#endregion
}
SpriteResolver.cs:
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
[ExecuteInEditMode]
public class SpriteResolver : MonoBehaviour
{
//Set this on Applicaiton start up to determine which sprites get loaded.
public static float scaleFactor = 1;
public string spriteName;
public string assetPath;
public List<SpriteResolution> resolutions;
private SpriteRenderer spriteRenderer;
private Image imageRenderer;
//Called from editor script
public void Preinitialize()
{
if(resolutions == null)
{
resolutions = new List<SpriteResolution>();
}
}
void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
imageRenderer = GetComponent<Image>();
//No point loading if the component has just been created by the editor
if (resolutions != null && resolutions.Count > 0)
{
Load(GetResolution());
}
}
private void Load(SpriteResolution resolution)
{
var sprite = GetSprite(resolution);
if(sprite == null)
{
Debug.LogError(string.Format("The sprite {0} doesn't exist", spriteName));
}
if (spriteRenderer != null)
{
spriteRenderer.sprite = sprite;
}
if (imageRenderer != null)
{
imageRenderer.sprite = sprite;
}
}
private SpriteResolution GetResolution()
{
//No point seeing high res on pc //Can override for testing
if (Application.isEditor || !Application.isMobilePlatform)
{
return GetResolution(1);
}
//Normal behavior
return GetResolution(scaleFactor);
}
public SpriteResolution GetResolution(float scale, bool fallback = true)
{
for (int i = 0; i < resolutions.Count; i++)
{
if(resolutions[i].scale == scale)
{
return resolutions[i];
}
}
//Try accomodate missing assets
if(fallback && resolutions.Count > 0)
{
Debug.LogError(string.Format("The sprite {0} doesn't exist at scale {1}, falling back to scale {2}", spriteName,scale, resolutions[0].scale));
return resolutions[0];
}
return null;
}
private Sprite GetSprite(SpriteResolution resolution)
{
if(resolution == null)
{
return null;
}
var fullAssetPath = assetPath + resolution.suffix;
var sprites = Resources.LoadAll<Sprite>(fullAssetPath);
if (sprites == null || sprites.Length < 1)
{
Debug.LogError("No sprite assets can be found at the path " + fullAssetPath + ". Please be sure to reprocess sprites");
return null;
}
Sprite sprite = null;
for (int i = 0; i < sprites.Length; i++)
{
string fullname = spriteName + resolution.suffix;
if (fullname == sprites[i].name)
{
sprite = sprites[i];
break;
}
}
return sprite;
}
//Use this for testing form the editor to check which sprites are present at each resolution
public void ForceHD()
{
Load(GetResolution(2, false));
}
//Use this for testing form the editor to check which sprites are present at each resolution
public void ForceSD()
{
Load(GetResolution(1, false));
}
}
[System.Serializable]
public class SpriteResolution
{
public float scale = 1;
public string suffix;
}