If you’ve modified the value of a variable in the inspector, then the modified value always overrides the value supplied in the code, even if you change it.
I got the same problem and I didn’t change its value. It’s changed to 0 when the serializable class instance is created.
Here is my code:
[System.Serializable]
public class AnimationStep {
public AnimationClip[] animations;
public float playNextInPercent = 1f;
public AnimationStep() {
}
}
I want my float to default to 1 but newly added components or instantiated ScriptableObjects with an array of [Serializable] members with floats in them always default to 0.
I guess it has something to do with having an array of [Serializable] classes with primitives, or other similar situations?
If you are very concerned with the amount of work being done you might even consider wrapping these calls with the “EditorGUI.BeginChangeCheck” and “EditorGUI.EndChangeCheck” calls and only operate when the end check returns true. Not sure if that works… I haven’t tried it yet.
This isn’t what i’d call an elegant solution, but given the lack of any easy hook, this is the best I came up with.
Important notes:
The unity object is used as the key in order to prevent memory from piling up as you traverse your hierarchy. Once the key changes, the in-memory map is wiped.
you can handle as many arrays as you like as long as you provide unique keys.
because arrays can be null, the decision was made to pass in the array reference so that the null check could be encapsulated.
(1) The basic logic here is to have the editor watch for when the array increases in length, right? And then if it does, it calls your custom delegate to set values.
I guess this would work.
Another caveat would be this:
It makes sense when you set the length of the array from 0 to 1, because that’s when the setting-defult-values behavior fails.
But after that, Unity’s default behavior is also to make a copy of the last item of the array when you add to its length. This is actually desired behavior sometimes (though maybe not always) when you want to make several items with similar values. Point is though that it’s useful enough that it should probably still be an option. I suppose it’s a simple change to only check a change in length from 0 to 1 instead of from any previousLength to any number higher.
It’s unfortunate then that this also means anywhere you have the array of [Serializable] stuff, you’d need to make a new custom Editor for that class.
Could the MonitorArray call possibly be put in a PropertyDrawer/PropertyAttribute to remove the need for a custom Editor for each class that needs it? (I’m not too experienced with Editor stuff so I don’t know what’s possible or what makes sense in this area. I’ve always found this part of the docs really vague too. The js code doesn’t help)
[edit] oh, they actually added c# examples now.
(2) You’re passing MyComponent.FirstArray as the third parameter. What is that? Is .FirstArray passed as a static property of MyComponent? Is that really the syntax?
I completely agree with you about how inconvenient this is. I would love for unity to expose a better solution, but for the time being, this is (for my project) the best solution. I investigated using a property drawer before I investigated this. I like the idea of just adding a [DefaultArrayValue] tag (or something) to a variable and having it automatically pull in defaults whenever the collection is increased. Unfortunately, I found that there is no elegant way for that to work either. Just by adding a custom property drawer (even if you don’t override any functionality or even if you call the base functionality) the default UI becomes disabled and you get an ugly “No GUI Implemented” message stacked on top of some other text rendering it unreadable. If this were an array of some other monobehaviour, the Editor.CreateEditor would be enough to draw the proper default GUI, but this does not work for Serializable objects. I’ve spent some time in the past creating a function that renders a serializable object’s default editor, but it is incredibly complex and I stopped wasting my time on it. Sadly, custom property drawers did not provide a better solution than the custom editor solution.
I like what you said about the repeating elements, however, in my game I want to force my new elements to the defaults. You may want it a different way, which is why I exposed delegates for adding custom behaviour (that as well as not liking the idea of creating an interface that tightly couples your production and editor code together).
Lastly, the MyComponent.FirstArray section was the result of me trying to transform my game code from something I don’t want to share on the internet to something generic for everyone. It was a mistake :). I have edited my code to make that more clear. MyComponent was supposed to be a reference to the component you are editing, and FirstArray is the array reference (not the elements, but the array reference itself). I will add a class declaration to clear things up.
Once again, I wish there were a more elegant solution, but this is the best I could come up with :). Feel free to find your own solutions and post them!
You’re right. Nothing to fix since it’s not a bug. I meant “no implementation yet”, sorry.
Well, since it’s not a bug (I shouldn’t say “fix”) but a missing feature of the serialization process - that I’m sure the Unity team is aware of - and it has been 7 years with no one to look into it, it makes it hard to write clean code in that context when working with the Editor and Editor tools.
I’ve ran into this problem when I was writing my own serializable List class (yes unity already serializes lists, my class just adds an inspector drawer plus a couple features). And the solution for fixing this is a simple one-liner:
Set the new instance to default(Type).
Thats it. The serializer system will then handle the rest and insert the default values you’ve inserted in code.
which is one of the reasons I made my own serializableList class. That way when I add an instance to the list (via the inspector), it can load in the default as the new element for that list.
Sure, everything can be done. Is it the right way to do it? Weeeell, although there is no right way but the one that works, I try to avoid “hacks”. It produces nasty garbage code and it’s definitely bad practice and hard to maintain.
In the future, if anyone else comes across this issue with List custom serializable classes, the following worked really well for me.
The Reset() method is your friend.
With the code below, when you add your component in the Inspector, your array of MyCustomClass objects should get created for you automatically with the default value being set as expected.
In your MonoBehaviour class:
public List<MyCustomClass> MyList = new List<MyCustomClass>();
[System.Serializable]
public class MyCustomClass
{
public Color MyCustomColor = new Color(1, 0, 0, 0.5f);
}
void Reset()
{
MyList = new List<MyCustomClass>()
{
new MyCustomClass()
};
}