1

I have the following scriptable object:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    
    [CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
    public class Style : ScriptableObject
    {
        public Texture2D[] textures;
        public Sprite[] sprites;
        public AnimationCurve animationCurve;
        Sprite[] MakeSprites(Texture2D[] baseTextures){
            Sprite[] output = new Sprite[baseTextures.Length];
            for (int i = 0; i < baseTextures.Length; i++)
            {
                output[i] = MakeSprite(baseTextures[i]);
            }
            return output;
        }
        
        Sprite MakeSprite(Texture2D baseTexture)
        {
            Sprite sprite = Sprite.Create(baseTexture, new Rect(0, 0, baseTexture.width, baseTexture.height), new Vector2(baseTexture.width / 2f, baseTexture.height / 2f), Mathf.Max(baseTexture.width, baseTexture.height));
            return sprite;
        }
        public void UpdateSprites()
        {
            sprites = MakeSprites(textures);
        }
    }
    
    [CustomEditor(typeof(Style))]
    public class customStyleEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            DrawDefaultInspector();
    
            Style style = (Style) target;
            if (GUILayout.Button("Update Sprites"))
            {
                style.UpdateSprites();
                EditorUtility.SetDirty(target);
            }
        }
    }

This works as I would expect, but when I enter play mode, the sprites field resets to being empty, I'm pretty sure this is to do with my custom editor script, but I have tried several fixes without any success. Including: serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(target) Everything else I have tried has caused an error.

So, what is the problem here and how do I fix it?

Jacob
  • 135
  • 2
  • 12

1 Answers1

1

It looks to be that sprites get saved as a reference to a unity asset rather then getting serialized. So it seems that it needs to be a saved sprite in the project to save it in this way.

Meaning that if you generate a sprite that is not stored in the project then it's going to lose the reference as the only place it will be stored is in memory, which gets wiped on starting play mode.

If you look at the asset file you'll actually see how it stores it. This is how it looks if i drag in the image in the textures and the sprites. See that the guid and fileId are all the same and reference to an image that it has in the project. (this will be maintained between play and edit mode)

  textures:
 - {fileID: 2800000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}
 - {fileID: 2800000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}
  sprites:
 - {fileID: 21300000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}
 - {fileID: 21300000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}

However when i run the provided code and save that, this is what gets saved:

 textures:
 - {fileID: 2800000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}
 - {fileID: 2800000, guid: e8a45d89f3b96e44e82022b97a4860a3, type: 3}
  sprites:
 - {fileID: 0}
 - {fileID: 0}

It's not saving the sprite but rather a reference to it which doesn't exist. When that object no longer exists after the memory is reset, it now has a missing reference which gets translated to null.

So to fix this you could save the sprites to separate files or you could just generate them on start. The type sprite can't be properly serialized so you'll have to store it in a different data type if you want to cache it.

This is how you can save a cache of the sprites in the project. Do note that you'd have to do some management of the sprites, to make sure the old ones get deleted.

Sprite[] MakeSprites(Texture2D[] baseTextures){

    Sprite[] output = new Sprite[baseTextures.Length];
    for (int i = 0; i < baseTextures.Length; i++)
    {
        output[i] = MakeSprite(baseTextures[i]);
    }
    
    //Now save the sprites to the project 
    List<Sprite> savedSprites = new List<Sprite>();
    foreach (Sprite sprite in output)
    {
        //Create a sprite name, make sure it's prefixed with Assets/ as the assetDatabase won't like it otherwise
        string spriteName = "Assets/" + Guid.NewGuid().ToString() + ".asset";
        //Create the asset
        AssetDatabase.CreateAsset(sprite, spriteName);
        //Load the asset back in so we have a reference to the asset that exists in the project and not the reference to the sprite in memory
        Sprite newSavedSprite = (Sprite)AssetDatabase.LoadAssetAtPath(spriteName, typeof(Sprite));
        savedSprites.Add(newSavedSprite);
    }

    return savedSprites.ToArray();
}
Lieke
  • 186
  • 6
  • How would I save sprites to seperate files? – Jacob May 29 '22 at 08:31
  • `AssetDatabase.CreateAsset(mySprite, fileNameResources);` `Sprite t = (Sprite)AssetDatabase.LoadAssetAtPath(fileNameResources, typeof(Sprite));` This seems to be it although i've never tried it http://answers.unity.com/comments/1513776/view.html . Does this solve your issue? – Lieke May 29 '22 at 09:54
  • Doesn't using AssetDataBase to load assets mean you can't build your project – Jacob May 29 '22 at 10:13
  • You only have to call the assetDatabase in the editor when you cache the reference to the sprite so you shouldn't have a problem. This will generate a new file for the sprite that is now added to the build so you don't have to use the assetdatabase to load it in. The AssetDatabase is just used here to create the file and then load it back in to get the correct reference, Also i'm not sure how scriptableobjects behave in a build. They're mainly used for edit mode data. – Lieke May 29 '22 at 10:27
  • I've also added an example of how to Create and load the asset back in. – Lieke May 29 '22 at 10:39