0
\$\begingroup\$

I have a ScriptableObject that's already used extensively in my project:

public class SFXClip : ScriptableObject
{
    public AudioClip clip;

    // other properties
}

I want to extend it to a derived version something like this:

public class SFXRandom : SFXClip
{
    [SerializeField] List<AudioClip> audioClips;

    override public AudioClip clip
    {
        get
        {
            return audioClips[Random.Range(0, audioClips.Count)];
        }
    }
}

But of course, I can't do that without making the clip field on the original object into a virtual property. If I do that, I'll lose all the AudioClip references in existing SFXClip instances in my project's Assets.

Is there a way I can implement this change without losing that data?

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

Here's a cool trick you can use:

using UnityEngine.Serialization;

public class SFXClip : ScriptableObject
{
    [SerializeField, FormerlySerializedAs("clip")]
    private AudioClip _clip;

    public virtual AudioClip clip { 
        get { return _clip; } 
        set { _clip = value; } 
    }

    // other properties
}

The FormerlySerializedAs attribute from the UnityEngine.Serializaton namespace allows you to migrate saved parameters from an old name to a new one.

When the serializer doesn't find a member named _clip in the saved asset, it will look for one with its old name, clip. If it finds one, it will use that value for the new field.

When you next open and save your asset, it will be saved under the new name. So this way, you can migrate this field to be a property you can override, with no data loss.

To migrate the data from a field into a list, we need a method to do the data fix-up after the object has been deserialized:

public class SFXClip : ScriptableObject {
    [SerializeField] List<AudioClip> audioClips;

    [SerializeField, HideInInspector, FormerlySerializedAs("clip")] 
    AudioClip _legacyClip;

    void OnValidate()
    {
        if (_legacyClip == null) return;

        if (!audioClips.Contains(_legacyClip))
            audioClips.Add(_legacyClip);

        _legacyClip = null;
    }
}

This gets called on assets included in a build during the build process, or referenced in an open scene or selected object in the inspector. Once the old data has been successfully updated and saved, you can delete this code - although it's not called in a built executable anyway (only in editor), so it doesn't add significant overhead.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .